@peter.naydenov/morph 3.3.0 → 3.3.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.
@@ -1,15 +1,14 @@
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'
1
+ import _chopTemplate from "./_chopTemplates.js"
2
+ import _readTemplate from "./_readTemplate.js"
3
+ import _defineDataType from "./_defineType.js"
4
+ import walk from '@peter.naydenov/walk'
5
+ import processPlaceholders from './processPlaceholders.js'
6
+ import {
7
+ handleDebug
8
+ , handleSet
9
+ , handleSnippets } from './processCommands.js'
9
10
 
10
11
 
11
- import walk from '@peter.naydenov/walk'
12
-
13
12
  /**
14
13
  * @callback UseHelperFn
15
14
  * @param {string} name - Name of the helper to call.
@@ -50,465 +49,103 @@ import walk from '@peter.naydenov/walk'
50
49
  * @param {object} [buildDependencies] - Optional. External dependencies injected;
51
50
  * @returns {function|tupleResult} - rendering function
52
51
  */
53
- function build ( tpl, extra=false, buildDependencies={} ) {
54
- let { hasError, placeholders, chop, helpers, handshake, snippets } = _readTemplate ( tpl );
55
-
52
+ function build ( tpl, extra = false, buildDependencies = {}) {
53
+ let { hasError, placeholders, chop, helpers, handshake, snippets } = _readTemplate(tpl);
54
+
56
55
  if ( hasError ) {
57
- function fail () { return hasError }
58
- return extra ? [ false, fail ] : fail
59
- }
60
- else { // If NO Error:
56
+ function fail() { return hasError }
57
+ return extra ? [false, fail] : fail
58
+ }
59
+ else {
61
60
  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) {
61
+
62
+ function success ( command = 'render', d = {}, dependencies = {}, ...args ) {
102
63
  const cuts = structuredClone(chop)
103
64
  let onlySnippets = false;
65
+
66
+ // Command Validation
104
67
  if (typeof command !== 'string') return `Error: Wrong command "${command}". Available commands: render, debug, snippets, set, curry.`
105
68
  if (!['render', 'debug', 'snippets', 'set', 'curry'].includes(command) && !command.startsWith('snippets')) return `Error: Wrong command "${command}". Available commands: render, debug, snippets, set, curry.`
106
69
 
107
- if (command.startsWith('snippets') && command.includes(':')) {
70
+
71
+ // Handle Commands
72
+ if (command.startsWith('snippets')) {
108
73
  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
- }
74
+ const subset = handleSnippets(command, snippets)
75
+ if (subset) placeholders = subset
76
+ }
116
77
  else if (command === 'snippets') {
117
78
  onlySnippets = true
118
- }
79
+ }
119
80
  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
81
+ return handleSet(d, { helpers, handshake, placeholders, chop, build, buildDependencies })
82
+ }
83
+ else if (command === 'curry') {
84
+ // Curry logic
85
+ const rendered = success ( 'render', d, dependencies, ...args );
152
86
  const newTpl = {
153
87
  template: rendered,
154
88
  helpers,
155
89
  handshake
156
- }
157
- const newFn = build(newTpl, false, buildDependencies)
158
- return newFn
159
- } else placeholders = structuredClone(originalPlaceholders) // Reset placeholders if not snippets
160
-
90
+ };
91
+ return build ( newTpl, false, buildDependencies )
92
+ }
93
+ else {
94
+ placeholders = structuredClone ( originalPlaceholders )
95
+ }
96
+
97
+
98
+ // Handle 'DEBUG' (if d is string and command render/debug?)
99
+ // Original logic: if (typeof d === 'string').
100
+ // Wait, success('debug', 'raw') -> command='debug', d='raw'.
101
+ // success('render', 'demo') -> command='render', d='demo'.
102
+ // So any string d is treated as instruction ??
161
103
  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 = [];
104
+ const res = handleDebug(d, { handshake, helpers, placeholders, cuts })
105
+ // If handleDebug returns string/number/object, return it.
106
+ // Unless d='demo' which returns handshake object, then we proceed to render?
107
+ if (d === 'demo' && typeof res === 'object') d = res
108
+ else return res
109
+ }
110
+ // Proceed to Rendering
184
111
  const memory = {};
185
-
186
112
  let topLevelType = _defineDataType(d);
187
113
  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
114
 
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
- ;
115
+ d = walk({ data: d })
116
+ let original = walk({ data: d })
212
117
 
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
118
  if (topLevelType === 'null') return cuts.join('')
229
119
  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]
252
- }
253
- else {
254
- data.split('/').forEach(d => {
255
- if (info.hasOwnProperty(d)) info = info[d]
256
- else info = []
257
- })
258
- }
259
- } // If data contains '/'
260
- else if (data === '@all' || data === null || data === '@root') info = dElement
261
- else if (data) info = info[data]
262
-
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
422
- break
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)
431
- }
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}`
438
- }
439
- })
440
- theData.length = 1
441
- break
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
- ;
450
-
451
- theData.forEach((x, i) => theData.splice(i, 1))
452
- theData.length = 0
453
- switch (valType) {
454
- case 'primitive':
455
- theData[0] = val
456
- break
457
- case 'array':
458
- theData.push(...val)
459
- break
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('')
120
+ if (topLevelType === 'null') return cuts.join('') // Redundant check?
121
+
122
+
123
+ const endData = processPlaceholders({
124
+ d
125
+ , chop
126
+ , placeholders
127
+ , original
128
+ , helpers
129
+ , dependencies: deps
130
+ , memory
131
+ , args
132
+ , onlySnippets
133
+ });
134
+
135
+
136
+ if ( topLevelType === 'array' ) return endData
137
+
138
+ // Post-process
139
+ if (args) {
140
+ return args.reduce ( ( acc, fn ) => {
141
+ if (typeof fn !== 'function') return acc
142
+ return fn ( acc, deps )
143
+ }, endData.join(''))
144
+ }
145
+ else return endData.join ( '' )
509
146
  } // success func.
510
147
  return extra ? [true, success] : success
511
- } // if no error
148
+ }
512
149
  } // build func.
513
150
 
514
151