diff options
author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2021-05-31 13:24:40 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-31 13:24:40 -0400 |
commit | 003cf791b1575d2e6e1e6f4bb5cd3d7f77601d7e (patch) | |
tree | 7253898b1943f1560f50e1c715dbb72a78ad8fc2 /dev/manifest-util.js | |
parent | 6da81d59c34e58454ad12afe34d363b0c1e33bc7 (diff) |
Update build script (#1722)
* Add support for making non-buildable variants
* Add support for running a command to assign a value
* Update chrome-dev inheritance
* Add base variant
* Update manifest to auto-fill the version based on most recent git tag
* Add support for changing the default manifest
* Change the default manifest
* Move some manifest utility functions into manifest-util.js
* Move more manifest functionality into ManifestUtil
* Revert "Update manifest to auto-fill the version based on most recent git tag"
This reverts commit 2a66e40ff24f9dc545783503bcf3404f21148356.
Diffstat (limited to 'dev/manifest-util.js')
-rw-r--r-- | dev/manifest-util.js | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/dev/manifest-util.js b/dev/manifest-util.js new file mode 100644 index 00000000..25392e13 --- /dev/null +++ b/dev/manifest-util.js @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2021 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +const fs = require('fs'); +const path = require('path'); +const childProcess = require('child_process'); + + +function clone(value) { + return JSON.parse(JSON.stringify(value)); +} + + +class ManifestUtil { + constructor() { + const fileName = path.join(__dirname, 'data', 'manifest-variants.json'); + const {manifest, variants, defaultVariant} = JSON.parse(fs.readFileSync(fileName)); + this._manifest = manifest; + this._variants = variants; + this._defaultVariant = defaultVariant; + this._variantMap = new Map(); + for (const variant of variants) { + this._variantMap.set(variant.name, variant); + } + } + + getManifest(variantName) { + if (typeof variantName === 'string') { + const variant = this._variantMap.get(variantName); + if (typeof variant !== 'undefined') { + return this._createVariantManifest(this._manifest, variant); + } + } + + if (typeof this._defaultVariant === 'string') { + const variant = this._variantMap.get(this._defaultVariant); + if (typeof variant !== 'undefined') { + return this._createVariantManifest(this._manifest, variant); + } + } + + return clone(this._manifest); + } + + getVariants() { + return [...this._variants]; + } + + getVariant(name) { + return this._variantMap.get(name); + } + + static createManifestString(manifest) { + return JSON.stringify(manifest, null, 4) + '\n'; + } + + // Private + + _evaluateModificationCommand(data) { + const {command, args, trim} = data; + const {stdout, status} = childProcess.spawnSync(command, args, { + cwd: __dirname, + stdio: 'pipe', + shell: false + }); + if (status !== 0) { + throw new Error(`Failed to execute ${command} ${args.join(' ')}`); + } + let result = stdout.toString('utf8'); + if (trim) { result = result.trim(); } + return result; + } + + _applyModifications(manifest, modifications) { + if (Array.isArray(modifications)) { + for (const modification of modifications) { + const {action, path: path2} = modification; + switch (action) { + case 'set': + { + let {value, before, after, command} = modification; + const object = this._getObjectProperties(manifest, path2, path2.length - 1); + const key = path2[path2.length - 1]; + + let {index} = modification; + if (typeof index !== 'number') { + index = -1; + } + if (typeof before === 'string') { + index = this._getObjectKeyIndex(object, before); + } + if (typeof after === 'string') { + index = this._getObjectKeyIndex(object, after); + if (index >= 0) { ++index; } + } + if (typeof command === 'object' && command !== null) { + value = this._evaluateModificationCommand(command); + } + + this._setObjectKeyAtIndex(object, key, value, index); + } + break; + case 'replace': + { + const {pattern, patternFlags, replacement} = modification; + const value = this._getObjectProperties(manifest, path2, path2.length - 1); + const regex = new RegExp(pattern, patternFlags); + const last = path2[path2.length - 1]; + let value2 = value[last]; + value2 = `${value2}`.replace(regex, replacement); + value[last] = value2; + } + break; + case 'delete': + { + const value = this._getObjectProperties(manifest, path2, path2.length - 1); + const last = path2[path2.length - 1]; + delete value[last]; + } + break; + case 'remove': + { + const {item} = modification; + const value = this._getObjectProperties(manifest, path2, path2.length); + const index = value.indexOf(item); + if (index >= 0) { value.splice(index, 1); } + } + break; + case 'splice': + { + const {start, deleteCount, items} = modification; + const value = this._getObjectProperties(manifest, path2, path2.length); + const itemsNew = items.map((v) => clone(v)); + value.splice(start, deleteCount, ...itemsNew); + } + break; + case 'copy': + case 'move': + { + const {newPath, before, after} = modification; + const oldKey = path2[path2.length - 1]; + const newKey = newPath[newPath.length - 1]; + const oldObject = this._getObjectProperties(manifest, path2, path2.length - 1); + const newObject = this._getObjectProperties(manifest, newPath, newPath.length - 1); + const oldObjectIsNewObject = this._arraysAreSame(path2, newPath, -1); + const value = oldObject[oldKey]; + + let {index} = modification; + if (typeof index !== 'number' || index < 0) { + index = (oldObjectIsNewObject && action !== 'copy') ? this._getObjectKeyIndex(oldObject, oldKey) : -1; + } + if (typeof before === 'string') { + index = this._getObjectKeyIndex(newObject, before); + } + if (typeof after === 'string') { + index = this._getObjectKeyIndex(newObject, after); + if (index >= 0) { ++index; } + } + + this._setObjectKeyAtIndex(newObject, newKey, value, index); + if (action !== 'copy' && (!oldObjectIsNewObject || oldKey !== newKey)) { + delete oldObject[oldKey]; + } + } + break; + case 'add': + { + const {items} = modification; + const value = this._getObjectProperties(manifest, path2, path2.length); + const itemsNew = items.map((v) => clone(v)); + value.push(...itemsNew); + } + break; + } + } + } + + return manifest; + } + + _arraysAreSame(array1, array2, lengthOffset) { + let ii = array1.length; + if (ii !== array2.length) { return false; } + ii += lengthOffset; + for (let i = 0; i < ii; ++i) { + if (array1[i] !== array2[i]) { return false; } + } + return true; + } + + _getObjectKeyIndex(object, key) { + return Object.keys(object).indexOf(key); + } + + _setObjectKeyAtIndex(object, key, value, index) { + if (index < 0 || typeof key === 'number' || Object.prototype.hasOwnProperty.call(object, key)) { + object[key] = value; + return; + } + + const entries = Object.entries(object); + index = Math.min(index, entries.length); + for (let i = index, ii = entries.length; i < ii; ++i) { + const [key2] = entries[i]; + delete object[key2]; + } + entries.splice(index, 0, [key, value]); + for (let i = index, ii = entries.length; i < ii; ++i) { + const [key2, value2] = entries[i]; + object[key2] = value2; + } + } + + _getObjectProperties(object, path2, count) { + for (let i = 0; i < count; ++i) { + object = object[path2[i]]; + } + return object; + } + + _getInheritanceChain(variant) { + const visited = new Set(); + const inheritance = []; + while (true) { + const {name, inherit} = variant; + if (visited.has(name)) { break; } + + visited.add(name); + inheritance.unshift(variant); + + if (typeof inherit !== 'string') { break; } + + const nextVariant = this._variantMap.get(inherit); + if (typeof nextVariant === 'undefined') { break; } + + variant = nextVariant; + } + return inheritance; + } + + _createVariantManifest(manifest, variant) { + let modifiedManifest = clone(manifest); + for (const {modifications} of this._getInheritanceChain(variant)) { + modifiedManifest = this._applyModifications(modifiedManifest, modifications); + } + return modifiedManifest; + } +} + + +module.exports = { + ManifestUtil +}; |