UNPKG

netherte

Version:

A template engine that supports custom filters and compiles templates into rendering functions.

12 lines (10 loc) 5.83 kB
class L{static cache=new Map;filters={};registerFilter(j,A){this.filters[j]=A}compile(j){let A=L.cache.get(j);if(A)return A;let q=this.generateRenderCode(j),B=new Function("data","filters",q),G=(J)=>{try{return B.call(null,J,this.filters)}catch(D){throw new Error(`Render error: ${D.message}`)}};return L.cache.set(j,G),G}parseTemplate(j){let A=/{{(.*?)}}|{%(.*?)%}|([^{<]+)/g,q=[],B;while((B=A.exec(j))!==null)if(B[1])q.push({type:"directive",value:B[1].trim()});else if(B[2]);else if(B[3])q.push({type:"literal",value:B[3]});return q}generateRenderCode(j){let A=this.parseTemplate(j),q=`let __output = []; `;return A.forEach((B)=>{if(B.type==="literal")q+=`__output.push(${JSON.stringify(B.value)}); `;else{let G=this.processDirective(B.value);if(G)q+=`${G} `}}),q+=`return __output.join("").trim(); `,`with(data || {}) { ${q} }`}processDirective(j){return`__output.push(${this.wrapExpression(j)});`}wrapExpression(j){let A=j.split(/\s*\|\s*/).map((B)=>B.trim()),q=this.parseExpression(A.shift());return A.reduce((B,G)=>{let[J,...D]=G.split(":").map((K)=>K.trim()),H=D.map((K)=>this.parseExpression(K)).join(", ");return`filters['${J}'](${B}${H.length?`, ${H}`:""})`},`(() => { try { return ${q} } catch { return '' } })()`)}parseExpression(j){return j.replace(/(\w+\.)+\w+/g,(A)=>A.split(".").reduce((q,B,G)=>G===0?B:`${q}?.${B}`,"")).replace(/(.*)\?(.*):(.*)/,(A,q,B,G)=>`((${q}) ? (${B}) : (${G}))`)}constructor(){this.registerDefaultFilters()}registerDefaultFilters(){this.registerFilter("uppercase",(j)=>typeof j==="string"?j.toUpperCase():j),this.registerFilter("lowercase",(j)=>typeof j==="string"?j.toLowerCase():j),this.registerFilter("capitalize",(j)=>typeof j==="string"?j.charAt(0).toUpperCase()+j.slice(1).toLowerCase():j),this.registerFilter("currency",(j,A="$")=>typeof j==="number"?`${A}${j.toFixed(2)}`:j),this.registerFilter("truncate",(j,A=100,q="...")=>{if(typeof j!=="string")return j;return j.length>A?j.substring(0,A)+q:j}),this.registerFilter("dateFormat",(j,A)=>{let q=new Date(j);return A.replace("YYYY",q.getFullYear().toString()).replace("MM",(q.getMonth()+1).toString().padStart(2,"0")).replace("DD",q.getDate().toString().padStart(2,"0"))}),this.registerFilter("escape",(j)=>typeof j==="string"?j.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;"):j)}}class M extends L{constructor(){super();this.registerExtraFilters()}registerExtraFilters(){this.registerFilter("reverse",(j)=>typeof j==="string"?[...j].reverse().join(""):j),this.registerFilter("pluralize",(j,A,q)=>{let B=`${A}s`;return j===1?A:q||B}),this.registerFilter("slugify",(j)=>{if(typeof j!=="string")return j;return j.toLowerCase().replace(/\s+/g,"-").replace(/[^\w-]+/g,"")}),this.registerFilter("stripTags",(j)=>typeof j==="string"?j.replace(/<[^>]*>/g,""):j),this.registerFilter("highlight",(j,A,q="highlight")=>{if(typeof j!=="string"||!A)return j;let B=new RegExp(`(${O(A)})`,"gi");return j.replace(B,`<span class="${q}">$1</span>`)}),this.registerFilter("thousands",(j)=>{let A=typeof j==="string"?parseFloat(j):j;if(Number.isNaN(A))return j;let B=String(j).includes(".")?{minimumFractionDigits:2,maximumFractionDigits:2}:void 0;return A.toLocaleString(void 0,B)}),this.registerFilter("markdown",(j)=>{if(typeof j!=="string")return j;let q=((D)=>D.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;"))(j).replace(/\r\n?/g,` `).replace(/\n{3,}/g,` `);[{pattern:/```[\s\S]*?```/g,replace:(D)=>`<pre><code>${D.slice(3,-3).trim()}</code></pre>`},{pattern:/^(#{1,6})\s+(.+)$/gm,replace:(D,H,K)=>`<h${H.length}>${K}</h${H.length}>`},{pattern:/^([ \t]*)[-*+]\s+(.+)$/gm,replace:(D,H,K)=>`${H}<li>${K}</li>`},{pattern:/^(\d+\.\s+.*)$/gm,replace:"<li>$1</li>"},{pattern:/^> (.*)$/gm,replace:"<blockquote>$1</blockquote>"},{pattern:/^[-*]{3,}$/gm,replace:"<hr>"}].forEach(({pattern:D,replace:H})=>{q=q.replace(D,H)}),[{pattern:/!\[(.*?)\]\((.*?)\)/g,replace:'<img src="$2" alt="$1">'},{pattern:/\[([^\]]+)\]\(([^)]+)\)/g,replace:'<a href="$2">$1</a>'},{pattern:/(\*\*|__)(.*?)\1/g,replace:"<strong>$2</strong>"},{pattern:/(\*|_)(.*?)\1/g,replace:"<em>$2</em>"},{pattern:/`([^`]+)`/g,replace:"<code>$1</code>"},{pattern:/(<(?:https?|ftp):\/\/[^\s]+)/g,replace:'<a href="$1">$1</a>'}].forEach(({pattern:D,replace:H})=>{q=q.replace(D,H)}),q=q.split(/\n{2,}/).map((D)=>{if(/^<(\w+)/.test(D)||!D.trim())return D;return`<p>${D.replace(/\n/g,"<br>")}</p>`}).join(` `);let J=(D,H)=>D.replace(new RegExp(`(<${H}>.*?</${H}>)`,"gs"),`<${H}>$1</${H}>`);return J(J(q,"ul"),"ol")}),this.registerFilter("typeof",(j)=>{return typeof j}),this.registerFilter("fileSize2",(j,A=2)=>{let q=["B","KiB","MiB","GiB","TiB"],B=Math.abs(j),G=0;while(B>=1024&&G<q.length-1)B/=1024,G++;return`${B.toFixed(A)} ${q[G]}`}),this.registerFilter("fileSize",(j,A=2)=>{let q=["B","KiB","MiB","GiB","TiB"],B=Math.abs(j),G=0;while(B>=1000&&G<q.length-1)B/=1000,G++;return`${B.toFixed(A)} ${q[G]}`}),this.registerFilter("mask",(j,A=4,q=4,B="*")=>{if(typeof j!=="string"||j.length<A+q)return j;let G=j.substring(0,A),J=q>0?j.slice(-q):"";return G+B.repeat(j.length-A-q)+J}),this.registerFilter("join",(j,A=", ")=>{return Array.isArray(j)?j.join(A):j}),this.registerFilter("relativeTime",(j,A="en")=>{let q=j instanceof Date?j:new Date(j);if(isNaN(q.getTime()))return j;let B=new Intl.RelativeTimeFormat(A,{numeric:"auto"}),G=Math.round((Date.now()-q.getTime())/1000),J=[[60,"second"],[3600,"minute"],[86400,"hour"],[604800,"day"],[2419200,"week"],[29030400,"month"],[290304000,"year"]],D=J.pop();while(D&&Math.abs(G)<D[0])D=J.pop();return B.format(-Math.round(G/D[0]),D[1])}),this.registerFilter("prettyJson",(j,A=2)=>{try{return JSON.stringify(j,null,A)}catch{return j}})}}function O(j){return j.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}export{M as NetherTEExtra};