@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.
@@ -0,0 +1,78 @@
1
+ import settings from "./settings.js";
2
+ import _chopTemplate from "./_chopTemplates.js"
3
+
4
+
5
+
6
+ function readData ( field ) {
7
+ return field ? field : null
8
+ } // readData func.
9
+
10
+
11
+
12
+ function _readTemplate ( tpl ) {
13
+ const
14
+ { template, helpers, handshake } = tpl
15
+ ,{ TG_PRX, TG_SFX, TG_SIZE_P, TG_SIZE_S } = settings
16
+ , placeholders = []
17
+ ;
18
+
19
+ let hasError = null;
20
+ const chop = _chopTemplate (settings)( template );
21
+
22
+ if ( typeof chop === 'string' ) hasError = chop
23
+ else {
24
+ chop.forEach ( (item,i) => {
25
+ const
26
+ // Placeholder contains: Opening tag(TG_PRX), dataName, delimiter(:), list of operations, closing tag(TG_SFX)
27
+ finding = RegExp ( TG_PRX + '\\s*(.*?)\\s*(?::\\s*(.*?)\\s*)?' + TG_SFX, 'g' )
28
+ , isPlaceholder = item.includes( TG_PRX )
29
+ ;
30
+
31
+ if ( isPlaceholder ) {
32
+ const x = finding.exec ( item )
33
+
34
+ placeholders.push ( {
35
+ index: i
36
+ , data : readData ( x[1] )
37
+ , action : x[2] ? x[2].split(',').map ( x => x.trim()) : null
38
+ })
39
+ } // if isPlaceholder
40
+ }) // forEach chop
41
+ } // else error
42
+
43
+
44
+
45
+ // Check helpers - sanity check
46
+ placeholders.forEach ( holder => {
47
+ if ( !holder.action ) return
48
+ holder.action.every ( act => {
49
+ if ( act === '#' ) return true
50
+ if ( act.startsWith ( '?' )) act = act.replace ( '?', '' )
51
+ if ( act.startsWith ( '+' )) act = act.replace ( '+', '' )
52
+ if ( act.startsWith ( '[]' )) act = act.replace ( '[]', '' )
53
+ if ( act.startsWith ( '>' )) act = act.replace ( '>', '' )
54
+ if ( act === '' ) return true
55
+ if ( helpers[act] ) return true
56
+ else {
57
+ hasError = `Error: Missing helper: ${act}`
58
+ return false
59
+ }
60
+ }) // every action
61
+ }) // forEach placeholders
62
+
63
+
64
+
65
+ return {
66
+ hasError
67
+ , placeholders
68
+ , chop
69
+ , helpers
70
+ , handshake
71
+ }
72
+ } // _readTemplate func.
73
+
74
+
75
+
76
+ export default _readTemplate
77
+
78
+
@@ -0,0 +1,26 @@
1
+ import _chopTemplate from './_chopTemplates.js'
2
+ import settings from './settings.js'
3
+
4
+
5
+
6
+ function _renderHolder ( template, data ) {
7
+ // Data should be an object. No array, no string.
8
+ if ( data == null ) return null
9
+ const
10
+ chop = _chopTemplate (settings)( template )
11
+ , set = settings
12
+ ;
13
+ chop.forEach ( (item,i) => {
14
+ const isPlaceholder = item.includes ( set.TG_PRX )
15
+ if ( isPlaceholder ) {
16
+ const field = item.replace ( set.TG_PRX, '' ).replace ( set.TG_SFX, '' ).trim();
17
+ if ( data[field] ) chop[i] = data[field]
18
+ } // if isPlaceholder
19
+ }) // forEach chop
20
+ return chop.join ( '' )
21
+ } // _renderHolder func.
22
+
23
+
24
+ export default _renderHolder
25
+
26
+
@@ -0,0 +1,69 @@
1
+ function _setupActions ( actions, dataDeepLevel=10 ) {
2
+ let
3
+ actSetup = {}
4
+ , actLevel = 0
5
+ , actionList = [...actions]
6
+ , i = 0
7
+ ;
8
+
9
+ do {
10
+ actSetup[i] = []
11
+ i++
12
+ } while ( i <= dataDeepLevel )
13
+
14
+ actionList.every ( act => {
15
+ if ( act === '#' ) { // it's a change level action
16
+ actLevel++
17
+ if ( actLevel > dataDeepLevel ) return false
18
+ return true
19
+ }
20
+ if ( act.startsWith ( '?' ) ) { // it's a condition render action
21
+ actSetup[actLevel].push ({
22
+ type: 'route'
23
+ , name: act.replace ( '?', '' )
24
+ , level: actLevel
25
+ })
26
+ return true
27
+ }
28
+ if ( act.startsWith ( '+' ) ) { // it's a extended render action
29
+ actSetup[actLevel].push ({
30
+ type: 'extendedRender'
31
+ , name: act.replace ( '+', '' )
32
+ , level: actLevel
33
+ })
34
+ return true
35
+ }
36
+ if ( act.startsWith ( '[]' )) { // it's a mixing action
37
+ actSetup[actLevel].push ({
38
+ type: 'mix'
39
+ , name: act.replace ( '[]', '' )
40
+ , level: actLevel
41
+ })
42
+ return true
43
+ }
44
+ if ( act.startsWith ( '>' ) ) { // it's a data action. Result will be merged to existing data
45
+ actSetup[actLevel].push ({
46
+ type: 'data'
47
+ , name: act.replace ( '>', '' )
48
+ , level: actLevel
49
+ })
50
+ return true
51
+ }
52
+ if ( act === '' ) {
53
+ return true
54
+ }
55
+ actSetup[actLevel].push ({
56
+ type: 'render'
57
+ , name: act
58
+ , level: actLevel
59
+ })
60
+ return true
61
+ }) // actionList every
62
+ return actSetup
63
+ } // _setupActions func.
64
+
65
+
66
+
67
+ export default _setupActions
68
+
69
+
@@ -0,0 +1,239 @@
1
+ import _chopTemplate from "./_chopTemplates.js"
2
+ import _defineData from "./_defineData.js"
3
+ import _actionSupply from "./_actionSupply.js"
4
+ import _setupActions from "./_setupActions.js"
5
+ import _readTemplate from "./_readTemplate.js"
6
+ import _renderHolder from './_renderHolder.js'
7
+ import _defineDataType from "./_defineType.js"
8
+ import render from './render.js'
9
+
10
+
11
+ import walk from '@peter.naydenov/walk'
12
+
13
+
14
+
15
+ function build ( tpl ) {
16
+ const { hasError, placeholders, chop, helpers, handshake } = _readTemplate ( tpl );
17
+ if ( hasError ) {
18
+ return function fail () {
19
+ return hasError
20
+ }
21
+ }
22
+ else { // If no errors:
23
+ let cuts = structuredClone ( chop );
24
+ // *** Template recognition complete. Start building the rendering function -->
25
+
26
+ return function success ( d={}, ...args ) {
27
+ const
28
+ topLevelType = _defineDataType ( d )
29
+ , endData = []
30
+ ;
31
+ if ( topLevelType === 'null' ) return cuts.join ( '' )
32
+ // Commands : raw, demo, handshake, placeholders
33
+ if ( typeof d === 'string' ) { // 'd' is a string when we want to debug the template
34
+ switch ( d ) {
35
+ case 'raw':
36
+ return cuts.join('') // Original template with placeholders
37
+ case 'demo':
38
+ if ( !handshake ) return `Error: No handshake data.`
39
+ d = handshake // Render with handshake object
40
+ break
41
+ case 'handshake':
42
+ if ( !handshake ) return `Error: No handshake data.`
43
+ return structuredClone (handshake) // return a copy of handshake object
44
+ case 'placeholders':
45
+ return placeholders.map ( h => cuts[h.index] ).join ( ', ')
46
+ default:
47
+ return `Error: Wrong command "${d}". Available commands: raw, demo, handshake, placeholders.`
48
+ }
49
+ } // if 'd' is string
50
+
51
+ if ( topLevelType !== 'array' ) d = [ d ]
52
+
53
+ // TODO: If 'd' is null -> then no data for all the placeholders
54
+ d.forEach ( d => {
55
+ placeholders.forEach ( holder => { // Placeholders
56
+ const
57
+ { index, data, action } = holder
58
+ , dataOnly = !action && data
59
+ ;
60
+
61
+ if ( dataOnly ) {
62
+ const
63
+ info = d[data]
64
+ , type = _defineDataType ( info )
65
+ ;
66
+ switch ( type ) {
67
+ case 'function' :
68
+ cuts[index] = info ()
69
+ return
70
+ case 'primitive':
71
+ cuts[index] = info
72
+ return
73
+ case 'array':
74
+ if ( _defineDataType(info[0]) === 'primitive' ) cuts[index] = info[0]
75
+ return
76
+ case 'object':
77
+ if ( info.text ) cuts[index] = info.text
78
+ return
79
+ } // switch
80
+ } // dataOnly
81
+ else { // Data and Actions
82
+ const
83
+ { dataDeepLevel, nestedData } = (data==='@all' || data===null || data==='@root' ) ? _defineData ( d ) : _defineData ( d[data] )
84
+ , actSetup = _actionSupply ( _setupActions ( action, dataDeepLevel ), dataDeepLevel )
85
+ ;
86
+
87
+ for ( let step of actSetup ) {
88
+ let { type, name, level } = step
89
+ let
90
+ theData = nestedData[level]
91
+ , dataType = _defineDataType ( theData )
92
+ ;
93
+ switch ( type ) { // Action type 'route','data', 'render', or mix -> different operations
94
+ case 'route':
95
+ switch ( dataType ) {
96
+ case 'array':
97
+ theData.forEach ( (d,i) => {
98
+ if ( d == null ) return
99
+ const dType = _defineDataType ( d )
100
+ const routeName = helpers[name]( d );
101
+ if ( routeName == null ) return
102
+ if ( dType === 'object' ) theData[i]['text'] = render ( d, routeName, helpers )
103
+ else theData[i] = render ( d, routeName, helpers )
104
+ })
105
+ break
106
+ case 'object':
107
+ theData['text'] = render ( theData, routeName, helpers )
108
+ break
109
+ case 'primitive':
110
+ nestedData[level] = render ( theData, routeName, helpers )
111
+ break
112
+ }
113
+ break
114
+ case 'data':
115
+ switch ( dataType ) {
116
+ case 'array':
117
+ theData.forEach ( (d,i) => theData[i] = ( d instanceof Function ) ? helpers[name]( d() ) : helpers[name]( d ) )
118
+ break
119
+ case 'object':
120
+ nestedData[level] = helpers[name]( theData )
121
+ break
122
+ case 'function':
123
+ nestedData[level] = helpers[name]( theData() )
124
+ break
125
+ case 'primitive':
126
+ nestedData[level] = helpers[name]( theData )
127
+ break
128
+ } // switch dataType
129
+
130
+ break
131
+ case 'render':
132
+ const isRenderFunction = typeof helpers[name] === 'function'; // Render could be a function and template.
133
+ switch ( dataType ) {
134
+ case 'array':
135
+ if ( isRenderFunction ) theData.forEach ( (d,i) => {
136
+ if ( d == null ) return
137
+ const dType = _defineDataType ( d );
138
+ const text = helpers[name]( d );
139
+ if ( text == null ) theData[i] = null
140
+ if ( dType === 'object' ) d['text'] = text
141
+ else theData[i] = text
142
+ })
143
+ else theData.forEach ( (d,i) => {
144
+ if ( d == null ) return
145
+ const
146
+ dType = _defineDataType ( d )
147
+ , text = render ( d, name, helpers )
148
+ ;
149
+ if ( text == null ) theData[i] = null
150
+ if ( dType === 'object' ) d['text'] = text
151
+ else theData[i] = text
152
+ })
153
+ break
154
+ case 'primitive':
155
+ nestedData[level] = render ( theData, name, helpers )
156
+ break
157
+ case 'object':
158
+ let kTest = Object.keys ( theData ).find ( k => k.includes ( '/' ) ); // Check if keys are breadcrumbs
159
+ if ( kTest ) Object.entries( theData ).forEach( ([k,v]) => v['text'] = render ( v, name, helpers ) )
160
+ else theData['text'] = render ( theData, name, helpers )
161
+ break
162
+ } // switch renderDataType
163
+ break;
164
+ case 'extendedRender':
165
+ // TODO: Test extendedRender
166
+ const isValid = typeof helpers[name] === 'function'; // Render could be a function and template.
167
+ if ( isValid ) {
168
+ nestedData[level] = helpers[name]( nestedData[0] )
169
+ }
170
+ else {
171
+ // TODO: Error...
172
+ }
173
+ break
174
+ case 'mix':
175
+ if ( name === '' ) { // when is anonymous mixing helper
176
+ switch ( dataType ) {
177
+ case 'object':
178
+ let kTest = Object.keys ( theData ).find ( k => k.includes ( '/' ) ); // Check if keys are breadcrumbs
179
+ if ( kTest ) Object.entries( theData ).forEach( ([k,v]) => nestedData[level][k] = v['text'] )
180
+ else nestedData[level] = theData['text']
181
+ for ( let i=level-1; i >= 0; i-- ) {
182
+ nestedData[i] = walk ({data:nestedData[i], objectCallback:check})
183
+ }
184
+ function check ({ value, breadcrumbs }) {
185
+ if ( nestedData[level][breadcrumbs] ) return nestedData[level][breadcrumbs]
186
+ return value
187
+ } // check
188
+ break
189
+ case 'array':
190
+ nestedData[level] = theData.map ( x => {
191
+ let xType = _defineDataType ( x );
192
+ if ( xType === 'object' ) return x.text
193
+ return x
194
+ })
195
+ .filter ( x => x != null )
196
+ .join ( '' )
197
+ break
198
+ } // switch dataType
199
+ } // if name === ''
200
+ else nestedData[level] = helpers[name]( theData )
201
+ break
202
+ default:
203
+ break
204
+ }
205
+ } // for step of actSetup
206
+ let accType = _defineDataType ( nestedData[0] )
207
+ let fineData = nestedData[0]
208
+
209
+ switch ( accType ) {
210
+ case 'primitive':
211
+ if ( fineData == null ) return
212
+ cuts[index] = fineData
213
+ break
214
+ case 'object':
215
+ if (fineData['text'] == null ) return
216
+ cuts[index] = fineData['text']
217
+ break
218
+ case 'array':
219
+ cuts[index] = fineData.join ( '' )
220
+ break
221
+ } // switch accType
222
+ } // else other
223
+ }) // forEach placeholders
224
+ endData.push ( cuts.join ( '' ))
225
+ }) // forEach d
226
+
227
+ if ( topLevelType === 'array' ) return endData
228
+ if (args) return args.reduce ( (acc, fn) => fn ( acc ), endData.join ( '' ) )
229
+ else return endData.join ( '' )
230
+
231
+ } // success func.
232
+ }
233
+ } // build func.
234
+
235
+
236
+
237
+ export default build
238
+
239
+
@@ -0,0 +1,26 @@
1
+ import _renderHolder from "./_renderHolder.js"
2
+
3
+
4
+
5
+ function render ( theData, name, helpers ) {
6
+ // *** Executes rendering and return the results
7
+ if ( theData instanceof Object ) { // Make sure all properties are not objects
8
+ Object.entries ( theData ).forEach ( ([key, value]) => {
9
+ if ( value instanceof Object ) theData[key] = value['text']
10
+ })
11
+ }
12
+ function setRenderData ( d={} ) {
13
+ if ( typeof d === 'string' ) return { text: d }
14
+ else return d
15
+ } // setRenderData func.
16
+ const isRenderFunction = typeof helpers[name] === 'function'; // Render could be a function or template.
17
+ theData = setRenderData ( theData )
18
+ if ( isRenderFunction ) return helpers[name]( theData )
19
+ else return _renderHolder ( helpers[name], theData )
20
+ } // render func.
21
+
22
+
23
+
24
+ export default render
25
+
26
+
@@ -0,0 +1,6 @@
1
+ export default {
2
+ TG_PRX: '{{'
3
+ , TG_SFX: '}}'
4
+ , TG_SIZE_P: 2
5
+ , TG_SIZE_S: 2
6
+ }
@@ -0,0 +1,24 @@
1
+ import chopTemplates from'../src/methods/_chopTemplates.js'
2
+ import { expect } from 'chai';
3
+
4
+ describe ( 't-templates: chop', () => {
5
+
6
+ it ( 'Chop', () => {
7
+ const
8
+ str = 'Hello, {{name:ul,li,a}}! Say: {{shout}}, {{shout}}!'
9
+ , settings = {
10
+ TG_PRX: '{{'
11
+ , TG_SFX: '}}'
12
+ , TG_SIZE_P: 2
13
+ , TG_SIZE_S: 2
14
+ }
15
+ ;
16
+ const tpl = chopTemplates ( settings);
17
+ const res = tpl(str);
18
+
19
+ expect(res).to.be.deep.equal ([
20
+ 'Hello, ', '{{name:ul,li,a}}', '! Say: ', '{{shout}}', ', ', '{{shout}}', '!'
21
+ ])
22
+ }) // it chop
23
+
24
+ }) // Describe