Add support for collection keys in toObject.
This commit is contained in:
parent
aebe488dad
commit
4105eb7e0d
@ -40,7 +40,11 @@ const lines = [
|
||||
` Further comments below. As I will now demonstrate, there is no simple`,
|
||||
` even if embedded`,
|
||||
` way of dealing with this problem.`,
|
||||
``
|
||||
`author`,
|
||||
` name Second Person`,
|
||||
` email second@secondperson.com`,
|
||||
` More text,`,
|
||||
` across two lines.`,
|
||||
]
|
||||
|
||||
// Schema
|
||||
@ -76,7 +80,10 @@ async function main() {
|
||||
}),
|
||||
'scripts': () => toObject({ '#any': true }),
|
||||
'devDependencies': () => toObject(),
|
||||
'author': () => toObject({ name: true, email: true, '#text': true })
|
||||
'author': {
|
||||
type: 'collection',
|
||||
handle: () => toObject({ name: true, email: true, '#text': true })
|
||||
}
|
||||
})
|
||||
|
||||
console.dir(structure, { depth: null })
|
||||
|
2
packages/js/core/dist/document.cjs
vendored
2
packages/js/core/dist/document.cjs
vendored
@ -1 +1 @@
|
||||
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const d=require("./parser.cjs");function D(v,b=" "){let a=0;const n=d.createLineData("",b);async function u(){switch(a){case 0:a=1;break;case 2:return a=1,!1}const e=await v.next();return e===null?(a=3,!0):(n.line=e,d.parseLine(n),!1)}const y=()=>a=2,r=()=>n.level,s=()=>n.line.slice(n.offsetHead),c=()=>n.line.slice(n.offsetHead,n.offsetTail),l=()=>n.line.slice(n.offsetTail),j=e=>e===c();async function w(e){const t=a===0?-1:r();for(;;){if(await u())return;if(r()<=t)return y();if(await e())return}}async function f(e=-1,t=[s()]){var i;return e===-1&&(e=r()+1),await u()?t:r()<e?(y(),t):(t.push(((i=n.line)==null?void 0:i.slice(e))||""),f(e,t))}async function x(e){const t={};return await w(async()=>{const i=c();if(!i)return;const o=e?e[i]||e["#any"]:null;if(e?o===!0?t[i]=l().trim():o?t[i]=await o():e!=null&&e["#text"]&&(t["#text"]=await f(r())):t[i]=l().trim(),e&&Object.keys(e).every(p=>t[p]!==void 0))return!0}),t}async function T(){const e=[["root",[]]];for(;!await u();){const t=r(),i=t+1,o=e[t];if(!o)continue;e.length=i;const p=e[i]=[s(),[]];o[1].push(p)}return e[0]}return{next:u,line:s,head:c,tail:l,level:r,match:j,each:w,blockAsText:f,toObject:x,toLineArray:T}}exports.useDocument=D;
|
||||
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const h=require("./parser.cjs");function O(m,b=" "){let o=0;const i=h.createLineData("",b);async function l(){switch(o){case 0:o=1;break;case 2:return o=1,!1}const e=await m.next();return e===null?(o=3,!0):(i.line=e,h.parseLine(i),!1)}const p=()=>o=2,a=()=>i.level,s=()=>i.line.slice(i.offsetHead),f=()=>i.line.slice(i.offsetHead,i.offsetTail),u=()=>i.line.slice(i.offsetTail),j=e=>e===f();async function y(e){const r=o===0?-1:a();for(;;){if(await l())return;if(a()<=r)return p();if(await e())return}}async function d(e=-1,r=[s()]){var t;return e===-1&&(e=a()+1),await l()?r:a()<e?(p(),r):(r.push(((t=i.line)==null?void 0:t.slice(e))||""),d(e,r))}async function v(e={}){const r={};let t={};return Object.keys(e).length?Object.keys(e).forEach(n=>{e[n]===!0&&(t[n]={type:"normal",handle:()=>u().trim()}),typeof e[n]=="function"&&(t[n]={type:"normal",handle:e[n]}),typeof e[n]=="object"&&(t[n]=e[n])}):t={"#any":{type:"normal",handle:()=>u().trim()}},await y(async()=>{const n=f();if(!n)return;const c=t[n]||t["#any"];if(!!c&&(c.type==="normal"?r[n]=await c.handle():c.type==="collection"?(r[n]||(r[n]=[]),r[n].push(await c.handle())):t!=null&&t["#text"]&&(r["#text"]=await d(a())),t&&Object.keys(t).every(w=>["collection"].includes(t[w].type)?!1:r[w]!==void 0)))return!0}),r}async function x(){const e=[["root",[]]];for(;!await l();){const r=a(),t=r+1,n=e[r];if(!n)continue;e.length=t;const c=e[t]=[s(),[]];n[1].push(c)}return e[0]}return{next:l,line:s,head:f,tail:u,level:a,match:j,each:y,blockAsText:d,toObject:v,toLineArray:x}}exports.useDocument=O;
|
||||
|
91
packages/js/core/dist/document.js
vendored
91
packages/js/core/dist/document.js
vendored
@ -1,70 +1,73 @@
|
||||
import { parseLine as j, createLineData as D } from "./parser.js";
|
||||
function k(x, d = " ") {
|
||||
let o = 0;
|
||||
const n = D("", d);
|
||||
async function c() {
|
||||
switch (o) {
|
||||
import { parseLine as v, createLineData as O } from "./parser.js";
|
||||
function D(h, m = " ") {
|
||||
let c = 0;
|
||||
const r = O("", m);
|
||||
async function l() {
|
||||
switch (c) {
|
||||
case 0:
|
||||
o = 1;
|
||||
c = 1;
|
||||
break;
|
||||
case 2:
|
||||
return o = 1, !1;
|
||||
return c = 1, !1;
|
||||
}
|
||||
const e = await x.next();
|
||||
return e === null ? (o = 3, !0) : (n.line = e, j(n), !1);
|
||||
const e = await h.next();
|
||||
return e === null ? (c = 3, !0) : (r.line = e, v(r), !1);
|
||||
}
|
||||
const w = () => o = 2, r = () => n.level, u = () => n.line.slice(n.offsetHead), s = () => n.line.slice(n.offsetHead, n.offsetTail), f = () => n.line.slice(n.offsetTail), v = (e) => e === s();
|
||||
async function y(e) {
|
||||
const t = o === 0 ? -1 : r();
|
||||
const y = () => c = 2, a = () => r.level, f = () => r.line.slice(r.offsetHead), s = () => r.line.slice(r.offsetHead, r.offsetTail), u = () => r.line.slice(r.offsetTail), b = (e) => e === s();
|
||||
async function w(e) {
|
||||
const i = c === 0 ? -1 : a();
|
||||
for (; ; ) {
|
||||
if (await c())
|
||||
if (await l())
|
||||
return;
|
||||
if (r() <= t)
|
||||
return w();
|
||||
if (a() <= i)
|
||||
return y();
|
||||
if (await e())
|
||||
return;
|
||||
}
|
||||
}
|
||||
async function l(e = -1, t = [u()]) {
|
||||
var i;
|
||||
return e === -1 && (e = r() + 1), await c() ? t : r() < e ? (w(), t) : (t.push(((i = n.line) == null ? void 0 : i.slice(e)) || ""), l(e, t));
|
||||
async function p(e = -1, i = [f()]) {
|
||||
var t;
|
||||
return e === -1 && (e = a() + 1), await l() ? i : a() < e ? (y(), i) : (i.push(((t = r.line) == null ? void 0 : t.slice(e)) || ""), p(e, i));
|
||||
}
|
||||
async function T(e) {
|
||||
const t = {};
|
||||
return await y(async () => {
|
||||
const i = s();
|
||||
if (!i)
|
||||
async function j(e = {}) {
|
||||
const i = {};
|
||||
let t = {};
|
||||
return Object.keys(e).length ? Object.keys(e).forEach((n) => {
|
||||
e[n] === !0 && (t[n] = { type: "normal", handle: () => u().trim() }), typeof e[n] == "function" && (t[n] = { type: "normal", handle: e[n] }), typeof e[n] == "object" && (t[n] = e[n]);
|
||||
}) : t = { "#any": { type: "normal", handle: () => u().trim() } }, await w(async () => {
|
||||
const n = s();
|
||||
if (!n)
|
||||
return;
|
||||
const a = e ? e[i] || e["#any"] : null;
|
||||
if (e ? a === !0 ? t[i] = f().trim() : a ? t[i] = await a() : e != null && e["#text"] && (t["#text"] = await l(r())) : t[i] = f().trim(), e && Object.keys(e).every((p) => t[p] !== void 0))
|
||||
const o = t[n] || t["#any"];
|
||||
if (!!o && (o.type === "normal" ? i[n] = await o.handle() : o.type === "collection" ? (i[n] || (i[n] = []), i[n].push(await o.handle())) : t != null && t["#text"] && (i["#text"] = await p(a())), t && Object.keys(t).every((d) => ["collection"].includes(t[d].type) ? !1 : i[d] !== void 0)))
|
||||
return !0;
|
||||
}), t;
|
||||
}), i;
|
||||
}
|
||||
async function b() {
|
||||
async function x() {
|
||||
const e = [["root", []]];
|
||||
for (; !await c(); ) {
|
||||
const t = r(), i = t + 1, a = e[t];
|
||||
if (!a)
|
||||
for (; !await l(); ) {
|
||||
const i = a(), t = i + 1, n = e[i];
|
||||
if (!n)
|
||||
continue;
|
||||
e.length = i;
|
||||
const p = e[i] = [u(), []];
|
||||
a[1].push(p);
|
||||
e.length = t;
|
||||
const o = e[t] = [f(), []];
|
||||
n[1].push(o);
|
||||
}
|
||||
return e[0];
|
||||
}
|
||||
return {
|
||||
next: c,
|
||||
line: u,
|
||||
next: l,
|
||||
line: f,
|
||||
head: s,
|
||||
tail: f,
|
||||
level: r,
|
||||
match: v,
|
||||
each: y,
|
||||
blockAsText: l,
|
||||
toObject: T,
|
||||
toLineArray: b
|
||||
tail: u,
|
||||
level: a,
|
||||
match: b,
|
||||
each: w,
|
||||
blockAsText: p,
|
||||
toObject: j,
|
||||
toLineArray: x
|
||||
};
|
||||
}
|
||||
export {
|
||||
k as useDocument
|
||||
D as useDocument
|
||||
};
|
||||
|
@ -84,25 +84,52 @@ export function useDocument (reader: Reader, indent: string = ' '): Document {
|
||||
return blockAsText(startLevel, blockLines)
|
||||
}
|
||||
|
||||
async function toObject (matchers?: { [key: string]: Function|boolean }) {
|
||||
async function toObject (inputMatchers: { [key: string]: Function|boolean|{ type: string, handle: Function } } = {}) {
|
||||
const obj: { [key: string]: any } = {}
|
||||
|
||||
let matchers: { [key: string]: {type: string, handle: Function } } = {}
|
||||
|
||||
// Normalize the matchers to an object-based format despite allowing flexible input types for convenience.
|
||||
// TODO: Decide whether to enforce verbose input once a DSL has been created.
|
||||
if (!Object.keys(inputMatchers).length) {
|
||||
// Default matcher
|
||||
matchers = { '#any': { type: 'normal', handle: () => tail().trim() } }
|
||||
} else {
|
||||
Object.keys(inputMatchers).forEach(key => {
|
||||
// If a matcher is specified as `true`, treat as a key-value pair where { [head]: tail }
|
||||
if(inputMatchers[key] === true) matchers[key] = { type: 'normal', handle: () => tail().trim() }
|
||||
// If a matcher is specified as a function, treat as a key-value pair where { [head]: handle() }
|
||||
if (typeof inputMatchers[key] === 'function') matchers[key] = { type: 'normal', handle: inputMatchers[key] as Function }
|
||||
// If a matcher is specified as an object, allow customization of the type and handle for various cases.
|
||||
if (typeof inputMatchers[key] === 'object') matchers[key] = inputMatchers[key] as { type: string, handle: Function }
|
||||
})
|
||||
}
|
||||
|
||||
await each(async () => {
|
||||
const currHead = head()
|
||||
if (!currHead) return
|
||||
|
||||
const propertyMatcher = matchers ? matchers[currHead] || matchers['#any'] : null
|
||||
// Set the object key matching the current head to tail() if no matchers are specified.
|
||||
if (!matchers) obj[currHead] = tail().trim()
|
||||
// Or if matchers[currHead] is `true`, or if #any is true.
|
||||
else if (propertyMatcher === true) obj[currHead] = tail().trim()
|
||||
const currentMatcher = matchers[currHead] || matchers['#any']
|
||||
if (!currentMatcher) return
|
||||
|
||||
if (currentMatcher.type === 'normal') obj[currHead] = await currentMatcher.handle()
|
||||
// Allows matching the same key more than once.
|
||||
else if (currentMatcher.type === 'collection') {
|
||||
if (!obj[currHead]) obj[currHead] = []
|
||||
obj[currHead].push(await currentMatcher.handle())
|
||||
}
|
||||
// If matchers[currHead] or matchers[#any] is a function, set object key to its output.
|
||||
else if (propertyMatcher) obj[currHead] = await propertyMatcher()
|
||||
// If we get to this point and matchers[#text] is set, parse all remaining block contents as text.
|
||||
// TODO: I still don't like this.
|
||||
else if (matchers?.['#text']) obj['#text'] = await blockAsText(level())
|
||||
|
||||
// Bail early as soon as we know all keys have been matched.
|
||||
if (matchers && Object.keys(matchers).every(k => obj[k] !== undefined)) return true
|
||||
if (matchers && Object.keys(matchers).every(key => {
|
||||
// If we have any collection keys, we have to continue searching all the way to the end of the current block
|
||||
// as there may be more than one entry.
|
||||
if (['collection'].includes(matchers[key].type)) return false
|
||||
return obj[key] !== undefined
|
||||
})) return true
|
||||
})
|
||||
return obj
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user