sommark 5.0.5 → 5.2.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 +212 -22
- 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 +59 -21
- package/core/parser.js +6 -3
- package/core/pathe-bundle.js +1 -0
- package/core/transpiler.js +130 -15
- package/core/validator.js +17 -4
- package/dist/sommark.browser.js +669 -214
- package/dist/sommark.browser.lite.js +458 -191
- package/dist/sommark.parser.js +6 -3
- package/esbuild.js +64 -0
- package/index.browser.js +4 -1
- package/index.js +102 -1
- package/index.shared.js +13 -2
- package/mappers/languages/markdown.js +99 -81
- package/mappers/languages/xml.js +76 -61
- package/mappers/shared/index.js +10 -1
- package/package.json +11 -3
- package/rollup.js +79 -0
- package/vite.js +20 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const patheBundleCode = "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)})();\n return __lib__.default || __lib__; };\nlet _match;\nconst zeptomatch = (path, pattern) => {\n if (!_match) {\n _match = _lazyMatch();\n _lazyMatch = null;\n }\n return _match(path, pattern);\n};\n\nconst _DRIVE_LETTER_START_RE = /^[A-Za-z]:\\//;\nfunction normalizeWindowsPath(input = \"\") {\n if (!input) {\n return input;\n }\n return input.replace(/\\\\/g, \"/\").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());\n}\n\nconst _UNC_REGEX = /^[/\\\\]{2}/;\nconst _IS_ABSOLUTE_RE = /^[/\\\\](?![/\\\\])|^[/\\\\]{2}(?!\\.)|^[A-Za-z]:[/\\\\]/;\nconst _DRIVE_LETTER_RE = /^[A-Za-z]:$/;\nconst _ROOT_FOLDER_RE = /^\\/([A-Za-z]:)?$/;\nconst _EXTNAME_RE = /.(\\.[^./]+|\\.)$/;\nconst _PATH_ROOT_RE = /^[/\\\\]|^[a-zA-Z]:[/\\\\]/;\nconst sep = \"/\";\nconst normalize = function(path) {\n if (path.length === 0) {\n return \".\";\n }\n path = normalizeWindowsPath(path);\n const isUNCPath = path.match(_UNC_REGEX);\n const isPathAbsolute = isAbsolute(path);\n const trailingSeparator = path[path.length - 1] === \"/\";\n path = normalizeString(path, !isPathAbsolute);\n if (path.length === 0) {\n if (isPathAbsolute) {\n return \"/\";\n }\n return trailingSeparator ? \"./\" : \".\";\n }\n if (trailingSeparator) {\n path += \"/\";\n }\n if (_DRIVE_LETTER_RE.test(path)) {\n path += \"/\";\n }\n if (isUNCPath) {\n if (!isPathAbsolute) {\n return `//./${path}`;\n }\n return `//${path}`;\n }\n return isPathAbsolute && !isAbsolute(path) ? `/${path}` : path;\n};\nconst join = function(...segments) {\n let path = \"\";\n for (const seg of segments) {\n if (!seg) {\n continue;\n }\n if (path.length > 0) {\n const pathTrailing = path[path.length - 1] === \"/\";\n const segLeading = seg[0] === \"/\";\n const both = pathTrailing && segLeading;\n if (both) {\n path += seg.slice(1);\n } else {\n path += pathTrailing || segLeading ? seg : `/${seg}`;\n }\n } else {\n path += seg;\n }\n }\n return normalize(path);\n};\nfunction cwd() {\n if (typeof process !== \"undefined\" && typeof process.cwd === \"function\") {\n return process.cwd().replace(/\\\\/g, \"/\");\n }\n return \"/\";\n}\nconst resolve = function(...arguments_) {\n arguments_ = arguments_.map((argument) => normalizeWindowsPath(argument));\n let resolvedPath = \"\";\n let resolvedAbsolute = false;\n for (let index = arguments_.length - 1; index >= -1 && !resolvedAbsolute; index--) {\n const path = index >= 0 ? arguments_[index] : cwd();\n if (!path || path.length === 0) {\n continue;\n }\n resolvedPath = `${path}/${resolvedPath}`;\n resolvedAbsolute = isAbsolute(path);\n }\n resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);\n if (resolvedAbsolute && !isAbsolute(resolvedPath)) {\n return `/${resolvedPath}`;\n }\n return resolvedPath.length > 0 ? resolvedPath : \".\";\n};\nfunction normalizeString(path, allowAboveRoot) {\n let res = \"\";\n let lastSegmentLength = 0;\n let lastSlash = -1;\n let dots = 0;\n let char = null;\n for (let index = 0; index <= path.length; ++index) {\n if (index < path.length) {\n char = path[index];\n } else if (char === \"/\") {\n break;\n } else {\n char = \"/\";\n }\n if (char === \"/\") {\n if (lastSlash === index - 1 || dots === 1) ; else if (dots === 2) {\n if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== \".\" || res[res.length - 2] !== \".\") {\n if (res.length > 2) {\n const lastSlashIndex = res.lastIndexOf(\"/\");\n if (lastSlashIndex === -1) {\n res = \"\";\n lastSegmentLength = 0;\n } else {\n res = res.slice(0, lastSlashIndex);\n lastSegmentLength = res.length - 1 - res.lastIndexOf(\"/\");\n }\n lastSlash = index;\n dots = 0;\n continue;\n } else if (res.length > 0) {\n res = \"\";\n lastSegmentLength = 0;\n lastSlash = index;\n dots = 0;\n continue;\n }\n }\n if (allowAboveRoot) {\n res += res.length > 0 ? \"/..\" : \"..\";\n lastSegmentLength = 2;\n }\n } else {\n if (res.length > 0) {\n res += `/${path.slice(lastSlash + 1, index)}`;\n } else {\n res = path.slice(lastSlash + 1, index);\n }\n lastSegmentLength = index - lastSlash - 1;\n }\n lastSlash = index;\n dots = 0;\n } else if (char === \".\" && dots !== -1) {\n ++dots;\n } else {\n dots = -1;\n }\n }\n return res;\n}\nconst isAbsolute = function(p) {\n return _IS_ABSOLUTE_RE.test(p);\n};\nconst toNamespacedPath = function(p) {\n return normalizeWindowsPath(p);\n};\nconst extname = function(p) {\n if (p === \"..\") return \"\";\n const match = _EXTNAME_RE.exec(normalizeWindowsPath(p));\n return match && match[1] || \"\";\n};\nconst relative = function(from, to) {\n const _from = resolve(from).replace(_ROOT_FOLDER_RE, \"$1\").split(\"/\");\n const _to = resolve(to).replace(_ROOT_FOLDER_RE, \"$1\").split(\"/\");\n if (_to[0][1] === \":\" && _from[0][1] === \":\" && _from[0] !== _to[0]) {\n return _to.join(\"/\");\n }\n const _fromCopy = [..._from];\n for (const segment of _fromCopy) {\n if (_to[0] !== segment) {\n break;\n }\n _from.shift();\n _to.shift();\n }\n return [..._from.map(() => \"..\"), ..._to].join(\"/\");\n};\nconst dirname = function(p) {\n const segments = normalizeWindowsPath(p).replace(/\\/$/, \"\").split(\"/\").slice(0, -1);\n if (segments.length === 1 && _DRIVE_LETTER_RE.test(segments[0])) {\n segments[0] += \"/\";\n }\n return segments.join(\"/\") || (isAbsolute(p) ? \"/\" : \".\");\n};\nconst format = function(p) {\n const ext = p.ext ? p.ext.startsWith(\".\") ? p.ext : `.${p.ext}` : \"\";\n const segments = [p.root, p.dir, p.base ?? (p.name ?? \"\") + ext].filter(\n Boolean\n );\n return normalizeWindowsPath(\n p.root ? resolve(...segments) : segments.join(\"/\")\n );\n};\nconst basename = function(p, extension) {\n const segments = normalizeWindowsPath(p).split(\"/\");\n let lastSegment = \"\";\n for (let i = segments.length - 1; i >= 0; i--) {\n const val = segments[i];\n if (val) {\n lastSegment = val;\n break;\n }\n }\n return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;\n};\nconst parse = function(p) {\n const root = _PATH_ROOT_RE.exec(p)?.[0]?.replace(/\\\\/g, \"/\") || \"\";\n const base = basename(p);\n const extension = extname(base);\n return {\n root,\n dir: dirname(p),\n base,\n ext: extension,\n name: base.slice(0, base.length - extension.length)\n };\n};\nconst matchesGlob = (path, pattern) => {\n return zeptomatch(pattern, normalize(path));\n};\n\nconst _path = {\n __proto__: null,\n basename: basename,\n dirname: dirname,\n extname: extname,\n format: format,\n isAbsolute: isAbsolute,\n join: join,\n matchesGlob: matchesGlob,\n normalize: normalize,\n normalizeString: normalizeString,\n parse: parse,\n relative: relative,\n resolve: resolve,\n sep: sep,\n toNamespacedPath: toNamespacedPath\n};\n\nconst delimiter = /* @__PURE__ */ (() => globalThis.process?.platform === \"win32\" ? \";\" : \":\")();\nconst _platforms = { posix: void 0, win32: void 0 };\nconst mix = (del = delimiter) => {\n return new Proxy(_path, {\n get(_, prop) {\n if (prop === \"delimiter\") return del;\n if (prop === \"posix\") return posix;\n if (prop === \"win32\") return win32;\n return _platforms[prop] || _path[prop];\n }\n });\n};\nconst posix = /* @__PURE__ */ mix(\":\");\nconst win32 = /* @__PURE__ */ mix(\";\");\n\nvar pathe = /*#__PURE__*/Object.freeze({\n __proto__: null,\n basename: basename,\n default: posix,\n delimiter: delimiter,\n dirname: dirname,\n extname: extname,\n format: format,\n isAbsolute: isAbsolute,\n join: join,\n matchesGlob: matchesGlob,\n normalize: normalize,\n normalizeString: normalizeString,\n parse: parse,\n posix: posix,\n relative: relative,\n resolve: resolve,\n sep: sep,\n toNamespacedPath: toNamespacedPath,\n win32: win32\n});\n\nglobalThis.pathHandler = pathe;\n";
|
package/core/transpiler.js
CHANGED
|
@@ -14,7 +14,7 @@ function warnDroppedVariables(variables) {
|
|
|
14
14
|
} else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
15
15
|
for (const [nestedKey, nestedVal] of Object.entries(value)) {
|
|
16
16
|
if (typeof nestedVal === "function") {
|
|
17
|
-
console.warn(`[SomMark] variables.${key}.${nestedKey}
|
|
17
|
+
console.warn(`[SomMark] variables.${key}.${nestedKey}: nested functions inside objects are not supported. Define it as a top-level function instead: variables.${nestedKey}`);
|
|
18
18
|
} else if (nestedVal === undefined) {
|
|
19
19
|
console.warn(`[SomMark] variables.${key}.${nestedKey} is undefined and will be ignored.`);
|
|
20
20
|
}
|
|
@@ -31,6 +31,7 @@ const randomBytesHex = (size) => {
|
|
|
31
31
|
|
|
32
32
|
const BODY_PLACEHOLDER = `SOMMARKBODYPLACEHOLDER${randomBytesHex(8)}SOMMARK`;
|
|
33
33
|
|
|
34
|
+
|
|
34
35
|
/**
|
|
35
36
|
* Extracts all plain text from a node and its children.
|
|
36
37
|
*
|
|
@@ -120,15 +121,28 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
120
121
|
const out = (result !== undefined && typeof result !== "object") ? String(result) : "";
|
|
121
122
|
return mapper_file ? mapper_file.text(out) : out;
|
|
122
123
|
} catch (err) {
|
|
124
|
+
const line = node.range?.start?.line + 1 || 1;
|
|
123
125
|
transpilerError([
|
|
124
126
|
`<$red:Logic Error:$> ${err.message}{line}`,
|
|
125
|
-
`<$yellow:Code:$> <$blue:${node.code}$>{line}
|
|
127
|
+
`<$yellow:Code:$> <$blue:${node.code}$>{line}`,
|
|
128
|
+
`at line <$yellow:${line}$>{line}`
|
|
126
129
|
]);
|
|
127
130
|
}
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
if (node.type === FOR_EACH) {
|
|
131
134
|
const transpiledArgs = await transpileArgs(node.props);
|
|
135
|
+
|
|
136
|
+
if (!node.props || (node.props[0] === undefined && node.props["items"] === undefined)) {
|
|
137
|
+
const line = node.range?.start?.line + 1 || 1;
|
|
138
|
+
transpilerError([
|
|
139
|
+
`<$red:Missing Prop Error in [for-each]:$>{line}`,
|
|
140
|
+
`[for-each] requires an array as its first prop, e.g. [for-each = \${ array }\$]{line}`,
|
|
141
|
+
`at line <$yellow:${line}$>{line}`
|
|
142
|
+
]);
|
|
143
|
+
return "";
|
|
144
|
+
}
|
|
145
|
+
|
|
132
146
|
const items = mapper_file ? mapper_file.safeArg({ props: transpiledArgs, index: 0, key: "items", fallBack: [] }) : [];
|
|
133
147
|
|
|
134
148
|
if (!Array.isArray(items)) {
|
|
@@ -142,11 +156,11 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
142
156
|
}
|
|
143
157
|
|
|
144
158
|
const asVar = transpiledArgs.as || "value";
|
|
145
|
-
if (asVar === "i") {
|
|
159
|
+
if (asVar === "i" || asVar === "length") {
|
|
146
160
|
const line = node.range?.start?.line + 1 || 1;
|
|
147
161
|
transpilerError([
|
|
148
162
|
`<$red:Reserved Variable Error in [for-each]:$>{line}`,
|
|
149
|
-
`'
|
|
163
|
+
`'${asVar}' is a reserved variable name.{N}Use a different name for the 'as' prop, e.g. as: "item"{line}`,
|
|
150
164
|
`at line <$yellow:${line}$>{line}`
|
|
151
165
|
]);
|
|
152
166
|
return "";
|
|
@@ -176,22 +190,28 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
176
190
|
}
|
|
177
191
|
}
|
|
178
192
|
|
|
179
|
-
|
|
193
|
+
const rawJoin = transpiledArgs.join ?? null;
|
|
194
|
+
const join = rawJoin !== null ? rawJoin.replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\r/g, "\r") : null;
|
|
195
|
+
const parts = [];
|
|
180
196
|
let idx = 0;
|
|
197
|
+
const length = items.length;
|
|
181
198
|
for (const item of items) {
|
|
182
199
|
evaluator.pushScope();
|
|
183
200
|
evaluator.inject({
|
|
184
201
|
[asVar]: item,
|
|
185
|
-
i: idx
|
|
202
|
+
i: idx++,
|
|
203
|
+
length
|
|
186
204
|
});
|
|
187
205
|
|
|
206
|
+
let iterOutput = "";
|
|
188
207
|
for (let j = 0; j < cleanedBody.length; j++) {
|
|
189
|
-
|
|
208
|
+
iterOutput += await generateOutput(cleanedBody, j, format, mapper_file, security, parentId, generateRuntimeOutput, hideRuntimeOutput, instance, idState, extraCtx);
|
|
190
209
|
}
|
|
191
210
|
|
|
192
211
|
await evaluator.popScope();
|
|
212
|
+
parts.push(iterOutput);
|
|
193
213
|
}
|
|
194
|
-
return
|
|
214
|
+
return join !== null ? parts.join(join) : parts.join("");
|
|
195
215
|
}
|
|
196
216
|
|
|
197
217
|
let secretId = null;
|
|
@@ -219,13 +239,12 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
219
239
|
}
|
|
220
240
|
|
|
221
241
|
// smark-raw block — body collected verbatim by lexer, bypasses normal body processing pipeline
|
|
222
|
-
if (node.type === BLOCK && (node.
|
|
242
|
+
if (node.type === BLOCK && (node.directives?.raw === "true" || node.directives?.raw === true)) {
|
|
223
243
|
if (generateRuntimeOutput) return "";
|
|
224
244
|
const rawContent = node.body?.map(n => String(n.text || "")).join("") || "";
|
|
225
|
-
const
|
|
226
|
-
const transpiledArgs = await transpileArgs(cleanArgs);
|
|
245
|
+
const transpiledArgs = await transpileArgs(node.props);
|
|
227
246
|
if (evaluator.active?.hasDynamicTag?.(node.id)) {
|
|
228
|
-
return await evaluator.active.executeDynamicTag(node.id, { props: transpiledArgs, content: rawContent, textContent: rawContent });
|
|
247
|
+
return await evaluator.active.executeDynamicTag(node.id, { props: transpiledArgs, directives: node.directives, content: rawContent, textContent: rawContent });
|
|
229
248
|
}
|
|
230
249
|
let rawTarget = mapper_file ? matchedValue(mapper_file.outputs, node.id) : null;
|
|
231
250
|
if (!rawTarget && mapper_file) rawTarget = mapper_file.getUnknownTag(node);
|
|
@@ -233,6 +252,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
233
252
|
const isManualMode = !!rawTarget.options?.handleAst;
|
|
234
253
|
return await rawTarget.render.call(mapper_file, {
|
|
235
254
|
props: transpiledArgs,
|
|
255
|
+
directives: node.directives,
|
|
236
256
|
content: rawContent,
|
|
237
257
|
textContent: rawContent,
|
|
238
258
|
ast: isManualMode ? node : undefined,
|
|
@@ -316,9 +336,11 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
316
336
|
const val = await evaluator.execute(child.code, child.baseDir || null);
|
|
317
337
|
if (val !== undefined && typeof val !== "object") richText += String(val);
|
|
318
338
|
} catch (err) {
|
|
339
|
+
const line = child.range?.start?.line + 1 || 1;
|
|
319
340
|
transpilerError([
|
|
320
341
|
`<$red:Logic Error:$> ${err.message}{line}`,
|
|
321
|
-
`<$yellow:Code:$> <$blue:${child.code}$>{line}
|
|
342
|
+
`<$yellow:Code:$> <$blue:${child.code}$>{line}`,
|
|
343
|
+
`at line <$yellow:${line}$>{line}`
|
|
322
344
|
]);
|
|
323
345
|
}
|
|
324
346
|
} else if (child.type === COMMENT) {
|
|
@@ -344,6 +366,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
344
366
|
|
|
345
367
|
return await target.render.call(mapper_file, {
|
|
346
368
|
props: transpiledArgs,
|
|
369
|
+
directives: node.directives,
|
|
347
370
|
content: "",
|
|
348
371
|
textContent: richText || textContent,
|
|
349
372
|
ast: cleanAst,
|
|
@@ -362,6 +385,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
362
385
|
}
|
|
363
386
|
result += await target.render.call(mapper_file, {
|
|
364
387
|
props: transpiledArgs,
|
|
388
|
+
directives: node.directives,
|
|
365
389
|
content,
|
|
366
390
|
textContent,
|
|
367
391
|
ast: new Proxy({}, {
|
|
@@ -444,9 +468,11 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
444
468
|
bodyOutput = mapper_file ? mapper_file.text(out, { ...target?.options, escape: parentEscape }) : out;
|
|
445
469
|
}
|
|
446
470
|
} catch (err) {
|
|
471
|
+
const line = body_node.range?.start?.line + 1 || 1;
|
|
447
472
|
transpilerError([
|
|
448
473
|
`<$red:Logic Error:$> ${err.message}{line}`,
|
|
449
|
-
`<$yellow:Code:$> <$blue:${body_node.code}$>{line}
|
|
474
|
+
`<$yellow:Code:$> <$blue:${body_node.code}$>{line}`,
|
|
475
|
+
`at line <$yellow:${line}$>{line}`
|
|
450
476
|
]);
|
|
451
477
|
}
|
|
452
478
|
break;
|
|
@@ -545,6 +571,10 @@ export async function transpiler(optionsOrAst, format, mapperFile) {
|
|
|
545
571
|
})();
|
|
546
572
|
|
|
547
573
|
const dualOutput = optionsOrAst?.dualOutput || false;
|
|
574
|
+
const webOutputs = optionsOrAst?.webOutputs || false;
|
|
575
|
+
if (webOutputs && dualOutput) {
|
|
576
|
+
throw new Error("[SomMark] Cannot use both 'webOutputs' and 'dualOutput' at the same time. Use 'webOutputs' (returns [html, css, js]) or 'dualOutput' (returns [html, js]).");
|
|
577
|
+
}
|
|
548
578
|
const placeholders = optionsOrAst?.placeholders || settings?.placeholders || {};
|
|
549
579
|
const variables = optionsOrAst?.variables || settings?.variables || {};
|
|
550
580
|
warnDroppedVariables(variables);
|
|
@@ -559,6 +589,89 @@ export async function transpiler(optionsOrAst, format, mapperFile) {
|
|
|
559
589
|
let prev_body_node = null;
|
|
560
590
|
let prev_was_silent = false;
|
|
561
591
|
|
|
592
|
+
if (webOutputs) {
|
|
593
|
+
// Use unique markers so [style] content is extracted precisely —
|
|
594
|
+
// no <style> regex on the final HTML, works with static logic inside [style].
|
|
595
|
+
const CSS_OPEN = `SOMMARKCSSOPEN${randomBytesHex(8)}SOMMARK`;
|
|
596
|
+
const CSS_CLOSE = `SOMMARKCSSCLOSE${randomBytesHex(8)}SOMMARK`;
|
|
597
|
+
|
|
598
|
+
const webMapper = targetMapper.clone();
|
|
599
|
+
webMapper.register("style", function ({ content }) {
|
|
600
|
+
return `${CSS_OPEN}${content}${CSS_CLOSE}`;
|
|
601
|
+
}, { escape: false });
|
|
602
|
+
// [head] injects CSS variables as a raw <style> string via this.cssVariables —
|
|
603
|
+
// override it so those variables go through markers too.
|
|
604
|
+
webMapper.register("head", function ({ content }) {
|
|
605
|
+
const varsMarker = this.cssVariables
|
|
606
|
+
? `${CSS_OPEN}:root { ${this.cssVariables} }${CSS_CLOSE}\n`
|
|
607
|
+
: "";
|
|
608
|
+
return this.tag("head").body(`${varsMarker}${content}`);
|
|
609
|
+
}, { escape: false });
|
|
610
|
+
|
|
611
|
+
const idState = { mode: 'record', ids: [], idx: 0 };
|
|
612
|
+
|
|
613
|
+
// HTML pass — [style] blocks emit markers instead of <style> tags
|
|
614
|
+
let htmlOutput = "";
|
|
615
|
+
try {
|
|
616
|
+
for (let i = 0; i < body.length; i++) {
|
|
617
|
+
const node = body[i];
|
|
618
|
+
const blockOutput = await generateOutput(body, i, targetFormat, webMapper, security, null, false, true, instance, idState);
|
|
619
|
+
let finalBlockOutput = blockOutput;
|
|
620
|
+
if (prev_was_silent && node.type === TEXT) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
|
|
621
|
+
if (finalBlockOutput) {
|
|
622
|
+
htmlOutput += finalBlockOutput;
|
|
623
|
+
prev_was_silent = false;
|
|
624
|
+
} else {
|
|
625
|
+
prev_was_silent = true;
|
|
626
|
+
if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
|
|
627
|
+
const nextNode = body[i + 1];
|
|
628
|
+
if (nextNode && nextNode.type === TEXT && (nextNode.text === "\n" || nextNode.text === "\r\n")) i++;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
} finally {
|
|
633
|
+
evaluator.destroy();
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Extract CSS from markers — exact, no HTML regex
|
|
637
|
+
const cssChunks = [];
|
|
638
|
+
const markerRe = new RegExp(`${CSS_OPEN}([\\s\\S]*?)${CSS_CLOSE}`, "g");
|
|
639
|
+
htmlOutput = htmlOutput.replace(markerRe, (_, chunk) => {
|
|
640
|
+
cssChunks.push(chunk.trim());
|
|
641
|
+
return "";
|
|
642
|
+
});
|
|
643
|
+
const css = cssChunks.join("\n").trim();
|
|
644
|
+
|
|
645
|
+
// JS pass — replay IDs so querySelector targets match HTML
|
|
646
|
+
idState.mode = 'replay';
|
|
647
|
+
idState.idx = 0;
|
|
648
|
+
prev_was_silent = false;
|
|
649
|
+
|
|
650
|
+
await evaluator.init(fileBaseDir, security, settings, targetMapper);
|
|
651
|
+
evaluator.inject(placeholders);
|
|
652
|
+
evaluator.inject(variables);
|
|
653
|
+
|
|
654
|
+
let jsOutput = "";
|
|
655
|
+
try {
|
|
656
|
+
for (let i = 0; i < body.length; i++) {
|
|
657
|
+
const node = body[i];
|
|
658
|
+
const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, true, false, instance, idState);
|
|
659
|
+
let finalBlockOutput = blockOutput;
|
|
660
|
+
if (prev_was_silent && node.type === TEXT) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
|
|
661
|
+
if (finalBlockOutput) {
|
|
662
|
+
jsOutput += finalBlockOutput;
|
|
663
|
+
prev_was_silent = false;
|
|
664
|
+
} else {
|
|
665
|
+
prev_was_silent = true;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
} finally {
|
|
669
|
+
evaluator.destroy();
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return [htmlOutput.trim(), css, jsOutput.trim()];
|
|
673
|
+
}
|
|
674
|
+
|
|
562
675
|
if (dualOutput) {
|
|
563
676
|
const idState = { mode: 'record', ids: [], idx: 0 };
|
|
564
677
|
|
|
@@ -671,9 +784,11 @@ async function transpileArgs(props) {
|
|
|
671
784
|
try {
|
|
672
785
|
result[key] = await evaluator.execute(value.code, value.baseDir || null);
|
|
673
786
|
} catch (err) {
|
|
787
|
+
const line = value.range?.start?.line + 1 || 1;
|
|
674
788
|
transpilerError([
|
|
675
789
|
`<$red:Logic Error (Argument):$> ${err.message}{line}`,
|
|
676
|
-
`<$yellow:Code:$> <$blue:${value.code}$>{line}
|
|
790
|
+
`<$yellow:Code:$> <$blue:${value.code}$>{line}`,
|
|
791
|
+
`at line <$yellow:${line}$>{line}`
|
|
677
792
|
]);
|
|
678
793
|
}
|
|
679
794
|
} else {
|
package/core/validator.js
CHANGED
|
@@ -55,10 +55,7 @@ const runValidations = (node, target, instance) => {
|
|
|
55
55
|
const isStructural = node.type === "Block";
|
|
56
56
|
if (isStructural && rules.required_args && Array.isArray(rules.required_args)) {
|
|
57
57
|
const missingArgs = rules.required_args.filter(arg => {
|
|
58
|
-
|
|
59
|
-
if (typeof arg === "number") {
|
|
60
|
-
return node.props[arg] === undefined;
|
|
61
|
-
}
|
|
58
|
+
if (typeof arg === "number") return node.props[arg] === undefined;
|
|
62
59
|
return node.props[arg] === undefined;
|
|
63
60
|
});
|
|
64
61
|
|
|
@@ -73,6 +70,22 @@ const runValidations = (node, target, instance) => {
|
|
|
73
70
|
);
|
|
74
71
|
}
|
|
75
72
|
}
|
|
73
|
+
|
|
74
|
+
// -- Directives Validation (Required Directives) ----------------------- //
|
|
75
|
+
if (isStructural && rules.required_directives && Array.isArray(rules.required_directives)) {
|
|
76
|
+
const missingDirectives = rules.required_directives.filter(key => node.directives?.[key] === undefined);
|
|
77
|
+
|
|
78
|
+
if (missingDirectives.length > 0) {
|
|
79
|
+
transpilerError(
|
|
80
|
+
[
|
|
81
|
+
"{N}",
|
|
82
|
+
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:is missing required directive props:$> <$red:${missingDirectives.map(k => `smark-${k}`).join(", ")}$>{N}`,
|
|
83
|
+
`<$blue:Please ensure these directive props are provided in the template usage.$>`
|
|
84
|
+
],
|
|
85
|
+
context
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
76
89
|
};
|
|
77
90
|
|
|
78
91
|
/**
|