sommark 5.0.4 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/async-hooks.js +19 -0
- package/core/evaluator.js +83 -13
- package/core/helpers/config-loader.js +29 -9
- package/core/helpers/lib.js +1 -1
- package/core/helpers/preprocessor.js +19 -0
- package/core/modules.js +35 -17
- package/core/parser.js +6 -3
- package/core/transpiler.js +30 -10
- package/core/validator.js +17 -4
- package/dist/sommark.browser.js +414 -195
- package/dist/sommark.browser.lite.js +331 -181
- package/dist/sommark.parser.js +6 -3
- package/esbuild.js +64 -0
- package/index.browser.js +4 -1
- package/index.js +32 -1
- package/index.shared.js +10 -1
- package/mappers/languages/markdown.js +99 -81
- package/mappers/languages/xml.js +76 -61
- package/mappers/shared/index.js +10 -1
- package/package.json +9 -2
- package/rollup.js +79 -0
- package/vite.js +20 -0
package/dist/sommark.browser.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
-
|
|
3
1
|
let _lazyMatch = () => { var __lib__=(()=>{var m=Object.defineProperty,V=Object.getOwnPropertyDescriptor,G=Object.getOwnPropertyNames,T=Object.prototype.hasOwnProperty,q=(r,e)=>{for(var n in e)m(r,n,{get:e[n],enumerable:true});},H=(r,e,n,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of G(e))!T.call(r,t)&&t!==n&&m(r,t,{get:()=>e[t],enumerable:!(a=V(e,t))||a.enumerable});return r},J=r=>H(m({},"__esModule",{value:true}),r),w={};q(w,{default:()=>re});var A=r=>Array.isArray(r),d=r=>typeof r=="function",Q=r=>r.length===0,W=r=>typeof r=="number",K=r=>typeof r=="object"&&r!==null,X=r=>r instanceof RegExp,b=r=>typeof r=="string",h=r=>r===void 0,Y=r=>{const e=new Map;return n=>{const a=e.get(n);if(a)return a;const t=r(n);return e.set(n,t),t}},rr=(r,e,n={})=>{const a={cache:{},input:r,index:0,indexMax:0,options:n,output:[]};if(v(e)(a)&&a.index===r.length)return a.output;throw new Error(`Failed to parse at index ${a.indexMax}`)},i=(r,e)=>A(r)?er(r,e):b(r)?ar(r,e):nr(r,e),er=(r,e)=>{const n={};for(const a of r){if(a.length!==1)throw new Error(`Invalid character: "${a}"`);const t=a.charCodeAt(0);n[t]=true;}return a=>{const t=a.index,o=a.input;for(;a.index<o.length&&o.charCodeAt(a.index)in n;)a.index+=1;const u=a.index;if(u>t){if(!h(e)&&!a.options.silent){const s=a.input.slice(t,u),c=d(e)?e(s,o,String(t)):e;h(c)||a.output.push(c);}a.indexMax=Math.max(a.indexMax,a.index);}return true}},nr=(r,e)=>{const n=r.source,a=r.flags.replace(/y|$/,"y"),t=new RegExp(n,a);return g(o=>{t.lastIndex=o.index;const u=t.exec(o.input);if(u){if(!h(e)&&!o.options.silent){const s=d(e)?e(...u,o.input,String(o.index)):e;h(s)||o.output.push(s);}return o.index+=u[0].length,o.indexMax=Math.max(o.indexMax,o.index),true}else return false})},ar=(r,e)=>n=>{if(n.input.startsWith(r,n.index)){if(!h(e)&&!n.options.silent){const t=d(e)?e(r,n.input,String(n.index)):e;h(t)||n.output.push(t);}return n.index+=r.length,n.indexMax=Math.max(n.indexMax,n.index),true}else return false},C=(r,e,n,a)=>{const t=v(r);return g(_(M(o=>{let u=0;for(;u<n;){const s=o.index;if(!t(o)||(u+=1,o.index===s))break}return u>=e})))},tr=(r,e)=>C(r,0,1),f=(r,e)=>C(r,0,1/0),x=(r,e)=>{const n=r.map(v);return g(_(M(a=>{for(let t=0,o=n.length;t<o;t++)if(!n[t](a))return false;return true})))},l=(r,e)=>{const n=r.map(v);return g(_(a=>{for(let t=0,o=n.length;t<o;t++)if(n[t](a))return true;return false}))},M=(r,e=false)=>{const n=v(r);return a=>{const t=a.index,o=a.output.length,u=n(a);return (!u||e)&&(a.index=t,a.output.length!==o&&(a.output.length=o)),u}},_=(r,e)=>{const n=v(r);return n},g=(()=>{let r=0;return e=>{const n=v(e),a=r+=1;return t=>{var o;if(t.options.memoization===false)return n(t);const u=t.index,s=(o=t.cache)[a]||(o[a]=new Map),c=s.get(u);if(c===false)return false;if(W(c))return t.index=c,true;if(c)return t.index=c.index,c.output?.length&&t.output.push(...c.output),true;{const Z=t.output.length;if(n(t)){const D=t.index,U=t.output.length;if(U>Z){const ee=t.output.slice(Z,U);s.set(u,{index:D,output:ee});}else s.set(u,D);return true}else return s.set(u,false),false}}}})(),E=r=>{let e;return n=>(e||(e=v(r())),e(n))},v=Y(r=>{if(d(r))return Q(r)?E(r):r;if(b(r)||X(r))return i(r);if(A(r))return x(r);if(K(r))return l(Object.values(r));throw new Error("Invalid rule")}),P="abcdefghijklmnopqrstuvwxyz",ir=r=>{let e="";for(;r>0;){const n=(r-1)%26;e=P[n]+e,r=Math.floor((r-1)/26);}return e},O=r=>{let e=0;for(let n=0,a=r.length;n<a;n++)e=e*26+P.indexOf(r[n])+1;return e},S=(r,e)=>{if(e<r)return S(e,r);const n=[];for(;r<=e;)n.push(r++);return n},or=(r,e,n)=>S(r,e).map(a=>String(a).padStart(n,"0")),R=(r,e)=>S(O(r),O(e)).map(ir),p=r=>r,z=r=>ur(e=>rr(e,r,{memoization:false}).join("")),ur=r=>{const e={};return n=>e[n]??(e[n]=r(n))},sr=i(/^\*\*\/\*$/,".*"),cr=i(/^\*\*\/(\*)?([ a-zA-Z0-9._-]+)$/,(r,e,n)=>`.*${e?"":"(?:^|/)"}${n.replaceAll(".","\\.")}`),lr=i(/^\*\*\/(\*)?([ a-zA-Z0-9._-]*)\{([ a-zA-Z0-9._-]+(?:,[ a-zA-Z0-9._-]+)*)\}$/,(r,e,n,a)=>`.*${e?"":"(?:^|/)"}${n.replaceAll(".","\\.")}(?:${a.replaceAll(",","|").replaceAll(".","\\.")})`),y=i(/\\./,p),pr=i(/[$.*+?^(){}[\]\|]/,r=>`\\${r}`),vr=i(/./,p),hr=i(/^(?:!!)*!(.*)$/,(r,e)=>`(?!^${L(e)}$).*?`),dr=i(/^(!!)+/,""),fr=l([hr,dr]),xr=i(/\/(\*\*\/)+/,"(?:/.+/|/)"),gr=i(/^(\*\*\/)+/,"(?:^|.*/)"),mr=i(/\/(\*\*)$/,"(?:/.*|$)"),_r=i(/\*\*/,".*"),j=l([xr,gr,mr,_r]),Sr=i(/\*\/(?!\*\*\/)/,"[^/]*/"),yr=i(/\*/,"[^/]*"),N=l([Sr,yr]),k=i("?","[^/]"),$r=i("[",p),wr=i("]",p),Ar=i(/[!^]/,"^/"),br=i(/[a-z]-[a-z]|[0-9]-[0-9]/i,p),Cr=i(/[$.*+?^(){}[\|]/,r=>`\\${r}`),Mr=i(/[^\]]/,p),Er=l([y,Cr,br,Mr]),B=x([$r,tr(Ar),f(Er),wr]),Pr=i("{","(?:"),Or=i("}",")"),Rr=i(/(\d+)\.\.(\d+)/,(r,e,n)=>or(+e,+n,Math.min(e.length,n.length)).join("|")),zr=i(/([a-z]+)\.\.([a-z]+)/,(r,e,n)=>R(e,n).join("|")),jr=i(/([A-Z]+)\.\.([A-Z]+)/,(r,e,n)=>R(e.toLowerCase(),n.toLowerCase()).join("|").toUpperCase()),Nr=l([Rr,zr,jr]),I=x([Pr,Nr,Or]),kr=i("{","(?:"),Br=i("}",")"),Ir=i(",","|"),Fr=i(/[$.*+?^(){[\]\|]/,r=>`\\${r}`),Lr=i(/[^}]/,p),Zr=E(()=>F),Dr=l([j,N,k,B,I,Zr,y,Fr,Ir,Lr]),F=x([kr,f(Dr),Br]),Ur=f(l([sr,cr,lr,fr,j,N,k,B,I,F,y,pr,vr])),Vr=Ur,Gr=z(Vr),L=Gr,Tr=i(/\\./,p),qr=i(/./,p),Hr=i(/\*\*\*+/,"*"),Jr=i(/([^/{[(!])\*\*/,(r,e)=>`${e}*`),Qr=i(/(^|.)\*\*(?=[^*/)\]}])/,(r,e)=>`${e}*`),Wr=f(l([Tr,Hr,Jr,Qr,qr])),Kr=Wr,Xr=z(Kr),Yr=Xr,$=(r,e)=>{const n=Array.isArray(r)?r:[r];if(!n.length)return false;const a=n.map($.compile),t=n.every(s=>/(\/(?:\*\*)?|\[\/\])$/.test(s)),o=e.replace(/[\\\/]+/g,"/").replace(/\/$/,t?"/":"");return a.some(s=>s.test(o))};$.compile=r=>new RegExp(`^${L(Yr(r))}$`,"s");var re=$;return J(w)})();
|
|
4
2
|
return __lib__.default || __lib__; };
|
|
5
3
|
let _match;
|
|
@@ -1476,6 +1474,7 @@ function makeBlockNode() {
|
|
|
1476
1474
|
structure: "Block",
|
|
1477
1475
|
id: "",
|
|
1478
1476
|
props: {},
|
|
1477
|
+
directives: {},
|
|
1479
1478
|
body: [],
|
|
1480
1479
|
depth: 0,
|
|
1481
1480
|
range: {
|
|
@@ -1961,9 +1960,11 @@ function parseBlock(tokens, i, filename = null, placeholders = {}, variables = {
|
|
|
1961
1960
|
i = valueIndex;
|
|
1962
1961
|
|
|
1963
1962
|
// Store Argument
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1963
|
+
if (k && k.startsWith("smark-")) {
|
|
1964
|
+
blockNode.directives[k.slice(6)] = v; // strip "smark-" prefix
|
|
1965
|
+
} else {
|
|
1966
|
+
blockNode.props[String(argIndex++)] = v;
|
|
1967
|
+
if (k) blockNode.props[k] = v;
|
|
1967
1968
|
}
|
|
1968
1969
|
k = "";
|
|
1969
1970
|
v = "";
|
|
@@ -8627,7 +8628,7 @@ function registerHostSettings(settings) {
|
|
|
8627
8628
|
hostSettings = settings || {};
|
|
8628
8629
|
}
|
|
8629
8630
|
|
|
8630
|
-
const version = "5.0
|
|
8631
|
+
const version = "5.1.0";
|
|
8631
8632
|
|
|
8632
8633
|
const SomMark$1 = {
|
|
8633
8634
|
version,
|
|
@@ -8678,14 +8679,19 @@ const SomMark$1 = {
|
|
|
8678
8679
|
// Freeze the entire Standard Library to make it completely immutable and tamper-proof
|
|
8679
8680
|
Object.freeze(SomMark$1);
|
|
8680
8681
|
|
|
8681
|
-
//
|
|
8682
|
-
|
|
8682
|
+
// Set by index.js (Node.js) or index.browser.js (shim) — never imported directly.
|
|
8683
|
+
let evaluatorStorage = null;
|
|
8684
|
+
|
|
8685
|
+
function setDefaultAsyncLocalStorage$1(cls) {
|
|
8686
|
+
evaluatorStorage = cls ? new cls() : null;
|
|
8687
|
+
}
|
|
8683
8688
|
|
|
8684
8689
|
/**
|
|
8685
8690
|
* Runs fn inside an isolated evaluator context.
|
|
8686
8691
|
* Concurrent transpile() calls each get their own stack — no cross-contamination.
|
|
8687
8692
|
*/
|
|
8688
8693
|
function withEvaluator(fn) {
|
|
8694
|
+
if (!evaluatorStorage) return fn();
|
|
8689
8695
|
return evaluatorStorage.run([], fn);
|
|
8690
8696
|
}
|
|
8691
8697
|
|
|
@@ -8843,6 +8849,7 @@ const customCompileAdapter = async (src, options, parentSecurity = {}) => {
|
|
|
8843
8849
|
registerHostCompile(customCompileAdapter);
|
|
8844
8850
|
|
|
8845
8851
|
let defaultFs$1 = null;
|
|
8852
|
+
let defaultEnv = null;
|
|
8846
8853
|
let quickJSInstance = null;
|
|
8847
8854
|
async function getQuickJSModule() {
|
|
8848
8855
|
if (!quickJSInstance) {
|
|
@@ -8866,6 +8873,16 @@ function objectToHandle(context, obj) {
|
|
|
8866
8873
|
return result.unwrap();
|
|
8867
8874
|
}
|
|
8868
8875
|
|
|
8876
|
+
function isPlainData(value, seen = new Set()) {
|
|
8877
|
+
if (value === null || value === undefined) return true;
|
|
8878
|
+
if (typeof value === "function") return false;
|
|
8879
|
+
if (typeof value !== "object") return true;
|
|
8880
|
+
if (seen.has(value)) return false;
|
|
8881
|
+
seen.add(value);
|
|
8882
|
+
if (Array.isArray(value)) return value.every(v => isPlainData(v, seen));
|
|
8883
|
+
return Object.values(value).every(v => isPlainData(v, seen));
|
|
8884
|
+
}
|
|
8885
|
+
|
|
8869
8886
|
function expose(context, vars, pendingDeferreds) {
|
|
8870
8887
|
for (const [key, value] of Object.entries(vars)) {
|
|
8871
8888
|
let handle;
|
|
@@ -8986,6 +9003,18 @@ class EvaluatorState {
|
|
|
8986
9003
|
});
|
|
8987
9004
|
|
|
8988
9005
|
this.expose({
|
|
9006
|
+
__hostEnv: (key) => {
|
|
9007
|
+
if (defaultEnv === null) {
|
|
9008
|
+
throw new Error(
|
|
9009
|
+
"[SomMark] SomMark.env() is not available in browser mode.\n" +
|
|
9010
|
+
"Environment variables are a server-side concept.\n" +
|
|
9011
|
+
"Read env values at build time and pass them as placeholders instead."
|
|
9012
|
+
);
|
|
9013
|
+
}
|
|
9014
|
+
const allowlist = this.security?.env;
|
|
9015
|
+
if (!Array.isArray(allowlist) || !allowlist.includes(key)) return undefined;
|
|
9016
|
+
return defaultEnv[key] ?? undefined;
|
|
9017
|
+
},
|
|
8989
9018
|
__hostSomMarkVersion: SomMark$1.version,
|
|
8990
9019
|
__hostSomMarkSettings: () => {
|
|
8991
9020
|
const clean = { ...SomMark$1.settings };
|
|
@@ -9197,6 +9226,12 @@ class EvaluatorState {
|
|
|
9197
9226
|
throw new Error("SomMark.static Error: Argument must be a string.");
|
|
9198
9227
|
}
|
|
9199
9228
|
return globalThis.eval(expr);
|
|
9229
|
+
},
|
|
9230
|
+
env: (key) => {
|
|
9231
|
+
if (typeof key !== "string" || !key) {
|
|
9232
|
+
throw new Error("SomMark.env Error: Key must be a non-empty string.");
|
|
9233
|
+
}
|
|
9234
|
+
return __hostEnv(key);
|
|
9200
9235
|
}
|
|
9201
9236
|
};
|
|
9202
9237
|
|
|
@@ -9219,15 +9254,20 @@ class EvaluatorState {
|
|
|
9219
9254
|
}
|
|
9220
9255
|
setupRes.value.dispose();
|
|
9221
9256
|
|
|
9222
|
-
// Configure module loader using virtual FS implementation
|
|
9257
|
+
// Configure module loader using virtual FS implementation.
|
|
9258
|
+
// The normalizer resolves every import to an absolute path so the module
|
|
9259
|
+
// cache key is always absolute — <smark> (the eval module name) can never
|
|
9260
|
+
// be reached by any user import regardless of what the file is named.
|
|
9223
9261
|
this.runtime.setModuleLoader((moduleName) => {
|
|
9224
9262
|
try {
|
|
9225
9263
|
const isRaw = moduleName.endsWith("?raw");
|
|
9226
9264
|
const cleanModuleName = isRaw ? moduleName.slice(0, -4) : moduleName;
|
|
9227
|
-
|
|
9228
|
-
|
|
9265
|
+
// moduleName is already an absolute path (supplied by the normalizer below),
|
|
9266
|
+
// so resolve() is a no-op for absolute paths and a safe fallback for URLs.
|
|
9267
|
+
const resolvedPath = /^https?:\/\//.test(cleanModuleName)
|
|
9268
|
+
? cleanModuleName
|
|
9229
9269
|
: posix.resolve(this.baseDir, cleanModuleName);
|
|
9230
|
-
|
|
9270
|
+
|
|
9231
9271
|
const fsImpl = this.settings?.fs || this.settings?.instance?.fs || this.nodeFs;
|
|
9232
9272
|
if (!fsImpl) {
|
|
9233
9273
|
throw new Error("No filesystem implementation available.");
|
|
@@ -9260,6 +9300,22 @@ class EvaluatorState {
|
|
|
9260
9300
|
} catch (err) {
|
|
9261
9301
|
throw err;
|
|
9262
9302
|
}
|
|
9303
|
+
}, (baseName, moduleName) => {
|
|
9304
|
+
// Resolve every import to an absolute path so no user import can ever
|
|
9305
|
+
// normalize to <smark> (or any other virtual eval module name).
|
|
9306
|
+
const isRaw = moduleName.endsWith("?raw");
|
|
9307
|
+
const clean = isRaw ? moduleName.slice(0, -4) : moduleName;
|
|
9308
|
+
if (/^https?:\/\//.test(clean)) return moduleName;
|
|
9309
|
+
const baseDir = (baseName === "<smark>" || !posix.isAbsolute(baseName))
|
|
9310
|
+
? this.baseDir
|
|
9311
|
+
: (/^https?:\/\//.test(baseName) ? baseName : posix.dirname(baseName));
|
|
9312
|
+
let resolved;
|
|
9313
|
+
if (/^https?:\/\//.test(baseDir)) {
|
|
9314
|
+
resolved = new URL(clean, baseDir).href;
|
|
9315
|
+
} else {
|
|
9316
|
+
resolved = posix.resolve(baseDir, clean);
|
|
9317
|
+
}
|
|
9318
|
+
return isRaw ? resolved + "?raw" : resolved;
|
|
9263
9319
|
});
|
|
9264
9320
|
}
|
|
9265
9321
|
|
|
@@ -9410,9 +9466,17 @@ class EvaluatorState {
|
|
|
9410
9466
|
|
|
9411
9467
|
inject(vars) {
|
|
9412
9468
|
if (!this.context) return;
|
|
9469
|
+
const safe = {};
|
|
9470
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
9471
|
+
if (!isPlainData(value)) {
|
|
9472
|
+
console.warn(`[SomMark] Security: "${key}" contains functions and was blocked. Only plain data can be injected. Use SomMark built-ins for host capabilities.`);
|
|
9473
|
+
continue;
|
|
9474
|
+
}
|
|
9475
|
+
safe[key] = value;
|
|
9476
|
+
}
|
|
9413
9477
|
const currentScope = this.scopes[this.scopes.length - 1];
|
|
9414
|
-
Object.assign(currentScope,
|
|
9415
|
-
this.expose(
|
|
9478
|
+
Object.assign(currentScope, safe);
|
|
9479
|
+
this.expose(safe);
|
|
9416
9480
|
}
|
|
9417
9481
|
|
|
9418
9482
|
async execute(code, baseDir = null) {
|
|
@@ -9496,7 +9560,7 @@ class EvaluatorState {
|
|
|
9496
9560
|
|
|
9497
9561
|
let result;
|
|
9498
9562
|
if (isModule) {
|
|
9499
|
-
const evalRes = this.context.evalCode(finalCode, "
|
|
9563
|
+
const evalRes = this.context.evalCode(finalCode, "<smark>", { type: 'module' });
|
|
9500
9564
|
if (evalRes.error) {
|
|
9501
9565
|
const err = this.context.dump(evalRes.error);
|
|
9502
9566
|
evalRes.error.dispose();
|
|
@@ -9565,7 +9629,7 @@ class EvaluatorState {
|
|
|
9565
9629
|
}
|
|
9566
9630
|
|
|
9567
9631
|
const defaultValue = this.context.dump(resolvedDefaultHandle);
|
|
9568
|
-
|
|
9632
|
+
|
|
9569
9633
|
if (isPromise) {
|
|
9570
9634
|
resolvedDefaultHandle.dispose();
|
|
9571
9635
|
}
|
|
@@ -9605,7 +9669,7 @@ class EvaluatorState {
|
|
|
9605
9669
|
result = res;
|
|
9606
9670
|
}
|
|
9607
9671
|
} else {
|
|
9608
|
-
const evalRes = this.context.evalCode(code, "
|
|
9672
|
+
const evalRes = this.context.evalCode(code, "<smark>");
|
|
9609
9673
|
if (evalRes.error) {
|
|
9610
9674
|
const err = this.context.dump(evalRes.error);
|
|
9611
9675
|
evalRes.error.dispose();
|
|
@@ -9641,7 +9705,7 @@ class EvaluatorState {
|
|
|
9641
9705
|
return result;
|
|
9642
9706
|
} catch (error) {
|
|
9643
9707
|
const stack = error.stack || "";
|
|
9644
|
-
const match = stack.match(/
|
|
9708
|
+
const match = stack.match(/__smark__\.js:(\d+):(\d+)/) || stack.match(/:(\d+):(\d+)/);
|
|
9645
9709
|
|
|
9646
9710
|
const err = new Error(error.message || error);
|
|
9647
9711
|
if (match) {
|
|
@@ -9704,6 +9768,14 @@ class Evaluator {
|
|
|
9704
9768
|
defaultFs$1 = fs;
|
|
9705
9769
|
}
|
|
9706
9770
|
|
|
9771
|
+
setDefaultEnv(env) {
|
|
9772
|
+
defaultEnv = env;
|
|
9773
|
+
}
|
|
9774
|
+
|
|
9775
|
+
setDefaultAsyncLocalStorage(cls) {
|
|
9776
|
+
setDefaultAsyncLocalStorage$1(cls);
|
|
9777
|
+
}
|
|
9778
|
+
|
|
9707
9779
|
get active() {
|
|
9708
9780
|
const stack = this._getStack();
|
|
9709
9781
|
if (stack.length === 0) {
|
|
@@ -9916,8 +9988,27 @@ async function preprocessRuntimeLogic(code, filename = null, security = {}, inst
|
|
|
9916
9988
|
if (filename && filename !== "anonymous") {
|
|
9917
9989
|
baseDir = posix.dirname(posix.resolve(filename));
|
|
9918
9990
|
}
|
|
9991
|
+
|
|
9992
|
+
// Block absolute paths — path.resolve would ignore baseDir entirely
|
|
9993
|
+
if (posix.isAbsolute(argValue)) {
|
|
9994
|
+
transpilerError([
|
|
9995
|
+
`<$red:Security Error:$> Absolute import paths are not allowed: <$magenta:${argValue}$>{line}`,
|
|
9996
|
+
`<$yellow:Use a path relative to the template file, e.g.$> <$green:SomMark.import("./data.json")$> <$yellow:or$> <$green:SomMark.import("../shared/data.json")$><$yellow:.$>{line}`,
|
|
9997
|
+
`<$yellow:Base directory:$> <$blue:${baseDir}$>{line}`
|
|
9998
|
+
]);
|
|
9999
|
+
}
|
|
10000
|
+
|
|
9919
10001
|
const resolvedPath = posix.resolve(baseDir, argValue);
|
|
9920
10002
|
|
|
10003
|
+
// Block path traversal — resolved path must stay inside baseDir
|
|
10004
|
+
const safeBases = baseDir.endsWith(posix.sep) ? baseDir : baseDir + posix.sep;
|
|
10005
|
+
if (!resolvedPath.startsWith(safeBases) && resolvedPath !== baseDir) {
|
|
10006
|
+
transpilerError([
|
|
10007
|
+
`<$red:Security Error:$> Import path escapes the project directory: <$magenta:${argValue}$>{line}`,
|
|
10008
|
+
`<$yellow:Resolved Path:$> <$blue:${resolvedPath}$>{line}`
|
|
10009
|
+
]);
|
|
10010
|
+
}
|
|
10011
|
+
|
|
9921
10012
|
const fsImpl = instance?.fs || await getNodeFs();
|
|
9922
10013
|
|
|
9923
10014
|
// File presence validation
|
|
@@ -10030,6 +10121,7 @@ const randomBytesHex = (size) => {
|
|
|
10030
10121
|
|
|
10031
10122
|
const BODY_PLACEHOLDER = `SOMMARKBODYPLACEHOLDER${randomBytesHex(8)}SOMMARK`;
|
|
10032
10123
|
|
|
10124
|
+
|
|
10033
10125
|
/**
|
|
10034
10126
|
* Extracts all plain text from a node and its children.
|
|
10035
10127
|
*
|
|
@@ -10128,6 +10220,17 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
10128
10220
|
|
|
10129
10221
|
if (node.type === FOR_EACH) {
|
|
10130
10222
|
const transpiledArgs = await transpileArgs(node.props);
|
|
10223
|
+
|
|
10224
|
+
if (!node.props || (node.props[0] === undefined && node.props["items"] === undefined)) {
|
|
10225
|
+
const line = node.range?.start?.line + 1 || 1;
|
|
10226
|
+
transpilerError([
|
|
10227
|
+
`<$red:Missing Prop Error in [for-each]:$>{line}`,
|
|
10228
|
+
`[for-each] requires an array as its first prop, e.g. [for-each = \${ array }\$]{line}`,
|
|
10229
|
+
`at line <$yellow:${line}$>{line}`
|
|
10230
|
+
]);
|
|
10231
|
+
return "";
|
|
10232
|
+
}
|
|
10233
|
+
|
|
10131
10234
|
const items = mapper_file ? mapper_file.safeArg({ props: transpiledArgs, index: 0, key: "items", fallBack: [] }) : [];
|
|
10132
10235
|
|
|
10133
10236
|
if (!Array.isArray(items)) {
|
|
@@ -10141,11 +10244,11 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
10141
10244
|
}
|
|
10142
10245
|
|
|
10143
10246
|
const asVar = transpiledArgs.as || "value";
|
|
10144
|
-
if (asVar === "i") {
|
|
10247
|
+
if (asVar === "i" || asVar === "length") {
|
|
10145
10248
|
const line = node.range?.start?.line + 1 || 1;
|
|
10146
10249
|
transpilerError([
|
|
10147
10250
|
`<$red:Reserved Variable Error in [for-each]:$>{line}`,
|
|
10148
|
-
`'
|
|
10251
|
+
`'${asVar}' is a reserved variable name.{N}Use a different name for the 'as' prop, e.g. as: "item"{line}`,
|
|
10149
10252
|
`at line <$yellow:${line}$>{line}`
|
|
10150
10253
|
]);
|
|
10151
10254
|
return "";
|
|
@@ -10175,22 +10278,28 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
10175
10278
|
}
|
|
10176
10279
|
}
|
|
10177
10280
|
|
|
10178
|
-
|
|
10281
|
+
const rawJoin = transpiledArgs.join ?? null;
|
|
10282
|
+
const join = rawJoin !== null ? rawJoin.replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\r/g, "\r") : null;
|
|
10283
|
+
const parts = [];
|
|
10179
10284
|
let idx = 0;
|
|
10285
|
+
const length = items.length;
|
|
10180
10286
|
for (const item of items) {
|
|
10181
10287
|
Evaluator$1.pushScope();
|
|
10182
10288
|
Evaluator$1.inject({
|
|
10183
10289
|
[asVar]: item,
|
|
10184
|
-
i: idx
|
|
10290
|
+
i: idx++,
|
|
10291
|
+
length
|
|
10185
10292
|
});
|
|
10186
10293
|
|
|
10294
|
+
let iterOutput = "";
|
|
10187
10295
|
for (let j = 0; j < cleanedBody.length; j++) {
|
|
10188
|
-
|
|
10296
|
+
iterOutput += await generateOutput(cleanedBody, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState, extraCtx);
|
|
10189
10297
|
}
|
|
10190
10298
|
|
|
10191
10299
|
await Evaluator$1.popScope();
|
|
10300
|
+
parts.push(iterOutput);
|
|
10192
10301
|
}
|
|
10193
|
-
return
|
|
10302
|
+
return join !== null ? parts.join(join) : parts.join("");
|
|
10194
10303
|
}
|
|
10195
10304
|
|
|
10196
10305
|
let secretId = null;
|
|
@@ -10218,13 +10327,12 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
10218
10327
|
}
|
|
10219
10328
|
|
|
10220
10329
|
// smark-raw block — body collected verbatim by lexer, bypasses normal body processing pipeline
|
|
10221
|
-
if (node.type === BLOCK && (node.
|
|
10330
|
+
if (node.type === BLOCK && (node.directives?.raw === "true" || node.directives?.raw === true)) {
|
|
10222
10331
|
if (generateRuntimeOutput) return "";
|
|
10223
10332
|
const rawContent = node.body?.map(n => String(n.text || "")).join("") || "";
|
|
10224
|
-
const
|
|
10225
|
-
const transpiledArgs = await transpileArgs(cleanArgs);
|
|
10333
|
+
const transpiledArgs = await transpileArgs(node.props);
|
|
10226
10334
|
if (Evaluator$1.active?.hasDynamicTag?.(node.id)) {
|
|
10227
|
-
return await Evaluator$1.active.executeDynamicTag(node.id, { props: transpiledArgs, content: rawContent, textContent: rawContent });
|
|
10335
|
+
return await Evaluator$1.active.executeDynamicTag(node.id, { props: transpiledArgs, directives: node.directives, content: rawContent, textContent: rawContent });
|
|
10228
10336
|
}
|
|
10229
10337
|
let rawTarget = mapper_file ? matchedValue(mapper_file.outputs, node.id) : null;
|
|
10230
10338
|
if (!rawTarget && mapper_file) rawTarget = mapper_file.getUnknownTag(node);
|
|
@@ -10232,6 +10340,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
10232
10340
|
const isManualMode = !!rawTarget.options?.handleAst;
|
|
10233
10341
|
return await rawTarget.render.call(mapper_file, {
|
|
10234
10342
|
props: transpiledArgs,
|
|
10343
|
+
directives: node.directives,
|
|
10235
10344
|
content: rawContent,
|
|
10236
10345
|
textContent: rawContent,
|
|
10237
10346
|
ast: isManualMode ? node : undefined,
|
|
@@ -10343,6 +10452,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
10343
10452
|
|
|
10344
10453
|
return await target.render.call(mapper_file, {
|
|
10345
10454
|
props: transpiledArgs,
|
|
10455
|
+
directives: node.directives,
|
|
10346
10456
|
content: "",
|
|
10347
10457
|
textContent: richText || textContent,
|
|
10348
10458
|
ast: cleanAst,
|
|
@@ -10361,6 +10471,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
10361
10471
|
}
|
|
10362
10472
|
result += await target.render.call(mapper_file, {
|
|
10363
10473
|
props: transpiledArgs,
|
|
10474
|
+
directives: node.directives,
|
|
10364
10475
|
content,
|
|
10365
10476
|
textContent,
|
|
10366
10477
|
ast: new Proxy({}, {
|
|
@@ -11784,11 +11895,20 @@ class Mapper {
|
|
|
11784
11895
|
|
|
11785
11896
|
/**
|
|
11786
11897
|
* Registers universal utility blocks shared across all SomMark mappers.
|
|
11787
|
-
* These blocks are considered "Format Agnostic."
|
|
11788
11898
|
*
|
|
11789
11899
|
* @param {Mapper} mapper - The mapper instance to register tags on.
|
|
11790
11900
|
*/
|
|
11791
11901
|
function registerSharedOutputs(mapper) {
|
|
11902
|
+
mapper.register(
|
|
11903
|
+
["raw", "Raw"],
|
|
11904
|
+
({ content }) => {
|
|
11905
|
+
return content;
|
|
11906
|
+
},
|
|
11907
|
+
{
|
|
11908
|
+
escape: false, rules: {
|
|
11909
|
+
required_directives: ["raw"]
|
|
11910
|
+
} }
|
|
11911
|
+
);
|
|
11792
11912
|
}
|
|
11793
11913
|
|
|
11794
11914
|
const SVG_ELEMENTS = new Set([
|
|
@@ -11941,6 +12061,7 @@ HTML.register(
|
|
|
11941
12061
|
return "";
|
|
11942
12062
|
},
|
|
11943
12063
|
);
|
|
12064
|
+
registerSharedOutputs(HTML);
|
|
11944
12065
|
|
|
11945
12066
|
/**
|
|
11946
12067
|
* The Markdown Mapper used for generating Markdown text.
|
|
@@ -11967,42 +12088,37 @@ const MARKDOWN = Mapper.define({
|
|
|
11967
12088
|
},
|
|
11968
12089
|
|
|
11969
12090
|
/**
|
|
11970
|
-
* Provides a fallback for unknown tags by
|
|
11971
|
-
|
|
12091
|
+
* Provides a fallback for unknown tags by rendering them as HTML elements.
|
|
12092
|
+
* Passes child nodes to the transpiler, which handles all node types (such as ForEach).
|
|
12093
|
+
**/
|
|
11972
12094
|
getUnknownTag(node) {
|
|
11973
|
-
const id = node.id
|
|
11974
|
-
|
|
12095
|
+
const id = node.id;
|
|
11975
12096
|
return {
|
|
11976
|
-
|
|
12097
|
+
options: { trimAndWrapBlocks: true },
|
|
12098
|
+
render: ({ props, content, isSelfClosing }) => {
|
|
11977
12099
|
const element = this.tag(id).smartAttributes(props, this.customProps, this.options);
|
|
11978
12100
|
if (isSelfClosing || VOID_ELEMENTS.has(id)) return element.selfClose();
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
for (const child of (ast.body || [])) {
|
|
11982
|
-
if (child.type === TEXT$1) rawContent += this.text(child.text);
|
|
11983
|
-
else if (child.type === BLOCK) rawContent += await renderChild(child);
|
|
11984
|
-
}
|
|
11985
|
-
rawContent = rawContent.trim();
|
|
11986
|
-
|
|
11987
|
-
const meaningful = (ast.body || []).filter(c => c.type !== TEXT$1 || c.text.trim());
|
|
11988
|
-
const finalContent = meaningful.length <= 1 ? rawContent : `\n${rawContent}\n`;
|
|
11989
|
-
return element.body(finalContent);
|
|
11990
|
-
},
|
|
11991
|
-
options: { handleAst: true }
|
|
12101
|
+
return element.body(content);
|
|
12102
|
+
}
|
|
11992
12103
|
};
|
|
11993
12104
|
}
|
|
11994
12105
|
});
|
|
11995
12106
|
|
|
11996
12107
|
MARKDOWN.inherit(HTML);
|
|
11997
12108
|
const { md, safeArg } = MARKDOWN;
|
|
12109
|
+
registerSharedOutputs(MARKDOWN);
|
|
11998
12110
|
|
|
11999
12111
|
/**
|
|
12000
12112
|
* Quote - Renders blockquote content or GFM alerts.
|
|
12001
12113
|
*/
|
|
12002
|
-
MARKDOWN.register(
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12114
|
+
MARKDOWN.register(
|
|
12115
|
+
"quote",
|
|
12116
|
+
({ props, content }) => {
|
|
12117
|
+
const type = safeArg({ props, index: 0, key: "type", fallBack: "" });
|
|
12118
|
+
return md.quote(content, type);
|
|
12119
|
+
},
|
|
12120
|
+
{ resolve: true }
|
|
12121
|
+
);
|
|
12006
12122
|
|
|
12007
12123
|
/**
|
|
12008
12124
|
* Headings - Renders H1-H6 block headings.
|
|
@@ -12082,12 +12198,12 @@ MARKDOWN.register(
|
|
|
12082
12198
|
"link",
|
|
12083
12199
|
({ props, content, isSelfClosing }) => {
|
|
12084
12200
|
if (isSelfClosing) {
|
|
12085
|
-
const text
|
|
12086
|
-
const src
|
|
12201
|
+
const text = safeArg({ props, index: 0, key: "text", fallBack: "" });
|
|
12202
|
+
const src = safeArg({ props, index: 1, key: "src", fallBack: "" });
|
|
12087
12203
|
const title = safeArg({ props, index: 2, key: "title", fallBack: "" });
|
|
12088
12204
|
return md.url("link", text, src, title);
|
|
12089
12205
|
}
|
|
12090
|
-
const src
|
|
12206
|
+
const src = safeArg({ props, index: 0, key: "src", fallBack: "" });
|
|
12091
12207
|
const title = safeArg({ props, index: 1, key: "title", fallBack: "" });
|
|
12092
12208
|
return md.url("link", content, src, title);
|
|
12093
12209
|
},
|
|
@@ -12125,10 +12241,14 @@ MARKDOWN.register(
|
|
|
12125
12241
|
* Escape - Escapes special Markdown characters.
|
|
12126
12242
|
* Self-closing: [escape = "text" !] or [escape = text: "text" !]
|
|
12127
12243
|
*/
|
|
12128
|
-
MARKDOWN.register(
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12244
|
+
MARKDOWN.register(
|
|
12245
|
+
["escape", "e"],
|
|
12246
|
+
function ({ props, content, isSelfClosing }) {
|
|
12247
|
+
const text = isSelfClosing ? safeArg({ props, index: 0, key: "text", fallBack: "" }) : content;
|
|
12248
|
+
return this.md.escape(text);
|
|
12249
|
+
},
|
|
12250
|
+
{ resolve: true }
|
|
12251
|
+
);
|
|
12132
12252
|
|
|
12133
12253
|
const ROW_SEP = "\x1E";
|
|
12134
12254
|
const CELL_SEP = "\x1F";
|
|
@@ -12144,12 +12264,16 @@ MARKDOWN.register(
|
|
|
12144
12264
|
const headers = [];
|
|
12145
12265
|
const rows = [];
|
|
12146
12266
|
|
|
12147
|
-
const extractRows = async
|
|
12267
|
+
const extractRows = async sectionNode => {
|
|
12148
12268
|
const sectionRows = [];
|
|
12149
|
-
for (const child of
|
|
12269
|
+
for (const child of sectionNode.body || []) {
|
|
12150
12270
|
if (child.type === BLOCK && child.id?.toLowerCase() === "row") {
|
|
12151
12271
|
const rendered = await renderChild(child, { inTable: true });
|
|
12152
|
-
const cells =
|
|
12272
|
+
const cells =
|
|
12273
|
+
rendered
|
|
12274
|
+
.split(ROW_SEP)[0]
|
|
12275
|
+
?.split(CELL_SEP)
|
|
12276
|
+
.filter(c => c !== "") ?? [];
|
|
12153
12277
|
if (cells.length > 0) sectionRows.push(cells);
|
|
12154
12278
|
} else if (child.type === FOR_EACH) {
|
|
12155
12279
|
const rendered = await renderChild(child, { inTable: true });
|
|
@@ -12183,25 +12307,29 @@ MARKDOWN.register(
|
|
|
12183
12307
|
*/
|
|
12184
12308
|
MARKDOWN.register(["header", "body"], ({ content }) => content);
|
|
12185
12309
|
|
|
12186
|
-
MARKDOWN.register(
|
|
12187
|
-
|
|
12188
|
-
|
|
12189
|
-
|
|
12190
|
-
|
|
12191
|
-
|
|
12310
|
+
MARKDOWN.register(
|
|
12311
|
+
"row",
|
|
12312
|
+
async function ({ ast, renderChild, inTable }) {
|
|
12313
|
+
if (!inTable) {
|
|
12314
|
+
let result = "";
|
|
12315
|
+
for (const child of ast.body) {
|
|
12316
|
+
if (child.type === TEXT$1) result += this.text(child.text);
|
|
12317
|
+
else if (child.type === BLOCK) result += await renderChild(child);
|
|
12318
|
+
}
|
|
12319
|
+
return result;
|
|
12192
12320
|
}
|
|
12193
|
-
|
|
12194
|
-
|
|
12195
|
-
|
|
12196
|
-
|
|
12197
|
-
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
cells += await renderChild(child, { inTable: true });
|
|
12321
|
+
let cells = "";
|
|
12322
|
+
for (const child of ast.body) {
|
|
12323
|
+
if (child.type !== BLOCK) continue;
|
|
12324
|
+
const id = child.id?.toLowerCase();
|
|
12325
|
+
if (id === "cell" || id === "th" || id === "td") {
|
|
12326
|
+
cells += await renderChild(child, { inTable: true });
|
|
12327
|
+
}
|
|
12201
12328
|
}
|
|
12202
|
-
|
|
12203
|
-
|
|
12204
|
-
|
|
12329
|
+
return cells + ROW_SEP;
|
|
12330
|
+
},
|
|
12331
|
+
{ handleAst: true }
|
|
12332
|
+
);
|
|
12205
12333
|
|
|
12206
12334
|
MARKDOWN.register(["cell", "th", "td"], ({ content, inTable }) => {
|
|
12207
12335
|
return inTable ? content.trim() + CELL_SEP : content;
|
|
@@ -12211,34 +12339,42 @@ MARKDOWN.register(["cell", "th", "td"], ({ content, inTable }) => {
|
|
|
12211
12339
|
* Lists - Authoritative Native AST List resolution.
|
|
12212
12340
|
* Supports Ordered (Number) and Unordered (Dotlex) lists with deep nesting.
|
|
12213
12341
|
*/
|
|
12214
|
-
MARKDOWN.register(
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
|
|
12218
|
-
|
|
12342
|
+
MARKDOWN.register(
|
|
12343
|
+
["list", "List"],
|
|
12344
|
+
async function ({ ast, props, renderChild }) {
|
|
12345
|
+
const indicator = safeArg({ props, index: 0, fallBack: "dot" });
|
|
12346
|
+
const isOrdered = indicator === "number" || indicator === "ol";
|
|
12347
|
+
const marker = isOrdered ? "" : indicator === "dot" ? "-" : indicator;
|
|
12348
|
+
const items = [];
|
|
12219
12349
|
|
|
12220
|
-
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12350
|
+
for (const node of ast.body) {
|
|
12351
|
+
if (node.type !== BLOCK) continue;
|
|
12352
|
+
const id = node.id?.toLowerCase();
|
|
12353
|
+
if (id === "item") {
|
|
12354
|
+
items.push((await renderChild(node)).trim());
|
|
12355
|
+
}
|
|
12225
12356
|
}
|
|
12226
|
-
}
|
|
12227
12357
|
|
|
12228
|
-
|
|
12229
|
-
},
|
|
12358
|
+
return isOrdered ? md.orderedList(items, 0) : md.unorderedList(items, 0, marker);
|
|
12359
|
+
},
|
|
12360
|
+
{ handleAst: true, trimAndWrapBlocks: false }
|
|
12361
|
+
);
|
|
12230
12362
|
|
|
12231
12363
|
/**
|
|
12232
12364
|
* List Helpers - Internal tags for list structural organization.
|
|
12233
12365
|
*/
|
|
12234
|
-
MARKDOWN.register(
|
|
12235
|
-
|
|
12236
|
-
|
|
12237
|
-
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
}
|
|
12366
|
+
MARKDOWN.register(
|
|
12367
|
+
["item", "Item"],
|
|
12368
|
+
async function ({ ast, renderChild }) {
|
|
12369
|
+
let result = "";
|
|
12370
|
+
for (const child of ast.body) {
|
|
12371
|
+
if (child.type === TEXT$1) result += this.text(child.text);
|
|
12372
|
+
else if (child.type === BLOCK) result += await renderChild(child);
|
|
12373
|
+
}
|
|
12374
|
+
return result.trim();
|
|
12375
|
+
},
|
|
12376
|
+
{ handleAst: true, trimAndWrapBlocks: false }
|
|
12377
|
+
);
|
|
12242
12378
|
|
|
12243
12379
|
/**
|
|
12244
12380
|
* Todo - Renders task list items with status markers.
|
|
@@ -12248,19 +12384,23 @@ MARKDOWN.register(["item", "Item"], async function ({ ast, renderChild }) {
|
|
|
12248
12384
|
* [todo = "Add feature", "x" !] positional self-closing (task, status)
|
|
12249
12385
|
* [todo = "x"]Add feature[end] status in prop, task in body
|
|
12250
12386
|
*/
|
|
12251
|
-
MARKDOWN.register(
|
|
12252
|
-
|
|
12387
|
+
MARKDOWN.register(
|
|
12388
|
+
"todo",
|
|
12389
|
+
({ props, content, isSelfClosing }) => {
|
|
12390
|
+
let status, task;
|
|
12253
12391
|
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12392
|
+
if (isSelfClosing) {
|
|
12393
|
+
task = safeArg({ props, index: 0, key: "task", fallBack: "" });
|
|
12394
|
+
status = safeArg({ props, index: 1, key: "status", fallBack: "" });
|
|
12395
|
+
} else {
|
|
12396
|
+
status = safeArg({ props, index: 0, fallBack: "" });
|
|
12397
|
+
task = content;
|
|
12398
|
+
}
|
|
12261
12399
|
|
|
12262
|
-
|
|
12263
|
-
},
|
|
12400
|
+
return md.todo(status, task);
|
|
12401
|
+
},
|
|
12402
|
+
{ trimAndWrapBlocks: false }
|
|
12403
|
+
);
|
|
12264
12404
|
|
|
12265
12405
|
/**
|
|
12266
12406
|
* The MDX Mapper used for generating Markdown with JSX.
|
|
@@ -12470,100 +12610,115 @@ Jsonc.register(["Array", "array"], async function ({ props, ast, depth = 0, inAr
|
|
|
12470
12610
|
/**
|
|
12471
12611
|
* Renders a standard XML tag based on the provided identifier and arguments.
|
|
12472
12612
|
* Ensures strict attribute quoting and handles self-closing tags for empty bodies.
|
|
12473
|
-
*
|
|
12613
|
+
*
|
|
12474
12614
|
* @param {string} id - The XML tag identifier (case-sensitive).
|
|
12475
12615
|
* @param {Object} props - Key-value pairs to be rendered as XML attributes.
|
|
12476
12616
|
* @param {string} content - The rendered inner content of the tag.
|
|
12477
12617
|
* @returns {string} The fully rendered XML tag string.
|
|
12478
12618
|
*/
|
|
12479
12619
|
const renderXmlTag = function (id, props, content, isSelfClosing) {
|
|
12480
|
-
|
|
12481
|
-
|
|
12482
|
-
|
|
12483
|
-
// Filter out positional indices (numeric keys) for XML attributes
|
|
12484
|
-
const namedArgs = {};
|
|
12485
|
-
Object.keys(props).forEach(key => {
|
|
12486
|
-
if (isNaN(parseInt(key))) {
|
|
12487
|
-
namedArgs[key] = props[key];
|
|
12488
|
-
}
|
|
12489
|
-
});
|
|
12620
|
+
// XML is case-sensitive, so we use the exact id provided
|
|
12621
|
+
const element = this.tag(id);
|
|
12490
12622
|
|
|
12491
|
-
|
|
12492
|
-
|
|
12623
|
+
// Filter out positional indices (numeric keys) for XML attributes
|
|
12624
|
+
const namedArgs = {};
|
|
12625
|
+
Object.keys(props).forEach(key => {
|
|
12626
|
+
if (isNaN(parseInt(key))) {
|
|
12627
|
+
namedArgs[key] = props[key];
|
|
12628
|
+
}
|
|
12629
|
+
});
|
|
12493
12630
|
|
|
12494
|
-
|
|
12631
|
+
// In XML, attributes must always have values (strict = true)
|
|
12632
|
+
element.attributes(namedArgs, true);
|
|
12495
12633
|
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12634
|
+
const hasBody = typeof content === "string" && content.trim().length > 0;
|
|
12635
|
+
|
|
12636
|
+
if (isSelfClosing || !hasBody) {
|
|
12637
|
+
return element.selfClose();
|
|
12638
|
+
}
|
|
12499
12639
|
|
|
12500
|
-
|
|
12640
|
+
return element.body(content);
|
|
12501
12641
|
};
|
|
12502
12642
|
|
|
12503
12643
|
/**
|
|
12504
12644
|
* The XML Mapper used for creating XML pages.
|
|
12505
12645
|
*/
|
|
12506
12646
|
const XML = Mapper.define({
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
|
|
12514
|
-
|
|
12647
|
+
/**
|
|
12648
|
+
* Renders a comment in XML format.
|
|
12649
|
+
* @param {string} text - The comment content.
|
|
12650
|
+
* @returns {string}
|
|
12651
|
+
*/
|
|
12652
|
+
comment(text) {
|
|
12653
|
+
return `<!-- ${text} -->`;
|
|
12654
|
+
},
|
|
12515
12655
|
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
|
|
12526
|
-
|
|
12527
|
-
|
|
12656
|
+
/**
|
|
12657
|
+
* Resolves unknown tags by preserving their original case and applying XML rules.
|
|
12658
|
+
* @param {Object} node - The AST node representing the unknown tag.
|
|
12659
|
+
* @returns {Object} Renderer definition for the tag.
|
|
12660
|
+
*/
|
|
12661
|
+
getUnknownTag(node) {
|
|
12662
|
+
const id = node.id;
|
|
12663
|
+
return {
|
|
12664
|
+
render: ({ props, content, isSelfClosing }) => renderXmlTag.call(this, id, props, content, isSelfClosing),
|
|
12665
|
+
options: {}
|
|
12666
|
+
};
|
|
12667
|
+
},
|
|
12668
|
+
options: {
|
|
12669
|
+
trimAndWrapBlocks: true
|
|
12670
|
+
}
|
|
12528
12671
|
});
|
|
12529
12672
|
|
|
12530
12673
|
/**
|
|
12531
12674
|
* Registers the XML declaration as a self-closing block.
|
|
12532
12675
|
* Usage: [xml = version: "1.0", encoding: "UTF-8"]
|
|
12533
12676
|
*/
|
|
12534
|
-
XML.register(
|
|
12535
|
-
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
|
|
12677
|
+
XML.register(
|
|
12678
|
+
"xml",
|
|
12679
|
+
({ props }) => {
|
|
12680
|
+
const version = props.version || "1.0";
|
|
12681
|
+
const encoding = props.encoding || "UTF-8";
|
|
12682
|
+
return `<?xml version="${version}" encoding="${encoding}"?>`;
|
|
12683
|
+
},
|
|
12684
|
+
{ rules: { is_empty_body: true } }
|
|
12685
|
+
);
|
|
12539
12686
|
|
|
12540
12687
|
/**
|
|
12541
12688
|
* Registers the DOCTYPE declaration.
|
|
12542
12689
|
* Usage: [doctype = root: "note", system: "note.dtd"]
|
|
12543
12690
|
*/
|
|
12544
|
-
XML.register(
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
|
|
12691
|
+
XML.register(
|
|
12692
|
+
["DOCTYPE", "doctype"],
|
|
12693
|
+
({ props }) => {
|
|
12694
|
+
const root = props.root || "root";
|
|
12695
|
+
const system = props.system;
|
|
12696
|
+
const pub = props.public || props.fpi;
|
|
12548
12697
|
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
},
|
|
12698
|
+
if (pub && system) {
|
|
12699
|
+
return `<!DOCTYPE ${root} PUBLIC "${pub}" "${system}">`;
|
|
12700
|
+
} else if (system) {
|
|
12701
|
+
return `<!DOCTYPE ${root} SYSTEM "${system}">`;
|
|
12702
|
+
}
|
|
12703
|
+
return `<!DOCTYPE ${root}>`;
|
|
12704
|
+
},
|
|
12705
|
+
{ rules: { is_empty_body: true } }
|
|
12706
|
+
);
|
|
12556
12707
|
|
|
12557
12708
|
/**
|
|
12558
12709
|
* Registers the XML stylesheet processing instruction.
|
|
12559
12710
|
* Usage: [xml-stylesheet = href: "style.xsl"]
|
|
12560
12711
|
*/
|
|
12561
|
-
XML.register(
|
|
12562
|
-
|
|
12563
|
-
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
|
|
12712
|
+
XML.register(
|
|
12713
|
+
"xml-stylesheet",
|
|
12714
|
+
({ props }) => {
|
|
12715
|
+
const type = props.type || "text/xsl";
|
|
12716
|
+
const href = props.href;
|
|
12717
|
+
if (!href) return "";
|
|
12718
|
+
return `<?xml-stylesheet type="${type}" href="${href}"?>`;
|
|
12719
|
+
},
|
|
12720
|
+
{ rules: { is_empty_body: true } }
|
|
12721
|
+
);
|
|
12567
12722
|
|
|
12568
12723
|
/**
|
|
12569
12724
|
* Registers CDATA sections.
|
|
@@ -12571,10 +12726,12 @@ XML.register("xml-stylesheet", ({ props }) => {
|
|
|
12571
12726
|
* Self-closing: [cdata = "raw content" !] or [cdata = text: "raw content" !]
|
|
12572
12727
|
*/
|
|
12573
12728
|
XML.register("cdata", ({ props, content, isSelfClosing }) => {
|
|
12574
|
-
|
|
12575
|
-
|
|
12729
|
+
const text = isSelfClosing ? (props[0] ?? props.text ?? "") : content;
|
|
12730
|
+
return `<![CDATA[${text}]]>`;
|
|
12576
12731
|
});
|
|
12577
12732
|
|
|
12733
|
+
registerSharedOutputs(XML);
|
|
12734
|
+
|
|
12578
12735
|
const csvEscape = (value) => {
|
|
12579
12736
|
const str = String(value ?? "").trim();
|
|
12580
12737
|
if (str.includes(",") || str.includes('"') || str.includes("\n") || str.includes("\r")) {
|
|
@@ -12631,6 +12788,8 @@ CSV.register(["col", "cell", "td"], ({ textContent }) => {
|
|
|
12631
12788
|
return csvEscape(textContent);
|
|
12632
12789
|
}, { handleAst: true, trimAndWrapBlocks: false });
|
|
12633
12790
|
|
|
12791
|
+
registerSharedOutputs(CSV);
|
|
12792
|
+
|
|
12634
12793
|
const isValidInt$1 = (v) => v !== "" && !isNaN(Number(v)) && !v.includes(".");
|
|
12635
12794
|
const isValidFloat$1 = (v) => v !== "" && !isNaN(Number(v)) && v.includes(".");
|
|
12636
12795
|
const isValidNumber$1 = (v) => v !== "" && !isNaN(Number(v));
|
|
@@ -12854,6 +13013,8 @@ TOML.register("array", async ({ props, ast, renderChild }) => {
|
|
|
12854
13013
|
return `${tomlKey(key)} = [${vals.join(", ")}]\n`;
|
|
12855
13014
|
}, { handleAst: true });
|
|
12856
13015
|
|
|
13016
|
+
registerSharedOutputs(TOML);
|
|
13017
|
+
|
|
12857
13018
|
const isValidInt = (v) => v !== "" && !isNaN(Number(v)) && !v.includes(".");
|
|
12858
13019
|
const isValidFloat = (v) => v !== "" && !isNaN(Number(v)) && v.includes(".");
|
|
12859
13020
|
const isValidNumber = (v) => v !== "" && !isNaN(Number(v));
|
|
@@ -13169,6 +13330,8 @@ YAML.register("doc-start", () => "---\n", { rules: { is_empty_body: true } });
|
|
|
13169
13330
|
*/
|
|
13170
13331
|
YAML.register("doc-end", () => "...\n", { rules: { is_empty_body: true } });
|
|
13171
13332
|
|
|
13333
|
+
registerSharedOutputs(YAML);
|
|
13334
|
+
|
|
13172
13335
|
/**
|
|
13173
13336
|
* The Text Mapper used for plain-text extraction.
|
|
13174
13337
|
*/
|
|
@@ -13445,34 +13608,48 @@ async function resolveModules(ast, context) {
|
|
|
13445
13608
|
const baseDir = context.instance.baseDir || ((filename === "anonymous") ? absFilename : posix.dirname(absFilename));
|
|
13446
13609
|
|
|
13447
13610
|
// 1. Helper: Trim AST to remove file-boundary whitespace and "ghost" newlines
|
|
13448
|
-
const trimAst = (nodes) => {
|
|
13611
|
+
const trimAst = (nodes, trimBoundaries = true) => {
|
|
13449
13612
|
if (!nodes) return [];
|
|
13450
13613
|
|
|
13451
|
-
// 1. Filter out
|
|
13452
|
-
// (Comments, Imports,
|
|
13614
|
+
// 1. Filter out whitespace-only text nodes adjacent (directly or through other whitespace)
|
|
13615
|
+
// to non-rendering nodes (Comments, Imports, USE_MODULE).
|
|
13453
13616
|
const nonRenderingTypes = [COMMENT, IMPORT, USE_MODULE];
|
|
13454
13617
|
let res = nodes.filter((node, idx) => {
|
|
13455
13618
|
if (node.type !== TEXT$1 || node.text.trim() !== "") return true;
|
|
13456
13619
|
|
|
13457
|
-
|
|
13458
|
-
|
|
13459
|
-
|
|
13460
|
-
(
|
|
13461
|
-
|
|
13620
|
+
// Walk backwards through consecutive whitespace nodes to find prev non-whitespace
|
|
13621
|
+
let prevIsNonRendering = false;
|
|
13622
|
+
for (let j = idx - 1; j >= 0; j--) {
|
|
13623
|
+
if (nodes[j].type === TEXT$1 && nodes[j].text.trim() === "") continue;
|
|
13624
|
+
prevIsNonRendering = nonRenderingTypes.includes(nodes[j].type);
|
|
13625
|
+
break;
|
|
13626
|
+
}
|
|
13462
13627
|
|
|
13463
|
-
|
|
13628
|
+
// Walk forwards through consecutive whitespace nodes to find next non-whitespace
|
|
13629
|
+
let nextIsNonRendering = false;
|
|
13630
|
+
for (let j = idx + 1; j < nodes.length; j++) {
|
|
13631
|
+
if (nodes[j].type === TEXT$1 && nodes[j].text.trim() === "") continue;
|
|
13632
|
+
nextIsNonRendering = nonRenderingTypes.includes(nodes[j].type);
|
|
13633
|
+
break;
|
|
13634
|
+
}
|
|
13635
|
+
|
|
13636
|
+
return !(prevIsNonRendering || nextIsNonRendering);
|
|
13464
13637
|
});
|
|
13465
13638
|
|
|
13466
|
-
|
|
13467
|
-
|
|
13468
|
-
res
|
|
13469
|
-
|
|
13470
|
-
|
|
13471
|
-
res
|
|
13639
|
+
if (trimBoundaries) {
|
|
13640
|
+
// 2. Final pass: trim leading/trailing newlines from the remaining boundary text nodes
|
|
13641
|
+
if (res.length > 0 && res[0].type === TEXT$1) {
|
|
13642
|
+
res[0].text = res[0].text.replace(/^[\r\n]+/, "");
|
|
13643
|
+
}
|
|
13644
|
+
if (res.length > 0 && res[res.length - 1].type === TEXT$1) {
|
|
13645
|
+
res[res.length - 1].text = res[res.length - 1].text.replace(/[\r\n]+\s*$/, "");
|
|
13646
|
+
}
|
|
13647
|
+
|
|
13648
|
+
// 3. Remove any nodes that became purely empty after trimming
|
|
13649
|
+
res = res.filter(node => node.type !== TEXT$1 || node.text !== "");
|
|
13472
13650
|
}
|
|
13473
13651
|
|
|
13474
|
-
|
|
13475
|
-
return res.filter(node => node.type !== TEXT$1 || node.text !== "");
|
|
13652
|
+
return res;
|
|
13476
13653
|
};
|
|
13477
13654
|
|
|
13478
13655
|
// 2. Helper: Inject Slots with Indentation Propagation
|
|
@@ -13543,6 +13720,10 @@ async function resolveModules(ast, context) {
|
|
|
13543
13720
|
// 1b. Resolve relative to current base (FS)
|
|
13544
13721
|
const absolutePath = resolveModulePath(resolvedPath, currentBaseDir);
|
|
13545
13722
|
|
|
13723
|
+
if (!context.instance.fs) {
|
|
13724
|
+
runtimeError([`<$red:Module Error:$> Cannot import <$magenta:${filePath}$> — no filesystem is available.{N}In browser mode, pass a URL-based <$cyan:baseDir$> or a <$cyan:files$> map to enable module loading.`]);
|
|
13725
|
+
}
|
|
13726
|
+
|
|
13546
13727
|
// Local Path Resolution with Auto-Extension
|
|
13547
13728
|
let localPath = absolutePath;
|
|
13548
13729
|
if (!await context.instance.fs.exists(localPath) && !localPath.endsWith(".smark")) {
|
|
@@ -13808,10 +13989,7 @@ const runValidations = (node, target, instance) => {
|
|
|
13808
13989
|
const isStructural = node.type === "Block";
|
|
13809
13990
|
if (isStructural && rules.required_args && Array.isArray(rules.required_args)) {
|
|
13810
13991
|
const missingArgs = rules.required_args.filter(arg => {
|
|
13811
|
-
|
|
13812
|
-
if (typeof arg === "number") {
|
|
13813
|
-
return node.props[arg] === undefined;
|
|
13814
|
-
}
|
|
13992
|
+
if (typeof arg === "number") return node.props[arg] === undefined;
|
|
13815
13993
|
return node.props[arg] === undefined;
|
|
13816
13994
|
});
|
|
13817
13995
|
|
|
@@ -13826,6 +14004,22 @@ const runValidations = (node, target, instance) => {
|
|
|
13826
14004
|
);
|
|
13827
14005
|
}
|
|
13828
14006
|
}
|
|
14007
|
+
|
|
14008
|
+
// -- Directives Validation (Required Directives) ----------------------- //
|
|
14009
|
+
if (isStructural && rules.required_directives && Array.isArray(rules.required_directives)) {
|
|
14010
|
+
const missingDirectives = rules.required_directives.filter(key => node.directives?.[key] === undefined);
|
|
14011
|
+
|
|
14012
|
+
if (missingDirectives.length > 0) {
|
|
14013
|
+
transpilerError(
|
|
14014
|
+
[
|
|
14015
|
+
"{N}",
|
|
14016
|
+
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:is missing required directive props:$> <$red:${missingDirectives.map(k => `smark-${k}`).join(", ")}$>{N}`,
|
|
14017
|
+
`<$blue:Please ensure these directive props are provided in the template usage.$>`
|
|
14018
|
+
],
|
|
14019
|
+
context
|
|
14020
|
+
);
|
|
14021
|
+
}
|
|
14022
|
+
}
|
|
13829
14023
|
};
|
|
13830
14024
|
|
|
13831
14025
|
/**
|
|
@@ -13997,6 +14191,14 @@ function setDefaultFs(fs) {
|
|
|
13997
14191
|
Evaluator$1.setDefaultFs(fs);
|
|
13998
14192
|
}
|
|
13999
14193
|
|
|
14194
|
+
function setDefaultEnv(env) {
|
|
14195
|
+
Evaluator$1.setDefaultEnv(env);
|
|
14196
|
+
}
|
|
14197
|
+
|
|
14198
|
+
function setDefaultAsyncLocalStorage(cls) {
|
|
14199
|
+
Evaluator$1.setDefaultAsyncLocalStorage(cls);
|
|
14200
|
+
}
|
|
14201
|
+
|
|
14000
14202
|
function setDefaultResolvePath(fn) {
|
|
14001
14203
|
defaultResolvePath = fn;
|
|
14002
14204
|
}
|
|
@@ -14068,7 +14270,8 @@ class SomMark {
|
|
|
14068
14270
|
allowFetch: security?.allowFetch !== false,
|
|
14069
14271
|
allowHttp: security?.allowHttp === true,
|
|
14070
14272
|
allowedOrigins: Array.isArray(security?.allowedOrigins) ? security.allowedOrigins.map(o => o.toLowerCase()) : null,
|
|
14071
|
-
allowedExtensions: Array.isArray(security?.allowedExtensions) ? security.allowedExtensions.map(e => e.toLowerCase()) : null
|
|
14273
|
+
allowedExtensions: Array.isArray(security?.allowedExtensions) ? security.allowedExtensions.map(e => e.toLowerCase()) : null,
|
|
14274
|
+
env: Array.isArray(security?.env) ? security.env : []
|
|
14072
14275
|
};
|
|
14073
14276
|
this.warnings = [];
|
|
14074
14277
|
this._prepared = false;
|
|
@@ -14380,8 +14583,24 @@ async function findAndLoadConfig(targetPath) {
|
|
|
14380
14583
|
}
|
|
14381
14584
|
setCompilerClass(SomMark);
|
|
14382
14585
|
|
|
14586
|
+
class AsyncLocalStorage {
|
|
14587
|
+
#store = undefined;
|
|
14588
|
+
run(store, fn) {
|
|
14589
|
+
const prev = this.#store;
|
|
14590
|
+
this.#store = store;
|
|
14591
|
+
try { return fn(); }
|
|
14592
|
+
finally { this.#store = prev; }
|
|
14593
|
+
}
|
|
14594
|
+
getStore() { return this.#store; }
|
|
14595
|
+
exit(fn) { return fn(); }
|
|
14596
|
+
enterWith(store) { this.#store = store; }
|
|
14597
|
+
disable() {}
|
|
14598
|
+
}
|
|
14599
|
+
|
|
14383
14600
|
setDefaultFs(null);
|
|
14384
14601
|
setDefaultCwd("/");
|
|
14602
|
+
setDefaultEnv(null);
|
|
14603
|
+
setDefaultAsyncLocalStorage(AsyncLocalStorage);
|
|
14385
14604
|
|
|
14386
14605
|
/**
|
|
14387
14606
|
* Resolves a relative path into a full URL using the current document location.
|
|
@@ -14463,4 +14682,4 @@ function renderCompiledHTML(container, html) {
|
|
|
14463
14682
|
}
|
|
14464
14683
|
}
|
|
14465
14684
|
|
|
14466
|
-
export { CSV, Evaluator$1 as Evaluator, formats as FORMATS, HTML, Json, Jsonc, MARKDOWN, MDX, Mapper, TOKEN_TYPES, TOML, XML, YAML, SomMark as default, enableColor, findAndLoadConfig, labels, lex, lexSync, parse, parseSync, preprocessRuntimeLogic, registerSharedOutputs, renderCompiledHTML, resolveBaseDir, safeArg$1 as safeArg, setDefaultCwd, setDefaultFindAndLoadConfig, setDefaultFs, setDefaultResolvePath, transpile };
|
|
14685
|
+
export { CSV, Evaluator$1 as Evaluator, formats as FORMATS, HTML, Json, Jsonc, MARKDOWN, MDX, Mapper, TOKEN_TYPES, TOML, XML, YAML, SomMark as default, enableColor, findAndLoadConfig, labels, lex, lexSync, parse, parseSync, preprocessRuntimeLogic, registerSharedOutputs, renderCompiledHTML, resolveBaseDir, safeArg$1 as safeArg, setDefaultAsyncLocalStorage, setDefaultCwd, setDefaultEnv, setDefaultFindAndLoadConfig, setDefaultFs, setDefaultResolvePath, transpile };
|