@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.
- package/.mocharc.json +1 -0
- package/.opencode/command/speckit.analyze.md +184 -0
- package/.opencode/command/speckit.checklist.md +294 -0
- package/.opencode/command/speckit.clarify.md +181 -0
- package/.opencode/command/speckit.constitution.md +82 -0
- package/.opencode/command/speckit.implement.md +135 -0
- package/.opencode/command/speckit.plan.md +89 -0
- package/.opencode/command/speckit.specify.md +257 -0
- package/.opencode/command/speckit.tasks.md +137 -0
- package/.opencode/command/speckit.taskstoissues.md +28 -0
- package/.specify/memory/constitution.md +43 -0
- package/.specify/scripts/bash/check-prerequisites.sh +166 -0
- package/.specify/scripts/bash/common.sh +156 -0
- package/.specify/scripts/bash/create-new-feature.sh +305 -0
- package/.specify/scripts/bash/setup-plan.sh +61 -0
- package/.specify/scripts/bash/update-agent-context.sh +790 -0
- package/.specify/templates/agent-file-template.md +28 -0
- package/.specify/templates/checklist-template.md +40 -0
- package/.specify/templates/plan-template.md +104 -0
- package/.specify/templates/spec-template.md +115 -0
- package/.specify/templates/tasks-template.md +251 -0
- package/Changelog.md +17 -0
- package/README.md +184 -13
- package/dist/methods/build.d.ts +28 -2
- package/dist/methods/render.d.ts +9 -20
- package/dist/morph.cjs +1 -1
- package/dist/morph.esm.mjs +1 -1
- package/dist/morph.umd.js +1 -1
- package/morph.png +0 -0
- package/package.json +3 -2
- package/simple.js +17 -0
- package/specs/001-extend-templates/checklists/requirements.md +35 -0
- package/specs/001-extend-templates/contracts/set-method.json +39 -0
- package/specs/001-extend-templates/data-model.md +76 -0
- package/specs/001-extend-templates/plan.md +90 -0
- package/specs/001-extend-templates/quickstart.md +56 -0
- package/specs/001-extend-templates/research.md +18 -0
- package/specs/001-extend-templates/spec.md +139 -0
- package/specs/001-extend-templates/tasks.md +255 -0
- package/src/methods/_readTemplate.js +22 -46
- package/src/methods/_renderHolder.js +13 -7
- package/src/methods/_setupActions.js +1 -8
- package/src/methods/build.js +458 -321
- package/src/methods/render.js +23 -46
package/src/methods/build.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
88
|
-
|
|
253
|
+
else {
|
|
254
|
+
data.split('/').forEach(d => {
|
|
255
|
+
if (info.hasOwnProperty(d)) info = info[d]
|
|
256
|
+
else info = []
|
|
257
|
+
})
|
|
89
258
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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 '
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
269
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
451
|
+
theData.forEach((x, i) => theData.splice(i, 1))
|
|
452
|
+
theData.length = 0
|
|
453
|
+
switch (valType) {
|
|
344
454
|
case 'primitive':
|
|
345
|
-
|
|
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
|
-
|
|
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
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
|