From e515f51c0ec6b3449bf81ab6f47356072b5876d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1n=20D=C3=A9nes?= Date: Sun, 3 Jan 2021 19:48:37 +0100 Subject: [PATCH] Parametric declarations --- package.json | 3 +- src/prepare.js | 96 +++++++++++++------ test/{complex => }/index.js | 13 ++- .../outlines/001_basic_outline.yaml | 0 .../001_basic_outline___outlines.json | 0 test/{complex => }/outlines/002_gluing.yaml | 0 test/{complex => }/points/001_basic_2x2.yaml | 0 .../points/001_basic_2x2___points_data.json | 0 .../{complex => }/points/002_adjustments.yaml | 0 test/unit/prepare.js | 52 +++++++++- 10 files changed, 128 insertions(+), 36 deletions(-) rename test/{complex => }/index.js (74%) rename test/{complex => }/outlines/001_basic_outline.yaml (100%) rename test/{complex => }/outlines/001_basic_outline___outlines.json (100%) rename test/{complex => }/outlines/002_gluing.yaml (100%) rename test/{complex => }/points/001_basic_2x2.yaml (100%) rename test/{complex => }/points/001_basic_2x2___points_data.json (100%) rename test/{complex => }/points/002_adjustments.yaml (100%) diff --git a/package.json b/package.json index 5074a80..7d7c72c 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "bin": "./src/cli.js", "scripts": { "build": "rollup -c", - "test": "nyc --reporter=html --reporter=text mocha -r chai/register-should test/unit/*.js test/complex/index.js" + "test": "mocha -r chai/register-should test/index.js", + "coverage": "nyc --reporter=html --reporter=text mocha -r chai/register-should test/index.js" }, "dependencies": { "fs-extra": "^9.0.1", diff --git a/src/prepare.js b/src/prepare.js index a27fed9..1d60699 100644 --- a/src/prepare.js +++ b/src/prepare.js @@ -1,15 +1,6 @@ const u = require('./utils') const a = require('./assert') -const unnest = exports.unnest = config => { - if (a.type(config)() !== 'object') return config - const result = {} - for (const [key, val] of Object.entries(config)) { - u.deep(result, key, unnest(val)) - } - return result -} - const _extend = exports._extend = (to, from) => { const to_type = a.type(to)() const from_type = a.type(from)() @@ -41,32 +32,79 @@ const extend = exports.extend = (...args) => { return res } -const _inherit = exports._inherit = (config, root, breadcrumbs) => { +const traverse = exports.traverse = (config, root, breadcrumbs, op) => { if (a.type(config)() !== 'object') return config const result = {} for (const [key, val] of Object.entries(config)) { breadcrumbs.push(key) - let newval = _inherit(val, root, breadcrumbs) - if (newval && newval.$extends !== undefined) { - let candidates = u.deepcopy(newval.$extends) - if (a.type(candidates)() !== 'array') candidates = [candidates] - const list = [newval] - while (candidates.length) { - const path = candidates.shift() - const other = u.deepcopy(u.deep(root, path)) - a.assert(other, `"${path}" (reached from "${breadcrumbs.join('.')}.$extends") does not name a valid inheritance target!`) - let parents = other.$extends || [] - if (a.type(parents)() !== 'array') parents = [parents] - candidates = candidates.concat(parents) - list.unshift(other) - } - newval = extend.apply(this, list) - delete newval.$extends - } - result[key] = newval + op(result, key, traverse(val, root, breadcrumbs, op), root, breadcrumbs) breadcrumbs.pop() } return result } -const inherit = exports.inherit = config => _inherit(config, config, []) +exports.unnest = config => traverse(config, config, [], (target, key, val) => { + u.deep(target, key, val) +}) + +exports.inherit = config => traverse(config, config, [], (target, key, val, root, breadcrumbs) => { + if (val && val.$extends !== undefined) { + let candidates = u.deepcopy(val.$extends) + if (a.type(candidates)() !== 'array') candidates = [candidates] + const list = [val] + while (candidates.length) { + const path = candidates.shift() + const other = u.deepcopy(u.deep(root, path)) + a.assert(other, `"${path}" (reached from "${breadcrumbs.join('.')}.$extends") does not name a valid inheritance target!`) + let parents = other.$extends || [] + if (a.type(parents)() !== 'array') parents = [parents] + candidates = candidates.concat(parents) + list.unshift(other) + } + val = extend.apply(this, list) + delete val.$extends + } + target[key] = val +}) + +exports.parameterize = config => traverse(config, config, [], (target, key, val, root, breadcrumbs) => { + let params = val.$params + let args = val.$args + + // explicitly skipped (probably intermediate) template, remove (by not setting it) + if (val.$skip) return + + // nothing to do here, just pass the original value through + if (!params && !args) { + target[key] = val + return + } + + // unused template, remove (by not setting it) + if (params && !args) return + + if (!params && args) { + throw new Error(`Trying to parameterize through "${breadcrumbs}.$args", but the corresponding "$params" field is missing!`) + } + + params = a.strarr(params, `${breadcrumbs}.$params`) + args = a.sane(args, `${breadcrumbs}.$args`, 'array')() + if (params.length !== args.length) { + throw new Error(`The number of "$params" and "$args" don't match for "${breadcrumbs}"!`) + } + + let str = JSON.stringify(val) + const zip = rows => rows[0].map((_, i) => rows.map(row => row[i])) + for (const [par, arg] of zip([params, args])) { + str = str.replace(new RegExp(`"${par}"`, 'g'), JSON.stringify(arg)) + } + try { + val = JSON.parse(str) + } catch (ex) { + throw new Error(`Replacements didn't lead to a valid JSON object at "${breadcrumbs}"! ` + ex) + } + + delete val.$params + delete val.$args + target[key] = val +}) \ No newline at end of file diff --git a/test/complex/index.js b/test/index.js similarity index 74% rename from test/complex/index.js rename to test/index.js index 4f9e3e3..3b3437e 100644 --- a/test/complex/index.js +++ b/test/index.js @@ -2,12 +2,21 @@ const fs = require('fs-extra') const path = require('path') const yaml = require('js-yaml') const glob = require('glob') -const u = require('../../src/utils') -const ergogen = require('../../src/ergogen') +const u = require('../src/utils') +const ergogen = require('../src/ergogen') + +let what = process.env.npm_config_what +what = what ? what.split(',') : false +for (const unit of glob.sync(path.join(__dirname, 'unit', '*.js'))) { + const base = path.basename(unit, '.js') + if (what && !what.includes(base)) continue + require(`./unit/${base}.js`) +} const cap = s => s.charAt(0).toUpperCase() + s.slice(1) for (const part of ['points', 'outlines', 'cases', 'pcbs']) { + if (what && !what.includes(part)) continue describe(cap(part), function() { const dir = path.join(__dirname, part) for (const input_path of glob.sync(path.join(dir, '*.yaml'))) { diff --git a/test/complex/outlines/001_basic_outline.yaml b/test/outlines/001_basic_outline.yaml similarity index 100% rename from test/complex/outlines/001_basic_outline.yaml rename to test/outlines/001_basic_outline.yaml diff --git a/test/complex/outlines/001_basic_outline___outlines.json b/test/outlines/001_basic_outline___outlines.json similarity index 100% rename from test/complex/outlines/001_basic_outline___outlines.json rename to test/outlines/001_basic_outline___outlines.json diff --git a/test/complex/outlines/002_gluing.yaml b/test/outlines/002_gluing.yaml similarity index 100% rename from test/complex/outlines/002_gluing.yaml rename to test/outlines/002_gluing.yaml diff --git a/test/complex/points/001_basic_2x2.yaml b/test/points/001_basic_2x2.yaml similarity index 100% rename from test/complex/points/001_basic_2x2.yaml rename to test/points/001_basic_2x2.yaml diff --git a/test/complex/points/001_basic_2x2___points_data.json b/test/points/001_basic_2x2___points_data.json similarity index 100% rename from test/complex/points/001_basic_2x2___points_data.json rename to test/points/001_basic_2x2___points_data.json diff --git a/test/complex/points/002_adjustments.yaml b/test/points/002_adjustments.yaml similarity index 100% rename from test/complex/points/002_adjustments.yaml rename to test/points/002_adjustments.yaml diff --git a/test/unit/prepare.js b/test/unit/prepare.js index d74d839..5f7f44c 100644 --- a/test/unit/prepare.js +++ b/test/unit/prepare.js @@ -11,12 +11,12 @@ describe('Prepare', function() { it('extend', function() { p.extend('something', undefined).should.equal('something') - should.equal(p.extend('something', '!!unset'), undefined) + should.equal(p.extend('something', '$unset'), undefined) p.extend(undefined, 'something').should.equal('something') p.extend(28, 'something').should.equal('something') p.extend('something', 28).should.equal(28) p.extend(27, 28).should.equal(28) - p.extend({a: 1, c: 1, d: 1}, {b: 2, c: 2, d: '!!unset'}).should.deep.equal({a: 1, b: 2, c: 2}) + p.extend({a: 1, c: 1, d: 1}, {b: 2, c: 2, d: '$unset'}).should.deep.equal({a: 1, b: 2, c: 2}) p.extend([3, 2, 1], [null, 4, 5]).should.deep.equal([3, 4, 5]) }) @@ -27,11 +27,11 @@ describe('Prepare', function() { y: 2 }, b: { - extends: 'a', + $extends: 'a', z: 3 }, c: { - extends: 'b', + $extends: ['b'], w: 4 } }).c.should.deep.equal({ @@ -41,4 +41,48 @@ describe('Prepare', function() { w: 4 }) }) + + it('parameterize', function() { + p.parameterize(1).should.equal(1) + + p.parameterize({ + unused: { + $params: ['PAR'] + }, + skip: { + $skip: true + } + }).should.deep.equal({}) + + p.parameterize({ + decl: { + a: 'PAR', + $params: ['PAR'], + $args: [1] + } + }).decl.should.deep.equal({ + a: 1 + }) + + p.parameterize.bind(this, { + decl: { + $args: [1] + } + }).should.throw('missing') + + p.parameterize.bind(this, { + decl: { + $params: ['PAR1', 'PAR2'], + $args: [1] + } + }).should.throw('match') + + p.parameterize.bind(this, { + decl: { + a: 'PAR', + $params: ['PAR'], + $args: [undefined] + } + }).should.throw('valid') + }) }) \ No newline at end of file