From 175dca8c15005dfd5a8f02ade0cdfa989e7f18ea Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Mar 2026 16:16:52 +0000 Subject: [PATCH 1/6] Update to latest jsonic plugin conventions - Move source to src/ directory with project references (tsc --build) - Switch from Jest to Node.js built-in test runner with @hapi/code - Output compiled files to dist/ and dist-test/ directories - Update package.json: main/types point to dist/, files includes src/dist - Remove minified version (ini.min.js) and browser field - Remove esbuild, es-jest, jest, prettier devDependencies - Add @hapi/code and @types/node devDependencies - Update .gitignore to exclude dist/, dist-test/, *.tsbuildinfo - Remove Coveralls step from CI workflow https://claude.ai/code/session_014bMuvf21f6AjmdEPgzvdr5 --- .github/workflows/build.yml | 11 +- .gitignore | 4 + ini.d.ts | 5 - ini.js | 271 ----------------------------- ini.js.map | 1 - ini.min.js | 1 - jest.config.js | 8 - package.json | 41 ++--- ini.ts => src/ini.ts | 3 - tsconfig.json => src/tsconfig.json | 14 +- test/ini.test.d.ts | 1 - test/ini.test.js | 229 ------------------------ test/ini.test.js.map | 1 - test/ini.test.ts | 26 +-- test/quick.js | 106 ----------- test/tsconfig.json | 13 ++ 16 files changed, 53 insertions(+), 682 deletions(-) delete mode 100644 ini.d.ts delete mode 100644 ini.js delete mode 100644 ini.js.map delete mode 100644 ini.min.js delete mode 100644 jest.config.js rename ini.ts => src/ini.ts (98%) rename tsconfig.json => src/tsconfig.json (51%) delete mode 100644 test/ini.test.d.ts delete mode 100644 test/ini.test.js delete mode 100644 test/ini.test.js.map delete mode 100644 test/quick.js create mode 100644 test/tsconfig.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 54879bb..664972c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,8 +18,8 @@ jobs: os: [ubuntu-latest, windows-latest, macos-latest] node-version: [18.x, 20.x, 22.x] - runs-on: ${{ matrix.os }} - + runs-on: ${{ matrix.os }} + steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} @@ -29,10 +29,3 @@ jobs: - run: npm i - run: npm run build --if-present - run: npm test - - - name: Coveralls - uses: coverallsapp/github-action@main - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: ./coverage/lcov.info - diff --git a/.gitignore b/.gitignore index 2921d03..b88cd27 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,7 @@ coverage package-lock.json yarn.lock + +dist +dist-test +*.tsbuildinfo diff --git a/ini.d.ts b/ini.d.ts deleted file mode 100644 index 3d45355..0000000 --- a/ini.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Jsonic } from 'jsonic'; -type IniOptions = {}; -declare function Ini(jsonic: Jsonic, _options: IniOptions): void; -export { Ini }; -export type { IniOptions }; diff --git a/ini.js b/ini.js deleted file mode 100644 index 730a8f2..0000000 --- a/ini.js +++ /dev/null @@ -1,271 +0,0 @@ -"use strict"; -/* Copyright (c) 2021-2025 Richard Rodger, MIT License */ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Ini = Ini; -const hoover_1 = require("@jsonic/hoover"); -function Ini(jsonic, _options) { - jsonic.use(hoover_1.Hoover, { - lex: { - order: 8.5e6, - }, - block: { - endofline: { - start: { - rule: { - parent: { - include: ['pair', 'elem'], - }, - }, - }, - end: { - fixed: ['\n', '\r\n', '#', ';', ''], - consume: ['\n', '\r\n'], - }, - escapeChar: '\\', - escape: { - '#': '#', - ';': ';', - '\\': '\\', - }, - preserveEscapeChar: true, - trim: true, - }, - key: { - token: '#HK', - start: { - rule: { - current: { - exclude: ['dive'], - }, - state: 'oc', - }, - }, - end: { - fixed: ['=', '\n', '\r\n', '#', ';', ''], - consume: false, - }, - escape: { - '#': '#', - ';': ';', - '\\': '\\', - }, - trim: true, - }, - divekey: { - token: '#DK', - start: { - rule: { - current: { - include: ['dive'], - }, - }, - }, - end: { - fixed: [']', '.'], - consume: false, - }, - escapeChar: '\\', - escape: { - ']': ']', - '.': '.', - '\\': '\\', - }, - allowUnknownEscape: true, - trim: true, - }, - }, - }); - jsonic.options({ - rule: { - start: 'ini', - exclude: 'jsonic', - }, - lex: { - emptyResult: {}, - }, - fixed: { - token: { - '#EQ': '=', - '#DOT': '.', - '#OB': null, - '#CB': null, - '#CL': null, - }, - }, - line: { - check: (lex) => { - if ('val' === lex.ctx.rule.name) { - return { done: true, token: undefined }; - } - }, - }, - number: { - lex: false, - }, - string: { - lex: true, - chars: `'"`, - abandon: true, - }, - text: { - lex: false, - }, - comment: { - def: { - hash: { eatline: true }, - slash: null, - multi: null, - semi: { line: true, start: ';', lex: true, eatline: true }, - }, - }, - }); - const { ZZ, ST, VL, OS, CS, CL, EQ, DOT, HV, HK, DK } = jsonic.token; - const KEY = [HK, ST, VL]; - jsonic.rule('ini', (rs) => { - rs.bo((r) => { - r.node = {}; - }).open([ - { s: [OS], p: 'table', b: 1 }, - { s: [KEY, EQ], p: 'table', b: 2 }, - { s: [HV, OS], p: 'table', b: 2 }, - { s: [ZZ] }, - ]); - }); - jsonic.rule('table', (rs) => { - rs.bo((r) => { - r.node = r.parent.node; - if (r.prev.u.dive) { - let dive = r.prev.u.dive; - for (let dI = 0; dI < dive.length; dI++) { - r.node = r.node[dive[dI]] = r.node[dive[dI]] || {}; - } - } - }) - .open([ - { s: [OS], p: 'dive' }, - { s: [KEY, EQ], p: 'map', b: 2 }, - { s: [HV, OS], p: 'map', b: 2 }, - { s: [CS], p: 'map' }, - { s: [ZZ] }, - ]) - .bc((r) => { - Object.assign(r.node, r.child.node); - }) - .close([ - { s: [OS], r: 'table', b: 1 }, - { s: [CS], r: 'table', a: (r) => (r.u.dive = r.child.u.dive) }, - { s: [ZZ] }, - ]); - }); - // TODO: maybe backport this to toml? - jsonic.rule('dive', (rs) => { - rs.open([ - { - s: [DK, DOT], - a: (r) => (r.u.dive = r.parent.u.dive || []).push(r.o0.val), - p: 'dive', - }, - { - s: [DK], - a: (r) => (r.u.dive = r.parent.u.dive || []).push(r.o0.val), - }, - ]).close([{ s: [CS], b: 1 }]); - }); - jsonic.rule('map', (rs) => { - rs.open([ - // Pair from implicit map. - { - s: [KEY, EQ], - c: (r) => 'table' === r.parent.name, - p: 'pair', - b: 2, - }, - { - s: [KEY], - c: (r) => 'table' === r.parent.name, - p: 'pair', - b: 1, - }, - ], { append: true }).close([{ s: [OS], b: 1 }, { s: [ZZ] }]); - }); - jsonic.rule('pair', (rs) => { - rs.open([ - { - s: [KEY, EQ], - c: (r) => 'table' === r.parent.parent.name, - p: 'val', - a: (r) => { - let key = '' + r.o0.val; - if (Array.isArray(r.node[key])) { - r.u.ini_array = r.node[key]; - } - else { - r.u.key = key; - if (2 < key.length && key.endsWith('[]')) { - key = r.u.key = key.slice(0, -2); - r.node[key] = r.u.ini_array = Array.isArray(r.node[key]) - ? r.node[key] - : undefined === r.node[key] - ? [] - : [r.node[key]]; - } - else { - r.u.pair = true; - } - } - }, - }, - // Special case: key by itself means key=true - { - s: [HK], - c: (r) => 'table' === r.parent.parent.name, - a: (r) => { - let key = r.o0.val; - if ('string' === typeof key && 0 < key.length) { - r.parent.node[key] = true; - } - }, - }, - ]).close([ - { - s: [KEY, CL], - c: (r) => 'table' === r.parent.parent.name, - e: (r) => { - return r.c1; - }, - }, - { s: [KEY], b: 1, r: 'pair' }, - { s: [OS], b: 1 }, - ]); - }); - jsonic.rule('val', (rs) => { - rs.open([ - // Since OS,CS are fixed tokens, concat them with string value - // if they appear as first char in a RHS value. - { - s: [[OS, CS]], - r: 'val', - u: { ini_prev: true }, - }, - { s: [ZZ], a: (r) => (r.node = '') }, - ], { - custom: (alts) => alts.filter((alt) => alt.g.join() !== 'json,list'), - }).ac((r) => { - if (ST === r.o0.tin && "'" === r.o0.src[0]) { - try { - r.node = JSON.parse(r.node); - } - catch (e) { - // Invalid JSON, just accept val as given - } - } - if (null != r.prev.u.ini_prev) { - r.prev.node = r.node = r.prev.o0.src + r.node; - } - else if (r.parent.u.ini_array) { - r.parent.u.ini_array.push(r.node); - } - }); - }); -} -//# sourceMappingURL=ini.js.map \ No newline at end of file diff --git a/ini.js.map b/ini.js.map deleted file mode 100644 index a596da0..0000000 --- a/ini.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ini.js","sourceRoot":"","sources":["ini.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/ini.min.js b/ini.min.js deleted file mode 100644 index 4b2c6ec..0000000 --- a/ini.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).JsonicIni=e()}}((function(){var e={exports:{}};(function(t){(function(){!function(n){"object"==typeof e.exports?e.exports=n():("undefined"!=typeof window?window:void 0!==t?t:"undefined"!=typeof self?self:this).JsonicHoover=n()}((function(){var e={exports:{}};(function(t){(function(){!function(n){"object"==typeof e.exports?e.exports=n():("undefined"!=typeof window?window:void 0!==t?t:"undefined"!=typeof self?self:this).Jsonic=n()}((function(){var e=function(e){var t;return function(n){return t||e(t={exports:{},parent:n},t.exports),t.exports}},t=e((function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.values=t.keys=t.omap=t.isarr=t.entries=t.defprop=t.assign=t.S=t.JsonicError=void 0,t.badlex=function(e,t,n){let r=e.next.bind(e);return e.next=(e,l,i,o)=>{let s=r(e,l,i,o);if(t===s.tin){let t={};throw null!=s.use&&(t.use=s.use),new p(s.why||d.unexpected,t,s,e,n)}return s},e},t.charset=S,t.clean=I,t.clone=function(e){return g(Object.create(Object.getPrototypeOf(e)),e)},t.configure=function(e,t,n){var r,l,c,d,p,g,v,k,b,x,y,j,O,E,T,w,M,N,P,_,C,R,A,L,Y,J,F,K,U,B,$,V,Z,z,D,H,q,G,W,X,Q,ee,te,ne,re,le,ie,oe,se,ae;const ce=t||{};ce.t=ce.t||{},ce.tI=ce.tI||1;const ue=e=>f(e,ce);!1!==n.standard$&&(ue("#BD"),ue("#ZZ"),ue("#UK"),ue("#AA"),ue("#SP"),ue("#LN"),ue("#CM"),ue("#NR"),ue("#ST"),ue("#TX"),ue("#VL")),ce.safe={key:!1!==(null===(r=n.safe)||void 0===r?void 0:r.key)},ce.fixed={lex:!!(null===(l=n.fixed)||void 0===l?void 0:l.lex),token:n.fixed?u(I(n.fixed.token),([e,t])=>[t,f(e,ce)]):{},ref:void 0,check:null===(c=n.fixed)||void 0===c?void 0:c.check},ce.fixed.ref=u(ce.fixed.token,([e,t])=>[e,t]),ce.fixed.ref=Object.assign(ce.fixed.ref,u(ce.fixed.ref,([e,t])=>[t,e])),ce.match={lex:!!(null===(d=n.match)||void 0===d?void 0:d.lex),value:n.match?u(I(n.match.value),([e,t])=>[e,t]):{},token:n.match?u(I(n.match.token),([e,t])=>[f(e,ce),t]):{},check:null===(p=n.match)||void 0===p?void 0:p.check},u(ce.match.token,([e,t])=>[e,(t.tin$=+e,t)]);const de=n.tokenSet?Object.keys(n.tokenSet).reduce((e,t)=>(e[t]=n.tokenSet[t].filter(e=>null!=e).map(e=>ue(e)),e),{}):{};ce.tokenSet=ce.tokenSet||{},s(de).map(e=>{let t=e[0],n=e[1];ce.tokenSet[t]?(ce.tokenSet[t].length=0,ce.tokenSet[t].push(...n)):ce.tokenSet[t]=n}),ce.tokenSetTins=s(ce.tokenSet).reduce((e,t)=>(e[t[0]]=e[t[0]]||{},t[1].map(n=>e[t[0]][n]=!0),e),{}),ce.tokenSetTins.IGNORE=ce.tokenSetTins.IGNORE||{},ce.space={lex:!!(null===(g=n.space)||void 0===g?void 0:g.lex),chars:S(null===(v=n.space)||void 0===v?void 0:v.chars),check:null===(k=n.space)||void 0===k?void 0:k.check},ce.line={lex:!!(null===(b=n.line)||void 0===b?void 0:b.lex),chars:S(null===(x=n.line)||void 0===x?void 0:x.chars),rowChars:S(null===(y=n.line)||void 0===y?void 0:y.rowChars),single:!!(null===(j=n.line)||void 0===j?void 0:j.single),check:null===(O=n.line)||void 0===O?void 0:O.check},ce.text={lex:!!(null===(E=n.text)||void 0===E?void 0:E.lex),modify:((null===(T=ce.text)||void 0===T?void 0:T.modify)||[]).concat(((null===(w=n.text)||void 0===w?void 0:w.modify)?[n.text.modify]:[]).flat()).filter(e=>null!=e),check:null===(M=n.text)||void 0===M?void 0:M.check},ce.number={lex:!!(null===(N=n.number)||void 0===N?void 0:N.lex),hex:!!(null===(P=n.number)||void 0===P?void 0:P.hex),oct:!!(null===(_=n.number)||void 0===_?void 0:_.oct),bin:!!(null===(C=n.number)||void 0===C?void 0:C.bin),sep:null!=(null===(R=n.number)||void 0===R?void 0:R.sep)&&""!==n.number.sep,exclude:null===(A=n.number)||void 0===A?void 0:A.exclude,sepChar:null===(L=n.number)||void 0===L?void 0:L.sep,check:null===(Y=n.number)||void 0===Y?void 0:Y.check},ce.value={lex:!!(null===(J=n.value)||void 0===J?void 0:J.lex),def:s((null===(F=n.value)||void 0===F?void 0:F.def)||{}).reduce((e,t)=>(null==t[1]||!1===t[1]||t[1].match||(e[t[0]]=t[1]),e),{}),defre:s((null===(K=n.value)||void 0===K?void 0:K.def)||{}).reduce((e,t)=>(t[1]&&t[1].match&&(e[t[0]]=t[1],e[t[0]].consume=!!e[t[0]].consume),e),{})},ce.rule={start:null==(null===(U=n.rule)||void 0===U?void 0:U.start)?"val":n.rule.start,maxmul:null==(null===(B=n.rule)||void 0===B?void 0:B.maxmul)?3:n.rule.maxmul,finish:!!(null===($=n.rule)||void 0===$?void 0:$.finish),include:(null===(V=n.rule)||void 0===V?void 0:V.include)?n.rule.include.split(/\s*,+\s*/).filter(e=>""!==e):[],exclude:(null===(Z=n.rule)||void 0===Z?void 0:Z.exclude)?n.rule.exclude.split(/\s*,+\s*/).filter(e=>""!==e):[]},ce.map={extend:!!(null===(z=n.map)||void 0===z?void 0:z.extend),merge:null===(D=n.map)||void 0===D?void 0:D.merge},ce.list={property:!!(null===(H=n.list)||void 0===H?void 0:H.property)};let pe=Object.keys(ce.fixed.token).sort((e,t)=>t.length-e.length).map(e=>h(e)).join("|"),fe=(null===(q=n.comment)||void 0===q?void 0:q.lex)?(n.comment.def?o(n.comment.def):[]).filter(e=>e&&e.lex).map(e=>h(e.start)).join("|"):"",me=["([",h(i(S(ce.space.lex&&ce.space.chars,ce.line.lex&&ce.line.chars)).join("")),"]",("string"==typeof n.ender?n.ender.split(""):Array.isArray(n.ender)?n.ender:[]).map(e=>"|"+h(e)).join(""),""===pe?"":"|",pe,""===fe?"":"|",fe,"|$)"];return ce.rePart={fixed:pe,ender:me,commentStart:fe},ce.re={ender:m(null,...me),rowChars:m(null,h(null===(G=n.line)||void 0===G?void 0:G.rowChars)),columns:m(null,"["+h(null===(W=n.line)||void 0===W?void 0:W.chars)+"]","(.*)$")},ce.lex={empty:!!(null===(X=n.lex)||void 0===X?void 0:X.empty),emptyResult:null===(Q=n.lex)||void 0===Q?void 0:Q.emptyResult,match:(null===(ee=n.lex)||void 0===ee?void 0:ee.match)?s(n.lex.match).reduce((e,t)=>{let r=t[0],l=t[1];if(l){let t=l.make(ce,n);t&&(t.matcher=r,t.make=l.make,t.order=l.order),e.push(t)}return e},[]).filter(e=>null!=e&&!1!==e&&-1<+e.order).sort((e,t)=>e.order-t.order):[]},ce.parse={prepare:o(null===(te=n.parse)||void 0===te?void 0:te.prepare)},ce.debug={get_console:(null===(ne=n.debug)||void 0===ne?void 0:ne.get_console)||(()=>console),maxlen:null==(null===(re=n.debug)||void 0===re?void 0:re.maxlen)?99:n.debug.maxlen,print:{config:!!(null===(ie=null===(le=n.debug)||void 0===le?void 0:le.print)||void 0===ie?void 0:ie.config),src:null===(se=null===(oe=n.debug)||void 0===oe?void 0:oe.print)||void 0===se?void 0:se.src}},ce.error=n.error||{},ce.hint=n.hint||{},(null===(ae=n.config)||void 0===ae?void 0:ae.modify)&&i(n.config.modify).forEach(e=>n.config.modify[e](ce,n)),ce.debug.print.config&&ce.debug.get_console().dir(ce,{depth:null}),ce.result={fail:[]},n.result&&(ce.result.fail=[...n.result.fail]),a(e.options,n),a(e.token,ce.t),a(e.tokenSet,ce.tokenSet),a(e.fixed,ce.fixed.ref),ce},t.deep=g,t.errdesc=x,t.errinject=v,t.escre=h,t.errsite=k,t.filterRules=function(e,t){let n=["open","close"];for(let r of n)e.def[r]=e.def[r].map(e=>(e.g="string"==typeof e.g?(e.g||"").split(/\s*,+\s*/):e.g||[],e)).filter(e=>t.rule.include.reduce((t,n)=>t||null!=e.g&&-1!==e.g.indexOf(n),0===t.rule.include.length)).filter(e=>t.rule.exclude.reduce((t,n)=>t&&(null==e.g||-1===e.g.indexOf(n)),!0));return e},t.makelog=function(e,t){var n,r,l;let i=null===(l=null===(r=null===(n=e.opts)||void 0===n?void 0:n.plugin)||void 0===r?void 0:r.debug)||void 0===l?void 0:l.trace;if(t||i)if("number"==typeof(null==t?void 0:t.log)||i){let n=!1,r=null==t?void 0:t.log;(-1===r||i)&&(r=1,n=!0),e.log=(...t)=>{if(n){let n=t.filter(e=>d.object!=typeof e).map(e=>d.function==typeof e?e.name:e).join(d.gap);e.cfg.debug.get_console().log(n)}else e.cfg.debug.get_console().dir(t,{depth:r})}}else"function"==typeof t.log&&(e.log=t.log);return e.log},t.mesc=function(e,t){return(t=new String(e)).esc=!0,t},t.regexp=m,t.snip=O,t.srcfmt=y,t.tokenize=f,t.trimstk=function(e){e.stack&&(e.stack=e.stack.split("\n").filter(e=>!e.includes("jsonic/jsonic")).map(e=>e.replace(/ at /,"at ")).join("\n"))},t.parserwrap=function(e){return{start:function(t,n,i,o){try{return e.start(t,n,i,o)}catch(s){if("SyntaxError"===s.name){let o=0,a=0,c=0,u=r.EMPTY,d=s.message.match(/^Unexpected token (.) .*position\s+(\d+)/i);if(d){u=d[1],o=parseInt(d[2]),a=t.substring(0,o).replace(/[^\n]/g,r.EMPTY).length;let e=o-1;for(;-1t,root:()=>{},plgn:()=>n.internal().plugins,inst:()=>n,rule:{name:"no-rule"},sub:{},xs:-1,v2:m,v1:m,t0:m,t1:m,tC:-1,kI:-1,rs:[],rsI:0,rsm:{},n:{},log:i?i.log:void 0,F:y(n.internal().config),u:{},NORULE:{name:"no-rule"},NOTOKEN:{name:"no-token"}})}throw s}}}},t.prop=E,t.str=j,t.findTokenSet=function(e,t){return t.tokenSet[e]},t.modlist=function(e,t){if(t&&e){if(0null!=e);n.length!==e.length&&(e.length=0,e.push(...n))}if(t.custom){let n=t.custom(e);null!=n&&(e=n)}}return e},t.errmsg=b;const l=n({}),i=e=>null==e?[]:Object.keys(e);t.keys=i;const o=e=>null==e?[]:Object.values(e);t.values=o;const s=e=>null==e?[]:Object.entries(e);t.entries=s;const a=(e,...t)=>Object.assign(null==e?{}:e,...t);t.assign=a,t.isarr=e=>Array.isArray(e);const c=Object.defineProperty;t.defprop=c;const u=(e,t)=>Object.entries(e||{}).reduce((e,n)=>{let r=t?t(n):n;void 0===r[0]?delete e[n[0]]:e[r[0]]=r[1];let l=2;for(;void 0!==r[l];)e[r[l]]=r[l+1],l+=2;return e},{});t.omap=u;const d={indent:". ",logindent:" ",space:" ",gap:" ",Object:"Object",Array:"Array",object:"object",string:"string",function:"function",unexpected:"unexpected",map:"map",list:"list",elem:"elem",pair:"pair",val:"val",node:"node",no_re_flags:r.EMPTY,unprintable:"unprintable",invalid_ascii:"invalid_ascii",invalid_unicode:"invalid_unicode",invalid_lex_state:"invalid_lex_state",unterminated_string:"unterminated_string",unterminated_comment:"unterminated_comment",lex:"lex",parse:"parse",error:"error",none:"none",imp_map:"imp,map",imp_list:"imp,list",imp_null:"imp,null",end:"end",open:"open",close:"close",rule:"rule",stack:"stack",nUll:"null",name:"name",make:"make",colon:":"};t.S=d;class p extends SyntaxError{constructor(e,t,n,r,l){let i=x(e,t=g({},t),n,r,l);super(i.message),a(this,i)}}function f(e,t,n){let l=t.t,i=l[e];return null==i&&r.STRING===typeof e&&(i=t.tI++,l[i]=e,l[e]=i,l[e.substring(1)]=i,null!=n&&a(n.token,t.t)),i}function m(e,...t){return new RegExp(t.map(e=>e.esc?h(e.toString()):e).join(r.EMPTY),null==e?"":e)}function h(e){return null==e?"":e.replace(/[-\\|\]{}()[^$+*?.!=]/g,"\\$&").replace(/\t/g,"\\t").replace(/\r/g,"\\r").replace(/\n/g,"\\n")}function g(e,...t){let n=d.function===typeof e,r=null!=e&&(d.object===typeof e||n);for(let l of t){let t,i=d.function===typeof l,o=null!=l&&(d.object===typeof l||i);if(r&&o&&!i&&Array.isArray(e)===Array.isArray(l))for(let n in l)e[n]=g(e[n],l[n]);else e=void 0===l?e:i?l:o?d.function===typeof(t=l.constructor)&&d.Object!==t.name&&d.Array!==t.name?l:g(Array.isArray(l)?[]:{},l):l,n=d.function===typeof e,r=null!=e&&(d.object===typeof e||n)}return e}function v(e,t,n,r,l,i){return function(e,t,n){let r=typeof e,l=Array.isArray(e)?"array":null==e?"string":"object"===r?r:"string",i="object"===l?e:"array"===l?e.reduce((e,t,n)=>(e[n]=t,e),{}):{_:e},o=null==t?{}:t;return Object.entries(i).map(e=>i[e[0]]=null==e[1]?"":(""+e[1]).replace(/\{([\w_0-9.]+)}/g,(e,t)=>{var r;let l=E(o,t);if(l=void 0===l?e:l,"object"==typeof l){let e=null===(r=null==l?void 0:l.constructor)||void 0===r?void 0:r.name;l="Object"===e||"Array"===e?JSON.stringify(l).replace(/([^"])"/g,"$1"):l.toString()}else l=""+l;return n&&"string"==typeof n.indent&&(l=l.replace(/\n/g,"\n"+n.indent)),l})),"string"===l?i._:"array"===l?Object.values(i):i}(e,{...i||{},...i.cfg||{},...i.opts||{},...r||{},...l||{},...i.meta||{},...n||{},code:t,details:n,token:r,rule:l,ctx:i},{indent:" "})}function k(e){let{src:t,sub:n,msg:l,cline:i,row:o,col:s,pos:a}=e;o=null!=o&&0e+(n(null==i?"":i)+(r.EMPTY+f++).padStart(p," ")+" | "+(null==i?"":"\x1b[0m")+(null==e?r.EMPTY:e),h=u.length;return[2null!=e).join("\n")}function b(e){const t=null!=e.color&&"object"==typeof e.color?e.color:void 0,n=!0===e.color||t,r={reset:n?"\x1b[0m":"",hi:n?"\x1b[91m":"",lo:n?"\x1b[2m":"",line:n?"\x1b[34m":"",...t||{}};return[null==e.prefix?null:"function"==typeof e.prefix?e.prefix(r,e):""+e.prefix,(null==e.code?"":r.hi+"["+(null==e.name?"":e.name+"/")+e.code+"]:")+r.reset+" "+(null==e.msg?"":e.msg),null!=e.row&&null!=e.col||null!=e.file?" "+r.line+"--\x3e"+r.reset+" "+(null==e.file?"":e.file)+(null==e.row||null==e.col?"":":"+e.row+":"+e.col):null,null==e.src?"":k({src:e.src,sub:e.sub,msg:e.smsg||e.msg,cline:r.line,row:e.row,col:e.col,pos:e.pos})+"\n",null==e.hint?null:e.hint,null==e.suffix?null:"function"==typeof e.suffix?e.suffix(r,e):""+e.suffix].filter(e=>null!=e).join("\n")}function x(e,t,n,r,l){var i,o,s;try{let a=l.cfg,c=l.meta,u=v({msg:a.error[e]||(null===(i=null==t?void 0:t.use)||void 0===i?void 0:i.err)&&(t.use.err.code||t.use.err.message)||a.error.unknown,hint:(a.hint[e]||(null===(s=null===(o=t.use)||void 0===o?void 0:o.err)||void 0===s?void 0:s.message)||a.hint.unknown||"").trim().split("\n").map(e=>" "+e).join("\n")},e,t,n,r,l),d=b({code:e,name:"jsonic",msg:u.msg,hint:u.hint,src:l.src(),file:c?c.fileName:void 0,row:n.rI,col:n.cI,pos:n.sI,sub:n.src,color:!0,suffix:e=>[""," "+e.lo+"https://jsonic.senecajs.org"+e.reset," "+e.lo+"--internal: tag="+(l.opts.tag||"")+"; rule="+r.name+"~"+r.state+"; token="+f(n.tin,l.cfg)+(null==n.why?"":"~"+n.why)+"; plugins="+l.plgn().map(e=>e.name).join(",")+"--"+e.reset].join("\n")}),p={internal:{token:n,ctx:l}};return p={...Object.create(p),message:d,code:e,details:t,meta:c,fileName:c?c.fileName:void 0,lineNumber:n.rI,columnNumber:n.cI},p}catch(a){return console.log(a),{}}}function y(e){return"function"==typeof e.debug.print.src?e.debug.print.src:t=>{let n=null==t?r.EMPTY:Array.isArray(t)?JSON.stringify(t).replace(/]$/,s(t).filter(e=>isNaN(e[0])).map((e,t)=>(0===t?", ":"")+e[0]+": "+JSON.stringify(e[1]))+"]"):JSON.stringify(t);return n=n.substring(0,e.debug.maxlen)+(e.debug.maxlen!1!==e).map(e=>"object"==typeof e?i(e).join(r.EMPTY):e).join(r.EMPTY).split(r.EMPTY).reduce((e,t)=>(e[t]=t.charCodeAt(0),e),{})}function I(e){for(let t in e)null==e[t]&&delete e[t];return e}function E(e,t,n){let r=e;try{let r,l=t.split(".");for(let t=0;tnew i(...e);n.makePoint=o;class s{constructor(e,t,n,l,i,o,s){this.isToken=!0,this.name=r.EMPTY,this.tin=-1,this.val=void 0,this.src=r.EMPTY,this.sI=-1,this.rI=-1,this.cI=-1,this.len=-1,this.name=e,this.tin=t,this.src=l,this.val=n,this.sI=i.sI,this.rI=i.rI,this.cI=i.cI,this.use=o,this.why=s,this.len=null==l?0:l.length}resolveVal(e,t){return"function"==typeof this.val?this.val(e,t):this.val}bad(e,t){return this.err=e,null!=t&&(this.use=(0,l.deep)(this.use||{},t)),this}toString(){return"Token["+this.name+"="+this.tin+" "+(0,l.snip)(this.src)+(void 0===this.val||"#ST"===this.name||"#TX"===this.name?"":"="+(0,l.snip)(this.val))+" "+[this.sI,this.rI,this.cI]+(null==this.use?"":" "+(0,l.snip)(""+JSON.stringify(this.use).replace(/"/g,""),22))+(null==this.err?"":" "+this.err)+(null==this.why?"":" "+(0,l.snip)(""+this.why,22))+"]"}[r.INSPECT](){return this.toString()}}const a=(...e)=>new s(...e);function c(e,t,n){let r=e.pnt,l=t;if(e.cfg.fixed.lex&&null!=n&&0a("",-1,void 0,r.EMPTY,o(-1)),n.makeFixedMatcher=(e,t)=>{let n=(0,l.regexp)(null,"^(",e.rePart.fixed,")");return function(t){let r=e.fixed;if(!r.lex)return;if(e.fixed.check){let n=e.fixed.check(t);if(n&&n.done)return n.token}let l=t.pnt,i=t.src.substring(l.sI).match(n);if(i){let e=i[1],n=e.length;if(0{let n=(0,l.values)(e.match.value),r=(0,l.values)(e.match.token);return 0===n.length&&0===r.length?null:function(t,l,i=0){if(!e.match.lex)return;if(e.match.check){let n=e.match.check(t);if(n&&n.done)return n.token}let o=t.pnt,s=t.src.substring(o.sI),a="o"===l.state?0:1;for(let e of n)if(e.match instanceof RegExp){let n=s.match(e.match);if(n){let r=n[0],l=r.length;if(0{let n=t.comment;e.comment={lex:!!n&&!!n.lex,def:((null==n?void 0:n.def)?(0,l.entries)(n.def):[]).reduce((e,[t,n])=>{if(null==n||!1===n)return e;let r={name:t,start:n.start,end:n.end,line:!!n.line,lex:!!n.lex,eatline:!!n.eatline};return e[t]=r,e},{})};let r=e.comment.lex?(0,l.values)(e.comment.def).filter(e=>e.lex&&e.line):[],i=e.comment.lex?(0,l.values)(e.comment.def).filter(e=>e.lex&&!e.line):[];return function(t,n){if(!e.comment.lex)return;if(e.comment.check){let n=e.comment.check(t);if(n&&n.done)return n.token}let o=t.pnt,s=t.src.substring(o.sI),a=o.rI,c=o.cI;for(let l of r)if(s.startsWith(l.start)){let n=s.length,r=l.start.length;for(c+=l.start.length;r{let n=(0,l.regexp)(e.line.lex?null:"s","^(.*?)",...e.rePart.ender);return function(r){if(e.text.check){let t=e.text.check(r);if(t&&t.done)return t.token}let l=e.text,i=r.pnt,o=r.src.substring(i.sI),s=e.value.def,a=e.value.defre,u=o.match(n);if(u){let n,d=u[1],p=u[2];if(null!=d){let t=d.length;if(0{let n=e.number,r=(0,l.regexp)(null,["^([-+]?(0(",[n.hex?"x[0-9a-fA-F_]+":null,n.oct?"o[0-7_]+":null,n.bin?"b[01_]+":null].filter(e=>null!=e).join("|"),")|\\.?[0-9]+([0-9_]*[0-9])?)","(\\.[0-9]?([0-9_]*[0-9])?)?","([eE][-+]?[0-9]+([0-9_]*[0-9])?)?"].join("").replace(/_/g,n.sep?(0,l.escre)(n.sepChar):""),")",...e.rePart.ender),i=n.sep?(0,l.regexp)("g",(0,l.escre)(n.sepChar)):void 0;return function(t){if(n=e.number,!n.lex)return;if(e.number.check){let n=e.number.check(t);if(n&&n.done)return n.token}let l=t.pnt,o=t.src.substring(l.sI),s=e.value.def,a=o.match(r);if(a){let n,r=a[1],o=a[9],u=!0;if(null!=r&&(u=!e.number.exclude||!r.match(e.number.exclude))){let o=r.length;if(0{let n=t.string||{};return e.string=e.string||{},e.string=(0,l.deep)(e.string,{lex:!!(null==n?void 0:n.lex),quoteMap:(0,l.charset)(n.chars),multiChars:(0,l.charset)(n.multiChars),escMap:{...n.escape},escChar:n.escapeChar,escCharCode:null==n.escapeChar?void 0:n.escapeChar.charCodeAt(0),allowUnknown:!!n.allowUnknown,replaceCodeMap:(0,l.omap)((0,l.clean)({...n.replace}),([e,t])=>[e.charCodeAt(0),t]),hasReplace:!1,abandon:!!n.abandon}),e.string.escMap=(0,l.clean)(e.string.escMap),e.string.hasReplace=0<(0,l.keys)(e.string.replaceCodeMap).length,function(t){let n=e.string;if(!n.lex)return;if(e.string.check){let n=e.string.check(t);if(n&&n.done)return n.token}let{quoteMap:i,escMap:o,escChar:s,escCharCode:a,multiChars:c,allowUnknown:u,replaceCodeMap:d,hasReplace:p}=n,{pnt:f,src:m}=t,{sI:h,rI:g,cI:v}=f,k=m.length;if(i[m[h]]){const i=m[h],b=h,x=g,y=c[i];++h,++v;let j,O=[];for(;hfunction(t){if(!e.line.lex)return;if(e.line.check){let n=e.line.check(t);if(n&&n.done)return n.token}let n,{chars:r,rowChars:l}=e.line,{pnt:i,src:o}=t,{sI:s,rI:a}=i,c=e.line.single;for(c&&(n={});r[o[s]]&&!(n&&(n[o[s]]=(n[o[s]]||0)+1,c&&1function(t){if(!e.space.lex)return;if(e.space.check){let n=e.space.check(t);if(n&&n.done)return n.token}let{chars:n}=e.space,{pnt:r,src:l}=t,{sI:i,cI:o}=r;for(;n[l[i]];)i++,o++;if(r.sIt(i,e,this.ctx)),i}tokenize(e){return(0,l.tokenize)(e,this.cfg)}bad(e,t,n){return this.token("#BD",void 0,0<=t&&t<=n?this.src.substring(t,n):this.src[this.pnt.sI],void 0,void 0,e)}}n.makeLex=(...e)=>new u(...e)})),r={};Object.defineProperty(r,"__esModule",{value:!0}),r.STRING=r.INSPECT=r.EMPTY=r.AFTER=r.BEFORE=r.CLOSE=r.OPEN=void 0,r.OPEN="o",r.CLOSE="c",r.BEFORE="b",r.AFTER="a",r.EMPTY="",r.INSPECT=Symbol.for("nodejs.util.inspect.custom"),r.STRING="string";var l={};Object.defineProperty(l,"__esModule",{value:!0}),l.defaults=void 0;const i=n({}),o={safe:{key:!0},tag:"-",fixed:{lex:!0,token:{"#OB":"{","#CB":"}","#OS":"[","#CS":"]","#CL":":","#CA":","}},match:{lex:!0,token:{}},tokenSet:{IGNORE:["#SP","#LN","#CM"],VAL:["#TX","#NR","#ST","#VL"],KEY:["#TX","#NR","#ST","#VL"]},space:{lex:!0,chars:" \t"},line:{lex:!0,chars:"\r\n",rowChars:"\n",single:!1},text:{lex:!0},number:{lex:!0,hex:!0,oct:!0,bin:!0,sep:"_",exclude:void 0},comment:{lex:!0,def:{hash:{line:!0,start:"#",lex:!0,eatline:!1},slash:{line:!0,start:"//",lex:!0,eatline:!1},multi:{line:!1,start:"/*",end:"*/",lex:!0,eatline:!1}}},string:{lex:!0,chars:"'\"`",multiChars:"`",escapeChar:"\\",escape:{b:"\b",f:"\f",n:"\n",r:"\r",t:"\t",v:"\v",'"':'"',"'":"'","`":"`","\\":"\\","/":"/"},allowUnknown:!0,abandon:!1},map:{extend:!0,merge:void 0},list:{property:!0},value:{lex:!0,def:{true:{val:!0},false:{val:!1},null:{val:null}}},ender:[],plugin:{},debug:{get_console:()=>console,maxlen:99,print:{config:!1,src:void 0}},error:{unknown:"unknown error: {code}",unexpected:"unexpected character(s): {src}",invalid_unicode:"invalid unicode escape: {src}",invalid_ascii:"invalid ascii escape: {src}",unprintable:"unprintable character: {src}",unterminated_string:"unterminated string: {src}",unterminated_comment:"unterminated comment: {src}",unknown_rule:"unknown rule: {rulename}",end_of_source:"unexpected end of source"},hint:{unknown:"\nSince the error is unknown, this is probably a bug inside jsonic\nitself, or a plugin. Please consider posting a github issue - thanks!\n\nCode: {code}, Details: \n{details}",unexpected:"\nThe character(s) {src} were not expected at this point as they do not\nmatch the expected syntax, even under the relaxed jsonic rules. If it\nis not obviously wrong, the actual syntax error may be elsewhere. Try\ncommenting out larger areas around this point until you get no errors,\nthen remove the comments in small sections until you find the\noffending syntax. NOTE: Also check if any plugins you are using\nexpect different syntax in this case.",invalid_unicode:"\nThe escape sequence {src} does not encode a valid unicode code point\nnumber. You may need to validate your string data manually using test\ncode to see how JavaScript will interpret it. Also consider that your\ndata may have become corrupted, or the escape sequence has not been\ngenerated correctly.",invalid_ascii:"\nThe escape sequence {src} does not encode a valid ASCII character. You\nmay need to validate your string data manually using test code to see\nhow JavaScript will interpret it. Also consider that your data may\nhave become corrupted, or the escape sequence has not been generated\ncorrectly.",unprintable:"\nString values cannot contain unprintable characters (character codes\nbelow 32). The character {src} is unprintable. You may need to remove\nthese characters from your source data. Also check that it has not\nbecome corrupted.",unterminated_string:"\nThis string has no end quote.",unterminated_comment:"\nThis comment is never closed.",unknown_rule:"\nNo rule named $rulename is defined. This is probably an error in the\ngrammar of a plugin.",end_of_source:"\nUnexpected end of source."},lex:{match:{match:{order:1e6,make:i.makeMatchMatcher},fixed:{order:2e6,make:i.makeFixedMatcher},space:{order:3e6,make:i.makeSpaceMatcher},line:{order:4e6,make:i.makeLineMatcher},string:{order:5e6,make:i.makeStringMatcher},comment:{order:6e6,make:i.makeCommentMatcher},number:{order:7e6,make:i.makeNumberMatcher},text:{order:8e6,make:i.makeTextMatcher}},empty:!0,emptyResult:void 0},parse:{prepare:{}},rule:{start:"val",finish:!0,maxmul:3,include:"",exclude:""},result:{fail:[]},config:{modify:{}},parser:{start:void 0}};l.defaults=o;var s={};Object.defineProperty(s,"__esModule",{value:!0}),s.makeRuleSpec=s.makeNoRule=s.makeRule=void 0;const a=t({});class c{constructor(e,t,n){this.i=-1,this.name=r.EMPTY,this.node=null,this.state=r.OPEN,this.n=Object.create(null),this.d=-1,this.u=Object.create(null),this.k=Object.create(null),this.bo=!1,this.ao=!1,this.bc=!1,this.ac=!1,this.os=0,this.cs=0,this.need=0,this.i=t.uI++,this.name=e.name,this.spec=e,this.child=t.NORULE,this.parent=t.NORULE,this.prev=t.NORULE,this.o0=t.NOTOKEN,this.o1=t.NOTOKEN,this.c0=t.NOTOKEN,this.c1=t.NOTOKEN,this.node=n,this.d=t.rsI,this.bo=null!=e.def.bo,this.ao=null!=e.def.ao,this.bc=null!=e.def.bc,this.ac=null!=e.def.ac}process(e,t){return this.spec.process(this,e,t,this.state)}eq(e,t=0){let n=this.n[e];return null==n||n===t}lt(e,t=0){let n=this.n[e];return null==n||nt}lte(e,t=0){let n=this.n[e];return null==n||n<=t}gte(e,t=0){let n=this.n[e];return null==n||n>=t}toString(){return"[Rule "+this.name+"~"+this.i+"]"}}const u=(...e)=>new c(...e);s.makeRule=u,s.makeNoRule=e=>u(g(e.cfg,{}),e);class d{constructor(){this.p=r.EMPTY,this.r=r.EMPTY,this.b=0}}const p=(...e)=>new d(...e),f=p(),m=p();class h{constructor(e,t){this.name=r.EMPTY,this.def={open:[],close:[],bo:[],bc:[],ao:[],ac:[],tcol:[]},this.cfg=e,this.def=Object.assign(this.def,t),this.def.open=(this.def.open||[]).filter(e=>null!=e),this.def.close=(this.def.close||[]).filter(e=>null!=e);for(let n of[...this.def.open,...this.def.close])v(n)}tin(e){return(0,a.tokenize)(e,this.cfg)}add(e,t,n){let r=(null==n?void 0:n.append)?"push":"unshift",l=((0,a.isarr)(t)?t:[t]).filter(e=>null!=e&&"object"==typeof e).map(e=>v(e)),i="o"===e?"open":"close",o=this.def[i];return o[r](...l),o=this.def[i]=(0,a.modlist)(o,n),(0,a.filterRules)(this,this.cfg),this.norm(),this}open(e,t){return this.add("o",e,t)}close(e,t){return this.add("c",e,t)}action(e,t,n,r){let l=this.def[t+n];return e?l.push(r):l.unshift(r),this}bo(e,t){return this.action(!t||!!e,r.BEFORE,r.OPEN,t||e)}ao(e,t){return this.action(!t||!!e,r.AFTER,r.OPEN,t||e)}bc(e,t){return this.action(!t||!!e,r.BEFORE,r.CLOSE,t||e)}ac(e,t){return this.action(!t||!!e,r.AFTER,r.CLOSE,t||e)}clear(){return this.def.open.length=0,this.def.close.length=0,this.def.bo.length=0,this.def.ao.length=0,this.def.bc.length=0,this.def.ac.length=0,this}norm(){this.def.open.map(e=>v(e)),this.def.close.map(e=>v(e));const e=[];function t(e,t,n){return n[e]=n[e]||[],[function(e,n){if(n.s&&n.s[t]){let r=[...new Set(e.concat(n.s[t]))];e.length=0,e.push(...r)}return e},n[e][t]=n[e][t]||[]]}return this.def.open.reduce(...t(0,0,e)),this.def.open.reduce(...t(0,1,e)),this.def.close.reduce(...t(1,0,e)),this.def.close.reduce(...t(1,1,e)),this.def.tcol=e,this}process(e,t,n,l){t.log&&t.log(a.S.rule,t,e,n);let i="o"===l,o=i?e:t.NORULE,s=i?"O":"C",c=this.def,d=i?c.open:c.close,p=i?e.bo?c.bo:null:e.bc?c.bc:null;if(p){let n;for(let r=0;rnew h(...e);function v(e){if(r.STRING===typeof e.g?e.g=e.g.split(/\s*,\s*/):null==e.g&&(e.g=[]),e.g=e.g.sort(),e.s&&0!==e.s.length){const t=e=>e.flat().filter(e=>"number"==typeof e),n=(e,t)=>e.filter(e=>31*t<=e&&e<31*(t+1)),r=(e,t)=>e.reduce((e,n)=>1<1+e/31|0))).fill(null).map((e,t)=>t).map(e=>r(n(l,e),e)):null,o.S1=01+e/31|0))).fill(null).map((e,t)=>t).map(e=>r(n(i,e),e)):null}else e.s=null;return e.p||(e.p=null),e.r||(e.r=null),e.b||(e.b=null),e}s.makeRuleSpec=g;var k={};Object.defineProperty(k,"__esModule",{value:!0}),k.makeParser=k.makeRuleSpec=k.makeRule=void 0;const b=t({}),x=n({});Object.defineProperty(k,"makeRule",{enumerable:!0,get:function(){return s.makeRule}}),Object.defineProperty(k,"makeRuleSpec",{enumerable:!0,get:function(){return s.makeRuleSpec}});class y{constructor(e,t){this.rsm={},this.options=e,this.cfg=t}rule(e,t){if(null==e)return this.rsm;let n=this.rsm[e];if(null===t)delete this.rsm[e];else if(void 0!==t)return n=this.rsm[e]=this.rsm[e]||(0,s.makeRuleSpec)(this.cfg,{}),n=this.rsm[e]=t(this.rsm[e],this)||this.rsm[e],void(n.name=e);return n}start(e,t,n,l){let i,o=(0,x.makeToken)("#ZZ",(0,b.tokenize)("#ZZ",this.cfg),void 0,r.EMPTY,(0,x.makePoint)(-1)),a=(0,x.makeNoToken)(),c={uI:0,opts:this.options,cfg:this.cfg,meta:n||{},src:()=>e,root:()=>i,plgn:()=>t.internal().plugins,inst:()=>t,rule:{},sub:t.internal().sub,xs:-1,v2:o,v1:o,t0:a,t1:a,tC:-2,kI:-1,rs:[],rsI:0,rsm:this.rsm,log:void 0,F:(0,b.srcfmt)(this.cfg),u:{},NOTOKEN:a,NORULE:{}};c=(0,b.deep)(c,l);let u=(0,s.makeNoRule)(c);if(c.NORULE=u,c.rule=u,n&&b.S.function===typeof n.log&&(c.log=n.log),this.cfg.parse.prepare.forEach(e=>e(t,c,n)),""===e){if(this.cfg.lex.empty)return this.cfg.lex.emptyResult;throw new b.JsonicError(b.S.unexpected,{src:e},c.t0,u,c)}let d=(0,b.badlex)((0,x.makeLex)(c),(0,b.tokenize)("#BD",this.cfg),c),p=this.rsm[this.cfg.rule.start];if(null==p)return;let f=(0,s.makeRule)(p,c);i=f;let m=2*(0,b.keys)(this.rsm).length*d.src.length*2*c.cfg.rule.maxmul,h=0;for(;u!==f&&he(f,c)),f=f.process(c,d),c.log&&c.log(b.S.stack,c,f,d),h++;if(o.tin!==d.next(f).tin)throw new b.JsonicError(b.S.unexpected,{},c.t0,u,c);const g=c.root().node;if(this.cfg.result.fail.includes(g))throw new b.JsonicError(b.S.unexpected,{},c.t0,u,c);return g}clone(e,t){let n=new y(e,t);return n.rsm=Object.keys(this.rsm).reduce((e,t)=>(e[t]=(0,b.filterRules)(this.rsm[t],this.cfg),e),{}),n.norm(),n}norm(){(0,b.values)(this.rsm).map(e=>e.norm())}}k.makeParser=(...e)=>new y(...e);var j={};function O(e){const{deep:t}=e.util,{OB:n,CB:r,OS:l,CS:i,CL:o,CA:s,TX:a,ST:c,ZZ:u}=e.token,{VAL:d,KEY:p}=e.tokenSet,f=(e,t)=>{if(!t.cfg.rule.finish)return t.t0.err="end_of_source",t.t0},m=e=>{const t=e.o0,n=c===t.tin||a===t.tin?t.val:t.src;e.u.key=n};e.rule("val",e=>{e.bo(e=>e.node=void 0).open([{s:[n],p:"map",b:1,g:"map,json"},{s:[l],p:"list",b:1,g:"list,json"},{s:[d],g:"val,json"}]).close([{s:[u],g:"end,json"},{b:1,g:"more,json"}]).bc((e,t)=>{e.node=void 0===e.node?void 0===e.child.node?0===e.os?void 0:e.o0.resolveVal(e,t):e.child.node:e.node})}),e.rule("map",e=>{e.bo(e=>{e.node=Object.create(null)}).open([{s:[n,r],b:1,n:{pk:0},g:"map,json"},{s:[n],p:"pair",n:{pk:0},g:"map,json,pair"}]).close([{s:[r],g:"end,json"}])}),e.rule("list",e=>{e.bo(e=>{e.node=[]}).open([{s:[l,i],b:1,g:"list,json"},{s:[l],p:"elem",g:"list,elem,json"}]).close([{s:[i],g:"end,json"}])}),e.rule("pair",e=>{e.open([{s:[p,o],p:"val",u:{pair:!0},a:m,g:"map,pair,key,json"}]).bc((e,t)=>{e.u.pair&&(e.u.prev=e.node[e.u.key],e.node[e.u.key]=e.child.node)}).close([{s:[s],r:"pair",g:"map,pair,json"},{s:[r],b:1,g:"map,pair,json"}])}),e.rule("elem",e=>{e.open([{p:"val",g:"list,elem,val,json"}]).bc(e=>{!0!==e.u.done&&e.node.push(e.child.node)}).close([{s:[s],r:"elem",g:"list,elem,json"},{s:[i],b:1,g:"list,elem,json"}])});const h=(e,n)=>{let r=e.u.key,l=e.child.node;const i=e.u.prev;l=void 0===l?null:l,e.u.list&&n.cfg.safe.key&&("__proto__"===r||"constructor"===r)||(e.node[r]=null==i?l:n.cfg.map.merge?n.cfg.map.merge(i,l,e,n):n.cfg.map.extend?t(i,l):l)};e.rule("val",e=>{e.open([{s:[p,o],p:"map",b:2,n:{pk:1},g:"pair,jsonic"},{s:[d],g:"val,json"},{s:[[r,i]],b:1,c:e=>00===e.d,p:"list",b:1,g:"list,imp,jsonic"},{s:[s],b:1,g:"list,val,imp,null,jsonic"},{s:[u],g:"jsonic"}],{append:!0,delete:[2]}).close([{s:[[r,i]],b:1,g:"val,json,close",e:(e,t)=>0===e.d?t.t0:void 0},{s:[s],c:e=>e.lte("dlist")&&e.lte("dmap"),r:"list",u:{implist:!0},g:"list,val,imp,comma,jsonic"},{c:e=>e.lte("dlist")&&e.lte("dmap"),r:"list",u:{implist:!0},g:"list,val,imp,space,jsonic",b:1},{s:[u],g:"jsonic"}],{append:!0,move:[1,-1]})}),e.rule("map",e=>{e.bo(e=>{e.n.dmap=1+(e.n.dmap?e.n.dmap:0)}).open([{s:[n,u],b:1,e:f,g:"end,jsonic"}]).open([{s:[p,o],p:"pair",b:2,g:"pair,list,val,imp,jsonic"}],{append:!0}).close([{s:[r],c:e=>e.lte("pk"),g:"end,json"},{s:[r],b:1,g:"path,jsonic"},{s:[[s,i,...d]],b:1,g:"end,path,jsonic"},{s:[u],e:f,g:"end,jsonic"}],{append:!0,delete:[0]})}),e.rule("list",e=>{e.bo(e=>{e.n.dlist=1+(e.n.dlist?e.n.dlist:0),e.prev.u.implist&&(e.node.push(e.prev.node),e.prev.node=e.node)}).open({c:e=>e.prev.u.implist,p:"elem"}).open([{s:[s],p:"elem",b:1,g:"list,elem,val,imp,jsonic"},{p:"elem",g:"list,elem.jsonic"}],{append:!0}).close([{s:[u],e:f,g:"end,jsonic"}],{append:!0})}),e.rule("pair",(e,t)=>{e.open([{s:[s],g:"map,pair,comma,jsonic"}],{append:!0}).bc((e,t)=>{e.u.pair&&h(e,t)}).close([{s:[r],c:e=>e.lte("pk"),b:1,g:"map,pair,json"},{s:[s,r],c:e=>e.lte("pk"),b:1,g:"map,pair,comma,jsonic"},{s:[s,u],g:"end,jsonic"},{s:[s],c:e=>e.lte("pk"),r:"pair",g:"map,pair,json"},{s:[s],c:e=>e.lte("dmap",1),r:"pair",g:"map,pair,jsonic"},{s:[p],c:e=>e.lte("dmap",1),r:"pair",b:1,g:"map,pair,imp,jsonic"},{s:[[r,s,i,...p]],c:e=>0e.c0,g:"end,jsonic"},{s:[u],e:f,g:"map,pair,json"},{r:"pair",b:1,g:"map,pair,imp,jsonic"}],{append:!0,delete:[0,1]})}),e.rule("elem",(e,t)=>{e.open([{s:[s,s],b:2,u:{done:!0},a:e=>e.node.push(null),g:"list,elem,imp,null,jsonic"},{s:[s],u:{done:!0},a:e=>e.node.push(null),g:"list,elem,imp,null,jsonic"},{s:[p,o],e:t.cfg.list.property?void 0:(e,t)=>t.t0,p:"val",n:{pk:1,dmap:1},u:{done:!0,pair:!0,list:!0},a:m,g:"elem,pair,jsonic"}]).bc((e,t)=>{!0===e.u.pair&&(e.u.prev=e.node[e.u.key],h(e,t))}).close([{s:[s,[i,u]],b:1,g:"list,elem,comma,jsonic"},{s:[s],r:"elem",g:"list,elem,json"},{s:[i],b:1,g:"list,elem,json"},{s:[u],e:f,g:"list,elem,json"},{s:[r],e:e=>e.c0,g:"end,jsonic"},{r:"elem",b:1,g:"list,elem,imp,jsonic"}],{delete:[-1,-2]})})}Object.defineProperty(j,"__esModule",{value:!0}),j.grammar=O,j.makeJSON=function(e){let t=e.make({grammar$:!1,text:{lex:!1},number:{hex:!1,oct:!1,bin:!1,sep:null,exclude:/^00+/},string:{chars:'"',multiChars:"",allowUnknown:!1,escape:{v:null}},comment:{lex:!1},map:{extend:!1},lex:{empty:!1},rule:{finish:!1,include:"json"},result:{fail:[void 0,NaN]},tokenSet:{KEY:["#ST",null,null,null]}});return O(t),t};var S={exports:{}};Object.defineProperty(S.exports,"__esModule",{value:!0}),S.exports.root=S.exports.S=S.exports.EMPTY=S.exports.AFTER=S.exports.BEFORE=S.exports.CLOSE=S.exports.OPEN=S.exports.makeTextMatcher=S.exports.makeNumberMatcher=S.exports.makeCommentMatcher=S.exports.makeStringMatcher=S.exports.makeLineMatcher=S.exports.makeSpaceMatcher=S.exports.makeFixedMatcher=S.exports.makeParser=S.exports.makeLex=S.exports.makeRuleSpec=S.exports.makeRule=S.exports.makePoint=S.exports.makeToken=S.exports.util=S.exports.JsonicError=S.exports.Jsonic=void 0,S.exports.make=w,Object.defineProperty(S.exports,"OPEN",{enumerable:!0,get:function(){return r.OPEN}}),Object.defineProperty(S.exports,"CLOSE",{enumerable:!0,get:function(){return r.CLOSE}}),Object.defineProperty(S.exports,"BEFORE",{enumerable:!0,get:function(){return r.BEFORE}}),Object.defineProperty(S.exports,"AFTER",{enumerable:!0,get:function(){return r.AFTER}}),Object.defineProperty(S.exports,"EMPTY",{enumerable:!0,get:function(){return r.EMPTY}});const I=t({});Object.defineProperty(S.exports,"JsonicError",{enumerable:!0,get:function(){return I.JsonicError}}),Object.defineProperty(S.exports,"S",{enumerable:!0,get:function(){return I.S}});const E=n({});Object.defineProperty(S.exports,"makePoint",{enumerable:!0,get:function(){return E.makePoint}}),Object.defineProperty(S.exports,"makeToken",{enumerable:!0,get:function(){return E.makeToken}}),Object.defineProperty(S.exports,"makeLex",{enumerable:!0,get:function(){return E.makeLex}}),Object.defineProperty(S.exports,"makeFixedMatcher",{enumerable:!0,get:function(){return E.makeFixedMatcher}}),Object.defineProperty(S.exports,"makeSpaceMatcher",{enumerable:!0,get:function(){return E.makeSpaceMatcher}}),Object.defineProperty(S.exports,"makeLineMatcher",{enumerable:!0,get:function(){return E.makeLineMatcher}}),Object.defineProperty(S.exports,"makeStringMatcher",{enumerable:!0,get:function(){return E.makeStringMatcher}}),Object.defineProperty(S.exports,"makeCommentMatcher",{enumerable:!0,get:function(){return E.makeCommentMatcher}}),Object.defineProperty(S.exports,"makeNumberMatcher",{enumerable:!0,get:function(){return E.makeNumberMatcher}}),Object.defineProperty(S.exports,"makeTextMatcher",{enumerable:!0,get:function(){return E.makeTextMatcher}}),Object.defineProperty(S.exports,"makeRule",{enumerable:!0,get:function(){return k.makeRule}}),Object.defineProperty(S.exports,"makeRuleSpec",{enumerable:!0,get:function(){return k.makeRuleSpec}}),Object.defineProperty(S.exports,"makeParser",{enumerable:!0,get:function(){return k.makeParser}});const T={tokenize:I.tokenize,srcfmt:I.srcfmt,clone:I.clone,charset:I.charset,trimstk:I.trimstk,makelog:I.makelog,badlex:I.badlex,errsite:I.errsite,errinject:I.errinject,errdesc:I.errdesc,configure:I.configure,parserwrap:I.parserwrap,mesc:I.mesc,escre:I.escre,regexp:I.regexp,prop:I.prop,str:I.str,clean:I.clean,errmsg:I.errmsg,deep:I.deep,omap:I.omap,keys:I.keys,values:I.values,entries:I.entries};function w(e,t){let n=!0;if("jsonic"===e)n=!1;else if("json"===e)return(0,j.makeJSON)(M);e="string"==typeof e?{}:e;let r={parser:null,config:null,plugins:[],sub:{lex:void 0,rule:void 0},mark:Math.random()},i=(0,I.deep)({},t?{...t.options}:!1===(null==e?void 0:e.defaults$)?{}:l.defaults,e||{}),o=function(e,t,n){var r;if(I.S.string===typeof e){let l=o.internal();return((null===(r=s.parser)||void 0===r?void 0:r.start)?(0,I.parserwrap)(s.parser):l.parser).start(e,o,t,n)}return e},s=e=>{if(null!=e&&I.S.object===typeof e){(0,I.deep)(i,e),(0,I.configure)(o,r.config,i);let t=o.internal().parser;r.parser=t.clone(i,r.config)}return{...o.options}},a={token:e=>(0,I.tokenize)(e,r.config,o),tokenSet:e=>(0,I.findTokenSet)(e,r.config),fixed:e=>r.config.fixed.ref[e],options:(0,I.deep)(s,i),config:()=>(0,I.deep)(r.config),parse:o,use:function(e,t){if(I.S.function!==typeof e)throw new Error("Jsonic.use: the first argument must be a function defining a plugin. See https://jsonic.senecajs.org/plugin");const n=e.name.toLowerCase(),r=(0,I.deep)({},e.defaults||{},t||{});o.options({plugin:{[n]:r}});let l=o.options.plugin[n];return o.internal().plugins.push(e),e.options=l,e(o,l)||o},rule:(e,t)=>o.internal().parser.rule(e,t)||o,make:e=>w(e,o),empty:e=>w({defaults$:!1,standard$:!1,grammar$:!1,...e||{}}),id:"Jsonic/"+Date.now()+"/"+(""+Math.random()).substring(2,8).padEnd(6,"0")+(null==s.tag?"":"/"+s.tag),toString:()=>a.id,sub:e=>(e.lex&&(r.sub.lex=r.sub.lex||[],r.sub.lex.push(e.lex)),e.rule&&(r.sub.rule=r.sub.rule||[],r.sub.rule.push(e.rule)),o),util:T};if((0,I.defprop)(a.make,I.S.name,{value:I.S.make}),n?(0,I.assign)(o,a):(0,I.assign)(o,{empty:a.empty,parse:a.parse,sub:a.sub,id:a.id,toString:a.toString}),(0,I.defprop)(o,"internal",{value:()=>r}),t){for(let n in t)void 0===o[n]&&(o[n]=t[n]);o.parent=t;let e=t.internal();r.config=(0,I.deep)({},e.config),(0,I.configure)(o,r.config,i),(0,I.assign)(o.token,r.config.t),r.plugins=[...e.plugins],r.parser=e.parser.clone(i,r.config)}else{let e={...o,...a};r.config=(0,I.configure)(e,void 0,i),r.plugins=[],r.parser=(0,k.makeParser)(i,r.config),!1!==i.grammar$&&(0,j.grammar)(e)}return o}let M;S.exports.util=T,S.exports.root=M;let N=S.exports.root=M=w("jsonic");return S.exports.Jsonic=N,M.Jsonic=M,M.JsonicError=I.JsonicError,M.makeLex=E.makeLex,M.makeParser=k.makeParser,M.makeToken=E.makeToken,M.makePoint=E.makePoint,M.makeRule=k.makeRule,M.makeRuleSpec=k.makeRuleSpec,M.makeFixedMatcher=E.makeFixedMatcher,M.makeSpaceMatcher=E.makeSpaceMatcher,M.makeLineMatcher=E.makeLineMatcher,M.makeStringMatcher=E.makeStringMatcher,M.makeCommentMatcher=E.makeCommentMatcher,M.makeNumberMatcher=E.makeNumberMatcher,M.makeTextMatcher=E.makeTextMatcher,M.OPEN=r.OPEN,M.CLOSE=r.CLOSE,M.BEFORE=r.BEFORE,M.AFTER=r.AFTER,M.EMPTY=r.EMPTY,M.util=T,M.make=w,M.S=I.S,S.exports.default=N,S.exports=N,S.exports}))}).call(this)}).call(this,void 0!==t?t:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{}),e=e.exports;var n={};Object.defineProperty(n,"__esModule",{value:!0}),n.Hoover=void 0,n.parseToEnd=i;const r=(t,n)=>{var r;const{entries:o}=t.util;let s=o(n.block).map(e=>({allowUnknownEscape:!0,preserveEscapeChar:!1,token:"#HV",...e[1],name:e[0]})),a={};for(let e of s)e.TOKEN=t.token(e.token),a[e.token]||t.rule("val",t=>{t.open({s:[e.TOKEN],a:n.action})}),a[e.token]=e.TOKEN;t.options({lex:{match:{hoover:{order:null===(r=n.lex)||void 0===r?void 0:r.order,make:(t,n)=>function(n){for(let r of s){const o=(0,e.makePoint)(n.pnt.len,n.pnt.sI,n.pnt.rI,n.pnt.cI);if(l(n,o,r).match){let e=i(n,o,r,t);if(e.done){let t=n.token(r.TOKEN,e.val,n.src.substring(n.pnt.sI,o.sI),o);return t.use={block:r.name},n.pnt.sI=o.sI,n.pnt.rI=o.rI,n.pnt.cI=o.cI,t}return e.bad||n.bad("invalid_text",n.pnt.sI,o.sI)}}}}}}})};function l(e,t,n){let r=e.src,l=t.sI,i=t.rI,o=t.cI,s=n.start||{},a=s.rule||{},c=null;a.parent&&(a.parent.include&&(c=a.parent.include.includes(e.ctx.rule.parent.name)&&(null===c||c)),a.parent.exclude&&(c=!a.parent.exclude.includes(e.ctx.rule.parent.name)&&(null===c||c))),a.current&&(a.current.include&&(c=a.current.include.includes(e.ctx.rule.name)&&(null===c||c)),a.current.exclude&&(c=!a.current.exclude.includes(e.ctx.rule.name)&&(null===c||c)));let u=""===a.state?"":a.state||"o";u&&(c=u.includes(e.ctx.rule.state)&&(null===c||c));let d=!0,p=s.fixed;if(c&&null!=p){d=!1,p=Array.isArray(p)?p:[p];for(let e=0;!d&&ee[0]),c=s.map(e=>e.substring(1)),u=n.escapeChar,d=t.sI,p=t.rI,f=t.cI,m=!1,h="",g=d,v=0;e:do{if(h=i[d],-1<(v=a.indexOf(h))){let e=c[v];if(void 0===e||""===e){g=d+1,m=!0;break e}if("string"==typeof e&&e===i.substring(d+1,d+1+e.length)){g=d+1+e.length,m=!0;break e}}if(u===h){let t=n.escape[i[d+1]];if(null!=t)h=t,d++,f++;else{if(!n.allowUnknownEscape)return{done:!1,val:"",bad:e.bad("invalid_escape",d,d+1)};h=n.preserveEscapeChar?i.substring(d,d+2):i[d+1],d++}}l.push(h),d++,f++,"\n"===h&&(p++,f=0)}while(d<=i.length);if(m){if(!1!==o.consume){let e=i.substring(d,g);if(!Array.isArray(o.consume)||o.consume.includes(e)){let e=d;for(;e{if("val"===e.ctx.rule.name)return{done:!0,token:void 0}}},number:{lex:!1},string:{lex:!0,chars:"'\"",abandon:!0},text:{lex:!1},comment:{def:{hash:{eatline:!0},slash:null,multi:null,semi:{line:!0,start:";",lex:!0,eatline:!0}}}});const{ZZ:r,ST:l,VL:i,OS:o,CS:s,CL:a,EQ:c,DOT:u,HV:d,HK:p,DK:f}=t.token,m=[p,l,i];t.rule("ini",e=>{e.bo(e=>{e.node={}}).open([{s:[o],p:"table",b:1},{s:[m,c],p:"table",b:2},{s:[d,o],p:"table",b:2},{s:[r]}])}),t.rule("table",e=>{e.bo(e=>{if(e.node=e.parent.node,e.prev.u.dive){let t=e.prev.u.dive;for(let n=0;n{Object.assign(e.node,e.child.node)}).close([{s:[o],r:"table",b:1},{s:[s],r:"table",a:e=>e.u.dive=e.child.u.dive},{s:[r]}])}),t.rule("dive",e=>{e.open([{s:[f,u],a:e=>(e.u.dive=e.parent.u.dive||[]).push(e.o0.val),p:"dive"},{s:[f],a:e=>(e.u.dive=e.parent.u.dive||[]).push(e.o0.val)}]).close([{s:[s],b:1}])}),t.rule("map",e=>{e.open([{s:[m,c],c:e=>"table"===e.parent.name,p:"pair",b:2},{s:[m],c:e=>"table"===e.parent.name,p:"pair",b:1}],{append:!0}).close([{s:[o],b:1},{s:[r]}])}),t.rule("pair",e=>{e.open([{s:[m,c],c:e=>"table"===e.parent.parent.name,p:"val",a:e=>{let t=""+e.o0.val;Array.isArray(e.node[t])?e.u.ini_array=e.node[t]:(e.u.key=t,2"table"===e.parent.parent.name,a:e=>{let t=e.o0.val;"string"==typeof t&&0"table"===e.parent.parent.name,e:e=>e.c1},{s:[m],b:1,r:"pair"},{s:[o],b:1}])}),t.rule("val",e=>{e.open([{s:[[o,s]],r:"val",u:{ini_prev:!0}},{s:[r],a:e=>e.node=""}],{custom:e=>e.filter(e=>"json,list"!==e.g.join())}).ac(e=>{if(l===e.o0.tin&&"'"===e.o0.src[0])try{e.node=JSON.parse(e.node)}catch(t){}null!=e.prev.u.ini_prev?e.prev.node=e.node=e.prev.o0.src+e.node:e.parent.u.ini_array&&e.parent.u.ini_array.push(e.node)})})},t})); \ No newline at end of file diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 95393f9..0000000 --- a/jest.config.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - transform: { - "^.+\\.tsx?$": "es-jest" - }, - testEnvironment: 'node', - testMatch: ['**/test/**/*.test.ts'], - watchPathIgnorePatterns: ['.*.js$'], -} diff --git a/package.json b/package.json index b6ba1f7..41761d5 100644 --- a/package.json +++ b/package.json @@ -2,17 +2,15 @@ "name": "@jsonic/ini", "version": "0.5.0", "description": "This plugin allows the [Jsonic](https://jsonic.senecajs.org) JSON parser to support INI syntax.", - "main": "ini.js", + "main": "dist/ini.js", "type": "commonjs", - "browser": "ini.min.js", - "types": "ini.d.ts", + "types": "dist/ini.d.ts", "homepage": "https://github.com/jsonicjs/ini", "keywords": [ - "pattern", - "matcher", - "object", - "property", - "json" + "json", + "jsonic", + "parser", + "ini" ], "author": "Richard Rodger (http://richardrodger.com)", "repository": { @@ -20,34 +18,27 @@ "url": "git://github.com/jsonicjs/ini.git" }, "scripts": { - "test": "jest --coverage", - "test-some": "jest -t", - "test-watch": "jest --coverage --watchAll", - "watch": "tsc -w -d", + "test": "node --enable-source-maps --test \"dist-test/*.test.js\"", + "test-some": "node --enable-source-maps --test-name-pattern=\"$npm_config_pattern\" --test \"dist-test/*.test.js\"", + "watch": "tsc --build src test -w", + "build": "tsc --build src test", + "clean": "rm -rf dist dist-test node_modules yarn.lock package-lock.json", "doc": "jsonic-doc", - "build": "tsc -d", - "prettier": "prettier --write --no-semi --single-quote *.ts test/*.js", - "clean": "rm -rf node_modules yarn.lock package-lock.json", "reset": "npm run clean && npm i && npm run build && npm test", "repo-tag": "REPO_VERSION=`node -e \"console.log(require('./package').version)\"` && echo TAG: v$REPO_VERSION && git commit -a -m v$REPO_VERSION && git push && git tag v$REPO_VERSION && git push --tags;", "repo-publish": "npm run clean && npm i && npm run repo-publish-quick", - "repo-publish-quick": "npm run prettier && npm run build && npm run test && npm run repo-tag && npm publish --access public --registry https://registry.npmjs.org " + "repo-publish-quick": "npm run build && npm run test && npm run doc && npm run repo-tag && npm publish --access public --registry https://registry.npmjs.org " }, "license": "MIT", "files": [ - "*.ts", - "*.js", - "*.map", + "src", + "dist", "LICENSE" ], "devDependencies": { + "@hapi/code": "^9.0.3", "@jsonic/doc": "^0.0.9", - "@types/jest": "^30.0.0", - "es-jest": "^2.1.0", - "esbuild": "^0.25.11", - "ini": "^6.0.0", - "jest": "^30.2.0", - "prettier": "^3.6.2", + "@types/node": "^25.4.0", "typescript": "^5.9.3" }, "peerDependencies": { diff --git a/ini.ts b/src/ini.ts similarity index 98% rename from ini.ts rename to src/ini.ts index 38c8ed6..34f9646 100644 --- a/ini.ts +++ b/src/ini.ts @@ -4,8 +4,6 @@ import { Jsonic, RuleSpec, NormAltSpec, Lex } from 'jsonic' import { Hoover } from '@jsonic/hoover' -// TODO: use a src folder - type IniOptions = {} function Ini(jsonic: Jsonic, _options: IniOptions) { @@ -171,7 +169,6 @@ function Ini(jsonic: Jsonic, _options: IniOptions) { ]) }) - // TODO: maybe backport this to toml? jsonic.rule('dive', (rs: RuleSpec) => { rs.open([ { diff --git a/tsconfig.json b/src/tsconfig.json similarity index 51% rename from tsconfig.json rename to src/tsconfig.json index 6070411..da88ae7 100644 --- a/tsconfig.json +++ b/src/tsconfig.json @@ -1,18 +1,14 @@ { "compilerOptions": { - "isolatedModules": true, "esModuleInterop": true, "module": "nodenext", - "moduleResolution": "nodenext", "noEmitOnError": true, - "noImplicitAny": true, + "outDir": "../dist", + "rootDir": ".", + "declaration": true, "resolveJsonModule": true, "sourceMap": true, "strict": true, - "target": "ES2019" - }, - "exclude": [ - "dist", - "node_modules" - ] + "target": "ES2021" + } } diff --git a/test/ini.test.d.ts b/test/ini.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/test/ini.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/test/ini.test.js b/test/ini.test.js deleted file mode 100644 index b200c20..0000000 --- a/test/ini.test.js +++ /dev/null @@ -1,229 +0,0 @@ -"use strict"; -/* Copyright (c) 2021-2025 Richard Rodger and other contributors, MIT License */ -Object.defineProperty(exports, "__esModule", { value: true }); -const jsonic_1 = require("jsonic"); -const ini_1 = require("../ini"); -const j = jsonic_1.Jsonic.make().use(ini_1.Ini); -describe('ini', () => { - test('happy', () => { - expect(j('a=1')).toEqual({ a: "1" }); - expect(j('[A]')).toEqual({ A: {} }); - expect(j(`[A.B]\nc='2'`)).toEqual({ A: { B: { c: 2 } } }); - expect(j('a[]=1\na[]=2')).toEqual({ a: ['1', '2'] }); - expect(j('a=\nb=')).toEqual({ a: '', b: '' }); - expect(j(';X\n#Y\na=1;2\nb=2')).toEqual({ a: '1', b: '2' }); - }); - test('basic', () => { - expect(j(` -; comment -a = 1 -b = x -c = y y -c0 = true -" c1 c2 " = null -'[]'='[]' - -[d] -e = 2 -e0[]=q q -e0[]=w w -"[]"="[]" - -[f] -# x:11 -g = 'G' -# x:12 - - -[h.i] -j = [3,4] -j0 = ]3,4[ -k = false - -[l.m.n.o] -p = "P" -q = {x:1} -u = v = 5 -w = '{"y":{"z":6}}' -aa = 7 - -`)) - .toEqual({ - a: '1', - b: 'x', - c: 'y y', - c0: true, - ' c1 c2 ': null, - '[]': [], - d: { - e: '2', - e0: ['q q', 'w w'], - '[]': '[]', - }, - f: { g: 'G' }, - h: { i: { j: '[3,4]', j0: ']3,4[', k: false } }, - l: { - m: { - n: { - o: { - p: 'P', - q: '{x:1}', - u: 'v = 5', - w: { y: { z: 6 } }, - aa: '7' - }, - } - } - } - }); - }); - // NOTE: Copyright (c) Isaac Z. Schlueter and Contributors, ISC License - test('ini-module-test', () => { - expect(j(` -o = p - -a with spaces = b c - -; wrap in quotes to JSON-decode and preserve spaces -" xa n p " = "\\"\\r\\nyoyoyo\\r\\r\\n" - -; wrap in quotes to get a key with a bracket, not a section. -"[disturbing]" = hey you never know - -; Test single quotes -s = 'something' - -; Test mixing quotes - -s1 = "something' - -; Test double quotes -s2 = "something else" - -; Test blank value -s3 = - -; Test value with only spaces -s4 = - -; Test quoted value with only spaces -s5 = ' ' - -; Test quoted value with leading and trailing spaces -s6 = ' a ' - -; Test no equal sign -s7 - -; Test bool(true) -true = true - -; Test bool(false) -false = false - -; Test null -null = null - -; Test undefined -undefined = undefined - -; Test arrays -zr[] = deedee -ar[] = one -ar[] = three -; This should be included in the array -ar = this is included - -; Test resetting of a value (and not turn it into an array) -br = cold -br = warm - -eq = "eq=eq" - -; a section -[a] -av = a val -e = { o: p, a: { av: a val, b: { c: { e: 'this [value]' } } } } -j = "{ o: \\"p\\", a: { av: \\"a val\\", b: { c: { e: \\"this [value]\\" } } } }" -"[]" = a square? - -; Nested array -cr[] = four -cr[] = eight - -; nested child without middle parent -; should create otherwise-empty a.b -[a.b.c] -e = 1 -j = 2 - -; dots in the section name should be literally interpreted -[x\\.y\\.z] -x.y.z = xyz - -[x\\.y\\.z.a\\.b\\.c] -a.b.c = abc - -; this next one is not a comment! it's escaped! -nocomment = this\\; this is not a comment - -# Support the use of the number sign (#) as an alternative to the semicolon for indicating comments. -# http://en.wikipedia.org/wiki/INI_file#Comments - -# this next one is not a comment! it's escaped! -noHashComment = this\\# this is not a comment`)) - .toEqual({ - " xa n p ": "\"\r\nyoyoyo\r\r\n", - "[disturbing]": "hey you never know", - "a": { - "[]": "a square?", - "av": "a val", - "b": { - "c": { - "e": "1", - "j": "2", - }, - }, - "cr": [ - "four", - "eight", - ], - "e": "{ o: p, a: { av: a val, b: { c: { e: 'this [value]' } } } }", - "j": "{ o: \"p\", a: { av: \"a val\", b: { c: { e: \"this [value]\" } } } }", - }, - "a with spaces": "b c", - "ar": [ - "one", - "three", - "this is included", - ], - "br": "warm", - "eq": "eq=eq", - "false": false, - "null": null, - "o": "p", - "s": "something", - "s1": "\"something'", - "s2": "something else", - "s3": "", - "s4": "", - "s5": " ", - "s6": " a ", - "s7": true, - "true": true, - "undefined": "undefined", - "x.y.z": { - "a.b.c": { - "a.b.c": "abc", - "nocomment": "this; this is not a comment", - "noHashComment": "this# this is not a comment", - }, - "x.y.z": "xyz", - }, - "zr": [ - "deedee", - ], - }); - }); -}); -//# sourceMappingURL=ini.test.js.map \ No newline at end of file diff --git a/test/ini.test.js.map b/test/ini.test.js.map deleted file mode 100644 index ad48bfb..0000000 --- a/test/ini.test.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ini.test.js","sourceRoot":"","sources":["ini.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/ini.test.ts b/test/ini.test.ts index 50675fc..9e1b418 100644 --- a/test/ini.test.ts +++ b/test/ini.test.ts @@ -1,8 +1,10 @@ /* Copyright (c) 2021-2025 Richard Rodger and other contributors, MIT License */ +import { test, describe } from 'node:test' +import { expect } from '@hapi/code' import { Jsonic } from 'jsonic' -import { Ini } from '../ini' +import { Ini } from '../dist/ini' const j = Jsonic.make().use(Ini) @@ -11,12 +13,12 @@ const j = Jsonic.make().use(Ini) describe('ini', () => { test('happy', () => { - expect(j('a=1')).toEqual({ a: "1" }) - expect(j('[A]')).toEqual({ A: {} }) - expect(j(`[A.B]\nc='2'`)).toEqual({ A: { B: { c: 2 } } }) - expect(j('a[]=1\na[]=2')).toEqual({ a: ['1', '2'] }) - expect(j('a=\nb=')).toEqual({ a: '', b: '' }) - expect(j(';X\n#Y\na=1;2\nb=2')).toEqual({ a: '1', b: '2' }) + expect(j('a=1')).equal({ a: "1" }) + expect(j('[A]')).equal({ A: {} }) + expect(j(`[A.B]\nc='2'`)).equal({ A: { B: { c: 2 } } }) + expect(j('a[]=1\na[]=2')).equal({ a: ['1', '2'] }) + expect(j('a=\nb=')).equal({ a: '', b: '' }) + expect(j(';X\n#Y\na=1;2\nb=2')).equal({ a: '1', b: '2' }) }) @@ -26,7 +28,7 @@ describe('ini', () => { a = 1 b = x c = y y -c0 = true +c0 = true " c1 c2 " = null '[]'='[]' @@ -55,7 +57,7 @@ w = '{"y":{"z":6}}' aa = 7 `)) - .toEqual({ + .equal({ a: '1', b: 'x', c: 'y y', @@ -112,7 +114,7 @@ s2 = "something else" s3 = ; Test value with only spaces -s4 = +s4 = ; Test quoted value with only spaces s5 = ' ' @@ -180,7 +182,7 @@ nocomment = this\\; this is not a comment # this next one is not a comment! it's escaped! noHashComment = this\\# this is not a comment`)) - .toEqual({ + .equal({ " xa n p ": "\"\r\nyoyoyo\r\r\n", "[disturbing]": "hey you never know", "a": { @@ -236,5 +238,3 @@ noHashComment = this\\# this is not a comment`)) }) - - diff --git a/test/quick.js b/test/quick.js deleted file mode 100644 index ac9c4c8..0000000 --- a/test/quick.js +++ /dev/null @@ -1,106 +0,0 @@ -const ini = require('ini') - -const { Jsonic } = require('@jsonic/jsonic-next') -const { Debug } = require('@jsonic/jsonic-next/debug') - -// console.log(Debug) - -const { Ini } = require('..') - -const j = Jsonic.make().use(Debug, { trace: true }).use(Ini, {}) - -console.dir( - j(` -a=1 - -`), -) - -// console.dir(j(` -// ; comment -// e[]=11 -// e[]=22 -// a=1 -// b=2 -// c c=3 -// [q] -// d=4 -// `)) - -// console.dir(j(` -// a.a=A -// s="\\n" -// q="'Q'" -// qq='Q' -// "[]"='[]' -// `)) - -// console.dir(j(` -// a0=0 -// a[]=1 -// a[]=2 -// a=3 -// a1=33 -// b[]=11 -// c=44 -// [q] -// w[]=55 -// w[]=66 -// `)) - -// console.dir(j(` -// [A] -// [B] -// [C] -// `)) - -// console.dir(j(` -// t=true -// [a.b] -// c=1 -// d=[1,2] -// e=]1,2[ -// `)) - -/* -console.dir(j(` -; comment -a = 1 -b = x -c = y y - - -[d] -e = 2 - -[f] -g = '1' - -[f1] -[f2] - - -[h.i] -#j = [3,4] -k = true - -[l.m.n.o] -p = "P" -q = {x:5} -u = v = 5 -w = "{y:{z:6}}" -aa = 7 - -`), { depth: null }) - - - -console.log(ini.decode(` -a = "{x:1}" -b = '{"x":1}' -aa = "1" -bb = '1' -`)) -*/ - -console.log(ini.decode(`=1`)) diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..e6024df --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "module": "nodenext", + "noEmitOnError": true, + "outDir": "../dist-test", + "rootDir": ".", + "resolveJsonModule": true, + "sourceMap": true, + "strict": true, + "target": "ES2021" + } +} From ce8f0371b4d7d3d86cc383c685d30e8fe7f97d6e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Mar 2026 16:31:02 +0000 Subject: [PATCH 2/6] Update CI workflow to target Node 24 and modernize actions - Change node-version matrix from [18.x, 20.x, 22.x] to [24.x] - Update actions/checkout from v2 to v4 - Update actions/setup-node from v1 to v4 https://claude.ai/code/session_014bMuvf21f6AjmdEPgzvdr5 --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 664972c..6e09b44 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,14 +16,14 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node-version: [18.x, 20.x, 22.x] + node-version: [24.x] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm i From fd4a6a29c0dac763e7e1a8586a2af1b6677017fe Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Mar 2026 16:56:06 +0000 Subject: [PATCH 3/6] Add optional multiline value support with custom lex matcher Multiline values require a custom lex matcher (order 8.4e6) that runs before Hoover's endofline block, because newlines terminate values at the lex level and cannot be handled in the parser. Options via `multiline` (boolean or object): - continuation: char before newline indicating continuation (default '\\') - indent: when true, indented continuation lines extend the value The matcher replicates Hoover endofline behavior (escapes, trimming, comment termination) and adds continuation/indent handling. It only activates in value context during rule open state, matching the same conditions as Hoover's endofline block (parent 'pair'/'elem', state 'o'). https://claude.ai/code/session_014bMuvf21f6AjmdEPgzvdr5 --- src/ini.ts | 143 ++++++++++++++++++++++++++++++++++++++++++++++- test/ini.test.ts | 87 ++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+), 2 deletions(-) diff --git a/src/ini.ts b/src/ini.ts index 34f9646..4945432 100644 --- a/src/ini.ts +++ b/src/ini.ts @@ -1,10 +1,19 @@ /* Copyright (c) 2021-2025 Richard Rodger, MIT License */ // Import Jsonic types used by plugin. -import { Jsonic, RuleSpec, NormAltSpec, Lex } from 'jsonic' +import { Jsonic, RuleSpec, NormAltSpec, Lex, makePoint, Token } from 'jsonic' import { Hoover } from '@jsonic/hoover' -type IniOptions = {} +type IniOptions = { + multiline?: { + // Character before newline indicating continuation. Default: '\\'. + // Set to false to disable backslash continuation. + continuation?: string | false + // When true, a continuation line must be indented (leading whitespace). + // Indented lines continue the previous value even without a continuation char. + indent?: boolean + } | boolean +} function Ini(jsonic: Jsonic, _options: IniOptions) { jsonic.use(Hoover, { @@ -126,6 +135,136 @@ function Ini(jsonic: Jsonic, _options: IniOptions) { }, }) + // Multiline value support via custom lex matcher. + // Newlines terminate values at the lex level (Hoover's endofline block), + // so continuation must be handled by a higher-priority lex matcher that + // replaces endofline in value contexts. + const multiline = true === _options.multiline ? {} : _options.multiline + if (multiline) { + const continuation: string | false = + multiline.continuation !== undefined ? multiline.continuation : '\\' + const indent = multiline.indent || false + const HV_TIN = jsonic.token('#HV') as number + + jsonic.options({ + lex: { + match: { + multiline: { + // Lower order than Hoover (8.5e6) so this runs first. + order: 8.4e6, + make: () => { + return function multilineMatcher(lex: Lex): Token | undefined { + // Only match in value context during rule open state + // (same as Hoover endofline block, which defaults to state 'o'). + let ctx = (lex as any).ctx + let parentName = ctx?.rule?.parent?.name + if (parentName !== 'pair' && parentName !== 'elem') { + return undefined + } + if (ctx?.rule?.state !== 'o') { + return undefined + } + + let src = lex.src + let sI = lex.pnt.sI + let rI = lex.pnt.rI + let cI = lex.pnt.cI + let startI = sI + let chars: string[] = [] + + while (sI < src.length) { + let c = src[sI] + + // Check for comment characters (end value). + if (c === '#' || c === ';') break + + // Check for backslash continuation before newline. + if (false !== continuation && c === continuation) { + if (src[sI + 1] === '\n') { + // \ continuation + sI += 2; rI++; cI = 0 + // Consume leading whitespace on continuation line. + while (sI < src.length && + (src[sI] === ' ' || src[sI] === '\t')) { + sI++; cI++ + } + continue + } + if (src[sI + 1] === '\r' && src[sI + 2] === '\n') { + // \ continuation + sI += 3; rI++; cI = 0 + while (sI < src.length && + (src[sI] === ' ' || src[sI] === '\t')) { + sI++; cI++ + } + continue + } + } + + // Check for newline. + if (c === '\n' || (c === '\r' && src[sI + 1] === '\n')) { + // Indent continuation: next line starts with whitespace. + if (indent) { + let nextI = c === '\r' ? sI + 2 : sI + 1 + if (nextI < src.length && + (src[nextI] === ' ' || src[nextI] === '\t')) { + rI++; cI = 0 + sI = nextI + // Consume leading whitespace. + while (sI < src.length && + (src[sI] === ' ' || src[sI] === '\t')) { + sI++; cI++ + } + chars.push(' ') + continue + } + } + + // Normal newline: end value and consume the newline. + if (c === '\r') { sI += 2 } else { sI++ } + rI++; cI = 0 + break + } + + // Handle escape sequences (same as Hoover endofline block). + if (c === '\\' && sI + 1 < src.length) { + let next = src[sI + 1] + if (next === '#' || next === ';') { + chars.push(next) + sI += 2; cI += 2 + continue + } + if (next === '\\') { + chars.push('\\') + sI += 2; cI += 2 + continue + } + } + + chars.push(c) + sI++; cI++ + } + + let val: string | undefined = chars.join('').trim() + + let pnt = makePoint(lex.pnt.len, sI, rI, cI) + let tkn = lex.token( + HV_TIN, val, src.substring(startI, sI), pnt) + tkn.use = { block: 'endofline' } + + lex.pnt.sI = sI + lex.pnt.rI = rI + lex.pnt.cI = cI + + return tkn + } + } + } + } + } + }) + } + const { ZZ, ST, VL, OS, CS, CL, EQ, DOT, HV, HK, DK } = jsonic.token const KEY = [HK, ST, VL] diff --git a/test/ini.test.ts b/test/ini.test.ts index 9e1b418..9f299ce 100644 --- a/test/ini.test.ts +++ b/test/ini.test.ts @@ -238,3 +238,90 @@ noHashComment = this\\# this is not a comment`)) }) + + +describe('multiline', () => { + + test('backslash-continuation', () => { + const jm = Jsonic.make().use(Ini, { multiline: true }) + + // Basic continuation with \ + expect(jm('a = hello \\\nworld')).equal({ a: 'hello world' }) + + // Continuation with leading whitespace on next line (consumed) + expect(jm('a = hello \\\n world')).equal({ a: 'hello world' }) + + // Multiple continuations + expect(jm('a = one \\\ntwo \\\nthree')).equal({ a: 'one two three' }) + + // No continuation: normal newline ends value + expect(jm('a = hello\nb = world')).equal({ a: 'hello', b: 'world' }) + + // Continuation with \ + expect(jm('a = hello \\\r\nworld')).equal({ a: 'hello world' }) + + // Escaped backslash before newline is NOT continuation + expect(jm('a = path\\\\\nb = next')).equal({ a: 'path\\', b: 'next' }) + + // Continuation in a section + expect(jm('[s]\na = hello \\\n world')).equal({ s: { a: 'hello world' } }) + + // Empty value with continuation + expect(jm('a = \\\nworld')).equal({ a: 'world' }) + + // Comment after continuation value + expect(jm('a = hello \\\nworld ;comment\nb = 2')) + .equal({ a: 'hello world', b: '2' }) + }) + + test('indent-continuation', () => { + const ji = Jsonic.make().use(Ini, { multiline: { indent: true, continuation: false } }) + + // Indented line continues previous value + expect(ji('a = hello\n world')).equal({ a: 'hello world' }) + + // Multiple indent continuations + expect(ji('a = line1\n line2\n line3')).equal({ a: 'line1 line2 line3' }) + + // Non-indented line is a new key + expect(ji('a = hello\nb = world')).equal({ a: 'hello', b: 'world' }) + + // Tab indent + expect(ji('a = hello\n\tworld')).equal({ a: 'hello world' }) + + // Indent continuation in section + expect(ji('[s]\na = hello\n world')) + .equal({ s: { a: 'hello world' } }) + }) + + test('multiline-with-boolean-option', () => { + // multiline: true enables defaults (backslash continuation, no indent) + const jm = Jsonic.make().use(Ini, { multiline: true }) + expect(jm('a = hello \\\nworld')).equal({ a: 'hello world' }) + }) + + test('multiline-both-modes', () => { + // Both continuation char and indent enabled + const jb = Jsonic.make().use(Ini, { + multiline: { continuation: '\\', indent: true } + }) + + // Backslash continuation works + expect(jb('a = hello \\\nworld')).equal({ a: 'hello world' }) + + // Indent continuation also works + expect(jb('a = hello\n world')).equal({ a: 'hello world' }) + }) + + test('multiline-escapes', () => { + const jm = Jsonic.make().use(Ini, { multiline: true }) + + // Escaped comment chars still work with continuation + expect(jm('a = one\\; two \\\nthree')) + .equal({ a: 'one; two three' }) + + // Escaped hash + expect(jm('a = one\\# two \\\nthree')) + .equal({ a: 'one# two three' }) + }) +}) From 0af71906ea25c138a2e956fd4203ced829b8309c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Mar 2026 17:23:39 +0000 Subject: [PATCH 4/6] Add duplicate section merging awareness with configurable policy New `section.duplicate` option controls behavior when the same section header appears multiple times: - 'merge' (default): combine keys from all occurrences, last value wins - 'override': last section occurrence replaces earlier ones entirely - 'error': throw when a previously declared section header reappears Tracks explicitly declared section paths per parse call using a Set (cleared on each parse). Intermediate paths created by nested sections like [a.b] do NOT count as declared [a] sections, so [a.b] followed by [a] does not trigger an error. https://claude.ai/code/session_014bMuvf21f6AjmdEPgzvdr5 --- src/ini.ts | 33 +++++++++++++++++++- test/ini.test.ts | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/src/ini.ts b/src/ini.ts index 4945432..b085052 100644 --- a/src/ini.ts +++ b/src/ini.ts @@ -13,6 +13,13 @@ type IniOptions = { // Indented lines continue the previous value even without a continuation char. indent?: boolean } | boolean + section?: { + // How to handle duplicate section headers. Default: 'merge'. + // 'merge': combine keys from all occurrences (last value wins for duplicate keys) + // 'override': last section occurrence replaces earlier ones entirely + // 'error': throw when a previously declared section header appears again + duplicate?: 'merge' | 'override' | 'error' + } } function Ini(jsonic: Jsonic, _options: IniOptions) { @@ -269,9 +276,16 @@ function Ini(jsonic: Jsonic, _options: IniOptions) { const KEY = [HK, ST, VL] + const dupSection = _options.section?.duplicate || 'merge' + + // Track explicitly declared section paths per parse call. + // Cleared in the ini rule's bo handler, used in the table rule. + const declaredSections = new Set() + jsonic.rule('ini', (rs: RuleSpec) => { rs.bo((r) => { r.node = {} + declaredSections.clear() }).open([ { s: [OS], p: 'table', b: 1 }, { s: [KEY, EQ], p: 'table', b: 2 }, @@ -286,9 +300,26 @@ function Ini(jsonic: Jsonic, _options: IniOptions) { if (r.prev.u.dive) { let dive = r.prev.u.dive + // Use null char as separator to avoid collisions with dots in key names. + let sectionKey = dive.join('\x00') + let isDuplicate = declaredSections.has(sectionKey) + + if (isDuplicate && dupSection === 'error') { + throw new Error( + 'Duplicate section: [' + dive.join('.') + ']' + ) + } + for (let dI = 0; dI < dive.length; dI++) { - r.node = r.node[dive[dI]] = r.node[dive[dI]] || {} + if (dI === dive.length - 1 && isDuplicate && dupSection === 'override') { + // Override: replace the section object entirely. + r.node = r.node[dive[dI]] = {} + } else { + r.node = r.node[dive[dI]] = r.node[dive[dI]] || {} + } } + + declaredSections.add(sectionKey) } }) .open([ diff --git a/test/ini.test.ts b/test/ini.test.ts index 9f299ce..fa5e607 100644 --- a/test/ini.test.ts +++ b/test/ini.test.ts @@ -325,3 +325,82 @@ describe('multiline', () => { .equal({ a: 'one# two three' }) }) }) + + +describe('section-duplicate', () => { + + test('merge-default', () => { + const j = Jsonic.make().use(Ini) + + // Default: merge keys from duplicate sections + expect(j('[a]\nx=1\ny=2\n[a]\nz=3')) + .equal({ a: { x: '1', y: '2', z: '3' } }) + + // Duplicate key: last value wins + expect(j('[a]\nx=1\n[a]\nx=2')) + .equal({ a: { x: '2' } }) + + // Nested duplicate sections merge + expect(j('[a.b]\nx=1\n[a.b]\ny=2')) + .equal({ a: { b: { x: '1', y: '2' } } }) + + // Intermediate path preserved when merging + expect(j('[a.b]\nx=1\n[a]\ny=2')) + .equal({ a: { b: { x: '1' }, y: '2' } }) + }) + + test('merge-explicit', () => { + const jm = Jsonic.make().use(Ini, { section: { duplicate: 'merge' } }) + + expect(jm('[a]\nx=1\n[a]\ny=2')) + .equal({ a: { x: '1', y: '2' } }) + }) + + test('override', () => { + const jo = Jsonic.make().use(Ini, { section: { duplicate: 'override' } }) + + // Second occurrence replaces first + expect(jo('[a]\nx=1\ny=2\n[a]\nz=3')) + .equal({ a: { z: '3' } }) + + // First occurrence works normally + expect(jo('[a]\nx=1')) + .equal({ a: { x: '1' } }) + + // Override clears subsections too + expect(jo('[a.b]\nx=1\n[a]\ny=2\n[a]\nz=3')) + .equal({ a: { z: '3' } }) + + // Non-duplicate sections unaffected + expect(jo('[a]\nx=1\n[b]\ny=2')) + .equal({ a: { x: '1' }, b: { y: '2' } }) + + // Nested override + expect(jo('[a.b]\nx=1\n[a.b]\ny=2')) + .equal({ a: { b: { y: '2' } } }) + }) + + test('error', () => { + const je = Jsonic.make().use(Ini, { section: { duplicate: 'error' } }) + + // Single section: no error + expect(je('[a]\nx=1')).equal({ a: { x: '1' } }) + + // Multiple distinct sections: no error + expect(je('[a]\nx=1\n[b]\ny=2')) + .equal({ a: { x: '1' }, b: { y: '2' } }) + + // Duplicate section: throws + expect(() => je('[a]\nx=1\n[a]\ny=2')) + .to.throw(/Duplicate section/) + + // Duplicate nested section: throws + expect(() => je('[a.b]\nx=1\n[a.b]\ny=2')) + .to.throw(/Duplicate section/) + + // Intermediate path is NOT a declared section + // [a.b] creates intermediate [a] but does not declare it + expect(je('[a.b]\nx=1\n[a]\ny=2')) + .equal({ a: { b: { x: '1' }, y: '2' } }) + }) +}) From cd6c5c7dffa76a0e2250df9a5a71d45affe40ff7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Mar 2026 17:27:20 +0000 Subject: [PATCH 5/6] Add tests for number.lex: true option Tests cover enabling Jsonic's number lexer via post-config so numeric INI values are parsed as actual numbers: integers, floats, scientific notation, hex, mixed types with strings, values in sections, and arrays. Also verifies the default behavior (numbers as strings). https://claude.ai/code/session_014bMuvf21f6AjmdEPgzvdr5 --- test/ini.test.ts | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/test/ini.test.ts b/test/ini.test.ts index fa5e607..cbbefe1 100644 --- a/test/ini.test.ts +++ b/test/ini.test.ts @@ -404,3 +404,85 @@ describe('section-duplicate', () => { .equal({ a: { b: { x: '1' }, y: '2' } }) }) }) + + +describe('number-lex', () => { + + // Enable number lexing via post-config so Jsonic parses numeric values as numbers + function makeWithNumbers() { + const jn = Jsonic.make().use(Ini) + jn.options({ number: { lex: true } }) + return jn + } + + test('integers', () => { + const jn = makeWithNumbers() + + expect(jn('a=1')).equal({ a: 1 }) + expect(jn('a=0')).equal({ a: 0 }) + expect(jn('a=-3')).equal({ a: -3 }) + expect(jn('a=+2')).equal({ a: 2 }) + expect(jn('a=42\nb=99')).equal({ a: 42, b: 99 }) + }) + + test('floats', () => { + const jn = makeWithNumbers() + + expect(jn('a=2.5')).equal({ a: 2.5 }) + expect(jn('a=0.0')).equal({ a: 0 }) + expect(jn('a=-1.25')).equal({ a: -1.25 }) + }) + + test('scientific-notation', () => { + const jn = makeWithNumbers() + + expect(jn('a=1e10')).equal({ a: 1e10 }) + }) + + test('hex', () => { + const jn = makeWithNumbers() + + expect(jn('a=0xFF')).equal({ a: 255 }) + }) + + test('mixed-types', () => { + const jn = makeWithNumbers() + + // Numbers and strings coexist + expect(jn('a=1\nb=hello\nc=2.5\nd=true')) + .equal({ a: 1, b: 'hello', c: 2.5, d: true }) + + // Non-numeric strings stay as strings + expect(jn('a=1abc')).equal({ a: '1abc' }) + + // Empty value stays as empty string + expect(jn('a=\nb=1')).equal({ a: '', b: 1 }) + }) + + test('in-sections', () => { + const jn = makeWithNumbers() + + expect(jn('[s]\na=42\nb=text')) + .equal({ s: { a: 42, b: 'text' } }) + + expect(jn('[s]\na=1\n[t]\nb=2')) + .equal({ s: { a: 1 }, t: { b: 2 } }) + }) + + test('arrays', () => { + const jn = makeWithNumbers() + + expect(jn('a[]=1\na[]=2\na[]=hello')) + .equal({ a: [1, 2, 'hello'] }) + }) + + test('default-numbers-are-strings', () => { + // Without number.lex, all values are strings + const j = Jsonic.make().use(Ini) + + expect(j('a=1')).equal({ a: '1' }) + expect(j('a=2.5')).equal({ a: '2.5' }) + expect(j('a=-3')).equal({ a: '-3' }) + expect(j('a=0xFF')).equal({ a: '0xFF' }) + }) +}) From 3056df3247dce5ee6bfea3f6819ebe9bf8c37f2b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Mar 2026 17:54:44 +0000 Subject: [PATCH 6/6] Add inline comment option map with configurable activation, chars, and escaping Inline comments are now off by default (breaking change). The new comment.inline option controls: - active: whether inline comments are enabled (default: false) - chars: which characters trigger inline comments (default: ['#', ';']) - escape.backslash: allow \; and \# to produce literals (default: true) - escape.whitespace: require whitespace before comment char (default: false) The custom value lex matcher is now also registered when whitespace-prefix detection is needed (not just for multiline), handling the case where Hoover's fixed end tokens can't express "preceded by whitespace" logic. https://claude.ai/code/session_014bMuvf21f6AjmdEPgzvdr5 --- src/ini.ts | 127 ++++++++++++++++++++++++++++------- test/ini.test.ts | 171 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 266 insertions(+), 32 deletions(-) diff --git a/src/ini.ts b/src/ini.ts index b085052..b44ed26 100644 --- a/src/ini.ts +++ b/src/ini.ts @@ -4,6 +4,20 @@ import { Jsonic, RuleSpec, NormAltSpec, Lex, makePoint, Token } from 'jsonic' import { Hoover } from '@jsonic/hoover' +type InlineCommentOptions = { + // Whether inline comments are active. Default: false. + active?: boolean + // Characters that start an inline comment. Default: ['#', ';']. + chars?: string[] + // Escape mechanisms for literal comment characters in values. + escape?: { + // Allow \; and \# to produce literal ; and #. Default: true. + backslash?: boolean + // Require whitespace before comment char to trigger. Default: false. + whitespace?: boolean + } +} + type IniOptions = { multiline?: { // Character before newline indicating continuation. Default: '\\'. @@ -20,9 +34,52 @@ type IniOptions = { // 'error': throw when a previously declared section header appears again duplicate?: 'merge' | 'override' | 'error' } + comment?: { + // Control inline comment behavior. Default: inactive. + inline?: InlineCommentOptions + } } function Ini(jsonic: Jsonic, _options: IniOptions) { + // Resolve inline comment options. + const inlineComment = { + active: _options.comment?.inline?.active ?? false, + chars: _options.comment?.inline?.chars ?? ['#', ';'], + escape: { + backslash: _options.comment?.inline?.escape?.backslash ?? true, + whitespace: _options.comment?.inline?.escape?.whitespace ?? false, + }, + } + + // Build Hoover end.fixed arrays based on inline comment config. + // When active without whitespace mode, include comment chars as terminators. + // When whitespace mode is on, the custom value matcher handles detection instead. + const inlineCharsInFixed = + inlineComment.active && !inlineComment.escape.whitespace + + const eolEndFixed: string[] = ['\n', '\r\n'] + if (inlineCharsInFixed) { + eolEndFixed.push(...inlineComment.chars) + } + eolEndFixed.push('') + + const keyEndFixed: string[] = ['=', '\n', '\r\n'] + if (inlineCharsInFixed) { + keyEndFixed.push(...inlineComment.chars) + } + keyEndFixed.push('') + + // Build escape maps. Always include '\\' -> '\\'. + // Add comment char escapes when inline comments are active with backslash escaping. + const eolEscape: Record = { '\\': '\\' } + const keyEscape: Record = { '\\': '\\' } + if (inlineComment.active && inlineComment.escape.backslash) { + for (const ch of inlineComment.chars) { + eolEscape[ch] = ch + keyEscape[ch] = ch + } + } + jsonic.use(Hoover, { lex: { order: 8.5e6, @@ -37,15 +94,12 @@ function Ini(jsonic: Jsonic, _options: IniOptions) { }, }, end: { - fixed: ['\n', '\r\n', '#', ';', ''], + fixed: eolEndFixed, consume: ['\n', '\r\n'], }, escapeChar: '\\', - escape: { - '#': '#', - ';': ';', - '\\': '\\', - }, + escape: eolEscape, + allowUnknownEscape: true, preserveEscapeChar: true, trim: true, }, @@ -60,14 +114,10 @@ function Ini(jsonic: Jsonic, _options: IniOptions) { }, }, end: { - fixed: ['=', '\n', '\r\n', '#', ';', ''], + fixed: keyEndFixed, consume: false, }, - escape: { - '#': '#', - ';': ';', - '\\': '\\', - }, + escape: keyEscape, trim: true, }, divekey: { @@ -142,17 +192,24 @@ function Ini(jsonic: Jsonic, _options: IniOptions) { }, }) - // Multiline value support via custom lex matcher. - // Newlines terminate values at the lex level (Hoover's endofline block), - // so continuation must be handled by a higher-priority lex matcher that - // replaces endofline in value contexts. + // Custom value lex matcher. + // Needed when: (a) multiline continuation is enabled, or + // (b) inline comments are active with whitespace-prefix detection. + // Runs at higher priority than Hoover's endofline block to intercept values. const multiline = true === _options.multiline ? {} : _options.multiline - if (multiline) { - const continuation: string | false = - multiline.continuation !== undefined ? multiline.continuation : '\\' - const indent = multiline.indent || false + const needCustomMatcher = + !!multiline || (inlineComment.active && inlineComment.escape.whitespace) + + if (needCustomMatcher) { + const continuation: string | false = multiline + ? (multiline.continuation !== undefined ? multiline.continuation : '\\') + : false + const indent = multiline ? (multiline.indent || false) : false const HV_TIN = jsonic.token('#HV') as number + // Build a Set for fast comment char lookup in the matcher. + const commentCharSet = new Set(inlineComment.chars) + jsonic.options({ lex: { match: { @@ -182,8 +239,24 @@ function Ini(jsonic: Jsonic, _options: IniOptions) { while (sI < src.length) { let c = src[sI] - // Check for comment characters (end value). - if (c === '#' || c === ';') break + // Check for inline comment characters (end value). + if (inlineComment.active && commentCharSet.has(c)) { + if (inlineComment.escape.whitespace) { + // Only treat as comment if preceded by whitespace. + if ( + chars.length > 0 && + (chars[chars.length - 1] === ' ' || + chars[chars.length - 1] === '\t') + ) { + break + } + // Not preceded by whitespace: treat as literal. + chars.push(c) + sI++; cI++ + continue + } + break + } // Check for backslash continuation before newline. if (false !== continuation && c === continuation) { @@ -233,10 +306,14 @@ function Ini(jsonic: Jsonic, _options: IniOptions) { break } - // Handle escape sequences (same as Hoover endofline block). + // Handle escape sequences. if (c === '\\' && sI + 1 < src.length) { let next = src[sI + 1] - if (next === '#' || next === ';') { + if ( + inlineComment.active && + inlineComment.escape.backslash && + commentCharSet.has(next) + ) { chars.push(next) sI += 2; cI += 2 continue @@ -462,4 +539,4 @@ function Ini(jsonic: Jsonic, _options: IniOptions) { export { Ini } -export type { IniOptions } +export type { IniOptions, InlineCommentOptions } diff --git a/test/ini.test.ts b/test/ini.test.ts index cbbefe1..c06b039 100644 --- a/test/ini.test.ts +++ b/test/ini.test.ts @@ -18,7 +18,8 @@ describe('ini', () => { expect(j(`[A.B]\nc='2'`)).equal({ A: { B: { c: 2 } } }) expect(j('a[]=1\na[]=2')).equal({ a: ['1', '2'] }) expect(j('a=\nb=')).equal({ a: '', b: '' }) - expect(j(';X\n#Y\na=1;2\nb=2')).equal({ a: '1', b: '2' }) + // Inline comments are off by default; ; and # mid-value are literal. + expect(j(';X\n#Y\na=1;2\nb=2')).equal({ a: '1;2', b: '2' }) }) @@ -225,8 +226,8 @@ noHashComment = this\\# this is not a comment`)) "x.y.z": { "a.b.c": { "a.b.c": "abc", - "nocomment": "this; this is not a comment", - "noHashComment": "this# this is not a comment", + "nocomment": "this\\; this is not a comment", + "noHashComment": "this\\# this is not a comment", }, "x.y.z": "xyz", }, @@ -269,9 +270,9 @@ describe('multiline', () => { // Empty value with continuation expect(jm('a = \\\nworld')).equal({ a: 'world' }) - // Comment after continuation value - expect(jm('a = hello \\\nworld ;comment\nb = 2')) - .equal({ a: 'hello world', b: '2' }) + // Inline comments off by default: ; is literal in value + expect(jm('a = hello \\\nworld ;not-a-comment\nb = 2')) + .equal({ a: 'hello world ;not-a-comment', b: '2' }) }) test('indent-continuation', () => { @@ -314,7 +315,11 @@ describe('multiline', () => { }) test('multiline-escapes', () => { - const jm = Jsonic.make().use(Ini, { multiline: true }) + // Multiline with inline comments active and backslash escaping + const jm = Jsonic.make().use(Ini, { + multiline: true, + comment: { inline: { active: true, escape: { backslash: true } } }, + }) // Escaped comment chars still work with continuation expect(jm('a = one\\; two \\\nthree')) @@ -324,6 +329,17 @@ describe('multiline', () => { expect(jm('a = one\\# two \\\nthree')) .equal({ a: 'one# two three' }) }) + + test('multiline-no-inline-comments', () => { + // Multiline without inline comments: ; and # are literal + const jm = Jsonic.make().use(Ini, { multiline: true }) + + expect(jm('a = one; two \\\nthree')) + .equal({ a: 'one; two three' }) + + expect(jm('a = one# two \\\nthree')) + .equal({ a: 'one# two three' }) + }) }) @@ -406,6 +422,147 @@ describe('section-duplicate', () => { }) +describe('inline-comment', () => { + + test('off-by-default', () => { + // Default: inline comments are off. ; and # mid-value are literal. + const j = Jsonic.make().use(Ini) + + expect(j('a = hello ; world')).equal({ a: 'hello ; world' }) + expect(j('a = hello # world')).equal({ a: 'hello # world' }) + expect(j('a = x;y;z')).equal({ a: 'x;y;z' }) + + // Line-start comments still work + expect(j('; comment\na = 1')).equal({ a: '1' }) + expect(j('# comment\na = 1')).equal({ a: '1' }) + }) + + test('active-basic', () => { + // Inline comments active with defaults (chars: ['#', ';']) + const j = Jsonic.make().use(Ini, { + comment: { inline: { active: true } }, + }) + + expect(j('a = hello ; comment')).equal({ a: 'hello' }) + expect(j('a = hello # comment')).equal({ a: 'hello' }) + expect(j('a = x;y')).equal({ a: 'x' }) + expect(j('a = value\nb = other')).equal({ a: 'value', b: 'other' }) + }) + + test('custom-chars', () => { + // Only ; is an inline comment char, not # + const j = Jsonic.make().use(Ini, { + comment: { inline: { active: true, chars: [';'] } }, + }) + + expect(j('a = hello ; comment')).equal({ a: 'hello' }) + expect(j('a = hello # not a comment')).equal({ a: 'hello # not a comment' }) + }) + + test('backslash-escape', () => { + // Backslash escaping enabled (default when active) + const j = Jsonic.make().use(Ini, { + comment: { inline: { active: true, escape: { backslash: true } } }, + }) + + expect(j('a = hello\\; world')).equal({ a: 'hello; world' }) + expect(j('a = hello\\# world')).equal({ a: 'hello# world' }) + expect(j('a = x\\;y ; comment')).equal({ a: 'x;y' }) + }) + + test('backslash-escape-disabled', () => { + // Backslash escaping explicitly disabled: \; keeps both chars but + // the escapeChar still prevents ; from terminating. The difference + // is that backslash is preserved in the output rather than consumed. + const j = Jsonic.make().use(Ini, { + comment: { inline: { active: true, escape: { backslash: false } } }, + }) + + // \; → \; (backslash preserved, ; did not terminate) + expect(j('a = hello\\; world')).equal({ a: 'hello\\; world' }) + + // Unescaped ; still terminates + expect(j('a = hello ; comment')).equal({ a: 'hello' }) + }) + + test('whitespace-prefix', () => { + // Whitespace-prefix mode: only treat as comment if preceded by whitespace + const j = Jsonic.make().use(Ini, { + comment: { inline: { active: true, escape: { whitespace: true } } }, + }) + + // No whitespace before ; → literal + expect(j('a = x;y;z')).equal({ a: 'x;y;z' }) + + // Whitespace before ; → inline comment + expect(j('a = hello ;comment')).equal({ a: 'hello' }) + expect(j('a = hello\t;comment')).equal({ a: 'hello' }) + + // Same for # + expect(j('a = x#y')).equal({ a: 'x#y' }) + expect(j('a = hello #comment')).equal({ a: 'hello' }) + }) + + test('whitespace-prefix-with-backslash', () => { + // Both whitespace and backslash escaping + const j = Jsonic.make().use(Ini, { + comment: { + inline: { + active: true, + escape: { whitespace: true, backslash: true }, + }, + }, + }) + + // No whitespace: literal + expect(j('a = x;y')).equal({ a: 'x;y' }) + + // Whitespace present: comment + expect(j('a = hello ;comment')).equal({ a: 'hello' }) + + // Backslash escape overrides whitespace: literal + expect(j('a = hello \\;not-a-comment')).equal({ a: 'hello ;not-a-comment' }) + }) + + test('with-multiline', () => { + // Inline comments active + multiline continuation + const j = Jsonic.make().use(Ini, { + multiline: true, + comment: { inline: { active: true } }, + }) + + // Comment terminates continued value + expect(j('a = hello \\\nworld ;comment\nb = 2')) + .equal({ a: 'hello world', b: '2' }) + + // Escaped comment char in multiline value + expect(j('a = hello\\; \\\nworld')) + .equal({ a: 'hello; world' }) + }) + + test('with-sections', () => { + const j = Jsonic.make().use(Ini, { + comment: { inline: { active: true } }, + }) + + expect(j('[s]\na = val ; comment\nb = other')) + .equal({ s: { a: 'val', b: 'other' } }) + }) + + test('line-comments-always-work', () => { + // Line-start comments work regardless of inline comment setting + const jOff = Jsonic.make().use(Ini) + const jOn = Jsonic.make().use(Ini, { + comment: { inline: { active: true } }, + }) + + const input = '; line comment\n# hash comment\na = 1' + expect(jOff(input)).equal({ a: '1' }) + expect(jOn(input)).equal({ a: '1' }) + }) +}) + + describe('number-lex', () => { // Enable number lexing via post-config so Jsonic parses numeric values as numbers