mirror of
https://github.com/TECHNOFAB11/ergogen.git
synced 2025-12-12 08:00:06 +01:00
Refactor, units, tests
This commit is contained in:
parent
519c34bc60
commit
cd90705ba1
24 changed files with 1221 additions and 1222 deletions
61
src/anchor.js
Normal file
61
src/anchor.js
Normal file
|
|
@ -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
|
||||
}
|
||||
170
src/assert.js
170
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
|
||||
}
|
||||
59
src/cli.js
59
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
19
src/operation.js
Normal file
19
src/operation.js
Normal file
|
|
@ -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
|
||||
}
|
||||
107
src/outlines.js
107
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)
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
71
src/prepare.js
Normal file
71
src/prepare.js
Normal file
|
|
@ -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, [])
|
||||
14
src/utils.js
14
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]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue