@peter.naydenov/morph 3.1.5 → 3.3.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.
Files changed (44) hide show
  1. package/.mocharc.json +1 -0
  2. package/.opencode/command/speckit.analyze.md +184 -0
  3. package/.opencode/command/speckit.checklist.md +294 -0
  4. package/.opencode/command/speckit.clarify.md +181 -0
  5. package/.opencode/command/speckit.constitution.md +82 -0
  6. package/.opencode/command/speckit.implement.md +135 -0
  7. package/.opencode/command/speckit.plan.md +89 -0
  8. package/.opencode/command/speckit.specify.md +257 -0
  9. package/.opencode/command/speckit.tasks.md +137 -0
  10. package/.opencode/command/speckit.taskstoissues.md +28 -0
  11. package/.specify/memory/constitution.md +43 -0
  12. package/.specify/scripts/bash/check-prerequisites.sh +166 -0
  13. package/.specify/scripts/bash/common.sh +156 -0
  14. package/.specify/scripts/bash/create-new-feature.sh +305 -0
  15. package/.specify/scripts/bash/setup-plan.sh +61 -0
  16. package/.specify/scripts/bash/update-agent-context.sh +790 -0
  17. package/.specify/templates/agent-file-template.md +28 -0
  18. package/.specify/templates/checklist-template.md +40 -0
  19. package/.specify/templates/plan-template.md +104 -0
  20. package/.specify/templates/spec-template.md +115 -0
  21. package/.specify/templates/tasks-template.md +251 -0
  22. package/Changelog.md +17 -0
  23. package/README.md +184 -13
  24. package/dist/methods/build.d.ts +28 -2
  25. package/dist/methods/render.d.ts +9 -20
  26. package/dist/morph.cjs +1 -1
  27. package/dist/morph.esm.mjs +1 -1
  28. package/dist/morph.umd.js +1 -1
  29. package/morph.png +0 -0
  30. package/package.json +3 -2
  31. package/simple.js +17 -0
  32. package/specs/001-extend-templates/checklists/requirements.md +35 -0
  33. package/specs/001-extend-templates/contracts/set-method.json +39 -0
  34. package/specs/001-extend-templates/data-model.md +76 -0
  35. package/specs/001-extend-templates/plan.md +90 -0
  36. package/specs/001-extend-templates/quickstart.md +56 -0
  37. package/specs/001-extend-templates/research.md +18 -0
  38. package/specs/001-extend-templates/spec.md +139 -0
  39. package/specs/001-extend-templates/tasks.md +255 -0
  40. package/src/methods/_readTemplate.js +22 -46
  41. package/src/methods/_renderHolder.js +13 -7
  42. package/src/methods/_setupActions.js +1 -8
  43. package/src/methods/build.js +458 -321
  44. package/src/methods/render.js +23 -46
@@ -10,10 +10,28 @@ import render from './render.js'
10
10
 
11
11
  import walk from '@peter.naydenov/walk'
12
12
 
13
+ /**
14
+ * @callback UseHelperFn
15
+ * @param {string} name - Name of the helper to call.
16
+ * @param {any} [data] - Optional data override.
17
+ * @returns {any} Result of the helper call.
18
+ */
19
+
20
+ /**
21
+ * @callback HelperFn
22
+ * @param {object} args
23
+ * @param {any} args.data - The data context.
24
+ * @param {object} args.dependencies - Injected dependencies.
25
+ * @param {any} [args.full] - Full data context.
26
+ * @param {UseHelperFn} [args.useHelper] - Function to call other helpers.
27
+ * @param {object} [args.memory] - internal memory state.
28
+ * @returns {any} Rendered output.
29
+ */
30
+
13
31
  /**
14
32
  * @typedef {Object} Template
15
33
  * @property {string} template - Data to be rendered;
16
- * @property {object} [helpers] - Optional. Object with helper functions or simple templates for this template;
34
+ * @property {Object.<string, HelperFn|string>} [helpers] - Optional. Object with helper functions or simple templates for this template;
17
35
  * @property {object} [handshake] - Optional. Example for data to be rendered with;
18
36
  */
19
37
 
@@ -34,344 +52,463 @@ import walk from '@peter.naydenov/walk'
34
52
  */
35
53
  function build ( tpl, extra=false, buildDependencies={} ) {
36
54
  let { hasError, placeholders, chop, helpers, handshake, snippets } = _readTemplate ( tpl );
37
-
38
-
39
55
 
40
56
  if ( hasError ) {
41
57
  function fail () { return hasError }
42
58
  return extra ? [ false, fail ] : fail
43
59
  }
44
60
  else { // If NO Error:
45
- const originalPlaceholders = structuredClone ( placeholders );
46
- // *** Template recognition complete. Start building the rendering function -->
47
- /**
48
- * Processes template rendering commands with provided data, dependencies, and optional post-processing functions.
49
- *
50
- * @function success
51
- * @typedef { 'render' | 'debug' | 'snippets' } Command
52
- * @param {Command} [command='render'] - The command to execute. Supported commands: 'render', 'debug', 'snippets', or 'snippets:<names>'.
53
- * @param {Object|string} [d={}] - The data object to render, or a string instruction ('raw', 'demo', 'handshake', 'placeholders').
54
- * @param {Object} [dependencies={}] - Additional dependencies to be merged with internal dependencies.
55
- * @param {...any} args - Optional post-processing functions to apply to the rendered output.
56
- * @returns {string|Array[]} The rendered template, snippets, or data depending on the command and input.
57
- *
58
- * @throws {Error} If an unsupported command or instruction is provided.
59
- *
60
- * @example
61
- * // Render a template with data
62
- * success('render', { name: 'Alice' }, { helperFn });
63
- *
64
- * @example
65
- * // Get raw template
66
- * success('debug', 'raw');
67
- *
68
- * @example
69
- * // Render only specific snippets
70
- * success('snippets:header,footer', { ... });
71
- */
72
- function success ( command='render', d={}, dependencies={}, ...args ) {
73
- const cuts = structuredClone ( chop )
74
- let onlySnippets = false;
75
-
76
- if ( ![ 'render', 'debug', 'snippets'].includes ( command ) && !command.startsWith('snippets') ) return `Error: Wrong command "${command}". Available commands: render, debug, snippets.`
77
-
78
- if ( command.startsWith ( 'snippets') && command.includes ( ':' ) ) {
79
- onlySnippets = true
80
- let snippetNames = command.split ( ':' )
81
- .slice ( 1 )[0]
82
- .trim()
83
- .split ( ',' )
84
- .map ( t => t.trim() )
85
- placeholders = snippetNames.map ( item => snippets [ item ])
61
+ const originalPlaceholders = structuredClone(placeholders);
62
+ // *** Template recognition complete. Start building the rendering function -->
63
+ /**
64
+ * Processes template rendering commands with provided data, dependencies, and optional post-processing functions.
65
+ *
66
+ * @function success
67
+ * @typedef { 'render' | 'debug' | 'snippets' | 'curry' | 'set' } Command
68
+ * @param {Command} [command='render'] - The command to execute. Supported commands: 'render', 'debug', 'snippets', 'curry', 'set', or 'snippets:<names>'.
69
+ * @param {Object|string} [d={}] - The data object to render, or a string instruction ('raw', 'demo', 'handshake', 'placeholders', 'count').
70
+ * @param {Object} [dependencies={}] - Additional dependencies to be merged with internal dependencies.
71
+ * @param {...any} args - Optional post-processing functions to apply to the rendered output.
72
+ * @returns {string|Array[]|function} The rendered template, snippets, data, or new function depending on the command and input.
73
+ *
74
+ * @throws {Error} If an unsupported command or instruction is provided.
75
+ *
76
+ * @example
77
+ * // Render a template with data
78
+ * success('render', { name: 'Alice' }, { helperFn });
79
+ *
80
+ * @example
81
+ * // Get raw template
82
+ * success('debug', 'raw');
83
+ *
84
+ * @example
85
+ * // Get count of remaining placeholders
86
+ * success('debug', 'count');
87
+ *
88
+ * @example
89
+ * // Render only specific snippets
90
+ * success('snippets:header,footer', { ... });
91
+ *
92
+ * @example
93
+ * // Curry partial data and get new render function
94
+ * const curried = success('curry', { partial: 'data' });
95
+ * curried('render', { more: 'data' });
96
+ *
97
+ * @example
98
+ * // Set placeholders and get modified template
99
+ * const modified = success('set', { placeholders: { key: 'value' } });
100
+ */
101
+ function success(command = 'render', d = {}, dependencies = {}, ...args) {
102
+ const cuts = structuredClone(chop)
103
+ let onlySnippets = false;
104
+ if (typeof command !== 'string') return `Error: Wrong command "${command}". Available commands: render, debug, snippets, set, curry.`
105
+ if (!['render', 'debug', 'snippets', 'set', 'curry'].includes(command) && !command.startsWith('snippets')) return `Error: Wrong command "${command}". Available commands: render, debug, snippets, set, curry.`
106
+
107
+ if (command.startsWith('snippets') && command.includes(':')) {
108
+ onlySnippets = true
109
+ let snippetNames = command.split(':')
110
+ .slice(1)[0]
111
+ .trim()
112
+ .split(',')
113
+ .map(t => t.trim())
114
+ placeholders = snippetNames.map(item => snippets[item])
115
+ }
116
+ else if (command === 'snippets') {
117
+ onlySnippets = true
118
+ }
119
+ else if (command === 'set') {
120
+ if (typeof d !== 'object' || !d) return `Error: 'set' command requires an object with placeholders, helpers, handshake.`
121
+ // Merge helpers
122
+ const newHelpers = { ...helpers, ...(d.helpers || {}) }
123
+
124
+ // Merge handshake
125
+ const newHandshake = handshake ? { ...handshake, ...(d.handshake || {}) } : d.handshake || {}
126
+ // Modify chop for placeholders
127
+ const newChop = [...chop]
128
+ if (d.placeholders) {
129
+ Object.entries(d.placeholders).forEach(([k, v]) => {
130
+ if (!isNaN(k)) { // If k is a number
131
+ let index = placeholders[k].index;
132
+ newChop[index] = v
133
+ } // if k is a number
134
+ else { // If k is a string
135
+ let plx = placeholders.find(p => p.name === k)
136
+ newChop[plx.index] = v
137
+ }
138
+ }) // placeholders
139
+ } // if d.placeholders
140
+ const newTemplateStr = newChop.join('');
141
+ const newTpl = {
142
+ template: newTemplateStr,
143
+ helpers: newHelpers,
144
+ handshake: newHandshake
145
+ }
146
+ const result = build(newTpl, false, buildDependencies)
147
+ return typeof result === 'function' ? result : () => result
148
+ } else if (command === 'curry') {
149
+ // Render the template with current data
150
+ const rendered = success('render', d, dependencies, ...args)
151
+ // Create new template with rendered as template, keep helpers and handshake
152
+ const newTpl = {
153
+ template: rendered,
154
+ helpers,
155
+ handshake
156
+ }
157
+ const newFn = build(newTpl, false, buildDependencies)
158
+ return newFn
159
+ } else placeholders = structuredClone(originalPlaceholders) // Reset placeholders if not snippets
160
+
161
+ if (typeof d === 'string') {
162
+ switch (d) {
163
+ case 'raw':
164
+ return cuts.join('') // Original template with placeholders
165
+ case 'demo':
166
+ if (!handshake) return `Error: No handshake data.`
167
+ d = handshake // Render with handshake object
168
+ break
169
+ case 'handshake':
170
+ if (!handshake) return `Error: No handshake data.`
171
+ return structuredClone(handshake) // return a copy of handshake object
172
+ case 'helpers':
173
+ return Object.keys(helpers).join(', ')
174
+ case 'placeholders':
175
+ return placeholders.map(h => cuts[h.index]).join(', ')
176
+ case 'count':
177
+ return placeholders.length
178
+ default:
179
+ return `Error: Wrong instruction "${d}". Available instructions: raw, demo, handshake, helpers, placeholders, count.`
180
+ }
181
+ } // if d is string
182
+
183
+ const endData = [];
184
+ const memory = {};
185
+
186
+ let topLevelType = _defineDataType(d);
187
+ let deps = { ...buildDependencies, ...dependencies }
188
+ d = walk({ data: d }) // Creates copy of data to avoid mutation of the original
189
+ let original = walk({ data: d }) // Creates copy of data to avoid mutation of the original
190
+
191
+ // useHelper definition for build.js
192
+ const useHelper = (targetName, targetData) => {
193
+ // If targetData provided, user that. If not, we can't easily know "current" data here outside loop.
194
+ // But each call site passes 'theData'.
195
+ // So useHelper actually needs to be a factory or passed with current data.
196
+ // Wait, in build.js we are iterating.
197
+ // We can define a generic useHelper here that takes (targetName, targetData).
198
+ // But if targetData is missing, it should default to the caller's data.
199
+ // The caller is the helper function.
200
+ // See implementation plan: "Implementation plan: useHelper(name, override). if override missing -> current data."
201
+ // In build.js loop:
202
+ // const paramUseHelper = (tName, tData) => render(tData || theData, tName, helpers, original, deps)
203
+ // We must define this INSIDE the loop or pass 'theData' to a factory.
204
+ // It's cleaner to define a factory here if possible, but 'theData' changes.
205
+ // So we will define a createUseHelper function here.
206
+ }
207
+ const createUseHelper = (currentData) => (targetName, targetData) => {
208
+ let
209
+ targetFn = helpers[targetName]
210
+ , isCompiledTemplate = targetFn && typeof targetFn === 'function' && targetFn.length >= 2 // success(command, d) has > 1 params
211
+ ;
212
+
213
+ if (!targetFn) return `{{ Error: Helper '${targetName}' is not available}}`
214
+
215
+ if (isCompiledTemplate) {
216
+ // It's likely a compiled template (success function)
217
+ // Try calling it with 'render' command
218
+ try {
219
+ return targetFn('render', targetData || currentData, dependencies)
220
+ }
221
+ catch (e) {
222
+ // Fallback if it wasn't a compiled template but just a function with arguments
223
+ return render(targetData || currentData, targetName, helpers, original, deps, ...args)
224
+ }
225
+ }
226
+ return render(targetData || currentData, targetName, helpers, original, deps, ...args)
227
+ }
228
+ if (topLevelType === 'null') return cuts.join('')
229
+ if (topLevelType !== 'array') d = [d]
230
+
231
+ // Handle null data case - just return the template without placeholders
232
+ if (topLevelType === 'null') return cuts.join('')
233
+
234
+ d.forEach(dElement => {
235
+ placeholders.forEach(holder => { // Placeholders
236
+ const
237
+ { index, data, action } = holder // index - placeholder index, data - key of data, action - list of operations
238
+ , dataOnly = !action
239
+ , mem = structuredClone(memory)
240
+ // We cannot define extendArguments here with useHelper because we don't have 'theData' yet.
241
+ // It varies per dElement and deeper levels.
242
+ // Wait, 'info' is the data for this placeholder sort of.
243
+ // But 'extendArguments' is passed to helper.
244
+ // We need to inject 'useHelper' at the call site or update extendArguments inside the loops.
245
+ , extendArguments = { dependencies: deps, memory: mem }
246
+ ;
247
+ let info = dElement;
248
+
249
+ if (data && data.includes('/')) {
250
+ if (info.hasOwnProperty(data)) {
251
+ info = info[data]
86
252
  }
87
- else if ( command === 'snippets' ) {
88
- onlySnippets = true
253
+ else {
254
+ data.split('/').forEach(d => {
255
+ if (info.hasOwnProperty(d)) info = info[d]
256
+ else info = []
257
+ })
89
258
  }
90
- else placeholders = structuredClone ( originalPlaceholders ) // Reset placeholders if not snippets
91
-
92
- if ( typeof d === 'string' ) {
93
- switch ( d ) {
94
- case 'raw':
95
- return cuts.join ( '' ) // Original template with placeholders
96
- case 'demo':
97
- if ( !handshake ) return `Error: No handshake data.`
98
- d = handshake // Render with handshake object
99
- break
100
- case 'handshake':
101
- if ( !handshake ) return `Error: No handshake data.`
102
- return structuredClone (handshake) // return a copy of handshake object
103
- case 'placeholders':
104
- return placeholders.map ( h => cuts[h.index] ).join ( ', ')
105
- default:
106
- return `Error: Wrong instruction "${d}". Available instructions: raw, demo, handshake, placeholders.`
107
- }
108
- } // if d is string
109
-
110
- const endData = [];
111
- const memory = {};
112
-
113
- let topLevelType = _defineDataType ( d );
114
- let deps = { ...buildDependencies, ...dependencies }
115
- d = walk ({data:d}) // Creates copy of data to avoid mutation of the original
116
- let original = walk ({data:d}) // Creates copy of data to avoid mutation of the original
117
- if ( topLevelType === 'null' ) return cuts.join ( '' )
118
- if ( topLevelType !== 'array' ) d = [ d ]
119
-
120
- // Handle null data case - just return the template without placeholders
121
- if ( topLevelType === 'null' ) return cuts.join ( '' )
122
-
123
- d.forEach ( dElement => {
124
- placeholders.forEach ( holder => { // Placeholders
125
- const
126
- { index, data, action } = holder // index - placeholder index, data - key of data, action - list of operations
127
- , dataOnly = !action && data
128
- , mem = structuredClone ( memory )
129
- , extendArguments = { dependencies: deps, memory:mem }
130
- ;
131
- let info = dElement;
132
-
133
- if ( data && data.includes('/') ) {
134
- if ( info.hasOwnProperty ( data )) {
135
- info = info[data]
136
- }
137
- else {
138
- data.split('/').forEach ( d => {
139
- if ( info.hasOwnProperty(d) ) info = info[d]
140
- else info = []
141
- })
142
- }
143
- } // If data contains '/'
144
- else if ( data==='@all' || data===null || data==='@root' ) info = dElement
145
- else if ( data ) info = info[data]
146
-
147
-
148
-
149
- if ( dataOnly ) {
150
- const type = _defineDataType ( info );
151
- switch ( type ) {
152
- case 'function' :
153
- cuts[index] = info ()
154
- return
155
- case 'primitive':
156
- cuts[index] = info
157
- return
158
- case 'array':
159
- if ( _defineDataType(info[0]) === 'primitive' ) cuts[index] = info[0]
160
- return
161
- case 'object':
162
- if ( info.text ) cuts[index] = info.text
163
- return
164
- } // switch
165
- } // dataOnly
166
- else { // Data and Actions or only Actions
167
- let
168
- { dataDeepLevel, nestedData } = _defineData ( info, action )
169
- , actSetup = _actionSupply ( _setupActions ( action, dataDeepLevel ), dataDeepLevel )
170
- ;
171
-
172
- for ( let step of actSetup ) {
173
- let
174
- { type, name, level } = step
175
- , levelData = nestedData[level] || []
176
- ;
259
+ } // If data contains '/'
260
+ else if (data === '@all' || data === null || data === '@root') info = dElement
261
+ else if (data) info = info[data]
177
262
 
178
- levelData.forEach ( (theData, iData ) => {
179
-
180
- let dataType = _defineDataType ( theData )
181
-
182
- switch ( type ) { // Action type 'route','data', 'render', or mix -> different operations
183
- case 'route':
184
- switch ( dataType ) {
185
- case 'array':
186
- theData.forEach ( (d,i) => {
187
- if ( d == null ) return
188
- const dType = _defineDataType ( d )
189
- const routeName = helpers[name]( {data:d, ...extendArguments, full: d });
190
- if ( routeName == null ) return
191
- if ( dType === 'object' ) theData[i]['text'] = render ( d, routeName, helpers, original, deps )
192
- else theData[i] = render ( d, routeName, helpers, original, deps )
193
- })
194
- break
195
- case 'object':
196
- theData['text'] = render ( theData, name, helpers, original, deps )
197
- break
198
- }
199
- break
200
- case 'save' :
201
- memory[name] = structuredClone ( theData )
202
- break
203
- case 'overwrite':
204
- dElement = structuredClone ( theData )
205
- break
206
- case 'data':
207
- switch ( dataType ) {
208
- case 'array':
209
- theData.forEach ( (d,i) => theData[i] = ( d instanceof Function ) ? helpers[name]({ data:d(), ...extendArguments, full: d }) : helpers[name]( {data:d, ...extendArguments, full: d}) )
210
- break
211
- case 'object':
212
- nestedData[level] = [helpers[name]( {data:theData,...extendArguments, full: d } )]
213
- break
214
- case 'function':
215
- nestedData[level] = [helpers[name]( {data:theData(),...extendArguments, full: d } )]
216
- break
217
- case 'primitive':
218
- nestedData[level] = helpers[name]( {data:theData,...extendArguments, full: d } )
219
- break
220
- } // switch dataType
221
-
263
+
264
+
265
+ if (dataOnly) {
266
+ const type = _defineDataType(info);
267
+ switch (type) {
268
+ case 'function':
269
+ cuts[index] = info()
270
+ return
271
+ case 'primitive':
272
+ cuts[index] = info
273
+ return
274
+ case 'array':
275
+ if (_defineDataType(info[0]) === 'primitive') cuts[index] = info[0]
276
+ return
277
+ case 'object':
278
+ if (info.text) cuts[index] = info.text
279
+ return
280
+ } // switch
281
+ } // dataOnly
282
+ else { // Data and Actions or only Actions
283
+ let
284
+ { dataDeepLevel, nestedData } = _defineData(info, action)
285
+ , actSetup = _actionSupply(_setupActions(action, dataDeepLevel), dataDeepLevel)
286
+ ;
287
+
288
+ for (let step of actSetup) {
289
+ let
290
+ { type, name, level } = step
291
+ , levelData = nestedData[level] || []
292
+ ;
293
+
294
+ levelData.forEach((theData, iData) => {
295
+
296
+ let dataType = _defineDataType(theData)
297
+
298
+ switch (type) { // Action type 'route','data', 'render', or mix -> different operations
299
+ case 'route': // DEPRICATED in version 3.2.0. This functionality can be replaced with normal render functions
300
+ switch (dataType) {
301
+ case 'array':
302
+ theData.forEach((d, i) => {
303
+ if (d == null) return
304
+ const dType = _defineDataType(d)
305
+ const useHelper = createUseHelper(d);
306
+ const routeName = helpers[name]({ data: d, ...extendArguments, full: d, useHelper });
307
+ if (routeName == null) return
308
+ if (dType === 'object') theData[i]['text'] = render(d, routeName, helpers, original, deps)
309
+ else theData[i] = render(d, routeName, helpers, original, deps)
310
+ })
311
+ break
312
+ case 'object':
313
+ theData['text'] = render(theData, name, helpers, original, deps)
314
+ break
315
+ }
316
+ break
317
+ case 'save':
318
+ memory[name] = structuredClone(theData)
319
+ break
320
+ case 'overwrite':
321
+ dElement = structuredClone(theData)
322
+ break
323
+ case 'data':
324
+ switch (dataType) {
325
+ case 'array':
326
+ theData.forEach((d, i) => {
327
+ const useHelper = createUseHelper(d);
328
+ theData[i] = (d instanceof Function) ? helpers[name]({ data: d(), ...extendArguments, full: d, useHelper }) : helpers[name]({ data: d, ...extendArguments, full: d, useHelper })
329
+ })
330
+ break
331
+ case 'object':
332
+ const uhObj = createUseHelper(theData);
333
+ nestedData[level] = [helpers[name]({ data: theData, ...extendArguments, full: d, useHelper: uhObj })]
334
+ break
335
+ case 'function':
336
+ const fnData = theData();
337
+ const uhFn = createUseHelper(fnData);
338
+ nestedData[level] = [helpers[name]({ data: fnData, ...extendArguments, full: d, useHelper: uhFn })]
339
+ break
340
+ case 'primitive':
341
+ const uhPrim = createUseHelper(theData);
342
+ nestedData[level] = helpers[name]({ data: theData, ...extendArguments, full: d, useHelper: uhPrim })
343
+ break
344
+ } // switch dataType
345
+
346
+ break
347
+ case 'render':
348
+ const isRenderFunction = typeof helpers[name] === 'function'; // Render could be a function and template.
349
+ switch (dataType) {
350
+ case 'array':
351
+ if (isRenderFunction) theData.forEach((d, i) => {
352
+ if (d == null) return
353
+ const dType = _defineDataType(d);
354
+ const useHelper = createUseHelper(d);
355
+ const text = helpers[name]({ data: d, ...extendArguments, full: original, useHelper });
356
+
357
+ if (text == null) theData[i] = null
358
+ if (dType === 'object') d['text'] = text
359
+ else theData[i] = text
360
+ })
361
+ else theData.forEach((d, i) => {
362
+ if (d == null) return
363
+ const
364
+ dType = _defineDataType(d)
365
+ , text = render(d, name, helpers, original, deps)
366
+ ;
367
+ if (text == null) theData[i] = null
368
+ else if (dType === 'object') d['text'] = text
369
+ else theData[i] = text
370
+ })
371
+ break
372
+ case 'function':
373
+ const fD = theData();
374
+ const useHelperFn = createUseHelper(fD);
375
+ nestedData[level] = helpers[name]({ data: fD, ...extendArguments, full: d, useHelper: useHelperFn })
376
+ break
377
+ case 'primitive':
378
+ if (isRenderFunction) {
379
+ const uhPrim = createUseHelper(theData);
380
+ nestedData[level] = helpers[name]({ data: theData, ...extendArguments, full: d, useHelper: uhPrim })
381
+ }
382
+ else nestedData[level] = render(theData, name, helpers, original, deps)
383
+ break
384
+ case 'object':
385
+ if (isRenderFunction) {
386
+ const uhO = createUseHelper(theData);
387
+ nestedData[level][iData]['text'] = helpers[name]({ data: theData, ...extendArguments, full: d, useHelper: uhO })
388
+ }
389
+ else {
390
+ theData['text'] = render(theData, name, helpers, original, deps)
391
+ }
392
+ break
393
+ } // switch renderDataType
394
+ break;
395
+ case 'extendedRender':
396
+ // TODO: Test extendedRender
397
+ const isValid = typeof helpers[name] === 'function'; // Render could be
398
+ if (isValid) {
399
+ nestedData[0].forEach((d, i) => {
400
+ const uh = createUseHelper(d);
401
+ nestedData[0][i] = helpers[name]({ data: d, ...extendArguments, full: d, useHelper: uh })
402
+ })
403
+ }
404
+ else {
405
+ // TODO: Error...
406
+ }
407
+ break
408
+ case 'mix':
409
+ if (name === '') { // when is anonymous mixing action
410
+ switch (dataType) {
411
+ case 'object':
412
+ let kTest = Object.keys(theData).find(k => k.includes('/')); // Check if keys are breadcrumbs
413
+ if (kTest) Object.entries(theData).forEach(([k, v]) => nestedData[level][k] = v['text'])
414
+ else nestedData[level] = theData['text']
415
+ for (let i = level - 1; i >= 0; i--) {
416
+ nestedData[i] = walk({ data: nestedData[i], objectCallback: check })
417
+ }
418
+ function check({ value, breadcrumbs }) {
419
+ if (nestedData[level][breadcrumbs]) return nestedData[level][breadcrumbs]
420
+ return value
421
+ } // check
222
422
  break
223
- case 'render':
224
- const isRenderFunction = typeof helpers[name] === 'function'; // Render could be a function and template.
225
- switch ( dataType ) {
226
- case 'array':
227
- if ( isRenderFunction ) theData.forEach ( (d,i) => {
228
- if ( d == null ) return
229
- const dType = _defineDataType ( d );
230
- const text = helpers[name]( {data:d, ...extendArguments, full: original });
231
-
232
- if ( text == null ) theData[i] = null
233
- if ( dType === 'object' ) d['text'] = text
234
- else theData[i] = text
235
- })
236
- else theData.forEach ( (d,i) => {
237
- if ( d == null ) return
238
- const
239
- dType = _defineDataType ( d )
240
- , text = render ( d, name, helpers, original, deps )
241
- ;
242
- if ( text == null ) theData[i] = null
243
- else if ( dType === 'object' ) d['text'] = text
244
- else theData[i] = text
245
- })
246
- break
247
- case 'function':
248
- nestedData[level] = helpers[name]( {data:theData(), ...extendArguments, full: d} )
249
- break
250
- case 'primitive':
251
- if ( isRenderFunction ) nestedData[level] = helpers[name]({ data:theData, ...extendArguments, full: d} )
252
- else nestedData[level] = render ( theData, name, helpers, original, deps )
253
- break
254
- case 'object':
255
- if ( isRenderFunction ) nestedData[level][iData]['text'] = helpers[name]({ data:theData, ...extendArguments, full: d })
256
- else {
257
- theData [ 'text' ] = render ( theData, name, helpers, original, deps )
258
- }
259
- break
260
- } // switch renderDataType
261
- break;
262
- case 'extendedRender':
263
- // TODO: Test extendedRender
264
- const isValid = typeof helpers[name] === 'function'; // Render could be a function and template.
265
- if ( isValid ) {
266
- nestedData[0].forEach ( (d,i) => nestedData[0][i] = helpers[name]({ data:d, ...extendArguments, full: d }) )
423
+ case 'array':
424
+ theData.forEach((x, i) => {
425
+ if (i > 0) {
426
+ let xType = _defineDataType(x);
427
+ if (x == null) return
428
+ if (xType === 'object') theData[0] += `${x.text}`
429
+ else theData[0] += `${x}`
430
+ theData.toSpliced(i, 1)
267
431
  }
268
- else {
269
- // TODO: Error...
432
+ else {
433
+ let xxType = _defineDataType(x);
434
+ theData[0] = ''
435
+ if (xxType === null) return
436
+ else if (xxType === 'object') theData[0] = `${x.text}`
437
+ else theData[0] = `${x}`
270
438
  }
439
+ })
440
+ theData.length = 1
271
441
  break
272
- case 'mix':
273
- if ( name === '' ) { // when is anonymous mixing helper
274
- switch ( dataType ) {
275
- case 'object':
276
- let kTest = Object.keys ( theData ).find ( k => k.includes ( '/' ) ); // Check if keys are breadcrumbs
277
- if ( kTest ) Object.entries( theData ).forEach( ([k,v]) => nestedData[level][k] = v['text'] )
278
- else nestedData[level] = theData['text']
279
- for ( let i=level-1; i >= 0; i-- ) {
280
- nestedData[i] = walk ({data:nestedData[i], objectCallback:check})
281
- }
282
- function check ({ value, breadcrumbs }) {
283
- if ( nestedData[level][breadcrumbs] ) return nestedData[level][breadcrumbs]
284
- return value
285
- } // check
286
- break
287
- case 'array':
288
- theData.forEach ( (x,i) => {
289
- if ( i > 0 ) {
290
- let xType = _defineDataType ( x );
291
- if ( x == null ) return
292
- if ( xType === 'object' ) theData[0] += `${x.text}`
293
- else theData[0] += `${x}`
294
- theData.toSpliced(i,1)
295
- }
296
- else {
297
- let xxType = _defineDataType ( x );
298
- theData[0] = ''
299
- if ( xxType === null ) return
300
- else if ( xxType === 'object' ) theData[0] = `${x.text}`
301
- else theData[0] = `${x}`
302
- }
303
- })
304
- theData.length = 1
305
- break
306
- } // switch dataType
307
- } // if name === ''
308
- else {
309
- let
310
- val = helpers[name]({ data:theData, ...extendArguments, full: d })
311
- , valType = _defineDataType ( val )
312
- ;
313
-
314
- theData.forEach ( ( x, i ) => theData.splice ( i, 1) )
315
- theData.length = 0
316
- switch ( valType ) {
317
- case 'primitive':
318
- theData[0] = val
319
- break
320
- case 'array':
321
- theData.push ( ...val )
322
- break
323
- } // switch valType
324
- }
325
- break
326
- default:
327
- break
328
- }
329
- }) // levelData
330
- } // for step of actSetup
331
-
332
- if ( nestedData instanceof Array &&
333
- nestedData.length === 1 &&
334
- nestedData[0] instanceof Array
335
- ) nestedData = nestedData[0]
336
- if ( nestedData[0] == null ) return
337
-
338
- let
339
- accType = _defineDataType ( nestedData[0] )
340
- , fineData = nestedData[0]
341
- ;
442
+ } // switch dataType
443
+ } // if name === ''
444
+ else {
445
+ const dType = _defineDataType(d);
446
+ const useHelper = createUseHelper(d);
447
+ let val = helpers[name]({ data: theData, ...extendArguments, full: d, useHelper });
448
+ let valType = _defineDataType(val)
449
+ ;
342
450
 
343
- switch ( accType ) {
451
+ theData.forEach((x, i) => theData.splice(i, 1))
452
+ theData.length = 0
453
+ switch (valType) {
344
454
  case 'primitive':
345
- if ( fineData == null ) return
346
- cuts[index] = fineData
347
- break
348
- case 'object':
349
- if (fineData['text'] == null ) return
350
- cuts[index] = fineData['text']
455
+ theData[0] = val
351
456
  break
352
457
  case 'array':
353
- const aType = _defineDataType ( fineData[0] )
354
- if ( aType === 'object' ) cuts[index] = fineData.map ( x => x.text ).join ( '')
355
- else cuts[index] = fineData.join ( '' )
458
+ theData.push(...val)
356
459
  break
357
- } // switch accType
358
- } // else other
359
- }) // forEach placeholders
360
-
361
- if ( onlySnippets ) endData.push ( placeholders.map ( x => cuts[x.index] ).join ( '<~>' ) )
362
- else endData.push ( cuts.join ( '' ))
363
- }) // forEach d
364
-
365
- if ( topLevelType === 'array' ) return endData
366
- // Execute postprocess functions
367
- if (args) return args.reduce ( (acc, fn) => {
368
- if ( typeof fn !== 'function' ) return acc
369
- return fn ( acc, deps )
370
- }, endData.join ( '' ) )
371
- else return endData.join ( '' )
372
- } // success func.
373
- return extra ? [ true, success ] : success
374
- } // if no error
460
+ } // switch valType
461
+ }
462
+ break
463
+ default:
464
+ break
465
+ }
466
+ }) // levelData
467
+ } // for step of actSetup
468
+
469
+ if (nestedData instanceof Array &&
470
+ nestedData.length === 1 &&
471
+ nestedData[0] instanceof Array
472
+ ) nestedData = nestedData[0]
473
+ if (nestedData[0] == null) return
474
+
475
+ let
476
+ accType = _defineDataType(nestedData[0])
477
+ , fineData = nestedData[0]
478
+ ;
479
+
480
+ switch (accType) {
481
+ case 'primitive':
482
+ if (fineData == null) return
483
+ cuts[index] = fineData
484
+ break
485
+ case 'object':
486
+ if (fineData['text'] == null) return
487
+ cuts[index] = fineData['text']
488
+ break
489
+ case 'array':
490
+ const aType = _defineDataType(fineData[0])
491
+ if (aType === 'object') cuts[index] = fineData.map(x => x.text).join('')
492
+ else cuts[index] = fineData.join('')
493
+ break
494
+ } // switch accType
495
+ } // else other
496
+ }) // forEach placeholders
497
+
498
+ if (onlySnippets) endData.push(placeholders.map(x => cuts[x.index]).join('<~>'))
499
+ else endData.push(cuts.join(''))
500
+ }) // forEach d
501
+
502
+ if (topLevelType === 'array') return endData
503
+ // Execute postprocess functions
504
+ if (args) return args.reduce((acc, fn) => {
505
+ if (typeof fn !== 'function') return acc
506
+ return fn(acc, deps)
507
+ }, endData.join(''))
508
+ else return endData.join('')
509
+ } // success func.
510
+ return extra ? [true, success] : success
511
+ } // if no error
375
512
  } // build func.
376
513
 
377
514