@peter.naydenov/morph 0.0.1
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/Changelog.md +7 -0
- package/LICENSE +21 -0
- package/Migration.guide.md +3 -0
- package/README.md +207 -0
- package/dist/morph.cjs +1 -0
- package/dist/morph.esm.mjs +1 -0
- package/dist/morph.umd.js +1 -0
- package/package.json +61 -0
- package/rollup.config.js +42 -0
- package/src/main.js +100 -0
- package/src/methods/_actionSupply.js +19 -0
- package/src/methods/_chopTemplates.js +60 -0
- package/src/methods/_defineData.js +32 -0
- package/src/methods/_defineType.js +16 -0
- package/src/methods/_readTemplate.js +78 -0
- package/src/methods/_renderHolder.js +26 -0
- package/src/methods/_setupActions.js +69 -0
- package/src/methods/build.js +239 -0
- package/src/methods/render.js +26 -0
- package/src/methods/settings.js +6 -0
- package/test/01-chop.test.js +24 -0
- package/test/02-build.test.js +470 -0
- package/test/03-storage.test.js +78 -0
- package/test/04-commands.js +73 -0
- package/test/05-data.test.js +168 -0
package/Changelog.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Peter Naydenov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Morph (@peter.naydenov/morph)
|
|
2
|
+
**Experimental stage. Not tested well yet.**
|
|
3
|
+
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## General Information
|
|
13
|
+
|
|
14
|
+
`Morph` has a logic-less template syntax. Placeholders are places surrounded by double curly braces `{{ }}` and they represents the pleces where the data will be inserted.
|
|
15
|
+
|
|
16
|
+
Engine is text based, so it can be used for HTML, CSS, config files, source code, etc.
|
|
17
|
+
Some features of Morph:
|
|
18
|
+
- Simple logic-less template syntax;
|
|
19
|
+
- Builtin storage;
|
|
20
|
+
- Powerfull action system for data decoration;
|
|
21
|
+
- Nesting templates as part of the action system;
|
|
22
|
+
- Partial rendering (render only available data);
|
|
23
|
+
- Option to connect templates to external data sources;
|
|
24
|
+
- Post process plugin mechanism;
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
npm install @peter.naydenov/morph
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Morph can be used in browser and node.js.
|
|
37
|
+
```js
|
|
38
|
+
import morph from "@peter.naydenov/morph"
|
|
39
|
+
// Morpho supports also require:
|
|
40
|
+
// const morph = require("@peter.naydenov/morph")
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## Basic Usage
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
import morph from "@peter.naydenov/morph"
|
|
51
|
+
|
|
52
|
+
const myTemplateDescription = {
|
|
53
|
+
template: `Hello, {{name}}!` // simple template - a string with a placeholder
|
|
54
|
+
}
|
|
55
|
+
const myTemplate = morph.build ( myTemplateDescription ); // myTemplate is a render function
|
|
56
|
+
const htmlBlock = myTemplate ( { name: 'Peter' } ) // Provide data to the render function and get the result
|
|
57
|
+
// htmlBlock === 'Hello, Peter!'
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Morph contains also a builtin template storage. Instead of creating variable for each template, we can use the storage.
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
morph.add ( 'myTemplate', myTemplateDescription ) // add template to the storage. Automatically builds the render function
|
|
64
|
+
const htmlBlock = morph.get ( 'myTemplate' )({ name: 'Peter' }) // get template from the storage and render it
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Let's see a more complex example before we go into details:
|
|
68
|
+
```js
|
|
69
|
+
const myTemplateDescription = {
|
|
70
|
+
template: `Hello, {{ person : a, >getReady }}! Your age is {{ person : >getAge}}.`
|
|
71
|
+
, helpers: {
|
|
72
|
+
getReady: (person) => {
|
|
73
|
+
return {
|
|
74
|
+
text: person.name
|
|
75
|
+
, href: person.web
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
, a: `<a href="{{href}}">{{text}}</a>`
|
|
79
|
+
, getAge: (person) => person.age
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const myTemplate = morph.build ( myTemplateDescription );
|
|
83
|
+
const htmlBlock = myTemplate ( { person: {
|
|
84
|
+
name: 'Peter'
|
|
85
|
+
, age : 40
|
|
86
|
+
, web : 'example.com'
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
// htmlBlock === Hello, <a href="example.com">Peter</a>! Your age is 40.
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
## Template structure
|
|
97
|
+
Templates are objects with tree properties: `template`, `helpers`, and `handshake`:
|
|
98
|
+
```js
|
|
99
|
+
const myTemplateDescription = {
|
|
100
|
+
template: `` // (required) Template string
|
|
101
|
+
, helpers: {
|
|
102
|
+
// (optional) Functions and templates used by actions to decorate the data
|
|
103
|
+
}
|
|
104
|
+
, handshake: {
|
|
105
|
+
// (optional) Example data used to render the template.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
`Template` is a string with placeholders where we render our external data. It's a skeleton of the template. Placeholders are the dynamic parts of the template.
|
|
110
|
+
|
|
111
|
+
Helpers are a extra templates or functions needed for rendering the template. Example:
|
|
112
|
+
```js
|
|
113
|
+
const myTemplateDescription = {
|
|
114
|
+
template: `
|
|
115
|
+
<h1>{{title}}</h1>
|
|
116
|
+
<ul>
|
|
117
|
+
{{list:li,a}}
|
|
118
|
+
</ul>
|
|
119
|
+
`
|
|
120
|
+
, helpers: {
|
|
121
|
+
a: `<a href="{{href}}">{{text}}</a>`,
|
|
122
|
+
li: `<li>{{text}}</li>`
|
|
123
|
+
}
|
|
124
|
+
, handshake: {
|
|
125
|
+
title: 'My title'
|
|
126
|
+
, list: [
|
|
127
|
+
{ text: 'Item 1', href: 'item1.com' }
|
|
128
|
+
, { text: 'Item 2', href: 'item2.com' }
|
|
129
|
+
, { text: 'Item 3', href: 'item3.com' }
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
Helpers will be discussed in details in next documentation section - 'Helper Functions'.
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
## Placeholders
|
|
141
|
+
|
|
142
|
+
Template placeholders can contain data-source and actions separated by ':'. Data-source is a field of the data object used for the placeholder. Actions describe how the data should be decorated. Action is a list of operations separated by comma. Result from the first action is going as a argument to the second and so on. Executetion of actions is from right to left. Actions are optional.
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
`{{ name : act2, act1 }}`
|
|
146
|
+
// placeholder should take data from field 'name', execute 'act1' and 'act2' over it
|
|
147
|
+
// actions are separated by ',' and are executed from right to left
|
|
148
|
+
|
|
149
|
+
`{{ list : li, a }}`
|
|
150
|
+
// take data from 'list' and render each element first with 'a' then with 'li' actions
|
|
151
|
+
|
|
152
|
+
`{{ name }}` // render data from 'name'. Only data is provided to this placeholder
|
|
153
|
+
`{{ :someAction}}` // no data, but the result of the action will fill the placeholder
|
|
154
|
+
`{{ @all : someAction }}` // provide all the data to the action 'someAction'
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
## Actions
|
|
160
|
+
|
|
161
|
+
Actions are concise representations of a sequence of function calls. Some functions pertain to `data` manipulation, others to `rendering`, and some to `mixing`. We use a prefix system for enhanced readability and maintainability.
|
|
162
|
+
|
|
163
|
+
- `Render` functions are most used so they don't have any prefix;
|
|
164
|
+
- `Data` functions start with '>';
|
|
165
|
+
- `Mixing` functions start with '[]';
|
|
166
|
+
- `Conditional render` actions start with '?';
|
|
167
|
+
- `Extended render` start with '+';
|
|
168
|
+
|
|
169
|
+
Here are some examples:
|
|
170
|
+
```js
|
|
171
|
+
`
|
|
172
|
+
{{ : li }} // example for render actions
|
|
173
|
+
{{ : >setup}} // example for data actions
|
|
174
|
+
{{ friends : []coma }} // example for mixing action
|
|
175
|
+
{{ list : ul, [], li, a}} // example with render and mixing actions
|
|
176
|
+
`
|
|
177
|
+
// NOTE: See that mixing function has no name after the prefix.
|
|
178
|
+
// Anonymous mixing is a build in function that will do -> resultList.join ( '' )
|
|
179
|
+
// The data will be rendered with 'a', then with 'li'
|
|
180
|
+
// then all 'li' elements will be merged and will be provided to 'ul'
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
When input data is array, the result will be an array. Result for each element will stay separated until come the mixing function.
|
|
184
|
+
|
|
185
|
+
Learn more about how actions work in the section 'helper functions' below.
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
## Links
|
|
193
|
+
- [Release history](Changelog.md)
|
|
194
|
+
- [ Migration guide ](https://github.com/PeterNaydenov/morph/blob/master/Migration.guide.md)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
## Credits
|
|
199
|
+
'@peter.naydenov/morph' was created and supported by Peter Naydenov.
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
'@peter.naydenov/morph' is released under the MIT License.
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
package/dist/morph.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var e=require("@peter.naydenov/walk"),t=require("@peter.naydenov/stack");function r(e){return function t(r){const{TG_PRX:a,TG_SFX:o,TG_SIZE_P:s,TG_SIZE_S:c}=e;let i,l,u,f=[];if("string"!=typeof r)return n("notAString");if(0==r.length)return[];if(i=r.indexOf(a),0<i&&f.push(r.slice(0,i)),-1==i)return f.push(r),f;{if(u=r.indexOf(a,i+s),l=r.indexOf(o),-1==l)return n("missingClosing");if(l<i)return n("closedBeforeOpened");if(l+=c,-1!=u&&u<l)return n("newBeforeClosed");f.push(r.slice(i,l));let e=t(r.slice(l));return f.concat(e)}}}function n(e){switch(e){case"notAString":return"Error: Template is not a string.";case"missingClosing":return"Error: Placeholder with missing closing tag.";case"closedBeforeOpened":return"Error: Placeholder closing tag without starting one.";case"newBeforeClosed":return"Error: Nested placeholders. Close placeholder before open new one.";default:return"Error: Unknown template error."}}function a(t){const r={};let n=0;if(t instanceof Function)return{dataDeepLevel:0,nestedData:[t()]};if(null==t)return{dataDeepLevel:0,nestedData:[null]};if("string"==typeof t)return{dataDeepLevel:0,nestedData:[t]};return e({data:t,objectCallback:function({key:e,value:t,breadcrumbs:a}){let o=!1;return n=a.split("/").length-1,isNaN(e)||(o=!0),r[n]||(r[n]=o?[]:{}),"root"===e?r[n]=t:o?r[n].push(t):r[n][a]=t,t}}),{dataDeepLevel:n,nestedData:r}}var o={TG_PRX:"{{",TG_SFX:"}}",TG_SIZE_P:2,TG_SIZE_S:2};function s(e){return null==e?"null":"string"==typeof e||"number"==typeof e||"boolean"==typeof e?"primitive":"function"==typeof e?"function":e instanceof Array?"array":e instanceof Object?"object":void 0}function c(e,t,n){e instanceof Object&&Object.entries(e).forEach((([t,r])=>{r instanceof Object&&(e[t]=r.text)}));const a="function"==typeof n[t];return e=function(e={}){return"string"==typeof e?{text:e}:e}(e),a?n[t](e):function(e,t){if(null==t)return null;const n=r(o)(e),a=o;return n.forEach(((e,r)=>{if(e.includes(a.TG_PRX)){const o=e.replace(a.TG_PRX,"").replace(a.TG_SFX,"").trim();t[o]&&(n[r]=t[o])}})),n.join("")}(n[t],e)}function i(n){const{hasError:i,placeholders:l,chop:u,helpers:f,handshake:d}=function(e){const{template:t,helpers:n,handshake:a}=e,{TG_PRX:s,TG_SFX:c,TG_SIZE_P:i,TG_SIZE_S:l}=o,u=[];let f=null;const d=r(o)(t);return"string"==typeof d?f=d:d.forEach(((e,t)=>{const r=RegExp(s+"\\s*(.*?)\\s*(?::\\s*(.*?)\\s*)?"+c,"g");if(e.includes(s)){const a=r.exec(e);u.push({index:t,data:(n=a[1],n||null),action:a[2]?a[2].split(",").map((e=>e.trim())):null})}var n})),u.forEach((e=>{e.action&&e.action.every((e=>"#"===e||(e.startsWith("?")&&(e=e.replace("?","")),e.startsWith("+")&&(e=e.replace("+","")),e.startsWith("[]")&&(e=e.replace("[]","")),e.startsWith(">")&&(e=e.replace(">","")),""===e||!!n[e]||(f=`Error: Missing helper: ${e}`,!1))))})),{hasError:f,placeholders:u,chop:d,helpers:n,handshake:a}}(n);if(i)return function(){return i};{let r=structuredClone(u);return function(n={},...o){const i=s(n),u=[];if("null"===i)return r.join("");if("string"==typeof n)switch(n){case"raw":return r.join("");case"demo":if(!d)return"Error: No handshake data.";n=d;break;case"handshake":return d?structuredClone(d):"Error: No handshake data.";case"placeholders":return l.map((e=>r[e.index])).join(", ");default:return`Error: Wrong command "${n}". Available commands: raw, demo, handshake, placeholders.`}return"array"!==i&&(n=[n]),n.forEach((n=>{l.forEach((o=>{const{index:i,data:l,action:u}=o;if(!u&&l){const d=n[l];switch(s(d)){case"function":return void(r[i]=d());case"primitive":return void(r[i]=d);case"array":return void("primitive"===s(d[0])&&(r[i]=d[0]));case"object":return void(d.text&&(r[i]=d.text))}}else{const{dataDeepLevel:p,nestedData:h}=a("@all"===l||null===l||"@root"===l?n:n[l]),b=function*(e,r){let n=t({type:"LIFO"});for(let t=0;t<=r;t++)n.push(e[t]);for(;n&&!n.isEmpty();){let e=yield n.pull();e&&n.push(e)}}(function(e,t=10){let r={},n=0,a=[...e],o=0;do{r[o]=[],o++}while(o<=t);return a.every((e=>"#"===e?(n++,!(n>t)):e.startsWith("?")?(r[n].push({type:"route",name:e.replace("?",""),level:n}),!0):e.startsWith("+")?(r[n].push({type:"extendedRender",name:e.replace("+",""),level:n}),!0):e.startsWith("[]")?(r[n].push({type:"mix",name:e.replace("[]",""),level:n}),!0):e.startsWith(">")?(r[n].push({type:"data",name:e.replace(">",""),level:n}),!0):(""===e||r[n].push({type:"render",name:e,level:n}),!0))),r}(u,p),p);for(let E of b){let{type:v,name:k,level:j}=E,x=h[j],g=s(x);switch(v){case"route":switch(g){case"array":x.forEach(((e,t)=>{if(null==e)return;const r=s(e),n=f[k](e);null!=n&&("object"===r?x[t].text=c(e,n,f):x[t]=c(e,n,f))}));break;case"object":x.text=c(x,routeName,f);break;case"primitive":h[j]=c(x,routeName,f)}break;case"data":switch(g){case"array":x.forEach(((e,t)=>x[t]=e instanceof Function?f[k](e()):f[k](e)));break;case"object":case"primitive":h[j]=f[k](x);break;case"function":h[j]=f[k](x())}break;case"render":const _="function"==typeof f[k];switch(g){case"array":_?x.forEach(((e,t)=>{if(null==e)return;const r=s(e),n=f[k](e);null==n&&(x[t]=null),"object"===r?e.text=n:x[t]=n})):x.forEach(((e,t)=>{if(null==e)return;const r=s(e),n=c(e,k,f);null==n&&(x[t]=null),"object"===r?e.text=n:x[t]=n}));break;case"primitive":h[j]=c(x,k,f);break;case"object":Object.keys(x).find((e=>e.includes("/")))?Object.entries(x).forEach((([e,t])=>t.text=c(t,k,f))):x.text=c(x,k,f)}break;case"extendedRender":"function"==typeof f[k]&&(h[j]=f[k](h[0]));break;case"mix":if(""===k)switch(g){case"object":Object.keys(x).find((e=>e.includes("/")))?Object.entries(x).forEach((([e,t])=>h[j][e]=t.text)):h[j]=x.text;for(let T=j-1;T>=0;T--)h[T]=e({data:h[T],objectCallback:w});function w({value:e,breadcrumbs:t}){return h[j][t]?h[j][t]:e}break;case"array":h[j]=x.map((e=>"object"===s(e)?e.text:e)).filter((e=>null!=e)).join("")}else h[j]=f[k](x)}}let m=s(h[0]),y=h[0];switch(m){case"primitive":if(null==y)return;r[i]=y;break;case"object":if(null==y.text)return;r[i]=y.text;break;case"array":r[i]=y.join("")}}})),u.push(r.join(""))})),"array"===i?u:o?o.reduce(((e,t)=>t(e)),u.join("")):u.join("")}}}const l={default:{}};const u={build:i,get:function(e,t="default"){return l[t]?l[t][e]?l[t][e]:function(){return`Error: Template "${e}" does not exist in storage "${t}".`}:function(){return`Error: Storage "${t}" does not exist.`}},add:function(e,t,r="default"){let n=t;l[r]||(l[r]={}),"function"!=typeof t&&(n=i(t)),"success"===n.name?l[r][e]=n:console.error(`Error: Template "${e}" looks broken and is not added to storage.`)},list:function(e="default"){return l[e]?Object.keys(l[e]):[]},clear:function(){Object.keys(l).forEach((e=>{"default"!=e?delete l[e]:l.default={}}))},remove:function(e,t="default"){return l[t]?l[t][e]?void delete l[t][e]:`Error: Template "${e}" does not exist in storage "${t}".`:`Error: Storage "${t}" does not exist.`}};module.exports=u;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"@peter.naydenov/walk";import t from"@peter.naydenov/stack";function r(e){return function t(r){const{TG_PRX:a,TG_SFX:o,TG_SIZE_P:s,TG_SIZE_S:c}=e;let i,l,u,f=[];if("string"!=typeof r)return n("notAString");if(0==r.length)return[];if(i=r.indexOf(a),0<i&&f.push(r.slice(0,i)),-1==i)return f.push(r),f;{if(u=r.indexOf(a,i+s),l=r.indexOf(o),-1==l)return n("missingClosing");if(l<i)return n("closedBeforeOpened");if(l+=c,-1!=u&&u<l)return n("newBeforeClosed");f.push(r.slice(i,l));let e=t(r.slice(l));return f.concat(e)}}}function n(e){switch(e){case"notAString":return"Error: Template is not a string.";case"missingClosing":return"Error: Placeholder with missing closing tag.";case"closedBeforeOpened":return"Error: Placeholder closing tag without starting one.";case"newBeforeClosed":return"Error: Nested placeholders. Close placeholder before open new one.";default:return"Error: Unknown template error."}}function a(t){const r={};let n=0;if(t instanceof Function)return{dataDeepLevel:0,nestedData:[t()]};if(null==t)return{dataDeepLevel:0,nestedData:[null]};if("string"==typeof t)return{dataDeepLevel:0,nestedData:[t]};return e({data:t,objectCallback:function({key:e,value:t,breadcrumbs:a}){let o=!1;return n=a.split("/").length-1,isNaN(e)||(o=!0),r[n]||(r[n]=o?[]:{}),"root"===e?r[n]=t:o?r[n].push(t):r[n][a]=t,t}}),{dataDeepLevel:n,nestedData:r}}var o={TG_PRX:"{{",TG_SFX:"}}",TG_SIZE_P:2,TG_SIZE_S:2};function s(e){return null==e?"null":"string"==typeof e||"number"==typeof e||"boolean"==typeof e?"primitive":"function"==typeof e?"function":e instanceof Array?"array":e instanceof Object?"object":void 0}function c(e,t,n){e instanceof Object&&Object.entries(e).forEach((([t,r])=>{r instanceof Object&&(e[t]=r.text)}));const a="function"==typeof n[t];return e=function(e={}){return"string"==typeof e?{text:e}:e}(e),a?n[t](e):function(e,t){if(null==t)return null;const n=r(o)(e),a=o;return n.forEach(((e,r)=>{if(e.includes(a.TG_PRX)){const o=e.replace(a.TG_PRX,"").replace(a.TG_SFX,"").trim();t[o]&&(n[r]=t[o])}})),n.join("")}(n[t],e)}function i(n){const{hasError:i,placeholders:l,chop:u,helpers:f,handshake:d}=function(e){const{template:t,helpers:n,handshake:a}=e,{TG_PRX:s,TG_SFX:c,TG_SIZE_P:i,TG_SIZE_S:l}=o,u=[];let f=null;const d=r(o)(t);return"string"==typeof d?f=d:d.forEach(((e,t)=>{const r=RegExp(s+"\\s*(.*?)\\s*(?::\\s*(.*?)\\s*)?"+c,"g");if(e.includes(s)){const a=r.exec(e);u.push({index:t,data:(n=a[1],n||null),action:a[2]?a[2].split(",").map((e=>e.trim())):null})}var n})),u.forEach((e=>{e.action&&e.action.every((e=>"#"===e||(e.startsWith("?")&&(e=e.replace("?","")),e.startsWith("+")&&(e=e.replace("+","")),e.startsWith("[]")&&(e=e.replace("[]","")),e.startsWith(">")&&(e=e.replace(">","")),""===e||!!n[e]||(f=`Error: Missing helper: ${e}`,!1))))})),{hasError:f,placeholders:u,chop:d,helpers:n,handshake:a}}(n);if(i)return function(){return i};{let r=structuredClone(u);return function(n={},...o){const i=s(n),u=[];if("null"===i)return r.join("");if("string"==typeof n)switch(n){case"raw":return r.join("");case"demo":if(!d)return"Error: No handshake data.";n=d;break;case"handshake":return d?structuredClone(d):"Error: No handshake data.";case"placeholders":return l.map((e=>r[e.index])).join(", ");default:return`Error: Wrong command "${n}". Available commands: raw, demo, handshake, placeholders.`}return"array"!==i&&(n=[n]),n.forEach((n=>{l.forEach((o=>{const{index:i,data:l,action:u}=o;if(!u&&l){const d=n[l];switch(s(d)){case"function":return void(r[i]=d());case"primitive":return void(r[i]=d);case"array":return void("primitive"===s(d[0])&&(r[i]=d[0]));case"object":return void(d.text&&(r[i]=d.text))}}else{const{dataDeepLevel:p,nestedData:h}=a("@all"===l||null===l||"@root"===l?n:n[l]),b=function*(e,r){let n=t({type:"LIFO"});for(let t=0;t<=r;t++)n.push(e[t]);for(;n&&!n.isEmpty();){let e=yield n.pull();e&&n.push(e)}}(function(e,t=10){let r={},n=0,a=[...e],o=0;do{r[o]=[],o++}while(o<=t);return a.every((e=>"#"===e?(n++,!(n>t)):e.startsWith("?")?(r[n].push({type:"route",name:e.replace("?",""),level:n}),!0):e.startsWith("+")?(r[n].push({type:"extendedRender",name:e.replace("+",""),level:n}),!0):e.startsWith("[]")?(r[n].push({type:"mix",name:e.replace("[]",""),level:n}),!0):e.startsWith(">")?(r[n].push({type:"data",name:e.replace(">",""),level:n}),!0):(""===e||r[n].push({type:"render",name:e,level:n}),!0))),r}(u,p),p);for(let E of b){let{type:v,name:k,level:j}=E,x=h[j],g=s(x);switch(v){case"route":switch(g){case"array":x.forEach(((e,t)=>{if(null==e)return;const r=s(e),n=f[k](e);null!=n&&("object"===r?x[t].text=c(e,n,f):x[t]=c(e,n,f))}));break;case"object":x.text=c(x,routeName,f);break;case"primitive":h[j]=c(x,routeName,f)}break;case"data":switch(g){case"array":x.forEach(((e,t)=>x[t]=e instanceof Function?f[k](e()):f[k](e)));break;case"object":case"primitive":h[j]=f[k](x);break;case"function":h[j]=f[k](x())}break;case"render":const _="function"==typeof f[k];switch(g){case"array":_?x.forEach(((e,t)=>{if(null==e)return;const r=s(e),n=f[k](e);null==n&&(x[t]=null),"object"===r?e.text=n:x[t]=n})):x.forEach(((e,t)=>{if(null==e)return;const r=s(e),n=c(e,k,f);null==n&&(x[t]=null),"object"===r?e.text=n:x[t]=n}));break;case"primitive":h[j]=c(x,k,f);break;case"object":Object.keys(x).find((e=>e.includes("/")))?Object.entries(x).forEach((([e,t])=>t.text=c(t,k,f))):x.text=c(x,k,f)}break;case"extendedRender":"function"==typeof f[k]&&(h[j]=f[k](h[0]));break;case"mix":if(""===k)switch(g){case"object":Object.keys(x).find((e=>e.includes("/")))?Object.entries(x).forEach((([e,t])=>h[j][e]=t.text)):h[j]=x.text;for(let T=j-1;T>=0;T--)h[T]=e({data:h[T],objectCallback:w});function w({value:e,breadcrumbs:t}){return h[j][t]?h[j][t]:e}break;case"array":h[j]=x.map((e=>"object"===s(e)?e.text:e)).filter((e=>null!=e)).join("")}else h[j]=f[k](x)}}let m=s(h[0]),y=h[0];switch(m){case"primitive":if(null==y)return;r[i]=y;break;case"object":if(null==y.text)return;r[i]=y.text;break;case"array":r[i]=y.join("")}}})),u.push(r.join(""))})),"array"===i?u:o?o.reduce(((e,t)=>t(e)),u.join("")):u.join("")}}}const l={default:{}};const u={build:i,get:function(e,t="default"){return l[t]?l[t][e]?l[t][e]:function(){return`Error: Template "${e}" does not exist in storage "${t}".`}:function(){return`Error: Storage "${t}" does not exist.`}},add:function(e,t,r="default"){let n=t;l[r]||(l[r]={}),"function"!=typeof t&&(n=i(t)),"success"===n.name?l[r][e]=n:console.error(`Error: Template "${e}" looks broken and is not added to storage.`)},list:function(e="default"){return l[e]?Object.keys(l[e]):[]},clear:function(){Object.keys(l).forEach((e=>{"default"!=e?delete l[e]:l.default={}}))},remove:function(e,t="default"){return l[t]?l[t][e]?void delete l[t][e]:`Error: Template "${e}" does not exist in storage "${t}".`:`Error: Storage "${t}" does not exist.`}};export{u as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@peter.naydenov/walk")):"function"==typeof define&&define.amd?define(["@peter.naydenov/walk"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).morph=t(e.walk)}(this,(function(e){"use strict";function t(e){return function t(n){const{TG_PRX:o,TG_SFX:a,TG_SIZE_P:i,TG_SIZE_S:s}=e;let l,c,u,f=[];if("string"!=typeof n)return r("notAString");if(0==n.length)return[];if(l=n.indexOf(o),0<l&&f.push(n.slice(0,l)),-1==l)return f.push(n),f;{if(u=n.indexOf(o,l+i),c=n.indexOf(a),-1==c)return r("missingClosing");if(c<l)return r("closedBeforeOpened");if(c+=s,-1!=u&&u<c)return r("newBeforeClosed");f.push(n.slice(l,c));let e=t(n.slice(c));return f.concat(e)}}}function r(e){switch(e){case"notAString":return"Error: Template is not a string.";case"missingClosing":return"Error: Placeholder with missing closing tag.";case"closedBeforeOpened":return"Error: Placeholder closing tag without starting one.";case"newBeforeClosed":return"Error: Nested placeholders. Close placeholder before open new one.";default:return"Error: Unknown template error."}}function n(t){const r={};let n=0;if(t instanceof Function)return{dataDeepLevel:0,nestedData:[t()]};if(null==t)return{dataDeepLevel:0,nestedData:[null]};if("string"==typeof t)return{dataDeepLevel:0,nestedData:[t]};return e({data:t,objectCallback:function({key:e,value:t,breadcrumbs:o}){let a=!1;return n=o.split("/").length-1,isNaN(e)||(a=!0),r[n]||(r[n]=a?[]:{}),"root"===e?r[n]=t:a?r[n].push(t):r[n][o]=t,t}}),{dataDeepLevel:n,nestedData:r}}function*o(e,t){let r=function(e={}){let t=[],{type:r,limit:n,onLimit:o}=Object.assign({},{type:"FIFO",limit:!1,onLimit:"update"},e),a="LIFO"===r.toUpperCase(),i=!1;function s(e=1,r=0){let n=[];return r>0&&Array.from({length:r}).map((()=>t.pop())),1==e?t.pop():(Array.from({length:e}).map((()=>{let e=t.pop();null!=e&&n.push(e)})),n)}function l(e=1,r=0){let n=[],o=t.length-r;return e>1&&Array.from({length:e}).map((()=>{null!=t[o-1]&&n.push(t[o-1]),o--})),1==e?t[t.length-1]:n}function c(){}return c.prototype={pull:s,pullReverse:function(e=1,t=0){let r=s(e,t);return r instanceof Array?r.reverse():r},peek:l,peekReverse:function(e=1,t=0){const r=l(e,t);return r instanceof Array?r.reverse():r},getSize:()=>t.length,isEmpty:()=>0==t.length,reset:()=>(t=[],!0),debug:()=>[...t]},c.prototype.push=a?function(e){const r=e instanceof Array,a=r?e.length:1;let l=!1;if("full"!==o||!i){if(n&&r&&a>n&&(e=e.slice(0,-a+n)),n){const a=(r?e.length:1)+t.length;a>=n&&"full"===o&&(e=e.slice(0,-(a-n))),a>=n&&"update"===o&&(l=s(a-n))}return t=e instanceof Array?t.concat(e):t.concat([e]),i=!!n&&t.length===n,l||void 0}}:function(e){const r=e instanceof Array,a=r?e.length:1;let l=!1;if("full"!==o||!i){if(n&&r&&a>n&&(e=e.slice(a-n)),n){const a=(r?e.length:1)+t.length;a>=n&&"full"===o&&(e=e.slice(0,-(a-n))),a>=n&&"update"===o&&(l=s(a-n))}return t=r?e.reduce(((e,t)=>[t,...e]),t):[e].reduce(((e,t)=>[t,...e]),t),i=!!n&&t.length===n,l||void 0}},new c}({type:"LIFO"});for(let n=0;n<=t;n++)r.push(e[n]);for(;r&&!r.isEmpty();){let e=yield r.pull();e&&r.push(e)}}var a={TG_PRX:"{{",TG_SFX:"}}",TG_SIZE_P:2,TG_SIZE_S:2};function i(e){return null==e?"null":"string"==typeof e||"number"==typeof e||"boolean"==typeof e?"primitive":"function"==typeof e?"function":e instanceof Array?"array":e instanceof Object?"object":void 0}function s(e,r,n){e instanceof Object&&Object.entries(e).forEach((([t,r])=>{r instanceof Object&&(e[t]=r.text)}));const o="function"==typeof n[r];return e=function(e={}){return"string"==typeof e?{text:e}:e}(e),o?n[r](e):function(e,r){if(null==r)return null;const n=t(a)(e),o=a;return n.forEach(((e,t)=>{if(e.includes(o.TG_PRX)){const a=e.replace(o.TG_PRX,"").replace(o.TG_SFX,"").trim();r[a]&&(n[t]=r[a])}})),n.join("")}(n[r],e)}function l(r){const{hasError:l,placeholders:c,chop:u,helpers:f,handshake:p}=function(e){const{template:r,helpers:n,handshake:o}=e,{TG_PRX:i,TG_SFX:s,TG_SIZE_P:l,TG_SIZE_S:c}=a,u=[];let f=null;const p=t(a)(r);return"string"==typeof p?f=p:p.forEach(((e,t)=>{const r=RegExp(i+"\\s*(.*?)\\s*(?::\\s*(.*?)\\s*)?"+s,"g");if(e.includes(i)){const o=r.exec(e);u.push({index:t,data:(n=o[1],n||null),action:o[2]?o[2].split(",").map((e=>e.trim())):null})}var n})),u.forEach((e=>{e.action&&e.action.every((e=>"#"===e||(e.startsWith("?")&&(e=e.replace("?","")),e.startsWith("+")&&(e=e.replace("+","")),e.startsWith("[]")&&(e=e.replace("[]","")),e.startsWith(">")&&(e=e.replace(">","")),""===e||!!n[e]||(f=`Error: Missing helper: ${e}`,!1))))})),{hasError:f,placeholders:u,chop:p,helpers:n,handshake:o}}(r);if(l)return function(){return l};{let t=structuredClone(u);return function(r={},...a){const l=i(r),u=[];if("null"===l)return t.join("");if("string"==typeof r)switch(r){case"raw":return t.join("");case"demo":if(!p)return"Error: No handshake data.";r=p;break;case"handshake":return p?structuredClone(p):"Error: No handshake data.";case"placeholders":return c.map((e=>t[e.index])).join(", ");default:return`Error: Wrong command "${r}". Available commands: raw, demo, handshake, placeholders.`}return"array"!==l&&(r=[r]),r.forEach((r=>{c.forEach((a=>{const{index:l,data:c,action:u}=a;if(!u&&c){const p=r[c];switch(i(p)){case"function":return void(t[l]=p());case"primitive":return void(t[l]=p);case"array":return void("primitive"===i(p[0])&&(t[l]=p[0]));case"object":return void(p.text&&(t[l]=p.text))}}else{const{dataDeepLevel:d,nestedData:h}=n("@all"===c||null===c||"@root"===c?r:r[c]),m=o(function(e,t=10){let r={},n=0,o=[...e],a=0;do{r[a]=[],a++}while(a<=t);return o.every((e=>"#"===e?(n++,!(n>t)):e.startsWith("?")?(r[n].push({type:"route",name:e.replace("?",""),level:n}),!0):e.startsWith("+")?(r[n].push({type:"extendedRender",name:e.replace("+",""),level:n}),!0):e.startsWith("[]")?(r[n].push({type:"mix",name:e.replace("[]",""),level:n}),!0):e.startsWith(">")?(r[n].push({type:"data",name:e.replace(">",""),level:n}),!0):(""===e||r[n].push({type:"render",name:e,level:n}),!0))),r}(u,d),d);for(let g of m){let{type:v,name:E,level:k}=g,j=h[k],x=i(j);switch(v){case"route":switch(x){case"array":j.forEach(((e,t)=>{if(null==e)return;const r=i(e),n=f[E](e);null!=n&&("object"===r?j[t].text=s(e,n,f):j[t]=s(e,n,f))}));break;case"object":j.text=s(j,routeName,f);break;case"primitive":h[k]=s(j,routeName,f)}break;case"data":switch(x){case"array":j.forEach(((e,t)=>j[t]=e instanceof Function?f[E](e()):f[E](e)));break;case"object":case"primitive":h[k]=f[E](j);break;case"function":h[k]=f[E](j())}break;case"render":const w="function"==typeof f[E];switch(x){case"array":w?j.forEach(((e,t)=>{if(null==e)return;const r=i(e),n=f[E](e);null==n&&(j[t]=null),"object"===r?e.text=n:j[t]=n})):j.forEach(((e,t)=>{if(null==e)return;const r=i(e),n=s(e,E,f);null==n&&(j[t]=null),"object"===r?e.text=n:j[t]=n}));break;case"primitive":h[k]=s(j,E,f);break;case"object":Object.keys(j).find((e=>e.includes("/")))?Object.entries(j).forEach((([e,t])=>t.text=s(t,E,f))):j.text=s(j,E,f)}break;case"extendedRender":"function"==typeof f[E]&&(h[k]=f[E](h[0]));break;case"mix":if(""===E)switch(x){case"object":Object.keys(j).find((e=>e.includes("/")))?Object.entries(j).forEach((([e,t])=>h[k][e]=t.text)):h[k]=j.text;for(let _=k-1;_>=0;_--)h[_]=e({data:h[_],objectCallback:T});function T({value:e,breadcrumbs:t}){return h[k][t]?h[k][t]:e}break;case"array":h[k]=j.map((e=>"object"===i(e)?e.text:e)).filter((e=>null!=e)).join("")}else h[k]=f[E](j)}}let y=i(h[0]),b=h[0];switch(y){case"primitive":if(null==b)return;t[l]=b;break;case"object":if(null==b.text)return;t[l]=b.text;break;case"array":t[l]=b.join("")}}})),u.push(t.join(""))})),"array"===l?u:a?a.reduce(((e,t)=>t(e)),u.join("")):u.join("")}}}const c={default:{}};return{build:l,get:function(e,t="default"){return c[t]?c[t][e]?c[t][e]:function(){return`Error: Template "${e}" does not exist in storage "${t}".`}:function(){return`Error: Storage "${t}" does not exist.`}},add:function(e,t,r="default"){let n=t;c[r]||(c[r]={}),"function"!=typeof t&&(n=l(t)),"success"===n.name?c[r][e]=n:console.error(`Error: Template "${e}" looks broken and is not added to storage.`)},list:function(e="default"){return c[e]?Object.keys(c[e]):[]},clear:function(){Object.keys(c).forEach((e=>{"default"!=e?delete c[e]:c.default={}}))},remove:function(e,t="default"){return c[t]?c[t][e]?void delete c[t][e]:`Error: Template "${e}" does not exist in storage "${t}".`:`Error: Storage "${t}" does not exist.`}}}));
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@peter.naydenov/morph",
|
|
3
|
+
"description": "Template engine",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Peter Naydenov",
|
|
7
|
+
"main": "./dist/morph.umd.js",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/morph.esm.mjs",
|
|
12
|
+
"require": "./dist/morph.cjs",
|
|
13
|
+
"default": "./dist/morph.umd.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json",
|
|
16
|
+
"./dist/*": "./dist/*",
|
|
17
|
+
"./src/*": "./src/*"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "mocha test",
|
|
21
|
+
"cover": "c8 mocha",
|
|
22
|
+
"build": "rollup -c"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@peter.naydenov/stack": "^3.0.0",
|
|
26
|
+
"@peter.naydenov/walk": "^5.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@rollup/plugin-commonjs": "^28.0.0",
|
|
30
|
+
"@rollup/plugin-node-resolve": "^15.3.0",
|
|
31
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
32
|
+
"c8": "^10.1.2",
|
|
33
|
+
"chai": "5.1.2",
|
|
34
|
+
"mocha": "10.8.2",
|
|
35
|
+
"rollup": "^4.27.4"
|
|
36
|
+
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/PeterNaydenov/morph.git"
|
|
40
|
+
},
|
|
41
|
+
"c8": {
|
|
42
|
+
"include": [
|
|
43
|
+
"src/**/*.js"
|
|
44
|
+
],
|
|
45
|
+
"exclude": [
|
|
46
|
+
"node_modules",
|
|
47
|
+
"test"
|
|
48
|
+
],
|
|
49
|
+
"reporter": [
|
|
50
|
+
"lcov",
|
|
51
|
+
"text-summary"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"keywords": [
|
|
55
|
+
"template",
|
|
56
|
+
"engine",
|
|
57
|
+
"string",
|
|
58
|
+
"function"
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import resolve from '@rollup/plugin-node-resolve'
|
|
2
|
+
import commonjs from '@rollup/plugin-commonjs'
|
|
3
|
+
import terser from '@rollup/plugin-terser';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export default [
|
|
7
|
+
// browser-friendly UMD build
|
|
8
|
+
{
|
|
9
|
+
input: 'src/main.js',
|
|
10
|
+
output: {
|
|
11
|
+
name: 'morph',
|
|
12
|
+
file: 'dist/morph.umd.js',
|
|
13
|
+
format: 'umd',
|
|
14
|
+
globals: {
|
|
15
|
+
'@peter.naydenov/walk': 'walk'
|
|
16
|
+
, '@peter.naydenov/stack': 'stack'
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
external: ['@peter.naydenov/walk'],
|
|
20
|
+
plugins: [
|
|
21
|
+
resolve(), // so Rollup can find `ms`
|
|
22
|
+
commonjs() // so Rollup can convert `ms` to an ES module
|
|
23
|
+
, terser()
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
// CommonJS (for Node) and ES module (for bundlers) build.
|
|
28
|
+
// (We could have three entries in the configuration array
|
|
29
|
+
// instead of two, but it's quicker to generate multiple
|
|
30
|
+
// builds from a single configuration where possible, using
|
|
31
|
+
// an array for the `output` option, where we can specify
|
|
32
|
+
// `file` and `format` for each target)
|
|
33
|
+
{
|
|
34
|
+
input: 'src/main.js',
|
|
35
|
+
external: ['@peter.naydenov/walk', '@peter.naydenov/stack'],
|
|
36
|
+
output: [
|
|
37
|
+
{ file: 'dist/morph.cjs' , format: 'cjs' },
|
|
38
|
+
{ file: 'dist/morph.esm.mjs', format: 'es' }
|
|
39
|
+
],
|
|
40
|
+
plugins: [ terser() ]
|
|
41
|
+
}
|
|
42
|
+
];
|
package/src/main.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/***
|
|
2
|
+
* Morph (@peter.naydenov/morph)
|
|
3
|
+
*
|
|
4
|
+
* Text based template engine
|
|
5
|
+
*
|
|
6
|
+
*
|
|
7
|
+
* History notes:
|
|
8
|
+
* - Idea was born on October 28th, 2024.
|
|
9
|
+
* - Published on GitHub for first time: November 30th, 2024
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
import build from "./methods/build.js"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
const storage = { default: {}};
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
function get ( prop, strName='default' ) {
|
|
23
|
+
if ( !storage[strName] ) {
|
|
24
|
+
return function () {
|
|
25
|
+
return `Error: Storage "${strName}" does not exist.`
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if ( !storage[strName][prop] ) {
|
|
29
|
+
return function () {
|
|
30
|
+
return `Error: Template "${prop}" does not exist in storage "${strName}".`
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return storage[strName][prop]
|
|
34
|
+
} // get func.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
function add ( name, tplfn, strName='default' ) {
|
|
39
|
+
let fn = tplfn;
|
|
40
|
+
if( !storage[strName] ) storage[strName] = {}
|
|
41
|
+
if ( typeof tplfn !== 'function' ) fn = build ( tplfn );
|
|
42
|
+
if ( fn.name === 'success' ) storage[strName][name] = fn
|
|
43
|
+
else console.error ( `Error: Template "${name}" looks broken and is not added to storage.` )
|
|
44
|
+
} // add func.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
function list ( strName='default' ) {
|
|
49
|
+
if ( !storage[strName] ) return []
|
|
50
|
+
return Object.keys ( storage[strName] )
|
|
51
|
+
} // list func.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
function clear ( ) {
|
|
56
|
+
const keys = Object.keys ( storage )
|
|
57
|
+
keys.forEach ( key => {
|
|
58
|
+
if ( key != 'default' ) delete storage[key]
|
|
59
|
+
else storage['default'] = {}
|
|
60
|
+
})
|
|
61
|
+
} // clear func.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
function remove ( name, strName='default' ) {
|
|
66
|
+
if ( !storage[strName] ) return `Error: Storage "${strName}" does not exist.`
|
|
67
|
+
if ( !storage[strName][name] ) return `Error: Template "${name}" does not exist in storage "${strName}".`
|
|
68
|
+
delete storage[strName][name]
|
|
69
|
+
} // remove func.
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
const morphAPI = {
|
|
74
|
+
// Engine API
|
|
75
|
+
/**
|
|
76
|
+
*
|
|
77
|
+
* build -
|
|
78
|
+
* add - register a component to component storage
|
|
79
|
+
* get - get a component from component storage
|
|
80
|
+
* list - list all components in component storage
|
|
81
|
+
* clear - clear all templates in component storage
|
|
82
|
+
* remove - remove a template from component storage
|
|
83
|
+
*
|
|
84
|
+
* shine - extra step to remove all non used placeholders and <rs> tags
|
|
85
|
+
*
|
|
86
|
+
*/
|
|
87
|
+
build // build a component from template description
|
|
88
|
+
, get // get a component from component storage
|
|
89
|
+
, add // add a component to component storage
|
|
90
|
+
, list // list all components in component storage
|
|
91
|
+
, clear // clear all templates in component storage
|
|
92
|
+
, remove // remove a template from component storage
|
|
93
|
+
} // morphAPI
|
|
94
|
+
|
|
95
|
+
// TODO: How to change render templates on condition?
|
|
96
|
+
// - Use compiled versions of simile templates
|
|
97
|
+
|
|
98
|
+
export default morphAPI
|
|
99
|
+
|
|
100
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import stack from "@peter.naydenov/stack"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
function* _actionSupply ( act, level ) {
|
|
6
|
+
let action = stack ({ type:'LIFO' });
|
|
7
|
+
for ( let i=0; i<=level; i++ ) {
|
|
8
|
+
action.push ( act[i] )
|
|
9
|
+
}
|
|
10
|
+
while ( action && !action.isEmpty () ) {
|
|
11
|
+
let newAct = yield action.pull ()
|
|
12
|
+
if ( newAct ) action.push ( newAct )
|
|
13
|
+
}
|
|
14
|
+
} // _actionSupply func.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export default _actionSupply
|
|
18
|
+
|
|
19
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
function _chopTemplate ( settings ) {
|
|
2
|
+
return function _chopTemplate ( text ) {
|
|
3
|
+
const { TG_PRX, TG_SFX, TG_SIZE_P, TG_SIZE_S } = settings;
|
|
4
|
+
let
|
|
5
|
+
start // placeholder start
|
|
6
|
+
, end // placeholder end
|
|
7
|
+
, checkPoint // check
|
|
8
|
+
, res = [] // template result array
|
|
9
|
+
;
|
|
10
|
+
|
|
11
|
+
if ( typeof(text) != 'string' ) return showError( 'notAString' ) // Template is not a string
|
|
12
|
+
if ( text.length == 0 ) return []
|
|
13
|
+
|
|
14
|
+
start = text.indexOf ( TG_PRX )
|
|
15
|
+
if ( 0 < start ) res.push ( text.slice(0, start) )
|
|
16
|
+
if ( -1 == start ) {
|
|
17
|
+
res.push( text )
|
|
18
|
+
return res;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
checkPoint = text.indexOf ( TG_PRX, start+TG_SIZE_P )
|
|
22
|
+
end = text.indexOf( TG_SFX )
|
|
23
|
+
|
|
24
|
+
if ( end == -1 ) return showError('missingClosing') // Placeholder with missing closing tags
|
|
25
|
+
if ( end < start ) return showError('closedBeforeOpened') // Placeholder closing tags without starting ones
|
|
26
|
+
else end += TG_SIZE_S
|
|
27
|
+
|
|
28
|
+
if ( checkPoint != -1 && checkPoint < end ) {
|
|
29
|
+
return showError('newBeforeClosed') // New placeholder before first ends
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
res.push( text.slice(start,end) )
|
|
33
|
+
let nextText = text.slice (end)
|
|
34
|
+
let additional = _chopTemplate ( nextText )
|
|
35
|
+
return res.concat ( additional )
|
|
36
|
+
}
|
|
37
|
+
}} // chopTemplate func.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
function showError (type) {
|
|
42
|
+
switch (type) {
|
|
43
|
+
case 'notAString':
|
|
44
|
+
return `Error: Template is not a string.`
|
|
45
|
+
case 'missingClosing':
|
|
46
|
+
return `Error: Placeholder with missing closing tag.`
|
|
47
|
+
case 'closedBeforeOpened':
|
|
48
|
+
return `Error: Placeholder closing tag without starting one.`
|
|
49
|
+
case 'newBeforeClosed':
|
|
50
|
+
return `Error: Nested placeholders. Close placeholder before open new one.`
|
|
51
|
+
default:
|
|
52
|
+
return `Error: Unknown template error.`
|
|
53
|
+
}
|
|
54
|
+
} // showError func.
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
export default _chopTemplate
|
|
59
|
+
|
|
60
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import walk from '@peter.naydenov/walk'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
function _defineData ( d ) {
|
|
6
|
+
const nestedData = {};
|
|
7
|
+
let dataDeepLevel = 0;
|
|
8
|
+
|
|
9
|
+
if ( d instanceof Function ) return { dataDeepLevel:0, nestedData:[d()] }
|
|
10
|
+
if ( d == null ) return { dataDeepLevel:0, nestedData:[ null ] }
|
|
11
|
+
if ( typeof d === 'string' ) return { dataDeepLevel:0, nestedData:[d] }
|
|
12
|
+
|
|
13
|
+
function findObjects ({key, value, breadcrumbs}) {
|
|
14
|
+
let isArray = false;
|
|
15
|
+
dataDeepLevel = breadcrumbs.split('/').length -1;
|
|
16
|
+
if ( !isNaN(key) ) isArray = true
|
|
17
|
+
if ( !nestedData[dataDeepLevel] ) nestedData[dataDeepLevel] = isArray? [] : {}
|
|
18
|
+
if ( key === 'root' ) nestedData[dataDeepLevel] = value
|
|
19
|
+
else if ( isArray ) nestedData[dataDeepLevel].push ( value )
|
|
20
|
+
else nestedData[dataDeepLevel][breadcrumbs] = value
|
|
21
|
+
return value
|
|
22
|
+
} // step func.
|
|
23
|
+
|
|
24
|
+
walk ({ data:d, objectCallback:findObjects })
|
|
25
|
+
return { dataDeepLevel, nestedData }
|
|
26
|
+
} // _defineData func.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export default _defineData
|
|
31
|
+
|
|
32
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function _defineDataType ( data ) {
|
|
2
|
+
|
|
3
|
+
if ( data == null ) return 'null'
|
|
4
|
+
if ( typeof data === 'string' ) return 'primitive'
|
|
5
|
+
if ( typeof data === 'number' ) return 'primitive'
|
|
6
|
+
if ( typeof data === 'boolean' ) return 'primitive'
|
|
7
|
+
if ( typeof data === 'function') return 'function'
|
|
8
|
+
if ( data instanceof Array ) return 'array'
|
|
9
|
+
if ( data instanceof Object ) return 'object'
|
|
10
|
+
return undefined
|
|
11
|
+
} // _defineDataType func.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export default _defineDataType
|
|
15
|
+
|
|
16
|
+
|