@peter.naydenov/morph 3.1.4 → 3.2.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 (61) 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 +154 -3
  24. package/dist/main.d.ts +100 -0
  25. package/dist/methods/_actionSupply.d.ts +18 -0
  26. package/dist/methods/_chopTemplates.d.ts +22 -0
  27. package/dist/methods/_defineData.d.ts +25 -0
  28. package/dist/methods/_defineType.d.ts +15 -0
  29. package/dist/methods/_readTemplate.d.ts +24 -0
  30. package/dist/methods/_renderHolder.d.ts +17 -0
  31. package/dist/methods/_setupActions.d.ts +19 -0
  32. package/dist/methods/build.d.ts +35 -0
  33. package/dist/methods/render.d.ts +25 -0
  34. package/dist/methods/settings.d.ts +7 -0
  35. package/dist/morph.cjs +1 -1
  36. package/dist/morph.esm.mjs +1 -1
  37. package/dist/morph.umd.js +1 -1
  38. package/morph.png +0 -0
  39. package/package.json +10 -7
  40. package/simple.js +17 -0
  41. package/specs/001-extend-templates/checklists/requirements.md +35 -0
  42. package/specs/001-extend-templates/contracts/set-method.json +39 -0
  43. package/specs/001-extend-templates/data-model.md +76 -0
  44. package/specs/001-extend-templates/plan.md +90 -0
  45. package/specs/001-extend-templates/quickstart.md +56 -0
  46. package/specs/001-extend-templates/research.md +18 -0
  47. package/specs/001-extend-templates/spec.md +139 -0
  48. package/specs/001-extend-templates/tasks.md +255 -0
  49. package/src/main.js +70 -32
  50. package/src/methods/_actionSupply.js +16 -0
  51. package/src/methods/_chopTemplates.js +22 -1
  52. package/src/methods/_defineData.js +33 -1
  53. package/src/methods/_defineType.js +13 -0
  54. package/src/methods/_readTemplate.js +46 -46
  55. package/src/methods/_renderHolder.js +15 -0
  56. package/src/methods/_setupActions.js +18 -8
  57. package/src/methods/build.js +98 -43
  58. package/src/methods/render.js +32 -11
  59. package/src/methods/settings.js +11 -0
  60. package/tsconfig.json +27 -0
  61. package/types/index.d.ts +0 -60
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Parses and organizes template actions into a structured setup by processing level.
3
+ *
4
+ * Converts action strings into structured objects with types and levels. Validates that
5
+ * there are enough level markers (#) for the data depth.
6
+ *
7
+ * @param {string[]} actions - Array of action strings to process
8
+ * @param {number} [dataDeepLevel=10] - Maximum depth level for nested data processing
9
+ *
10
+ * @returns {Object} Object with numeric keys containing arrays of action objects by level
11
+ *
12
+ * @example
13
+ * const setup = _setupActions(['render', '#', 'save:var'], 2);
14
+ * // Returns: { 0: [{type: 'render', name: 'render', level: 0}],
15
+ * // 1: [{type: 'save', name: 'var', level: 1}],
16
+ * // 2: [] }
17
+ */
1
18
  function _setupActions ( actions, dataDeepLevel=10 ) {
2
19
  let
3
20
  actSetup = {}
@@ -23,14 +40,7 @@ function _setupActions ( actions, dataDeepLevel=10 ) {
23
40
  if ( actLevel > dataDeepLevel ) return false
24
41
  return true
25
42
  }
26
- if ( act.startsWith ( '?' ) ) { // it's a condition render action
27
- actSetup[actLevel].push ({
28
- type: 'route'
29
- , name: act.replace ( '?', '' )
30
- , level: actLevel
31
- })
32
- return true
33
- }
43
+
34
44
  if ( act.startsWith ('^') && act !== '^^' ) {
35
45
  actSetup[actLevel].push ({
36
46
  type: 'save'
@@ -34,8 +34,6 @@ import walk from '@peter.naydenov/walk'
34
34
  */
35
35
  function build ( tpl, extra=false, buildDependencies={} ) {
36
36
  let { hasError, placeholders, chop, helpers, handshake, snippets } = _readTemplate ( tpl );
37
-
38
-
39
37
 
40
38
  if ( hasError ) {
41
39
  function fail () { return hasError }
@@ -44,36 +42,49 @@ function build ( tpl, extra=false, buildDependencies={} ) {
44
42
  else { // If NO Error:
45
43
  const originalPlaceholders = structuredClone ( placeholders );
46
44
  // *** 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.`
45
+ /**
46
+ * Processes template rendering commands with provided data, dependencies, and optional post-processing functions.
47
+ *
48
+ * @function success
49
+ * @typedef { 'render' | 'debug' | 'snippets' | 'curry' | 'set' } Command
50
+ * @param {Command} [command='render'] - The command to execute. Supported commands: 'render', 'debug', 'snippets', 'curry', 'set', or 'snippets:<names>'.
51
+ * @param {Object|string} [d={}] - The data object to render, or a string instruction ('raw', 'demo', 'handshake', 'placeholders', 'count').
52
+ * @param {Object} [dependencies={}] - Additional dependencies to be merged with internal dependencies.
53
+ * @param {...any} args - Optional post-processing functions to apply to the rendered output.
54
+ * @returns {string|Array[]|function} The rendered template, snippets, data, or new function depending on the command and input.
55
+ *
56
+ * @throws {Error} If an unsupported command or instruction is provided.
57
+ *
58
+ * @example
59
+ * // Render a template with data
60
+ * success('render', { name: 'Alice' }, { helperFn });
61
+ *
62
+ * @example
63
+ * // Get raw template
64
+ * success('debug', 'raw');
65
+ *
66
+ * @example
67
+ * // Get count of remaining placeholders
68
+ * success('debug', 'count');
69
+ *
70
+ * @example
71
+ * // Render only specific snippets
72
+ * success('snippets:header,footer', { ... });
73
+ *
74
+ * @example
75
+ * // Curry partial data and get new render function
76
+ * const curried = success('curry', { partial: 'data' });
77
+ * curried('render', { more: 'data' });
78
+ *
79
+ * @example
80
+ * // Set placeholders and get modified template
81
+ * const modified = success('set', { placeholders: { key: 'value' } });
82
+ */
83
+ function success ( command='render', d={}, dependencies={}, ...args ) {
84
+ const cuts = structuredClone ( chop )
85
+ let onlySnippets = false;
86
+ if ( typeof command !== 'string' ) return `Error: Wrong command "${command}". Available commands: render, debug, snippets, set, curry.`
87
+ if ( ![ 'render', 'debug', 'snippets', 'set', 'curry'].includes ( command ) && !command.startsWith('snippets') ) return `Error: Wrong command "${command}". Available commands: render, debug, snippets, set, curry.`
77
88
 
78
89
  if ( command.startsWith ( 'snippets') && command.includes ( ':' ) ) {
79
90
  onlySnippets = true
@@ -87,9 +98,49 @@ function build ( tpl, extra=false, buildDependencies={} ) {
87
98
  else if ( command === 'snippets' ) {
88
99
  onlySnippets = true
89
100
  }
90
- else placeholders = structuredClone ( originalPlaceholders ) // Reset placeholders if not snippets
101
+ else if ( command === 'set' ) {
102
+ if ( typeof d !== 'object' || !d ) return `Error: 'set' command requires an object with placeholders, helpers, handshake.`
103
+ // Merge helpers
104
+ const newHelpers = { ...helpers, ...(d.helpers || {}) }
105
+
106
+ // Merge handshake
107
+ const newHandshake = handshake ? { ...handshake, ...(d.handshake || {}) } : d.handshake || {}
108
+ // Modify chop for placeholders
109
+ const newChop = [...chop]
110
+ if ( d.placeholders ) {
111
+ Object.entries ( d.placeholders ).forEach ( ([k,v]) => {
112
+ if ( !isNaN(k) ) { // If k is a number
113
+ let index = placeholders[k].index;
114
+ newChop[index] = v
115
+ } // if k is a number
116
+ else { // If k is a string
117
+ let plx = placeholders.find ( p => p.name === k )
118
+ newChop[ plx.index ] = v
119
+ }
120
+ }) // placeholders
121
+ } // if d.placeholders
122
+ const newTemplateStr = newChop.join ( '' );
123
+ const newTpl = {
124
+ template: newTemplateStr,
125
+ helpers: newHelpers,
126
+ handshake: newHandshake
127
+ }
128
+ const result = build ( newTpl, false, buildDependencies )
129
+ return typeof result === 'function' ? result : () => result
130
+ } else if ( command === 'curry' ) {
131
+ // Render the template with current data
132
+ const rendered = success('render', d, dependencies, ...args)
133
+ // Create new template with rendered as template, keep helpers and handshake
134
+ const newTpl = {
135
+ template: rendered,
136
+ helpers,
137
+ handshake
138
+ }
139
+ const newFn = build(newTpl, false, buildDependencies)
140
+ return newFn
141
+ } else placeholders = structuredClone ( originalPlaceholders ) // Reset placeholders if not snippets
91
142
 
92
- if ( typeof d === 'string' ) {
143
+ if ( typeof d === 'string' ) {
93
144
  switch ( d ) {
94
145
  case 'raw':
95
146
  return cuts.join ( '' ) // Original template with placeholders
@@ -100,13 +151,17 @@ function build ( tpl, extra=false, buildDependencies={} ) {
100
151
  case 'handshake':
101
152
  if ( !handshake ) return `Error: No handshake data.`
102
153
  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.`
154
+ case 'helpers' :
155
+ return Object.keys ( helpers ).join ( ', ' )
156
+ case 'placeholders':
157
+ return placeholders.map ( h => cuts[h.index] ).join ( ', ')
158
+ case 'count':
159
+ return placeholders.length
160
+ default:
161
+ return `Error: Wrong instruction "${d}". Available instructions: raw, demo, handshake, helpers, placeholders, count.`
107
162
  }
108
163
  } // if d is string
109
-
164
+
110
165
  const endData = [];
111
166
  const memory = {};
112
167
 
@@ -177,10 +232,10 @@ function build ( tpl, extra=false, buildDependencies={} ) {
177
232
 
178
233
  levelData.forEach ( (theData, iData ) => {
179
234
 
180
- let dataType = _defineDataType ( theData )
181
-
235
+ let dataType = _defineDataType ( theData )
236
+
182
237
  switch ( type ) { // Action type 'route','data', 'render', or mix -> different operations
183
- case 'route':
238
+ case 'route': // DEPRICATED in version 3.2.0. This functionality can be replaced with normal render functions
184
239
  switch ( dataType ) {
185
240
  case 'array':
186
241
  theData.forEach ( (d,i) => {
@@ -270,7 +325,7 @@ function build ( tpl, extra=false, buildDependencies={} ) {
270
325
  }
271
326
  break
272
327
  case 'mix':
273
- if ( name === '' ) { // when is anonymous mixing helper
328
+ if ( name === '' ) { // when is anonymous mixing action
274
329
  switch ( dataType ) {
275
330
  case 'object':
276
331
  let kTest = Object.keys ( theData ).find ( k => k.includes ( '/' ) ); // Check if keys are breadcrumbs
@@ -3,15 +3,27 @@ import _renderHolder from "./_renderHolder.js"
3
3
 
4
4
 
5
5
  /**
6
+ * Executes rendering and returns the rendered result.
6
7
  *
7
- * Execute rendering and return the results
8
+ * Handles both function-based and template-based rendering. Normalizes data objects
9
+ * by converting nested objects to their 'text' properties and arrays to their first elements.
8
10
  *
9
- * @param {object|string} theData - Data to be rendered. If it's a string, it's the value of 'text' property.
10
- * @param {string} name - Name of the render to be executed.
11
- * @param {object} helpers - Object with helper functions or templates.
12
- * @param {...any} args - Extra arguments to be passed to the render function.
11
+ * @param {object|string} theData - Data to be rendered. If string, becomes the 'text' property value.
12
+ * @param {string} name - Name of the render helper/template to execute
13
+ * @param {object} helpers - Object containing helper functions and templates
14
+ * @param {object} original - Original data context for full data access
15
+ * @param {object} dependencies - External dependencies available to helpers
16
+ * @param {...any} args - Additional arguments passed to the render function
13
17
  *
14
- * @returns {string} - Rendered string.
18
+ * @returns {string} Rendered string result
19
+ *
20
+ * @example
21
+ * // Render with function helper
22
+ * const result = render(data, 'myHelper', helpers, originalData, deps);
23
+ *
24
+ * @example
25
+ * // Render with template
26
+ * const result = render(data, 'myTemplate', helpers, originalData, deps);
15
27
  */
16
28
  function render ( theData, name, helpers, original, dependencies, ...args ) {
17
29
  // *** Executes rendering and return the results
@@ -21,15 +33,24 @@ function render ( theData, name, helpers, original, dependencies, ...args ) {
21
33
  if ( value instanceof Array ) theData[key] = value[0]
22
34
  })
23
35
  }
36
+
37
+ /**
38
+ * Normalizes render data by wrapping strings in text objects.
39
+ *
40
+ * @param {any} d - Data to normalize
41
+ * @returns {object} Returns { text: d } if d is string, otherwise returns d unchanged
42
+ */
24
43
  function setRenderData ( d={} ) {
25
44
  if ( typeof d === 'string' ) return { text: d }
26
45
  else return d
27
46
  } // setRenderData func.
28
- const isRenderFunction = typeof helpers[name] === 'function'; // Render could be a function or template.
29
- theData = setRenderData ( theData )
30
-
31
- if ( isRenderFunction ) return helpers[name]( { theData, dependencies, full:original}, ...args )
32
- else return _renderHolder ( helpers[name], theData )
47
+
48
+ if (!helpers[name]) return `{{ Error: Helper '${name}' is not available}}`
49
+ const isRenderFunction = typeof helpers[name] === 'function'; // Render could be a function or template.
50
+ theData = setRenderData ( theData )
51
+
52
+ if ( isRenderFunction ) return helpers[name]( { theData, dependencies, full:original}, ...args )
53
+ else return _renderHolder ( helpers[name], theData )
33
54
  } // render func.
34
55
 
35
56
 
@@ -1,3 +1,14 @@
1
+ /**
2
+ * Template engine configuration settings.
3
+ *
4
+ * Defines the delimiters and sizes used for template placeholder parsing.
5
+ *
6
+ * @type {Object}
7
+ * @property {string} TG_PRX - Opening tag prefix for placeholders ('{{')
8
+ * @property {string} TG_SFX - Closing tag suffix for placeholders ('}}')
9
+ * @property {number} TG_SIZE_P - Length of opening tag prefix (2)
10
+ * @property {number} TG_SIZE_S - Length of closing tag suffix (2)
11
+ */
1
12
  export default {
2
13
  TG_PRX: '{{'
3
14
  , TG_SFX: '}}'
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "declaration": true,
5
+ "emitDeclarationOnly": true,
6
+ "outDir": "./dist",
7
+ "module": "ESNext",
8
+ "moduleResolution": "node",
9
+ "target": "ES2020",
10
+ "lib": ["ES2020"],
11
+ "strict": false,
12
+ "skipLibCheck": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "esModuleInterop": true,
15
+ "allowSyntheticDefaultImports": true,
16
+ "resolveJsonModule": true
17
+ },
18
+ "include": [
19
+ "src/**/*"
20
+ ],
21
+ "exclude": [
22
+ "node_modules",
23
+ "dist",
24
+ "test",
25
+ "types"
26
+ ]
27
+ }
package/types/index.d.ts DELETED
@@ -1,60 +0,0 @@
1
- declare module '@peter.naydenov/morph' {
2
- /**
3
- * Template description object
4
- */
5
- interface TemplateDescription {
6
- /**
7
- * Template string with placeholders
8
- */
9
- template: string;
10
- /**
11
- * Optional helper functions or templates
12
- */
13
- helpers?: Record<string, any>;
14
- /**
15
- * Optional example data for rendering
16
- */
17
- handshake?: Record<string, any>;
18
- }
19
-
20
- /**
21
- * Build a component from template description
22
- */
23
- function build(tpl: TemplateDescription, extra?: boolean, buildDependencies?: Record<string, any>): Function;
24
-
25
- /**
26
- * Get a component from component storage
27
- */
28
- function get(location: [string, string?]): Function;
29
-
30
- /**
31
- * Add a component to component storage
32
- */
33
- function add(location: [string, string?], tplfn: Function | TemplateDescription, ...args: any[]): void;
34
-
35
- /**
36
- * List the names of all components in the component storage
37
- */
38
- function list(storageNames?: string[]): string[];
39
-
40
- /**
41
- * Clear up all the components in the storage
42
- */
43
- function clear(): void;
44
-
45
- /**
46
- * Remove a template from component storage
47
- */
48
- function remove(location: [string, string?]): void | string;
49
-
50
- const morphAPI: {
51
- build: typeof build;
52
- get: typeof get;
53
- add: typeof add;
54
- list: typeof list;
55
- clear: typeof clear;
56
- remove: typeof remove;
57
- };
58
-
59
- export default morphAPI;
60
- }