From 0b418ac635488943e88dbc751e0c97ba3446f198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczak?= Date: Wed, 16 Feb 2022 21:44:38 +0100 Subject: [PATCH 1/2] Breaking: support path expressions instead of level. --- CHANGELOG.md | 3 ++ README.md | 6 ++-- mod.js | 85 ++++++++++++++++++++++++++++++++++++++++++++------- quickstart.js | 8 ++--- test.js | 17 ++++++----- 5 files changed, 93 insertions(+), 26 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0f86c1e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# 0.3.0 + +Breaking: support path expressions instead of level. \ No newline at end of file diff --git a/README.md b/README.md index 9203c69..72aa14d 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,13 @@ A high-level wrapper over [JsonHilo](https://github.com/xtao-org/jsonhilo) which ```js // see also quickstart.js // replace vx.y.z below with latest/desired version -import {JsonStrum} from 'https://cdn.jsdelivr.net/gh/xtao-org/jsonstrum@v0.2.0/mod.js' +import {JsonStrum} from 'https://cdn.jsdelivr.net/gh/xtao-org/jsonstrum@v0.3.0/mod.js' const s = JsonStrum({ object: (object) => console.log('object', object), array: (array) => console.log('array', array), - // will only parse and emit objects at this level of nesting - level: 1, + // will only parse and emit objects at given path + path: [{}, {}] }) s.push(` diff --git a/mod.js b/mod.js index e413991..3c0ba3c 100644 --- a/mod.js +++ b/mod.js @@ -1,23 +1,68 @@ import { JsonHigh } from "./deps.js" +// [{e: '*'}] + +const matchPath = (path, expr) => { + if (path.length !== expr.length) return false + for (let i = 0; i < expr.length; ++i) { + const e = expr[i] + const p = path[i] + + if (e === p) continue + if (Array.isArray(e)) { + if (e.length === 0) throw Error('empty alternative') + for (const k of e) { + if (p === k) continue + } + return false + } + // todo: + // assume object + const {range, regex} = e + if (range !== undefined) { + if (regex !== undefined) throw Error(`can't have regex & range simultaneously`) + // assume no more than 2 elements + // could also support step to match every nth element + const [from, to] = e + if (from !== undefined) { + if (p < from) return false + } + if (to !== undefined) { + if (p > to) return false + } + } + if (regex !== undefined) { + // assume match is a regex + if (regex.test(p)) continue + // throw Error(`must have either match or range`) + } + // assume empty object + continue + } + return true +} + export const JsonStrum = ({ object, array, - level = 0 + // level = 0, + path = [{}], } = {}) => { const ancestors = [] let parent = null let current = null - let key = null let currentLevel = 0 + const currentPath = [-1] + const level = path.length - 1 const close = () => { --currentLevel - if (currentLevel === level) { + currentPath.pop() + if (currentLevel === level && matchPath(currentPath, path)) { if (Array.isArray(current)) { - array?.(current) + array?.(current, currentPath) } else { - object?.(current) + object?.(current, currentPath) } current = null parent = null @@ -25,45 +70,63 @@ export const JsonStrum = ({ if (Array.isArray(parent)) { parent.push(current) } else { - parent[key] = current + parent[currentPath.at(-1)] = current } current = parent parent = ancestors.pop() } } + const incrementIndex = () => { + const last = currentPath.at(-1) + if (typeof last === 'number') { + currentPath[currentPath.length - 1] += 1 + } + // else { + // currentPath.push(0) + // } + } + return JsonHigh({ openArray: () => { ++currentLevel - if (currentLevel > level) { + if (currentLevel > level && matchPath(currentPath.slice(0, path.length), path)) { if (current !== null) { ancestors.push(parent) parent = current } current = [] } + incrementIndex() + currentPath.push(-1) }, openObject: () => { ++currentLevel - if (currentLevel > level) { + if (currentLevel > level && matchPath(currentPath.slice(0, path.length), path)) { if (current !== null) { ancestors.push(parent) parent = current } current = {} } + incrementIndex() + currentPath.push("") }, closeArray: close, closeObject: close, - key: (k) => { if (currentLevel > level) key = k }, + key: (k) => { + // if (currentLevel > level && matchPath(currentPath.slice(0, path.length), path)) key = k + currentPath[currentPath.length - 1] = k + }, value: (value) => { - if (currentLevel > level) { + if (currentLevel > level && matchPath(currentPath.slice(0, path.length), path)) { if (Array.isArray(current)) { current.push(value) } else { - current[key] = value + current[currentPath.at(-1)] = value } } + incrementIndex() }, }) } diff --git a/quickstart.js b/quickstart.js index 8c8621c..bb4957a 100644 --- a/quickstart.js +++ b/quickstart.js @@ -1,9 +1,9 @@ -import {JsonStrum} from 'https://cdn.jsdelivr.net/gh/xtao-org/jsonstrum@v0.2.0/mod.js' +import {JsonStrum} from './mod.js' const s = JsonStrum({ - object: (object) => console.log('object', object), - array: (array) => console.log('array', array), - level: 1, + object: (object, p) => console.log('object', object, p), + array: (array, p) => console.log('array', array, p), + path: [{}, {}], }) s.push(` diff --git a/test.js b/test.js index abf66c6..522e055 100644 --- a/test.js +++ b/test.js @@ -1,8 +1,9 @@ import {JsonStrum} from './mod.js' const s = JsonStrum({ - object: (object) => console.log('object', object), - array: (array) => console.log('array', array), + object: (object, p) => console.log('object', object, p), + array: (array, p) => console.log('array', array, p), + path: [{}, "sub"] }) s.push(` @@ -19,9 +20,9 @@ s.push(`, [1, 2, 3]`) const s2 = JsonStrum({ - object: (object) => console.log('object', object), - array: (array) => console.log('array', array), - level: 1, + object: (object, p) => console.log('object', object, p), + array: (array, p) => console.log('array', array, p), + path: [{}, {}] }) s2.push(`[ @@ -37,9 +38,9 @@ s2.push(`[ const s3 = JsonStrum({ - object: (object) => console.log('object', object), - array: (array) => console.log('array', array), - level: 3, + object: (object, p) => console.log('object', object, p), + array: (array, p) => console.log('array', array, p), + path: [{}, {}, {}, {}] }) s3.push(` From 56241426906cde14b5245822d5fcf49b81014697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczak?= Date: Wed, 16 Feb 2022 23:26:28 +0100 Subject: [PATCH 2/2] todo comments --- mod.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/mod.js b/mod.js index 3c0ba3c..f40e4d4 100644 --- a/mod.js +++ b/mod.js @@ -1,8 +1,14 @@ import { JsonHigh } from "./deps.js" -// [{e: '*'}] +// todo: recursive descent, e.g. [{}, "x", {depthRange: []}, "obj"] +// depthRange: [0, 3] would be the same as ...([] | [{}] | [{}, {}] | [{}, {}, {}]) +// depthRange: [] would be unbounded +// could also abstract the matcher into a module const matchPath = (path, expr) => { + // perhaps this should only check if prefix of path matches + // i.e. it's enough that path.length >= expr.length + // or maybe there should be a flag parameter which determines whether the match should be exact or just prefix if (path.length !== expr.length) return false for (let i = 0; i < expr.length; ++i) { const e = expr[i] @@ -42,10 +48,11 @@ const matchPath = (path, expr) => { return true } +// perhaps this should also support non-object values (primitives) export const JsonStrum = ({ object, array, - // level = 0, + // perhaps rename to query or pathQuery or pathExpr path = [{}], } = {}) => { const ancestors = [] @@ -58,6 +65,7 @@ export const JsonStrum = ({ const close = () => { --currentLevel currentPath.pop() + // if currentPath matches exactly if (currentLevel === level && matchPath(currentPath, path)) { if (Array.isArray(current)) { array?.(current, currentPath) @@ -66,6 +74,7 @@ export const JsonStrum = ({ } current = null parent = null + // if a prefix of currentPath matches } else if (currentLevel > level) { if (Array.isArray(parent)) { parent.push(current) @@ -82,14 +91,12 @@ export const JsonStrum = ({ if (typeof last === 'number') { currentPath[currentPath.length - 1] += 1 } - // else { - // currentPath.push(0) - // } } return JsonHigh({ openArray: () => { ++currentLevel + // if a prefix of current path matches if (currentLevel > level && matchPath(currentPath.slice(0, path.length), path)) { if (current !== null) { ancestors.push(parent) @@ -102,6 +109,7 @@ export const JsonStrum = ({ }, openObject: () => { ++currentLevel + // if a prefix of current path matches if (currentLevel > level && matchPath(currentPath.slice(0, path.length), path)) { if (current !== null) { ancestors.push(parent) @@ -119,6 +127,7 @@ export const JsonStrum = ({ currentPath[currentPath.length - 1] = k }, value: (value) => { + // if a prefix of current path matches if (currentLevel > level && matchPath(currentPath.slice(0, path.length), path)) { if (Array.isArray(current)) { current.push(value)