@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.
- 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 +154 -3
- package/dist/main.d.ts +100 -0
- package/dist/methods/_actionSupply.d.ts +18 -0
- package/dist/methods/_chopTemplates.d.ts +22 -0
- package/dist/methods/_defineData.d.ts +25 -0
- package/dist/methods/_defineType.d.ts +15 -0
- package/dist/methods/_readTemplate.d.ts +24 -0
- package/dist/methods/_renderHolder.d.ts +17 -0
- package/dist/methods/_setupActions.d.ts +19 -0
- package/dist/methods/build.d.ts +35 -0
- package/dist/methods/render.d.ts +25 -0
- package/dist/methods/settings.d.ts +7 -0
- 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 +10 -7
- 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/main.js +70 -32
- package/src/methods/_actionSupply.js +16 -0
- package/src/methods/_chopTemplates.js +22 -1
- package/src/methods/_defineData.js +33 -1
- package/src/methods/_defineType.js +13 -0
- package/src/methods/_readTemplate.js +46 -46
- package/src/methods/_renderHolder.js +15 -0
- package/src/methods/_setupActions.js +18 -8
- package/src/methods/build.js +98 -43
- package/src/methods/render.js +32 -11
- package/src/methods/settings.js +11 -0
- package/tsconfig.json +27 -0
- 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
|
-
|
|
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'
|
package/src/methods/build.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 '
|
|
104
|
-
return
|
|
105
|
-
|
|
106
|
-
|
|
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
|
|
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
|
package/src/methods/render.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
10
|
-
* @param {string} name - Name of the render to
|
|
11
|
-
* @param {object} helpers - Object
|
|
12
|
-
* @param {
|
|
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}
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
package/src/methods/settings.js
CHANGED
|
@@ -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
|
-
}
|