diff options
| -rw-r--r-- | dev/build.js | 215 | ||||
| -rw-r--r-- | dev/data/manifest-variants.json | 14 | ||||
| -rw-r--r-- | dev/manifest-util.js | 267 | ||||
| -rw-r--r-- | dev/util.js | 18 | ||||
| -rw-r--r-- | test/test-manifest.js | 6 | 
5 files changed, 297 insertions, 223 deletions
| diff --git a/dev/build.js b/dev/build.js index 55358e59..7d670895 100644 --- a/dev/build.js +++ b/dev/build.js @@ -21,13 +21,10 @@ const assert = require('assert');  const readline = require('readline');  const childProcess = require('child_process');  const util = require('./util'); -const {getAllFiles, getDefaultManifestAndVariants, createManifestString, getArgs, testMain} = util; +const {getAllFiles, getArgs, testMain} = util; +const {ManifestUtil} = require('./manifest-util'); -function clone(value) { -    return JSON.parse(JSON.stringify(value)); -} -  async function createZip(directory, excludeFiles, outputFileName, sevenZipExes, onUpdate, dryRun) {      try {          fs.unlinkSync(outputFileName); @@ -110,179 +107,7 @@ function getIndexOfFilePath(array, item) {      return -1;  } -function applyModifications(manifest, modifications) { -    if (Array.isArray(modifications)) { -        for (const modification of modifications) { -            const {action, path: path2} = modification; -            switch (action) { -                case 'set': -                    { -                        const {value, before, after} = modification; -                        const object = 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 = getObjectKeyIndex(object, before); -                        } -                        if (typeof after === 'string') { -                            index = getObjectKeyIndex(object, after); -                            if (index >= 0) { ++index; } -                        } - -                        setObjectKeyAtIndex(object, key, value, index); -                    } -                    break; -                case 'replace': -                    { -                        const {pattern, patternFlags, replacement} = modification; -                        const value = 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 = getObjectProperties(manifest, path2, path2.length - 1); -                        const last = path2[path2.length - 1]; -                        delete value[last]; -                    } -                    break; -                case 'remove': -                    { -                        const {item} = modification; -                        const value = 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 = 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 = getObjectProperties(manifest, path2, path2.length - 1); -                        const newObject = getObjectProperties(manifest, newPath, newPath.length - 1); -                        const oldObjectIsNewObject = arraysAreSame(path2, newPath, -1); -                        const value = oldObject[oldKey]; - -                        let {index} = modification; -                        if (typeof index !== 'number' || index < 0) { -                            index = (oldObjectIsNewObject && action !== 'copy') ? getObjectKeyIndex(oldObject, oldKey) : -1; -                        } -                        if (typeof before === 'string') { -                            index = getObjectKeyIndex(newObject, before); -                        } -                        if (typeof after === 'string') { -                            index = getObjectKeyIndex(newObject, after); -                            if (index >= 0) { ++index; } -                        } - -                        setObjectKeyAtIndex(newObject, newKey, value, index); -                        if (action !== 'copy' && (!oldObjectIsNewObject || oldKey !== newKey)) { -                            delete oldObject[oldKey]; -                        } -                    } -                    break; -                case 'add': -                    { -                        const {items} = modification; -                        const value = getObjectProperties(manifest, path2, path2.length); -                        const itemsNew = items.map((v) => clone(v)); -                        value.push(...itemsNew); -                    } -                    break; -            } -        } -    } - -    return manifest; -} - -function 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; -} - -function getObjectKeyIndex(object, key) { -    return Object.keys(object).indexOf(key); -} - -function 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; -    } -} - -function getObjectProperties(object, path2, count) { -    for (let i = 0; i < count; ++i) { -        object = object[path2[i]]; -    } -    return object; -} - -function getInheritanceChain(variant, variantMap) { -    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 = variantMap.get(inherit); -        if (typeof nextVariant === 'undefined') { break; } - -        variant = nextVariant; -    } -    return inheritance; -} - -function createVariantManifest(manifest, variant, variantMap) { -    let modifiedManifest = clone(manifest); -    for (const {modifications} of getInheritanceChain(variant, variantMap)) { -        modifiedManifest = applyModifications(modifiedManifest, modifications); -    } -    return modifiedManifest; -} - -async function build(manifest, buildDir, extDir, manifestPath, variantMap, variantNames, dryRun, dryRunBuildZip) { +async function build(buildDir, extDir, manifestUtil, variantNames, manifestPath, dryRun, dryRunBuildZip) {      const sevenZipExes = ['7za', '7z'];      // Create build directory @@ -305,8 +130,8 @@ async function build(manifest, buildDir, extDir, manifestPath, variantMap, varia      };      for (const variantName of variantNames) { -        const variant = variantMap.get(variantName); -        if (typeof variant === 'undefined') { continue; } +        const variant = manifestUtil.getVariant(variantName); +        if (typeof variant === 'undefined' || variant.buildable === false) { continue; }          const {name, fileName, fileCopies} = variant;          let {excludeFiles} = variant; @@ -314,13 +139,13 @@ async function build(manifest, buildDir, extDir, manifestPath, variantMap, varia          process.stdout.write(`Building ${name}...\n`); -        const modifiedManifest = createVariantManifest(manifest, variant, variantMap); +        const modifiedManifest = manifestUtil.getManifest(variant.name);          const fileNameSafe = path.basename(fileName);          const fullFileName = path.join(buildDir, fileNameSafe);          ensureFilesExist(extDir, excludeFiles);          if (!dryRun) { -            fs.writeFileSync(manifestPath, createManifestString(modifiedManifest)); +            fs.writeFileSync(manifestPath, ManifestUtil.createManifestString(modifiedManifest));          }          if (!dryRun || dryRunBuildZip) { @@ -360,33 +185,27 @@ async function main(argv) {      const dryRun = args.get('dry-run');      const dryRunBuildZip = args.get('dry-run-build-zip'); -    const {manifest, variants} = getDefaultManifestAndVariants(); +    const manifestUtil = new ManifestUtil();      const rootDir = path.join(__dirname, '..');      const extDir = path.join(rootDir, 'ext');      const buildDir = path.join(rootDir, 'builds');      const manifestPath = path.join(extDir, 'manifest.json'); -    const variantMap = new Map(); -    for (const variant of variants) { -        variantMap.set(variant.name, variant); -    } -      try { -        const variantNames = (argv.length === 0 || args.get('all') ? variants.map(({name}) => name) : args.get(null)); -        await build(manifest, buildDir, extDir, manifestPath, variantMap, variantNames, dryRun, dryRunBuildZip); +        const variantNames = ( +            argv.length === 0 || args.get('all') ? +            manifestUtil.getVariants().filter(({buildable}) => buildable !== false).map(({name}) => name) : +            args.get(null) +        ); +        await build(buildDir, extDir, manifestUtil, variantNames, manifestPath, dryRun, dryRunBuildZip);      } finally {          // Restore manifest -        let restoreManifest = manifest; -        if (!args.get('default') && args.get('manifest') !== null) { -            const variant = variantMap.get(args.get('manifest')); -            if (typeof variant !== 'undefined') { -                restoreManifest = createVariantManifest(manifest, variant, variantMap); -            } -        } +        const manifestName = (!args.get('default') && args.get('manifest') !== null) ? args.get('manifest') : null; +        const restoreManifest = manifestUtil.getManifest(manifestName);          process.stdout.write('Restoring manifest...\n');          if (!dryRun) { -            fs.writeFileSync(manifestPath, createManifestString(restoreManifest)); +            fs.writeFileSync(manifestPath, ManifestUtil.createManifestString(restoreManifest));          }      }  } diff --git a/dev/data/manifest-variants.json b/dev/data/manifest-variants.json index 5a30ad14..2703d863 100644 --- a/dev/data/manifest-variants.json +++ b/dev/data/manifest-variants.json @@ -116,9 +116,15 @@          ],          "content_security_policy": "default-src 'self'; img-src blob: 'self'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *"      }, +    "defaultVariant": "base",      "variants": [          { +            "name": "base", +            "buildable": false +        }, +        {              "name": "chrome", +            "inherit": "base",              "fileName": "yomichan-chrome.zip",              "excludeFiles": [                  "sw.js", @@ -128,6 +134,7 @@          },          {              "name": "chrome-dev", +            "inherit": "chrome",              "fileName": "yomichan-chrome-dev.zip",              "modifications": [                  { @@ -144,15 +151,11 @@                      "patternFlags": "",                      "replacement": "$1. This is a development build; get the stable version here: https://tinyurl.com/yaatdjmp"                  } -            ], -            "excludeFiles": [ -                "sw.js", -                "js/dom/simple-dom-parser.js", -                "lib/parse5.js"              ]          },          {              "name": "chrome-mv3", +            "inherit": "base",              "fileName": "yomichan-chrome-mv3.zip",              "modifications": [                  {"action": "set",    "path": ["manifest_version"], "value": 3}, @@ -180,6 +183,7 @@          },          {              "name": "firefox", +            "inherit": "base",              "fileName": "yomichan-firefox.xpi",              "modifications": [                  { 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 +}; diff --git a/dev/util.js b/dev/util.js index a31cd9ac..1a2209ca 100644 --- a/dev/util.js +++ b/dev/util.js @@ -98,21 +98,6 @@ function getAllFiles(baseDirectory, predicate=null) {      return results;  } -function getDefaultManifest() { -    const {manifest} = getDefaultManifestAndVariants(); -    return manifest; -} - -function getDefaultManifestAndVariants() { -    const fileName = path.join(__dirname, 'data', 'manifest-variants.json'); -    const {manifest, variants} = JSON.parse(fs.readFileSync(fileName)); -    return {manifest, variants}; -} - -function createManifestString(manifest) { -    return JSON.stringify(manifest, null, 4) + '\n'; -} -  function createDictionaryArchive(dictionaryDirectory, dictionaryName) {      const fileNames = fs.readdirSync(dictionaryDirectory); @@ -151,9 +136,6 @@ module.exports = {      get JSZip() { return getJSZip(); },      getArgs,      getAllFiles, -    getDefaultManifest, -    getDefaultManifestAndVariants, -    createManifestString,      createDictionaryArchive,      testMain  }; diff --git a/test/test-manifest.js b/test/test-manifest.js index ab2119ac..8b9e754d 100644 --- a/test/test-manifest.js +++ b/test/test-manifest.js @@ -18,7 +18,8 @@  const fs = require('fs');  const path = require('path');  const assert = require('assert'); -const {getDefaultManifest, createManifestString, testMain} = require('../dev/util'); +const {testMain} = require('../dev/util'); +const {ManifestUtil} = require('../dev/manifest-util');  function loadManifestString() { @@ -27,8 +28,9 @@ function loadManifestString() {  }  function validateManifest() { +    const manifestUtil = new ManifestUtil();      const manifest1 = loadManifestString(); -    const manifest2 = createManifestString(getDefaultManifest()); +    const manifest2 = ManifestUtil.createManifestString(manifestUtil.getManifest());      assert.strictEqual(manifest1, manifest2, 'Manifest data does not match.');  } |