From cd90705ba1546a70550ed7cddf6cc63b8f773dd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1n=20D=C3=A9nes?= Date: Fri, 1 Jan 2021 00:50:04 +0100 Subject: [PATCH] Refactor, units, tests --- package-lock.json | 55 ++ package.json | 7 +- src/anchor.js | 61 ++ src/assert.js | 170 +---- src/cli.js | 59 +- src/ergogen.js | 58 +- src/operation.js | 19 + src/outlines.js | 107 ++- src/points.js | 97 +-- src/prepare.js | 71 ++ src/utils.js | 14 +- test/complex.js | 23 - test/complex/index.js | 28 + test/complex/outlines/001_basic_outline.yaml | 24 + .../001_basic_outline___outlines.json | 506 +++++++++++++ test/complex/outlines/002_gluing.yaml | 44 ++ test/complex/points/001_basic_2x2.yaml | 9 + .../points/001_basic_2x2___points_data.json | 130 ++++ test/complex/points/002_adjustments.yaml | 13 + test/fixtures/absolem.yaml | 707 ------------------ test/fixtures/absolem_points.json | 182 ----- test/point.js | 8 - test/unit/prepare.js | 44 ++ test/utils.js | 7 - 24 files changed, 1221 insertions(+), 1222 deletions(-) create mode 100644 src/anchor.js create mode 100644 src/operation.js create mode 100644 src/prepare.js delete mode 100644 test/complex.js create mode 100644 test/complex/index.js create mode 100644 test/complex/outlines/001_basic_outline.yaml create mode 100644 test/complex/outlines/001_basic_outline___outlines.json create mode 100644 test/complex/outlines/002_gluing.yaml create mode 100644 test/complex/points/001_basic_2x2.yaml create mode 100644 test/complex/points/001_basic_2x2___points_data.json create mode 100644 test/complex/points/002_adjustments.yaml delete mode 100644 test/fixtures/absolem.yaml delete mode 100644 test/fixtures/absolem_points.json delete mode 100644 test/point.js create mode 100644 test/unit/prepare.js delete mode 100644 test/utils.js diff --git a/package-lock.json b/package-lock.json index dab4816..4540775 100644 --- a/package-lock.json +++ b/package-lock.json @@ -604,6 +604,11 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "complex.js": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.11.tgz", + "integrity": "sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -644,6 +649,11 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" + }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -739,6 +749,11 @@ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, + "escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -804,6 +819,11 @@ "signal-exit": "^3.0.2" } }, + "fraction.js": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.13.tgz", + "integrity": "sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA==" + }, "fromentries": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.0.tgz", @@ -1226,6 +1246,11 @@ "iterate-iterator": "^1.0.1" } }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1344,6 +1369,21 @@ "kdbush": "^2.0.1" } }, + "mathjs": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-8.1.1.tgz", + "integrity": "sha512-b3TX3EgiZObujjwb8lZnTDLUuivC2jar4ZBjmGJ4stFYCDXx/DNwx5yry5t/z65p9mvejyZel1qoeR05KtChcQ==", + "requires": { + "complex.js": "^2.0.11", + "decimal.js": "^10.2.1", + "escape-latex": "^1.2.0", + "fraction.js": "^4.0.13", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^2.0.0" + } + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1867,6 +1907,11 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -2017,6 +2062,11 @@ "minimatch": "^3.0.4" } }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -2044,6 +2094,11 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, + "typed-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.0.0.tgz", + "integrity": "sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA==" + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", diff --git a/package.json b/package.json index e132daa..5074a80 100644 --- a/package.json +++ b/package.json @@ -11,18 +11,20 @@ "bin": "./src/cli.js", "scripts": { "build": "rollup -c", - "test": "nyc --reporter=html --reporter=text mocha -r chai/register-should" + "test": "nyc --reporter=html --reporter=text mocha -r chai/register-should test/unit/*.js test/complex/index.js" }, "dependencies": { "fs-extra": "^9.0.1", "js-yaml": "^3.14.0", "makerjs": "github:mrzealot/maker.js-dist", + "mathjs": "^8.1.1", "yargs": "^15.4.1" }, "devDependencies": { "@rollup/plugin-commonjs": "^13.0.2", "@rollup/plugin-replace": "^2.3.3", "chai": "^4.2.0", + "glob": "^7.1.6", "mocha": "^8.1.3", "nyc": "^15.1.0", "rollup": "^2.22.1" @@ -33,7 +35,8 @@ "src/*.js" ], "exclude": [ - "src/cli.js" + "src/cli.js", + "src/io.js" ] } } diff --git a/src/anchor.js b/src/anchor.js new file mode 100644 index 0000000..bc965bb --- /dev/null +++ b/src/anchor.js @@ -0,0 +1,61 @@ +const u = require('./utils') +const a = require('./assert') +const Point = require('./point') + +const anchor = module.exports = (raw, name, points={}, check_unexpected=true, default_point=new Point()) => units => { + if (a.type(raw) == 'array') { + // recursive call with incremental default_point mods, according to `affect`s + let current = () => default_point.clone() + for (const step of raw) { + current = anchor(step, name, points, check_unexpected, current(units)) + } + return current + } + if (check_unexpected) a.detect_unexpected(raw, name, ['ref', 'orient', 'shift', 'rotate', 'affect']) + let point = default_point.clone() + if (raw.ref !== undefined) { + if (a.type(raw.ref) == 'array') { + // averaging multiple anchors + let x = 0, y = 0, r = 0 + const len = raw.ref.length + for (const ref of raw.ref) { + a.assert(points[ref], `Unknown point reference "${ref}" in anchor "${name}"!`) + const resolved = points[ref] + x += resolved.x + y += resolved.y + r += resolved.r + } + point = new Point(x / len, y / len, r / len) + } else { + a.assert(points[raw.ref], `Unknown point reference "${raw.ref}" in anchor "${name}"!`) + point = points[raw.ref].clone() + } + } + if (raw.orient !== undefined) { + point.r += a.sane(raw.orient || 0, `${name}.orient`, 'number')(units) + } + if (raw.shift !== undefined) { + let xyval = a.wh(raw.shift || [0, 0], `${name}.shift`)(units) + if (point.meta.mirrored) { + xyval[0] = -xyval[0] + } + point.shift(xyval, true) + } + if (raw.rotate !== undefined) { + point.r += a.sane(raw.rotate || 0, `${name}.rotate`, 'number')(units) + } + if (raw.affect !== undefined) { + const candidate = point + point = default_point.clone() + const valid_affects = ['x', 'y', 'r'] + let affect = raw.affect || valid_affects + if (a.type(affect) == 'string') affect = affect.split('') + affect = a.strarr(affect, `${name}.affect`) + let i = 0 + for (const a of affect) { + a._in(a, `${name}.affect[${++i}]`, valid_affects) + point[a] = candidate[a] + } + } + return point +} \ No newline at end of file diff --git a/src/assert.js b/src/assert.js index 57f9048..d55fc24 100644 --- a/src/assert.js +++ b/src/assert.js @@ -1,6 +1,11 @@ const m = require('makerjs') const u = require('./utils') const Point = require('./point') +const mathjs = require('mathjs') + +const mathnum = exports.mathnum = raw => units => { + return mathjs.evaluate(`${raw}`, units || {}) +} const assert = exports.assert = (exp, msg) => { if (!exp) { @@ -8,19 +13,24 @@ const assert = exports.assert = (exp, msg) => { } } -const type = exports.type = (val) => { +const type = exports.type = val => units => { if (Array.isArray(val)) return 'array' if (val === null) return 'null' + try { + const num = mathnum(val)(units) + if (typeof num === 'number') return 'number' + } catch (err) {} return typeof val } -const sane = exports.sane = (val, name, _type) => { - assert(type(val) == _type, `Field "${name}" should be of type ${_type}!`) +const sane = exports.sane = (val, name, _type) => units => { + assert(type(val)(units) == _type, `Field "${name}" should be of type ${_type}!`) + if (_type == 'number') return mathnum(val)(units) return val } const detect_unexpected = exports.detect_unexpected = (obj, name, expected) => { - const sane_obj = sane(obj, name, 'object') + const sane_obj = sane(obj, name, 'object')() for (const key of Object.keys(sane_obj)) { assert(expected.includes(key), `Unexpected key "${key}" within field "${name}"!`) } @@ -31,155 +41,29 @@ const _in = exports.in = (raw, name, arr) => { return raw } -const arr = exports.arr = (raw, name, length, _type, _default) => { - assert(type(raw) == 'array', `Field "${name}" should be an array!`) +const arr = exports.arr = (raw, name, length, _type, _default) => units => { + assert(type(raw)(units) == 'array', `Field "${name}" should be an array!`) assert(length == 0 || raw.length == length, `Field "${name}" should be an array of length ${length}!`) raw = raw.map(val => val || _default) - raw.map(val => assert(type(val) == _type, `Field "${name}" should contain ${_type}s!`)) + raw.map(val => assert(type(val)(units) == _type, `Field "${name}" should contain ${_type}s!`)) + if (_type == 'number') { + raw = raw.map(val => mathnum(val)(units)) + } return raw } -const numarr = exports.numarr = (raw, name, length) => arr(raw, name, length, 'number', 0) -const strarr = exports.strarr = (raw, name) => arr(raw, name, 0, 'string', '') +const numarr = exports.numarr = (raw, name, length) => units => arr(raw, name, length, 'number', 0)(units) +const strarr = exports.strarr = (raw, name) => arr(raw, name, 0, 'string', '')() -const xy = exports.xy = (raw, name) => numarr(raw, name, 2) +const xy = exports.xy = (raw, name) => units => numarr(raw, name, 2)(units) -const wh = exports.wh = (raw, name) => { +const wh = exports.wh = (raw, name) => units => { if (!Array.isArray(raw)) raw = [raw, raw] - return xy(raw, name) + return xy(raw, name)(units) } -exports.trbl = (raw, name) => { +exports.trbl = (raw, name) => units => { if (!Array.isArray(raw)) raw = [raw, raw, raw, raw] if (raw.length == 2) raw = [raw[1], raw[0], raw[1], raw[0]] - return numarr(raw, name, 4) + return numarr(raw, name, 4, 'number', 0)(units) } - -const anchor = exports.anchor = (raw, name, points={}, check_unexpected=true, default_point=new Point()) => { - if (type(raw) == 'array') { - // recursive call with incremental default_point mods, according to `affect`s - let current = default_point.clone() - for (const step of raw) { - current = anchor(step, name, points, check_unexpected, current) - } - return current - } - if (check_unexpected) detect_unexpected(raw, name, ['ref', 'orient', 'shift', 'rotate', 'affect']) - let point = default_point.clone() - if (raw.ref !== undefined) { - if (type(raw.ref) == 'array') { - // averaging multiple anchors - let x = 0, y = 0, r = 0 - const len = raw.ref.length - for (const ref of raw.ref) { - assert(points[ref], `Unknown point reference "${ref}" in anchor "${name}"!`) - const resolved = points[ref] - x += resolved.x - y += resolved.y - r += resolved.r - } - point = new Point(x / len, y / len, r / len) - } else { - assert(points[raw.ref], `Unknown point reference "${raw.ref}" in anchor "${name}"!`) - point = points[raw.ref].clone() - } - } - if (raw.orient !== undefined) { - point.r += sane(raw.orient || 0, `${name}.orient`, 'number') - } - if (raw.shift !== undefined) { - let xyval = wh(raw.shift || [0, 0], `${name}.shift`) - if (point.meta.mirrored) { - xyval[0] = -xyval[0] - } - point.shift(xyval, true) - } - if (raw.rotate !== undefined) { - point.r += sane(raw.rotate || 0, `${name}.rotate`, 'number') - } - if (raw.affect !== undefined) { - const candidate = point - point = default_point.clone() - const valid_affects = ['x', 'y', 'r'] - let affect = raw.affect || valid_affects - if (type(affect) == 'string') affect = affect.split('') - affect = strarr(affect, `${name}.affect`) - let i = 0 - for (const a of affect) { - _in(a, `${name}.affect[${++i}]`, valid_affects) - point[a] = candidate[a] - } - } - return point -} - -const extend_pair = exports.extend_pair = (to, from) => { - const to_type = type(to) - const from_type = type(from) - if (from === undefined || from === null) return to - if (from === '!!unset') return undefined - if (to_type != from_type) return from - if (from_type == 'object') { - const res = u.deepcopy(to) - for (const key of Object.keys(from)) { - res[key] = extend_pair(to[key], from[key]) - if (res[key] === undefined) delete res[key] - } - return res - } else if (from_type == 'array') { - const res = u.deepcopy(to) - for (const [i, val] of from.entries()) { - res[i] = extend_pair(res[i], val) - } - return res - } else return from -} - -const extend = exports.extend = (...args) => { - let res = args[0] - for (const arg of args) { - if (res == arg) continue - res = extend_pair(res, arg) - } - return res -} - -const inherit = exports.inherit = (name_prefix, name, set) => { - let result = u.deepcopy(set[name]) - if (result.extends !== undefined) { - let candidates = [name] - const list = [] - while (candidates.length) { - const item = candidates.shift() - const other = u.deepcopy(set[item]) - assert(other, `"${item}" (reached from "${name_prefix}.${name}.extends") does not name a valid target!`) - let parents = other.extends || [] - if (type(parents) !== 'array') parents = [parents] - candidates = candidates.concat(parents) - delete other.extends - list.unshift(other) - } - result = extend.apply(this, list) - } - return result -} - -const op_prefix = exports.op_prefix = str => { - const suffix = str.slice(1) - if (str.startsWith('+')) return {name: suffix, operation: 'add'} - if (str.startsWith('-')) return {name: suffix, operation: 'subtract'} - if (str.startsWith('~')) return {name: suffix, operation: 'intersect'} - if (str.startsWith('^')) return {name: suffix, operation: 'stack'} - return {name: str, operation: 'add'} -} - -exports.op_str = (str, choices={}, order=Object.keys(choices)) => { - let res = op_prefix(str) - for (const key of order) { - if (choices[key].includes(res.name)) { - res.type = key - break - } - } - return res -} \ No newline at end of file diff --git a/src/cli.js b/src/cli.js index 3ab36de..c9b481d 100644 --- a/src/cli.js +++ b/src/cli.js @@ -9,12 +9,8 @@ const yargs = require('yargs') // internals -const u = require('./utils') const io = require('./io') -const points_lib = require('./points') -const outlines_lib = require('./outlines') -const pcbs_lib = require('./pcbs') -const cases_lib = require('./cases') +const ergogen = require('./ergogen') // command line args @@ -47,6 +43,8 @@ const args = yargs if (args.clean) fs.removeSync(args.o) fs.mkdirpSync(args.o) +// config parsing + let config_text try { config_text = fs.readFileSync(args.c).toString() @@ -62,45 +60,38 @@ try { } catch (err) { throw new Error(`Malformed input within "${args.c}": ${err}`) } -config = u.expand_nested_keys(config) -// points +// processing + +const results = ergogen.process(config, args.debug, s => console.log(s)) + +// output + +console.log('Writing output to disk...') -console.log('Parsing points...') -const points = points_lib.parse(config.points) if (args.debug) { - const points_demo = points_lib.visualize(points) - io.dump_model(points_demo, path.join(args.o, 'points/points_demo'), args.debug) - fs.writeJSONSync(path.join(args.o, 'points/points.json'), points, {spaces: 4}) + io.dump_model(results.points.demo, path.join(args.o, 'points/demo'), args.debug) + fs.writeJSONSync(path.join(args.o, 'points/data.json'), results.points.data, {spaces: 4}) } -// outlines - -console.log('Generating outlines...') -const outlines = outlines_lib.parse(config.outlines || {}, points) -for (const [name, outline] of Object.entries(outlines)) { - if (!args.debug && name.startsWith('_')) continue +for (const [name, outline] of Object.entries(results.outlines)) { io.dump_model(outline, path.join(args.o, `outlines/${name}`), args.debug) } -// pcbs +// for (const [name, pcb] of Object.entries(results.pcbs)) { +// const file = path.join(args.o, `pcbs/${name}.kicad_pcb`) +// fs.mkdirpSync(path.dirname(file)) +// fs.writeFileSync(file, pcb) +// } -console.log('Scaffolding PCBs...') -const pcbs = pcbs_lib.parse(config.pcbs || {}, points, outlines) -for (const [pcb_name, pcb_text] of Object.entries(pcbs)) { - const pcb_file = path.join(args.o, `pcbs/${pcb_name}.kicad_pcb`) - fs.mkdirpSync(path.dirname(pcb_file)) - fs.writeFileSync(pcb_file, pcb_text) -} +// for (const [name, _case] of Object.entries(results.cases)) { +// const file = path.join(args.o, `cases/${name}.jscad`) +// fs.mkdirpSync(path.dirname(file)) +// fs.writeFileSync(file, _case) +// } -// cases - -console.log('Extruding cases...') -const cases = cases_lib.parse(config.cases || {}, outlines) -for (const [case_name, case_text] of Object.entries(cases)) { - const case_file = path.join(args.o, `cases/${case_name}.jscad`) - fs.mkdirpSync(path.dirname(case_file)) - fs.writeFileSync(case_file, case_text) +if (args.debug) { + fs.writeJSONSync(path.join(args.o, 'results.json'), results, {spaces: 4}) } // goodbye diff --git a/src/ergogen.js b/src/ergogen.js index d12ad8a..4c0cff5 100644 --- a/src/ergogen.js +++ b/src/ergogen.js @@ -1,8 +1,54 @@ +const prepare = require('./prepare') +const points_lib = require('./points') +const outlines_lib = require('./outlines') +const cases_lib = require('./cases') +const pcbs_lib = require('./pcbs') + +const noop = () => {} + module.exports = { - utils: require('./utils'), - points: require('./points'), - outlines: require('./outlines'), - cases: require('./cases'), - pcbs: require('./pcbs'), - version: '__ergogen_version' + version: '__ergogen_version', + process: (config, debug=false, logger=noop) => { + + logger('Preparing input...') + config = prepare.unnest(config) + config = prepare.inherit(config) + const results = {} + + logger('Parsing points...') + const [points, units] = points_lib.parse(config.points) + if (debug) { + results.points = { + demo: points_lib.visualize(points), + data: points, + units: units + } + } + + logger('Generating outlines...') + const outlines = outlines_lib.parse(config.outlines || {}, points, units) + results.outlines = {} + for (const [name, outline] of Object.entries(outlines)) { + if (!debug && name.startsWith('_')) continue + results.outlines[name] = outline + } + + // logger('Extruding cases...') + // const cases = cases_lib.parse(config.cases || {}, outlines, units) + // results.cases = {} + // for (const [case_name, case_text] of Object.entries(cases)) { + // if (!debug && case_name.startsWith('_')) continue + // results.cases[case_name] = case_text + // } + + // logger('Scaffolding PCBs...') + // const pcbs = pcbs_lib.parse(config.pcbs || {}, points, outlines, units) + // results.pcbs = {} + // for (const [pcb_name, pcb_text] of Object.entries(pcbs)) { + // if (!debug && pcb_name.startsWith('_')) continue + // results.cases[pcb_name] = pcb_text + // } + + return results + } } \ No newline at end of file diff --git a/src/operation.js b/src/operation.js new file mode 100644 index 0000000..c8edc36 --- /dev/null +++ b/src/operation.js @@ -0,0 +1,19 @@ +const op_prefix = exports.op_prefix = str => { + const suffix = str.slice(1) + if (str.startsWith('+')) return {name: suffix, operation: 'add'} + if (str.startsWith('-')) return {name: suffix, operation: 'subtract'} + if (str.startsWith('~')) return {name: suffix, operation: 'intersect'} + if (str.startsWith('^')) return {name: suffix, operation: 'stack'} + return {name: str, operation: 'add'} +} + +exports.operation = (str, choices={}, order=Object.keys(choices)) => { + let res = op_prefix(str) + for (const key of order) { + if (choices[key].includes(res.name)) { + res.type = key + break + } + } + return res +} \ No newline at end of file diff --git a/src/outlines.js b/src/outlines.js index 217095c..b09ce09 100644 --- a/src/outlines.js +++ b/src/outlines.js @@ -1,7 +1,10 @@ const m = require('makerjs') const u = require('./utils') const a = require('./assert') +const o = require('./operation') const Point = require('./point') +const prep = require('./prepare') +const make_anchor = require('./anchor') const rectangle = (w, h, corner, bevel, name='') => { const error = (dim, val) => `Rectangle for "${name}" isn't ${dim} enough for its corner and bevel (${val} - 2 * ${corner} - 2 * ${bevel} <= 0)!` @@ -17,44 +20,29 @@ const rectangle = (w, h, corner, bevel, name='') => { return m.model.moveRelative(res, [corner + bevel, corner + bevel]) } -const relative_anchor = (decl, name, points={}, check_unexpected=true, default_point=new Point()) => { - decl.shift = a.wh(decl.shift || [0, 0], name + '.shift') - const relative = a.sane(decl.relative === undefined ? true : decl.relative, `${name}.relative`, 'boolean') - delete decl.relative - if (relative) { - return size => { - const copy = u.deepcopy(decl) - copy.shift = [copy.shift[0] * size[0], copy.shift[1] * size[1]] - return a.anchor(copy, name, points, check_unexpected, default_point) - } - } - return () => a.anchor(decl, name, points, check_unexpected, default_point) -} - -const layout = exports._layout = (config = {}, points = {}) => { +const layout = exports._layout = (config = {}, points = {}, units = {}) => { // Glue config sanitization - const parsed_glue = u.deepcopy(a.sane(config, 'outlines.glue', 'object')) + const parsed_glue = u.deepcopy(a.sane(config, 'outlines.glue', 'object')()) for (let [gkey, gval] of Object.entries(parsed_glue)) { - gval = a.inherit('outlines.glue', gkey, config) a.detect_unexpected(gval, `outlines.glue.${gkey}`, ['top', 'bottom', 'waypoints', 'extra']) for (const y of ['top', 'bottom']) { a.detect_unexpected(gval[y], `outlines.glue.${gkey}.${y}`, ['left', 'right']) - gval[y].left = relative_anchor(gval[y].left, `outlines.glue.${gkey}.${y}.left`, points) - if (a.type(gval[y].right) != 'number') { - gval[y].right = relative_anchor(gval[y].right, `outlines.glue.${gkey}.${y}.right`, points) + gval[y].left = make_anchor(gval[y].left, `outlines.glue.${gkey}.${y}.left`, points) + if (a.type(gval[y].right)(units) != 'number') { + gval[y].right = make_anchor(gval[y].right, `outlines.glue.${gkey}.${y}.right`, points) } } - gval.waypoints = a.sane(gval.waypoints || [], `outlines.glue.${gkey}.waypoints`, 'array') + gval.waypoints = a.sane(gval.waypoints || [], `outlines.glue.${gkey}.waypoints`, 'array')(units) let wi = 0 gval.waypoints = gval.waypoints.map(w => { const name = `outlines.glue.${gkey}.waypoints[${++wi}]` a.detect_unexpected(w, name, ['percent', 'width']) - w.percent = a.sane(w.percent, name + '.percent', 'number') - w.width = a.wh(w.width, name + '.width') + w.percent = a.sane(w.percent, name + '.percent', 'number')(units) + w.width = a.wh(w.width, name + '.width')(units) return w }) @@ -69,12 +57,19 @@ const layout = exports._layout = (config = {}, points = {}) => { // Layout params sanitization a.detect_unexpected(params, `${export_name}`, expected.concat(['side', 'tags', 'glue', 'size', 'corner', 'bevel', 'bound'])) + const size = a.wh(params.size, `${export_name}.size`)(units) + const relative_units = prep.extend({ + sx: size[0], + sy: size[1] + }, units) + + + const side = a.in(params.side, `${export_name}.side`, ['left', 'right', 'middle', 'both', 'glue']) - const tags = a.sane(params.tags || [], `${export_name}.tags`, 'array') - const size = a.wh(params.size, `${export_name}.size`) - const corner = a.sane(params.corner || 0, `${export_name}.corner`, 'number') - const bevel = a.sane(params.bevel || 0, `${export_name}.bevel`, 'number') - const bound = a.sane(params.bound === undefined ? true : params.bound, `${export_name}.bound`, 'boolean') + const tags = a.sane(params.tags || [], `${export_name}.tags`, 'array')() + const corner = a.sane(params.corner || 0, `${export_name}.corner`, 'number')(relative_units) + const bevel = a.sane(params.bevel || 0, `${export_name}.bevel`, 'number')(relative_units) + const bound = a.sane(params.bound === undefined ? true : params.bound, `${export_name}.bound`, 'boolean')() // Actual layout @@ -100,7 +95,7 @@ const layout = exports._layout = (config = {}, points = {}) => { // extra binding "material", if necessary if (bound) { - let bind = a.trbl(p.meta.bind || 0, `${pname}.bind`) + let bind = a.trbl(p.meta.bind || 0, `${pname}.bind`)(relative_units) // if it's a mirrored key, we swap the left and right bind values if (p.meta.mirrored) { bind = [bind[0], bind[3], bind[2], bind[1]] @@ -133,18 +128,17 @@ const layout = exports._layout = (config = {}, points = {}) => { if (bound && ['middle', 'both', 'glue'].includes(side)) { const default_glue_name = Object.keys(parsed_glue)[0] - const computed_glue_name = a.sane(params.glue || default_glue_name, `${export_name}.glue`, 'string') + const computed_glue_name = a.sane(params.glue || default_glue_name, `${export_name}.glue`, 'string')() const glue_def = parsed_glue[computed_glue_name] a.assert(glue_def, `Field "${export_name}.glue" does not name a valid glue!`) const get_line = (anchor) => { - if (a.type(anchor) == 'number') { + if (a.type(anchor)(relative_units) == 'number') { return u.line([anchor, -1000], [anchor, 1000]) } - // if it wasn't a number, then it's a function returning an achor - // have to feed it `size` first in case it's relative - const from = anchor(size).clone() + // if it wasn't a number, then it's a (possibly relative) achor + const from = anchor(relative_units).clone() const to = from.clone().shift([from.meta.mirrored ? -1 : 1, 0]) return u.line(from.p, to.p) @@ -182,7 +176,7 @@ const layout = exports._layout = (config = {}, points = {}) => { } let waypoints - const is_split = a.type(glue_def.top.right) == 'number' + const is_split = a.type(glue_def.top.right)(relative_units) == 'number' if (is_split) { waypoints = [tip, tlp] .concat(left_waypoints) @@ -210,24 +204,23 @@ const layout = exports._layout = (config = {}, points = {}) => { } } -exports.parse = (config = {}, points = {}) => { +exports.parse = (config = {}, points = {}, units = {}) => { a.detect_unexpected(config, 'outline', ['glue', 'exports']) - const layout_fn = layout(config.glue, points) + const layout_fn = layout(config.glue, points, units) const outlines = {} - const ex = a.sane(config.exports || {}, 'outlines.exports', 'object') + const ex = a.sane(config.exports || {}, 'outlines.exports', 'object')() for (let [key, parts] of Object.entries(ex)) { - parts = a.inherit('outlines.exports', key, ex) - if (a.type(parts) == 'array') { + if (a.type(parts)() == 'array') { parts = {...parts} } - parts = a.sane(parts, `outlines.exports.${key}`, 'object') + parts = a.sane(parts, `outlines.exports.${key}`, 'object')() let result = {models: {}} for (let [part_name, part] of Object.entries(parts)) { const name = `outlines.exports.${key}.${part_name}` - if (a.type(part) == 'string') { - part = a.op_str(part, {outline: Object.keys(outlines)}) + if (a.type(part)() == 'string') { + part = o.operation(part, {outline: Object.keys(outlines)}) } const expected = ['type', 'operation'] part.type = a.in(part.type || 'outline', `${name}.type`, ['keys', 'rectangle', 'circle', 'polygon', 'outline']) @@ -246,45 +239,49 @@ exports.parse = (config = {}, points = {}) => { break case 'rectangle': a.detect_unexpected(part, name, expected.concat(['ref', 'shift', 'rotate', 'size', 'corner', 'bevel', 'mirror'])) - anchor = a.anchor(part, name, points, false) - const size = a.wh(part.size, `${name}.size`) - const corner = a.sane(part.corner || 0, `${name}.corner`, 'number') - const bevel = a.sane(part.bevel || 0, `${name}.bevel`, 'number') - const rect_mirror = a.sane(part.mirror || false, `${name}.mirror`, 'boolean') + const size = a.wh(part.size, `${name}.size`)(units) + const rec_units = prep.extend({ + sx: size[0], + sy: size[1] + }, units) + anchor = a.anchor(part, name, points, false)(rec_units) + const corner = a.sane(part.corner || 0, `${name}.corner`, 'number')(rec_units) + const bevel = a.sane(part.bevel || 0, `${name}.bevel`, 'number')(rec_units) + const rect_mirror = a.sane(part.mirror || false, `${name}.mirror`, 'boolean')() const rect = rectangle(size[0], size[1], corner, bevel, name) arg = anchor.position(u.deepcopy(rect)) if (rect_mirror) { const mirror_part = u.deepcopy(part) a.assert(mirror_part.ref, `Field "${name}.ref" must be speficied if mirroring is required!`) mirror_part.ref = `mirror_${mirror_part.ref}` - anchor = a.anchor(mirror_part, name, points, false) + anchor = make_anchor(mirror_part, name, points, false)(rec_units) const mirror_rect = m.model.moveRelative(u.deepcopy(rect), [-size[0], 0]) arg = u.union(arg, anchor.position(mirror_rect)) } break case 'circle': a.detect_unexpected(part, name, expected.concat(['ref', 'shift', 'rotate', 'radius', 'mirror'])) - anchor = a.anchor(part, name, points, false) - const radius = a.sane(part.radius, `${name}.radius`, 'number') - const circle_mirror = a.sane(part.mirror || false, `${name}.mirror`, 'boolean') + anchor = make_anchor(part, name, points, false)(units) + const radius = a.sane(part.radius, `${name}.radius`, 'number')(units) + const circle_mirror = a.sane(part.mirror || false, `${name}.mirror`, 'boolean')() arg = u.circle(anchor.p, radius) if (circle_mirror) { const mirror_part = u.deepcopy(part) a.assert(mirror_part.ref, `Field "${name}.ref" must be speficied if mirroring is required!`) mirror_part.ref = `mirror_${mirror_part.ref}` - anchor = a.anchor(mirror_part, name, points, false) + anchor = make_anchor(mirror_part, name, points, false)(units) arg = u.union(arg, u.circle(anchor.p, radius)) } break case 'polygon': a.detect_unexpected(part, name, expected.concat(['points'])) - const poly_points = a.sane(part.points, `${name}.points`, 'array') + const poly_points = a.sane(part.points, `${name}.points`, 'array')() const parsed_points = [] let last_anchor = new Point() let poly_index = 0 for (const poly_point of poly_points) { const poly_name = `${name}.points[${++poly_index}]` - const anchor = a.anchor(poly_point, poly_name, points, true, last_anchor) + const anchor = make_anchor(poly_point, poly_name, points, true, last_anchor)(units) parsed_points.push(anchor.p) } arg = u.poly(parsed_points) diff --git a/src/points.js b/src/points.js index 15e06b2..5ed7289 100644 --- a/src/points.js +++ b/src/points.js @@ -1,6 +1,8 @@ const m = require('makerjs') const u = require('./utils') const a = require('./assert') +const prep = require('./prepare') +const make_anchor = require('./anchor') const push_rotation = exports._push_rotation = (list, angle, origin) => { let candidate = origin @@ -13,18 +15,18 @@ const push_rotation = exports._push_rotation = (list, angle, origin) => { }) } -const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key) => { +const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key, units) => { // zone-wide sanitization a.detect_unexpected(zone, `points.zones.${zone_name}`, ['columns', 'rows', 'key']) // the anchor comes from "above", because it needs other zones too (for references) - const cols = a.sane(zone.columns || {}, `points.zones.${zone_name}.columns`, 'object') - const zone_wide_rows = a.sane(zone.rows || {}, `points.zones.${zone_name}.rows`, 'object') + const cols = a.sane(zone.columns || {}, `points.zones.${zone_name}.columns`, 'object')() + const zone_wide_rows = a.sane(zone.rows || {}, `points.zones.${zone_name}.rows`, 'object')() for (const [key, val] of Object.entries(zone_wide_rows)) { - zone_wide_rows[key] = a.sane(val || {}, `points.zones.${zone_name}.rows.${key}`, 'object') + zone_wide_rows[key] = a.sane(val || {}, `points.zones.${zone_name}.rows.${key}`, 'object')() } - const zone_wide_key = a.sane(zone.key || {}, `points.zones.${zone_name}.key`, 'object') + const zone_wide_key = a.sane(zone.key || {}, `points.zones.${zone_name}.key`, 'object')() // algorithm prep @@ -54,47 +56,47 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key) col.stagger || 0, `points.zones.${zone_name}.columns.${col_name}.stagger`, 'number' - ) + )(units) col.spread = a.sane( - col.spread || (first_col ? 0 : 19), + col.spread || (first_col ? 0 : 'u'), `points.zones.${zone_name}.columns.${col_name}.spread`, 'number' - ) + )(units) col.rotate = a.sane( col.rotate || 0, `points.zones.${zone_name}.columns.${col_name}.rotate`, 'number' - ) + )(units) col.origin = a.xy( col.origin || [0, 0], - `points.zones.${zone_name}.columns.${col_name}.origin`, - ) + `points.zones.${zone_name}.columns.${col_name}.origin` + )(units) let override = false col.rows = a.sane( col.rows || {}, `points.zones.${zone_name}.columns.${col_name}.rows`, 'object' - ) + )() if (col.row_overrides) { override = true col.rows = a.sane( col.row_overrides, `points.zones.${zone_name}.columns.${col_name}.row_overrides`, 'object' - ) + )() } for (const [key, val] of Object.entries(col.rows)) { col.rows[key] = a.sane( val || {}, `points.zones.${zone_name}.columns.${col_name}.rows.${key}`, 'object' - ) + )() } col.key = a.sane( col.key || {}, `points.zones.${zone_name}.columns.${col_name}.key`, 'object' - ) + )() // propagating object key to name field @@ -104,7 +106,7 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key) // (while also handling potential overrides) const actual_rows = override ? Object.keys(col.rows) - : Object.keys(a.extend(zone_wide_rows, col.rows)) + : Object.keys(prep.extend(zone_wide_rows, col.rows)) if (!actual_rows.length) { actual_rows.push('default') } @@ -134,14 +136,14 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key) const default_key = { shift: [0, 0], rotate: 0, - padding: 19, + padding: 'u', width: 1, height: 1, skip: false, asym: 'both' } for (const row of actual_rows) { - const key = a.extend( + const key = prep.extend( default_key, global_key, zone_wide_key, @@ -152,12 +154,12 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key) key.name = key.name || `${zone_name}_${col_name}_${row}` key.colrow = `${col_name}_${row}` - key.shift = a.xy(key.shift, `${key.name}.shift`) - key.rotate = a.sane(key.rotate, `${key.name}.rotate`, 'number') - key.width = a.sane(key.width, `${key.name}.width`, 'number') - key.height = a.sane(key.height, `${key.name}.height`, 'number') - key.padding = a.sane(key.padding, `${key.name}.padding`, 'number') - key.skip = a.sane(key.skip, `${key.name}.skip`, 'boolean') + key.shift = a.xy(key.shift, `${key.name}.shift`)(units) + key.rotate = a.sane(key.rotate, `${key.name}.rotate`, 'number')(units) + key.width = a.sane(key.width, `${key.name}.width`, 'number')(units) + key.height = a.sane(key.height, `${key.name}.height`, 'number')(units) + key.padding = a.sane(key.padding, `${key.name}.padding`, 'number')(units) + key.skip = a.sane(key.skip, `${key.name}.skip`, 'boolean')() key.asym = a.in(key.asym, `${key.name}.asym`, ['left', 'right', 'both']) key.col = col key.row = row @@ -184,12 +186,12 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key) return points } -const parse_axis = exports._parse_axis = (config, name, points) => { - if (!['number', 'undefined'].includes(a.type(config))) { - const mirror_obj = a.sane(config || {}, name, 'object') - const distance = a.sane(mirror_obj.distance || 0, `${name}.distance`, 'number') +const parse_axis = exports._parse_axis = (config, name, points, units) => { + if (!['number', 'undefined'].includes(a.type(config)(units))) { + const mirror_obj = a.sane(config || {}, name, 'object')() + const distance = a.sane(mirror_obj.distance || 0, `${name}.distance`, 'number')(units) delete mirror_obj.distance - let axis = a.anchor(mirror_obj, name, points).x + let axis = make_anchor(mirror_obj, name, points)(units).x axis += distance / 2 return axis } else return config @@ -201,7 +203,7 @@ const perform_mirror = exports._perform_mirror = (point, axis) => { if (point.meta.asym == 'left') return ['', null] const mp = point.clone().mirror(axis) const mirrored_name = `mirror_${point.meta.name}` - mp.meta = a.extend(mp.meta, mp.meta.mirror || {}) + mp.meta = prep.extend(mp.meta, mp.meta.mirror || {}) mp.meta.name = mirrored_name mp.meta.colrow = `mirror_${mp.meta.colrow}` mp.meta.mirrored = true @@ -215,32 +217,41 @@ const perform_mirror = exports._perform_mirror = (point, axis) => { exports.parse = (config = {}) => { + // parsing units + const raw_units = prep.extend({ + u: 19, + cx: 18, + cy: 17 + }, a.sane(config.units || {}, 'points.units', 'object')()) + const units = {} + for (const [key, val] of Object.entries(raw_units)) { + units[key] = a.mathnum(val)(units) + } + // config sanitization - a.detect_unexpected(config, 'points', ['zones', 'key', 'rotate', 'mirror']) - const zones = a.sane(config.zones || {}, 'points.zones', 'object') - const global_key = a.sane(config.key || {}, 'points.key', 'object') - const global_rotate = a.sane(config.rotate || 0, 'points.rotate', 'number') + a.detect_unexpected(config, 'points', ['units', 'zones', 'key', 'rotate', 'mirror']) + const zones = a.sane(config.zones || {}, 'points.zones', 'object')() + const global_key = a.sane(config.key || {}, 'points.key', 'object')() + const global_rotate = a.sane(config.rotate || 0, 'points.rotate', 'number')(units) const global_mirror = config.mirror let points = {} let mirrored_points = {} let all_points = {} + // rendering zones for (let [zone_name, zone] of Object.entries(zones)) { - // handle zone-level `extends` clauses - zone = a.inherit('points.zones', zone_name, zones) - // extracting keys that are handled here, not at the zone render level - const anchor = a.anchor(zone.anchor || {}, `points.zones.${zone_name}.anchor`, all_points) - const rotate = a.sane(zone.rotate || 0, `points.zones.${zone_name}.rotate`, 'number') + const anchor = make_anchor(zone.anchor || {}, `points.zones.${zone_name}.anchor`, all_points)(units) + const rotate = a.sane(zone.rotate || 0, `points.zones.${zone_name}.rotate`, 'number')(units) const mirror = zone.mirror delete zone.anchor delete zone.rotate delete zone.mirror // creating new points - const new_points = render_zone(zone_name, zone, anchor, global_key) + const new_points = render_zone(zone_name, zone, anchor, global_key, units) // adjusting new points for (const [new_name, new_point] of Object.entries(new_points)) { @@ -261,7 +272,7 @@ exports.parse = (config = {}) => { all_points = Object.assign(all_points, points) // per-zone mirroring for the new keys - const axis = parse_axis(mirror, `points.zones.${zone_name}.mirror`, all_points) + const axis = parse_axis(mirror, `points.zones.${zone_name}.mirror`, all_points, units) if (axis) { for (const new_point of Object.values(new_points)) { const [mname, mp] = perform_mirror(new_point, axis) @@ -284,7 +295,7 @@ exports.parse = (config = {}) => { } // global mirroring for points that haven't been mirrored yet - const global_axis = parse_axis(global_mirror, `points.mirror`, points) + const global_axis = parse_axis(global_mirror, `points.mirror`, points, units) const global_mirrored_points = {} for (const point of Object.values(points)) { if (global_axis && point.mirrored === undefined) { @@ -306,7 +317,7 @@ exports.parse = (config = {}) => { } // done - return filtered + return [filtered, units] } exports.visualize = (points) => { diff --git a/src/prepare.js b/src/prepare.js new file mode 100644 index 0000000..365914c --- /dev/null +++ b/src/prepare.js @@ -0,0 +1,71 @@ +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)() + if (from === undefined || from === null) return to + if (from === '!!unset') return undefined + if (to_type != from_type) return from + if (from_type == 'object') { + const res = u.deepcopy(to) + for (const key of Object.keys(from)) { + res[key] = _extend(to[key], from[key]) + if (res[key] === undefined) delete res[key] + } + return res + } else if (from_type == 'array') { + const res = u.deepcopy(to) + for (const [i, val] of from.entries()) { + res[i] = _extend(res[i], val) + } + return res + } else return from +} + +const extend = exports.extend = (...args) => { + let res = args[0] + for (const arg of args) { + if (res == arg) continue + res = _extend(res, arg) + } + return res +} + +const _inherit = exports._inherit = (config, root, breadcrumbs) => { + 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 = [newval.extends] + 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('.')}.${key}.extends") does not name a valid 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 + breadcrumbs.pop() + } + return result +} + +const inherit = exports.inherit = (config) => _inherit(config, config, []) diff --git a/src/utils.js b/src/utils.js index d2c4c6f..58fb3c5 100644 --- a/src/utils.js +++ b/src/utils.js @@ -2,7 +2,7 @@ const m = require('makerjs') exports.deepcopy = (value) => JSON.parse(JSON.stringify(value)) -const deep_assign = exports.deep_assign = (obj, key, val) => { +const deep = exports.deep = (obj, key, val) => { const levels = key.split('.') const last = levels.pop() let step = obj @@ -10,21 +10,11 @@ const deep_assign = exports.deep_assign = (obj, key, val) => { step[level] = step[level] || {} step = step[level] } + if (val === undefined) return step[last] step[last] = val return obj } -const expand_nested_keys = exports.expand_nested_keys = (config) => { - if (typeof config == 'object') { - const result = {} - for (const [key, val] of Object.entries(config)) { - deep_assign(result, key, expand_nested_keys(val)) - } - return result - } - return config -} - const eq = exports.eq = (a=[], b=[]) => { return a[0] === b[0] && a[1] === b[1] } diff --git a/test/complex.js b/test/complex.js deleted file mode 100644 index e5221c0..0000000 --- a/test/complex.js +++ /dev/null @@ -1,23 +0,0 @@ -const fs = require('fs-extra') -const path = require('path') -const yaml = require('js-yaml') -const points_lib = require('../src/points') - -const fixtures = path.join(__dirname, 'fixtures') -const absolem_config = yaml.load(fs.readFileSync(path.join(fixtures, 'absolem.yaml')).toString()) - -describe('Absolem', function() { - it('#points', function() { - const expected = fs.readJSONSync(path.join(fixtures, 'absolem_points.json')) - const actual = points_lib.parse(absolem_config.points) - // remove metadata, so that it only checks the points - Object.values(actual).map(val => delete val.meta) - // only check points in the "main" zones - for (const key of Object.keys(actual)) { - if (!expected[key]) { - delete actual[key] - } - } - actual.should.deep.equal(expected) - }) -}) \ No newline at end of file diff --git a/test/complex/index.js b/test/complex/index.js new file mode 100644 index 0000000..4f9e3e3 --- /dev/null +++ b/test/complex/index.js @@ -0,0 +1,28 @@ +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 cap = s => s.charAt(0).toUpperCase() + s.slice(1) + +for (const part of ['points', 'outlines', 'cases', 'pcbs']) { + describe(cap(part), function() { + const dir = path.join(__dirname, part) + for (const input_path of glob.sync(path.join(dir, '*.yaml'))) { + const basename = path.basename(input_path, '.yaml') + const title = basename.split('_').join(' ') + it(title, function() { + const dirname = path.dirname(input_path) + const input = yaml.load(fs.readFileSync(input_path).toString()) + const actual = ergogen.process(input, true) + for (const expected_path of glob.sync(path.join(dirname, basename + '___*'))) { + const expected = JSON.parse(fs.readFileSync(expected_path).toString()) + const comp_path = expected_path.split('___')[1].split('.')[0].split('_').join('.') + u.deep(actual, comp_path).should.deep.equal(expected) + } + }) + } + }) +} \ No newline at end of file diff --git a/test/complex/outlines/001_basic_outline.yaml b/test/complex/outlines/001_basic_outline.yaml new file mode 100644 index 0000000..73c503b --- /dev/null +++ b/test/complex/outlines/001_basic_outline.yaml @@ -0,0 +1,24 @@ +points: + zones: + matrix: + columns: + left.key.bind: [,10,,] + right.key.bind: [,,,10] + rows: + bottom.key.bind: [10,,,] + top.key.bind: [,,10,] + key: + bind: [0, 0, 0, 0] +outlines: + exports: + outline: + main: + type: keys + side: left + size: 20 + min: + type: keys + side: left + bound: false + size: 14 + operation: subtract \ No newline at end of file diff --git a/test/complex/outlines/001_basic_outline___outlines.json b/test/complex/outlines/001_basic_outline___outlines.json new file mode 100644 index 0000000..258bf48 --- /dev/null +++ b/test/complex/outlines/001_basic_outline___outlines.json @@ -0,0 +1,506 @@ +{ + "outline": { + "models": { + "a": { + "models": { + "a": { + "models": {}, + "origin": [ + 0, + 0 + ] + }, + "b": { + "models": { + "a": { + "models": { + "a": { + "models": { + "a": { + "models": { + "a": { + "models": {}, + "origin": [ + 0, + 0 + ] + }, + "b": { + "models": { + "a": { + "models": { + "a": { + "paths": { + "ShapeLine1": { + "type": "line", + "origin": [ + -10, + -10 + ], + "end": [ + 29, + -10 + ] + }, + "ShapeLine4": { + "type": "line", + "origin": [ + -10, + -10 + ], + "end": [ + -10, + 29 + ] + } + }, + "origin": [ + 0, + 0 + ] + }, + "b": { + "paths": {}, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + }, + "b": { + "paths": {}, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + }, + "b": { + "models": { + "a": { + "models": { + "a": { + "paths": { + "ShapeLine3": { + "type": "line", + "origin": [ + -10, + 29 + ], + "end": [ + 29, + 29 + ] + } + }, + "origin": [ + 0, + 0 + ] + }, + "b": { + "paths": {}, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + }, + "b": { + "paths": {}, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + }, + "b": { + "models": { + "a": { + "models": { + "a": { + "paths": { + "ShapeLine2": { + "type": "line", + "origin": [ + 29, + -10 + ], + "end": [ + 29, + 29 + ] + } + }, + "origin": [ + 0, + 0 + ] + }, + "b": { + "paths": {}, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + }, + "b": { + "paths": {}, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + }, + "b": { + "models": { + "a": { + "models": { + "a": { + "paths": {}, + "origin": [ + 0, + 0 + ] + }, + "b": { + "paths": {}, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + }, + "b": { + "paths": {}, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + }, + "b": { + "models": { + "a": { + "models": { + "a": { + "models": { + "a": { + "models": { + "a": { + "models": {}, + "origin": [ + 0, + 0 + ] + }, + "b": { + "paths": { + "ShapeLine1": { + "type": "line", + "origin": [ + -7, + -7 + ], + "end": [ + 7, + -7 + ] + }, + "ShapeLine2": { + "type": "line", + "origin": [ + 7, + -7 + ], + "end": [ + 7, + 7 + ] + }, + "ShapeLine3": { + "type": "line", + "origin": [ + 7, + 7 + ], + "end": [ + -7, + 7 + ] + }, + "ShapeLine4": { + "type": "line", + "origin": [ + -7, + 7 + ], + "end": [ + -7, + -7 + ] + } + }, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + }, + "b": { + "paths": { + "ShapeLine1": { + "type": "line", + "origin": [ + -7, + 12 + ], + "end": [ + 7, + 12 + ] + }, + "ShapeLine2": { + "type": "line", + "origin": [ + 7, + 12 + ], + "end": [ + 7, + 26 + ] + }, + "ShapeLine3": { + "type": "line", + "origin": [ + 7, + 26 + ], + "end": [ + -7, + 26 + ] + }, + "ShapeLine4": { + "type": "line", + "origin": [ + -7, + 26 + ], + "end": [ + -7, + 12 + ] + } + }, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + }, + "b": { + "paths": { + "ShapeLine1": { + "type": "line", + "origin": [ + 12, + -7 + ], + "end": [ + 26, + -7 + ] + }, + "ShapeLine2": { + "type": "line", + "origin": [ + 26, + -7 + ], + "end": [ + 26, + 7 + ] + }, + "ShapeLine3": { + "type": "line", + "origin": [ + 26, + 7 + ], + "end": [ + 12, + 7 + ] + }, + "ShapeLine4": { + "type": "line", + "origin": [ + 12, + 7 + ], + "end": [ + 12, + -7 + ] + } + }, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + }, + "b": { + "paths": { + "ShapeLine1": { + "type": "line", + "origin": [ + 12, + 12 + ], + "end": [ + 26, + 12 + ] + }, + "ShapeLine2": { + "type": "line", + "origin": [ + 26, + 12 + ], + "end": [ + 26, + 26 + ] + }, + "ShapeLine3": { + "type": "line", + "origin": [ + 26, + 26 + ], + "end": [ + 12, + 26 + ] + }, + "ShapeLine4": { + "type": "line", + "origin": [ + 12, + 26 + ], + "end": [ + 12, + 12 + ] + } + }, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + } + }, + "origin": [ + 0, + 0 + ] + } +} \ No newline at end of file diff --git a/test/complex/outlines/002_gluing.yaml b/test/complex/outlines/002_gluing.yaml new file mode 100644 index 0000000..cd1a898 --- /dev/null +++ b/test/complex/outlines/002_gluing.yaml @@ -0,0 +1,44 @@ +points: + zones: + matrix: + columns: + left.key.bind: [,10,,] + right.key.bind: [,,,10] + rows: + bottom.key.bind: [10,,,] + top.key.bind: [,,10,] + key: + bind: [0, 0, 0, 0] + rotate: -20 + mirror: + ref: matrix_right_top + distance: 30 +outlines: + glue: + default: + top: + left: + ref: matrix_right_top + shift: [,sy / 2] + right: + ref: mirror_matrix_right_top + shift: [,sy / 2] + bottom: + left: + ref: matrix_right_bottom + shift: [,sy / -2] + right: + ref: mirror_matrix_right_bottom + shift: [,sy / -2] + exports: + outline: + main: + type: keys + side: both + size: 20 + min: + type: keys + side: both + bound: false + size: 14 + operation: subtract \ No newline at end of file diff --git a/test/complex/points/001_basic_2x2.yaml b/test/complex/points/001_basic_2x2.yaml new file mode 100644 index 0000000..0714f57 --- /dev/null +++ b/test/complex/points/001_basic_2x2.yaml @@ -0,0 +1,9 @@ +points: + zones: + matrix: + columns: + left: + right: + rows: + bottom: + top: \ No newline at end of file diff --git a/test/complex/points/001_basic_2x2___points_data.json b/test/complex/points/001_basic_2x2___points_data.json new file mode 100644 index 0000000..8da5055 --- /dev/null +++ b/test/complex/points/001_basic_2x2___points_data.json @@ -0,0 +1,130 @@ +{ + "matrix_left_bottom": { + "x": 0, + "y": 0, + "r": 0, + "meta": { + "shift": [ + 0, + 0 + ], + "rotate": 0, + "padding": 19, + "width": 1, + "height": 1, + "skip": false, + "asym": "both", + "name": "matrix_left_bottom", + "colrow": "left_bottom", + "col": { + "stagger": 0, + "spread": 0, + "rotate": 0, + "origin": [ + 0, + 0 + ], + "rows": {}, + "key": {}, + "name": "left" + }, + "row": "bottom" + } + }, + "matrix_left_top": { + "x": 0, + "y": 19, + "r": 0, + "meta": { + "shift": [ + 0, + 0 + ], + "rotate": 0, + "padding": 19, + "width": 1, + "height": 1, + "skip": false, + "asym": "both", + "name": "matrix_left_top", + "colrow": "left_top", + "col": { + "stagger": 0, + "spread": 0, + "rotate": 0, + "origin": [ + 0, + 0 + ], + "rows": {}, + "key": {}, + "name": "left" + }, + "row": "top" + } + }, + "matrix_right_bottom": { + "x": 19, + "y": 0, + "r": 0, + "meta": { + "shift": [ + 0, + 0 + ], + "rotate": 0, + "padding": 19, + "width": 1, + "height": 1, + "skip": false, + "asym": "both", + "name": "matrix_right_bottom", + "colrow": "right_bottom", + "col": { + "stagger": 0, + "spread": 19, + "rotate": 0, + "origin": [ + 0, + 0 + ], + "rows": {}, + "key": {}, + "name": "right" + }, + "row": "bottom" + } + }, + "matrix_right_top": { + "x": 19, + "y": 19, + "r": 0, + "meta": { + "shift": [ + 0, + 0 + ], + "rotate": 0, + "padding": 19, + "width": 1, + "height": 1, + "skip": false, + "asym": "both", + "name": "matrix_right_top", + "colrow": "right_top", + "col": { + "stagger": 0, + "spread": 19, + "rotate": 0, + "origin": [ + 0, + 0 + ], + "rows": {}, + "key": {}, + "name": "right" + }, + "row": "top" + } + } +} diff --git a/test/complex/points/002_adjustments.yaml b/test/complex/points/002_adjustments.yaml new file mode 100644 index 0000000..45026a4 --- /dev/null +++ b/test/complex/points/002_adjustments.yaml @@ -0,0 +1,13 @@ +points: + zones: + matrix: + columns: + left: + right: + stagger: 5 + spread: 25 + rotate: 5 + origin: [-9, -9] + rows: + bottom: + top: \ No newline at end of file diff --git a/test/fixtures/absolem.yaml b/test/fixtures/absolem.yaml deleted file mode 100644 index 1a381e9..0000000 --- a/test/fixtures/absolem.yaml +++ /dev/null @@ -1,707 +0,0 @@ -points: - zones: - matrix: - anchor: - rotate: 5 - columns: - pinky: - rotate: -5 - origin: [7, -7] - rows: - bottom: - home: - bind: [,15,-1] - top: - bind: [,15,-1] - key: - column_net: P1 - column_mark: P - ring: - stagger: 12 - rows: - bottom: - bind: [,,,10] - home: - bind: [,10] - top: - bind: [,10] - key: - column_net: P0 - column_mark: R - middle: - stagger: 5 - rows: - bottom: - bind: [,10,,10] - home: - bind: [,10,,10] - top: - key: - column_net: P2 - column_mark: M - index: - stagger: -6 - rows: - bottom: - bind: [,10] - home: - bind: [,,,10] - top: - bind: [,,,10] - key: - column_net: P3 - column_mark: X - inner: - stagger: -2 - rows: - bottom: - bind: [,,10,] - home: - bind: [,,,10] - top: - bind: [,,,10] - key: - column_net: P4 - column_mark: I - rows: - bottom: - bind: [10] - row_net: P16 - row_mark: LB - mirror: - row_net: P7 - row_mark: RB - home: - bind: [10] - row_net: P14 - row_mark: LH - mirror: - row_net: P6 - row_mark: RH - top: - row_net: P15 - row_mark: LT - mirror: - row_net: P5 - row_mark: RT - s19: - extends: matrix - columns: - pinky: - rows: - bottom: - footprints: - diode: - anchor: - shift: [,12] - home: - footprints: - diode: - anchor: - shift: [,6] - top: - footprints: - row_ext: - anchor: - shift: [, -10] - extra_column: - type: pad - anchor: - shift: [-4, 4] - nets: - net: 'P19' - params: - width: 2 - height: 2 - front: false - text: 'E' - align: right - key: - footprints: - row_ext: - type: pad - anchor: - shift: [-4, -4] - nets: - net: '!row_net' - params: - width: 2 - height: 2 - front: false - text: '!row_mark' - align: right - ring: - rows: - bottom: - footprints: - diode: - anchor: - shift: [,5] - home: - footprints: - diode: - anchor: - shift: [,2.5] - middle: - rows: - top: - footprints: - diode: - anchor: - shift: [,-6] - home: - footprints: - diode: - anchor: - shift: [,-3] - index: - rows: - bottom: - footprints: - diode: - anchor: - shift: [-8.25,6] - home: - footprints: - diode: - anchor: - shift: [-8.25,3] - top: - footprints: - diode: - anchor: - shift: [-8.25,] - rows: - top: - footprints: - mx: - anchor: - rotate: 180 - alps: - anchor: - rotate: 180 - choc: - anchor: - rotate: 180 - col_ext: - type: pad - anchor: - shift: [4, 4] - nets: - net: '!column_net' - params: - width: 2 - height: 2 - front: false - text: '!column_mark' - bottom: - footprints: - diode: - anchor: - rotate: 270 - col_ext: - type: pad - anchor: - shift: [4, -4] - nets: - net: '!column_net' - params: - width: 2 - height: 2 - front: false - text: '!column_mark' - key: - tags: - s19: true - footprints: &quad - mx: - type: mx - nets: - from: '!colrow' - to: '!column_net' - alps: - type: alps - nets: - from: '!colrow' - to: '!column_net' - <<: &choc - choc: - type: choc - nets: - from: '!colrow' - to: '!column_net' - diode: - type: diode - anchor: - rotate: 90 - shift: [8.25, 0] - nets: - from: '!colrow' - to: '!row_net' - mirror: - footprints: &quad_mirror - mx: - nets: - from: '!column_net' - to: '!colrow' - alps: - nets: - from: '!column_net' - to: '!colrow' - <<: &choc_mirror - choc: - nets: - from: '!column_net' - to: '!colrow' - s18: - extends: matrix - columns: - pinky: - stagger: 1 - origin: [7, -8] - rows: - top: - footprints: - choc: - anchor: - rotate: 180 - key: - padding: 18 - tags: - s18: true - footprints: *choc - mirror: - footprints: *choc_mirror - thumbfan: - anchor: - ref: matrix_inner_bottom - shift: [-7, -19] - columns: - near: - spread: 21.25 - rotate: -28 - origin: [9.5, -9] - rows: - thumb: - bind: [10,5,,] - column_net: P2 - tags: - classic: true - footprints: - choc: - anchor: - rotate: 180 - diode: - anchor: - shift: [8,] - row_ext: - type: pad - anchor: - shift: [-3, 9] - nets: - net: '!row_net' - params: - width: 2 - height: 2 - front: false - text: '!row_mark' - align: right - home: - spread: 21.25 - rotate: -28 - origin: [11.75, -9] - rows: - thumb: - bind: [,10,,15] - column_net: P3 - tags: - classic: true - uniform: true - footprints: - diode: - anchor: - shift: [8,] - far: - rows: - thumb: - bind: [-1,,,5] - column_net: P4 - tags: - classic: true - footprints: - choc: - anchor: - rotate: 180 - diode: - anchor: - shift: [-4,] - rotate: 0 - mirror: - footprints: - diode: - anchor: - rotate: 180 - rows: - thumb: - row_net: P10 - row_mark: LF - footprints: - diode: - anchor: - shift: [0, 9] - rotate: 180 - mirror: - row_net: P8 - row_mark: RF - footprints: - diode: - anchor: - rotate: 0 - key: - footprints: *quad - mirror: *quad_mirror - unifar: - anchor: - ref: thumbfan_home_thumb - columns: - home_again: - rotate: -28 - origin: [9.5, -9] - key: - skip: true - far: - rows: - thumb: - bind: [-1,,,5] - key: - column_net: P4 - footprints: - choc: - anchor: - rotate: 180 - diode: '!!unset' - mirror: - footprints: - diode: '!!unset' - tags: - uniform: true - rows: - thumb: - row_net: P10 - mirror: - row_net: P8 - key: - footprints: *quad - mirror: *quad_mirror - uninear: - anchor: - ref: thumbfan_home_thumb - columns: - home_again: - spread: -19 - rotate: 28 - origin: [-9.5, -9] - key: - skip: true - near: - rows: - thumb: - bind: [10,5,,] - key: - column_net: P2 - footprints: - choc: - anchor: - rotate: 180 - diode: '!!unset' - mirror: - footprints: - diode: '!!unset' - tags: - uniform: true - rows: - thumb: - row_net: P10 - mirror: - row_net: P8 - key: - footprints: *quad - mirror: *quad_mirror - key: - bind: [0,0,0,0] - rotate: -20 - mirror: - ref: matrix_pinky_home - distance: 223.7529778 -outlines: - glue: - classic_s19: - top: - left: - ref: matrix_inner_top - shift: [, 0.5] - right: - ref: mirror_matrix_inner_top - shift: [, 0.5] - bottom: - left: - ref: thumbfan_far_thumb - shift: [0.5, 0] - rotate: 90 - right: - ref: mirror_thumbfan_far_thumb - shift: [0.5, 0] - rotate: 90 - waypoints: - - percent: 50 - width: 50 - - percent: 90 - width: 25 - uniform_s19: - extends: classic_s19 - bottom: - left: - ref: unifar_far_thumb - right: - ref: mirror_unifar_far_thumb - classic_s18: - extends: classic_s19 - top: - left: - ref: s18_inner_top - right: - ref: mirror_s18_inner_top - uniform_s18: - extends: - - uniform_s19 - - classic_s18 - exports: - classic_s19_outline: - main: - type: keys - side: both - tags: - - s19 - - classic - glue: classic_s19 - size: 13.5 - corner: .5 - uniform_s19_outline: - extends: classic_s19_outline - main: - tags: - - s19 - - uniform - glue: uniform_s19 - uniform_s18_outline: - extends: uniform_s19_outline - main: - tags: - - s18 - - uniform - glue: uniform_s18 - mounting_holes: - ring_top: - type: circle - ref: matrix_ring_home - shift: [-10, 5] - radius: 2.25 - mirror: true - ring_bottom: - type: circle - ref: matrix_ring_home - shift: [-9, -9] - radius: 2.25 - mirror: true - operation: stack - index_top: - type: circle - ref: matrix_index_home - shift: [9.5, 9.5] - radius: 2.25 - mirror: true - operation: stack - index_bottom: - type: circle - ref: matrix_index_home - shift: [9.5, -9.5] - radius: 2.25 - mirror: true - operation: stack - thumb_near: - type: circle - ref: thumbfan_home_thumb - shift: [-12, 2] - radius: 2.25 - mirror: true - operation: stack - thumb_far: - type: circle - ref: thumbfan_home_thumb - shift: [12, 2] - radius: 2.25 - mirror: true - operation: stack - middle: - type: circle - ref: thumbfan_home_thumb - shift: [-8, 19] - radius: 2.25 - mirror: true - operation: stack - intersected_outline: - one: - type: outline - name: classic_s19_outline - two: - type: outline - name: uniform_s18_outline - operation: intersect - controller_cutout: - type: rectangle - ref: - - s18_inner_top - - mirror_s18_inner_top - shift: [-10, -5] - size: [20, 10] - operation: subtract - mounting_holes: - type: outline - name: mounting_holes - operation: subtract - classic_s19_switches: - main: - type: keys - side: both - tags: - - classic - glue: classic_s19 - size: 14 - bound: false - uniform_s19_switches: - main: - type: keys - side: both - tags: - - uniform - glue: uniform_s19 - size: 14 - bound: false - pcb_middle: - raw: - type: keys - side: middle - tags: - - s19 - - classic - glue: classic_s19 - size: 24 - helper1: - type: rectangle - size: [25, 5] - ref: thumbfan_home_thumb - shift: [0, 12] - mirror: true - helper2: - type: rectangle - size: [25, 5] - ref: thumbfan_far_thumb - shift: [-25, 12] - mirror: true - outer_bounds: - type: outline - name: classic_s19_outline - operation: intersect -pcbs: - main: - outlines: - edge: - outline: intersected_outline - layer: Edge.Cuts - middle: - outline: pcb_middle - layer: F.SilkS - footprints: - mcu: - type: promicro - anchor: - ref: - - s18_inner_top - - mirror_s18_inner_top - shift: [0, -23] - rotate: 270 - slider: - type: slider - anchor: - ref: - - s18_inner_top - - mirror_s18_inner_top - shift: [0, -7.5] - nets: - from: RAWER - to: RAW - params: - side: B - reset: - type: reset - anchor: - ref: - - s18_inner_top - - mirror_s18_inner_top - shift: [0, -17] - rotate: 90 - nets: - from: RST - to: GND - params: - side: B - extra_row_left: - type: pad - anchor: - ref: - - s18_inner_top - - mirror_s18_inner_top - shift: [-15, -10] - nets: - net: 'P20' - params: - width: 3 - height: 3 - front: false - text: 'LN' - align: right - extra_row_right: - type: pad - anchor: - ref: - - s18_inner_top - - mirror_s18_inner_top - shift: [15, -10] - nets: - net: 'P21' - params: - width: 3 - height: 3 - front: false - text: 'RN' - battery: - type: jstph - anchor: - ref: - - matrix_inner_bottom - - mirror_matrix_inner_bottom - shift: [0, -43] - rotate: 180 - nets: - pos: RAWER - neg: GND - led: - type: rgb - anchor: - ref: - - matrix_inner_bottom - - mirror_matrix_inner_bottom - shift: [0, -48] - nets: - din: 'P9' - dout: '' diff --git a/test/fixtures/absolem_points.json b/test/fixtures/absolem_points.json deleted file mode 100644 index 308dc28..0000000 --- a/test/fixtures/absolem_points.json +++ /dev/null @@ -1,182 +0,0 @@ -{ - "matrix_pinky_bottom": { - "x": 0, - "y": 0, - "r": -15 - }, - "matrix_pinky_home": { - "x": 4.9175619, - "y": 18.3525907, - "r": -15 - }, - "matrix_pinky_top": { - "x": 9.8351237, - "y": 36.7051814, - "r": -15 - }, - "matrix_ring_bottom": { - "x": 22.7244416, - "y": 5.176704, - "r": -20 - }, - "matrix_ring_home": { - "x": 29.2228244, - "y": 23.0308638, - "r": -20 - }, - "matrix_ring_top": { - "x": 35.7212071, - "y": 40.8850235, - "r": -20 - }, - "matrix_middle_bottom": { - "x": 42.2887022, - "y": 3.3767843, - "r": -20 - }, - "matrix_middle_home": { - "x": 48.7870849, - "y": 21.2309442, - "r": -20 - }, - "matrix_middle_top": { - "x": 55.2854676, - "y": 39.0851039, - "r": -20 - }, - "matrix_index_bottom": { - "x": 58.0907411, - "y": -8.7597541, - "r": -20 - }, - "matrix_index_home": { - "x": 64.5891238, - "y": 9.0944057, - "r": -20 - }, - "matrix_index_top": { - "x": 71.0875065, - "y": 26.9485655, - "r": -20 - }, - "matrix_inner_bottom": { - "x": 75.2608606, - "y": -17.1375221, - "r": -20 - }, - "matrix_inner_home": { - "x": 81.7592433, - "y": 0.7166377, - "r": -20 - }, - "matrix_inner_top": { - "x": 88.257626, - "y": 18.5707976, - "r": -20 - }, - "thumbfan_near_thumb": { - "x": 62.1846295, - "y": -32.5975409, - "r": -20 - }, - "thumbfan_home_thumb": { - "x": 82.5841162, - "y": -47.013742, - "r": -48 - }, - "thumbfan_far_thumb": { - "x": 94.7890169, - "y": -68.8083815, - "r": -76 - }, - "mirror_matrix_pinky_bottom": { - "x": 233.5881016, - "y": 0, - "r": 15 - }, - "mirror_matrix_pinky_home": { - "x": 228.67053969999998, - "y": 18.3525907, - "r": 15 - }, - "mirror_matrix_pinky_top": { - "x": 223.7529779, - "y": 36.7051814, - "r": 15 - }, - "mirror_matrix_ring_bottom": { - "x": 210.86365999999998, - "y": 5.176704, - "r": 20 - }, - "mirror_matrix_ring_home": { - "x": 204.36527719999998, - "y": 23.0308638, - "r": 20 - }, - "mirror_matrix_ring_top": { - "x": 197.8668945, - "y": 40.8850235, - "r": 20 - }, - "mirror_matrix_middle_bottom": { - "x": 191.29939939999997, - "y": 3.3767843, - "r": 20 - }, - "mirror_matrix_middle_home": { - "x": 184.8010167, - "y": 21.2309442, - "r": 20 - }, - "mirror_matrix_middle_top": { - "x": 178.30263399999998, - "y": 39.0851039, - "r": 20 - }, - "mirror_matrix_index_bottom": { - "x": 175.49736049999998, - "y": -8.7597541, - "r": 20 - }, - "mirror_matrix_index_home": { - "x": 168.99897779999998, - "y": 9.0944057, - "r": 20 - }, - "mirror_matrix_index_top": { - "x": 162.5005951, - "y": 26.9485655, - "r": 20 - }, - "mirror_matrix_inner_bottom": { - "x": 158.327241, - "y": -17.1375221, - "r": 20 - }, - "mirror_matrix_inner_home": { - "x": 151.82885829999998, - "y": 0.7166377, - "r": 20 - }, - "mirror_matrix_inner_top": { - "x": 145.3304756, - "y": 18.5707976, - "r": 20 - }, - "mirror_thumbfan_near_thumb": { - "x": 171.4034721, - "y": -32.5975409, - "r": 20 - }, - "mirror_thumbfan_home_thumb": { - "x": 151.00398539999998, - "y": -47.013742, - "r": 48 - }, - "mirror_thumbfan_far_thumb": { - "x": 138.79908469999998, - "y": -68.8083815, - "r": 76 - } -} diff --git a/test/point.js b/test/point.js deleted file mode 100644 index dff000d..0000000 --- a/test/point.js +++ /dev/null @@ -1,8 +0,0 @@ -const Point = require('../src/point') - -describe('Point', function() { - it('#constructor', function() { - const point = new Point(1, 2, 45) - point.p.should.deep.equal([1, 2]) - }) -}) \ No newline at end of file diff --git a/test/unit/prepare.js b/test/unit/prepare.js new file mode 100644 index 0000000..d74d839 --- /dev/null +++ b/test/unit/prepare.js @@ -0,0 +1,44 @@ +const p = require('../../src/prepare') + +describe('Prepare', function() { + it('unnest', function() { + p.unnest({'a.b.c': 1}).should.deep.equal({a: {b: {c: 1}}}) + p.unnest({'a.b.c': { + d: 2, + 'e.f': 3 + }}).should.deep.equal({a: {b: {c: {d: 2, e: {f: 3}}}}}) + }) + + it('extend', function() { + p.extend('something', undefined).should.equal('something') + 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([3, 2, 1], [null, 4, 5]).should.deep.equal([3, 4, 5]) + }) + + it('inherit', function() { + p.inherit({ + a: { + x: 1, + y: 2 + }, + b: { + extends: 'a', + z: 3 + }, + c: { + extends: 'b', + w: 4 + } + }).c.should.deep.equal({ + x: 1, + y: 2, + z: 3, + w: 4 + }) + }) +}) \ No newline at end of file diff --git a/test/utils.js b/test/utils.js deleted file mode 100644 index cd7e4f2..0000000 --- a/test/utils.js +++ /dev/null @@ -1,7 +0,0 @@ -const u = require('../src/utils') - -describe('Utils', function() { - it('deep_assign', function() { - u.deep_assign({}, 'a.b.c', 1).should.deep.equal({a: {b: {c: 1}}}) - }) -}) \ No newline at end of file