Skip to content

Commit

Permalink
feat: Add generic TOML updater (#1833)
Browse files Browse the repository at this point in the history
* feat: Add generic TOML updater

* chore(docs): Add generic YAML & TOML updaters
  • Loading branch information
phated authored Jan 26, 2023
1 parent bee5596 commit 2768a4c
Show file tree
Hide file tree
Showing 9 changed files with 292 additions and 3 deletions.
69 changes: 69 additions & 0 deletions __snapshots__/generic-toml.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
exports['GenericToml updateContent updates deep entry in toml 1'] = `
[package]
name = "rust-test-repo"
version = "12.0.0"
# To learn about other keys, check out Cargo's documentation
[dependencies]
normal-dep = "1.2.3"
[dev-dependencies]
dev-dep = { version = "2.3.4" }
dev-dep-2 = { path = "../dev-dep-2" }
[build-dependencies]
# this is using a private registry
build-dep = { version = "1.2.3", registry = "private", path = ".." } # trailing comment
[target.'cfg(windows)'.dev-dependencies]
windows-dep = { version = "1.2.3", registry = "private", path = ".." }
[target.'cfg(unix)'.dependencies]
unix-dep = { version = "1.2.3", registry = "private", path = ".." }
[target.'cfg(target_arch = "x86")'.dependencies]
x86-dep = { version = "1.2.3", registry = "private", path = ".." }
[target.'cfg(target_arch = "x86_64")'.dependencies]
x86-64-dep = { version = "1.2.3", registry = "private", path = ".." }
[target.'cfg(foobar)'.dependencies]
foobar-dep = "1.2.3"
`

exports['GenericToml updateContent updates matching entry 1'] = `
[package]
name = "rust-test-repo"
version = "2.3.4"
# To learn about other keys, check out Cargo's documentation
[dependencies]
normal-dep = "1.2.3"
[dev-dependencies]
dev-dep = { version = "1.2.3" }
dev-dep-2 = { path = "../dev-dep-2" }
[build-dependencies]
# this is using a private registry
build-dep = { version = "1.2.3", registry = "private", path = ".." } # trailing comment
[target.'cfg(windows)'.dev-dependencies]
windows-dep = { version = "1.2.3", registry = "private", path = ".." }
[target.'cfg(unix)'.dependencies]
unix-dep = { version = "1.2.3", registry = "private", path = ".." }
[target.'cfg(target_arch = "x86")'.dependencies]
x86-dep = { version = "1.2.3", registry = "private", path = ".." }
[target.'cfg(target_arch = "x86_64")'.dependencies]
x86-64-dep = { version = "1.2.3", registry = "private", path = ".." }
[target.'cfg(foobar)'.dependencies]
foobar-dep = "1.2.3"
`
38 changes: 38 additions & 0 deletions docs/customizing.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,41 @@ configuration.
]
}
```

## Updating arbitrary YAML files

For most release strategies, you can provide additional files to update
using the [GenericYaml](/src/updaters/generic-yaml.ts) updater. You can
specify a configuration object in the `extra-files` option in the manifest
configuration.

```json
{
"extra-files": [
{
"type": "yaml",
"path": "path/to/file.yaml",
"jsonpath": "$.json.path.to.field"
}
]
}
```

## Updating arbitrary TOML files

For most release strategies, you can provide additional files to update
using the [GenericToml](/src/updaters/generic-toml.ts) updater. You can
specify a configuration object in the `extra-files` option in the manifest
configuration.

```json
{
"extra-files": [
{
"type": "toml",
"path": "path/to/file.toml",
"jsonpath": "$.json.path.to.field"
}
]
}
```
9 changes: 8 additions & 1 deletion src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,19 @@ type ExtraPomFile = {
path: string;
glob?: boolean;
};
type ExtraTomlFile = {
type: 'toml';
path: string;
jsonpath: string;
glob?: boolean;
};
export type ExtraFile =
| string
| ExtraJsonFile
| ExtraYamlFile
| ExtraXmlFile
| ExtraPomFile;
| ExtraPomFile
| ExtraTomlFile;
/**
* These are configurations provided to each strategy per-path.
*/
Expand Down
8 changes: 8 additions & 0 deletions src/strategies/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {GenericJson} from '../updaters/generic-json';
import {GenericXml} from '../updaters/generic-xml';
import {PomXml} from '../updaters/java/pom-xml';
import {GenericYaml} from '../updaters/generic-yaml';
import {GenericToml} from '../updaters/generic-toml';

const DEFAULT_CHANGELOG_PATH = 'CHANGELOG.md';

Expand Down Expand Up @@ -398,6 +399,13 @@ export abstract class BaseStrategy implements Strategy {
updater: new GenericYaml(extraFile.jsonpath, version),
});
break;
case 'toml':
extraFileUpdates.push({
path: this.addPath(path),
createIfMissing: false,
updater: new GenericToml(extraFile.jsonpath, version),
});
break;
case 'xml':
extraFileUpdates.push({
path: this.addPath(path),
Expand Down
65 changes: 65 additions & 0 deletions src/updaters/generic-toml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {Updater} from '../update';
import {Version} from '../version';
import * as jp from 'jsonpath';
import {parseWith, replaceTomlValue} from '../util/toml-edit';
import * as toml from '@iarna/toml';
import {logger as defaultLogger, Logger} from '../util/logger';

/**
* Updates TOML document according to given JSONPath.
*
* Note that used parser does reformat the document and removes all comments,
* and converts everything to pure TOML.
* If you want to retain formatting, use generic updater with comment hints.
*/
export class GenericToml implements Updater {
readonly jsonpath: string;
readonly version: Version;

constructor(jsonpath: string, version: Version) {
this.jsonpath = jsonpath;
this.version = version;
}
/**
* Given initial file contents, return updated contents.
* @param {string} content The initial content
* @returns {string} The updated content
*/
updateContent(content: string, logger: Logger = defaultLogger): string {
let data: toml.JsonMap;
try {
data = parseWith(content);
} catch (e) {
logger.warn('Invalid toml, cannot be parsed', e);
return content;
}

const paths = jp.paths(data, this.jsonpath);
if (!paths || paths.length === 0) {
logger.warn(`No entries modified in ${this.jsonpath}`);
return content;
}

let processed = content;
paths.forEach(path => {
if (path[0] === '$') path = path.slice(1);
processed = replaceTomlValue(processed, path, this.version.toString());
});

return processed;
}
}
7 changes: 5 additions & 2 deletions src/util/toml-edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ class TaggedTOMLParser extends TOMLParser {
* @param input A string
* @param parserType The TOML parser to use (might be custom)
*/
function parseWith(input: string, parserType: typeof TOMLParser): JsonMap {
export function parseWith(
input: string,
parserType: typeof TOMLParser = TaggedTOMLParser
): JsonMap {
const parser = new parserType();
parser.parse(input);
return parser.finish();
Expand Down Expand Up @@ -114,7 +117,7 @@ function isTaggedValue(x: unknown): x is TaggedValue {
*/
export function replaceTomlValue(
input: string,
path: string[],
path: (string | number)[],
newValue: string
) {
// our pointer into the object "tree", initially points to the root.
Expand Down
18 changes: 18 additions & 0 deletions test/strategies/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {Generic} from '../../src/updaters/generic';
import {GenericXml} from '../../src/updaters/generic-xml';
import {PomXml} from '../../src/updaters/java/pom-xml';
import {GenericYaml} from '../../src/updaters/generic-yaml';
import {GenericToml} from '../../src/updaters/generic-toml';

const sandbox = sinon.createSandbox();

Expand Down Expand Up @@ -145,6 +146,23 @@ describe('Strategy', () => {
assertHasUpdate(updates!, '0', Generic);
assertHasUpdate(updates!, '3.yaml', GenericYaml);
});
it('updates extra TOML files', async () => {
const strategy = new TestStrategy({
targetBranch: 'main',
github,
component: 'google-cloud-automl',
extraFiles: ['0', {type: 'toml', path: '/3.toml', jsonpath: '$.foo'}],
});
const pullRequest = await strategy.buildReleasePullRequest(
buildMockConventionalCommit('fix: a bugfix'),
undefined
);
expect(pullRequest).to.exist;
const updates = pullRequest?.updates;
expect(updates).to.be.an('array');
assertHasUpdate(updates!, '0', Generic);
assertHasUpdate(updates!, '3.toml', GenericToml);
});
it('updates extra Xml files', async () => {
const strategy = new TestStrategy({
targetBranch: 'main',
Expand Down
1 change: 1 addition & 0 deletions test/updaters/fixtures/toml/invalid.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invalid =
80 changes: 80 additions & 0 deletions test/updaters/generic-toml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {readFileSync} from 'fs';
import {resolve} from 'path';
import * as snapshot from 'snap-shot-it';
import {describe, it} from 'mocha';
import {Version} from '../../src/version';
import {expect, assert} from 'chai';
import {GenericToml} from '../../src/updaters/generic-toml';

const fixturesPath = './test/updaters/fixtures';

describe('GenericToml', () => {
describe('updateContent', () => {
it('updates matching entry', async () => {
const oldContent = readFileSync(
resolve(fixturesPath, './Cargo.toml'),
'utf8'
).replace(/\r\n/g, '\n');
const updater = new GenericToml(
'$.package.version',
Version.parse('v2.3.4')
);
const newContent = updater.updateContent(oldContent);
snapshot(newContent);
});
it('updates deep entry in toml', async () => {
const oldContent = readFileSync(
resolve(fixturesPath, './Cargo.toml'),
'utf8'
).replace(/\r\n/g, '\n');
const updater = new GenericToml(
"$['dev-dependencies']..version",
Version.parse('v2.3.4')
);
const newContent = updater.updateContent(oldContent);
snapshot(newContent);
});
it('ignores non-matching entry', async () => {
const oldContent = readFileSync(
resolve(fixturesPath, './Cargo.toml'),
'utf8'
).replace(/\r\n/g, '\n');
const updater = new GenericToml('$.nonExistent', Version.parse('v2.3.4'));
const newContent = updater.updateContent(oldContent);
expect(newContent).to.eql(oldContent);
});
it('warns on invalid jsonpath', async () => {
const oldContent = readFileSync(
resolve(fixturesPath, './Cargo.toml'),
'utf8'
).replace(/\r\n/g, '\n');
const updater = new GenericToml('bad jsonpath', Version.parse('v2.3.4'));
assert.throws(() => {
updater.updateContent(oldContent);
});
});
it('ignores invalid file', async () => {
const oldContent = readFileSync(
resolve(fixturesPath, './toml/invalid.txt'),
'utf8'
).replace(/\r\n/g, '\n');
const updater = new GenericToml('$.boo', Version.parse('v2.3.4'));
const newContent = updater.updateContent(oldContent);
expect(newContent).to.eql(oldContent);
});
});
});

0 comments on commit 2768a4c

Please sign in to comment.