boxwood 2.16.0 → 2.18.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.
@@ -1,284 +0,0 @@
1
- const { parseCustomTag } = require("./parseCustomTag")
2
- const { replaceVariables } = require("./replaceVariables")
3
- const { format } = require("./format")
4
-
5
- // Markdown patterns
6
- const ORDERED_LIST_REGEXP = /^\d+\.\s/
7
- const UNORDERED_MARKERS = ["- ", "— ", "– ", "• "]
8
- const HORIZONTAL_RULE_REGEXP = /^(?:\*\s*\*\s*\*+|-\s*-\s*-+|_\s*_\s*_+)\s*$/
9
- const HEADINGS = [
10
- { prefix: "###### ", type: "h6" },
11
- { prefix: "##### ", type: "h5" },
12
- { prefix: "#### ", type: "h4" },
13
- { prefix: "### ", type: "h3" },
14
- { prefix: "## ", type: "h2" },
15
- { prefix: "# ", type: "h1" },
16
- ]
17
-
18
- /**
19
- * Checks if a line is a code block delimiter (```)
20
- */
21
- function isCodeBlockDelimiter(trimmedLine) {
22
- return trimmedLine.startsWith("```")
23
- }
24
-
25
- /**
26
- * Parses a code block starting from the current index
27
- * Returns the code block item and the next index to process
28
- */
29
- function parseCodeBlock(allLines, startIndex) {
30
- const line = allLines[startIndex]
31
- const language = line.trim().substring(3).trim()
32
- const codeLines = []
33
- let i = startIndex + 1
34
-
35
- // Collect lines until closing ```
36
- while (i < allLines.length) {
37
- const codeLine = allLines[i]
38
- if (isCodeBlockDelimiter(codeLine.trim())) {
39
- i++ // Skip closing delimiter
40
- break
41
- }
42
- codeLines.push(codeLine)
43
- i++
44
- }
45
-
46
- return {
47
- item: {
48
- type: "pre",
49
- content: codeLines.join("\n"),
50
- language: language || undefined,
51
- indent: 0,
52
- },
53
- nextIndex: i,
54
- }
55
- }
56
-
57
- /**
58
- * Collects content lines for a multi-line custom component
59
- */
60
- function collectComponentContent(allLines, startIndex, tagName, allComponents) {
61
- const contentLines = []
62
- let i = startIndex
63
- let depth = 1
64
-
65
- while (i < allLines.length && depth > 0) {
66
- const contentLine = allLines[i]
67
- const contentTag = parseCustomTag(contentLine, allComponents)
68
-
69
- if (contentTag && contentTag.tagName === tagName) {
70
- if (contentTag.type === "custom-component-open") {
71
- depth++
72
- contentLines.push(contentLine)
73
- } else if (contentTag.type === "custom-component-close") {
74
- depth--
75
- if (depth === 0) {
76
- i++
77
- break
78
- }
79
- contentLines.push(contentLine)
80
- }
81
- } else {
82
- contentLines.push(contentLine)
83
- }
84
- i++
85
- }
86
-
87
- return {
88
- content: contentLines.join("\n"),
89
- nextIndex: i,
90
- }
91
- }
92
-
93
- /**
94
- * Processes a custom component tag and returns the appropriate item
95
- */
96
- function processCustomComponent(
97
- line,
98
- allLines,
99
- currentIndex,
100
- customTag,
101
- data,
102
- allComponents,
103
- ) {
104
- const indent = line.length - line.trimStart().length
105
-
106
- // Single-line component with content
107
- if (customTag.type === "custom-component-single-line") {
108
- const processedContent = replaceVariables(customTag.content, data)
109
- const formattedContent = format(processedContent, allComponents)
110
- return {
111
- item: {
112
- type: "custom-component",
113
- tagName: customTag.tagName,
114
- component: customTag.component,
115
- attributes: customTag.attributes,
116
- content: formattedContent,
117
- selfClosing: false,
118
- singleLine: true,
119
- indent,
120
- },
121
- nextIndex: currentIndex + 1,
122
- }
123
- }
124
-
125
- // Self-closing component
126
- if (customTag.type === "custom-component" && customTag.selfClosing) {
127
- return {
128
- item: {
129
- type: "custom-component",
130
- tagName: customTag.tagName,
131
- component: customTag.component,
132
- attributes: customTag.attributes,
133
- selfClosing: true,
134
- indent,
135
- },
136
- nextIndex: currentIndex + 1,
137
- }
138
- }
139
-
140
- // Multi-line component
141
- if (customTag.type === "custom-component-open") {
142
- const { content, nextIndex } = collectComponentContent(
143
- allLines,
144
- currentIndex + 1,
145
- customTag.tagName,
146
- allComponents,
147
- )
148
-
149
- return {
150
- item: {
151
- type: "custom-component",
152
- tagName: customTag.tagName,
153
- component: customTag.component,
154
- attributes: customTag.attributes,
155
- content,
156
- selfClosing: false,
157
- indent,
158
- },
159
- nextIndex,
160
- }
161
- }
162
-
163
- return null
164
- }
165
-
166
- /**
167
- * Determines the type and content of a markdown line
168
- */
169
- function parseMarkdownLine(line) {
170
- const trimmed = line.trim()
171
- const leadingSpaces = line.length - line.trimStart().length
172
-
173
- // Horizontal rule
174
- if (HORIZONTAL_RULE_REGEXP.test(trimmed)) {
175
- return { type: "hr", indent: leadingSpaces }
176
- }
177
-
178
- // Unordered list
179
- const unorderedMarker = UNORDERED_MARKERS.find((marker) =>
180
- trimmed.startsWith(marker),
181
- )
182
- if (unorderedMarker) {
183
- const content = trimmed.substring(2)
184
- if (content) {
185
- return { type: "li", list: "ul", content, indent: leadingSpaces }
186
- }
187
- }
188
-
189
- // Ordered list
190
- if (ORDERED_LIST_REGEXP.test(trimmed)) {
191
- const content = trimmed.replace(ORDERED_LIST_REGEXP, "")
192
- if (content) {
193
- return { type: "li", list: "ol", content, indent: leadingSpaces }
194
- }
195
- }
196
-
197
- // Headings
198
- for (const { prefix, type } of HEADINGS) {
199
- if (trimmed.startsWith(prefix)) {
200
- return {
201
- type,
202
- content: trimmed.substring(prefix.length),
203
- indent: leadingSpaces,
204
- }
205
- }
206
- }
207
-
208
- // Blockquote
209
- if (trimmed.startsWith("> ")) {
210
- return {
211
- type: "blockquote",
212
- content: trimmed.substring(2),
213
- indent: leadingSpaces,
214
- }
215
- }
216
-
217
- // Default to paragraph
218
- return { type: "p", content: trimmed, indent: leadingSpaces }
219
- }
220
-
221
- /**
222
- * Parses all lines of markdown into structured items
223
- */
224
- function parseMarkdownLines(children, allComponents, data) {
225
- const allLines = children.trim().split("\n")
226
- const items = []
227
- let i = 0
228
-
229
- while (i < allLines.length) {
230
- const line = allLines[i]
231
- const trimmed = line.trim()
232
-
233
- // Skip empty lines
234
- if (trimmed.length === 0) {
235
- i++
236
- continue
237
- }
238
-
239
- // Check for custom component tags
240
- const customTag = parseCustomTag(line, allComponents)
241
- if (customTag) {
242
- const result = processCustomComponent(
243
- line,
244
- allLines,
245
- i,
246
- customTag,
247
- data,
248
- allComponents,
249
- )
250
- if (result) {
251
- items.push(result.item)
252
- i = result.nextIndex
253
- continue
254
- }
255
- }
256
-
257
- // Check for code blocks
258
- if (isCodeBlockDelimiter(trimmed)) {
259
- const { item, nextIndex } = parseCodeBlock(allLines, i)
260
- items.push(item)
261
- i = nextIndex
262
- continue
263
- }
264
-
265
- // Parse regular markdown line
266
- const item = parseMarkdownLine(line)
267
- if (item) {
268
- items.push(item)
269
- }
270
-
271
- i++
272
- }
273
-
274
- return items
275
- }
276
-
277
- module.exports = {
278
- isCodeBlockDelimiter,
279
- parseCodeBlock,
280
- collectComponentContent,
281
- processCustomComponent,
282
- parseMarkdownLine,
283
- parseMarkdownLines,
284
- }
@@ -1,120 +0,0 @@
1
- /**
2
- * Resolve a path like "images[0].src" or "user.name" from a data object
3
- * @param {Object} data - The data object to resolve the path from
4
- * @param {string} path - The path to resolve (e.g., "images[0].src", "user.name")
5
- * @returns {*} - The resolved value or undefined
6
- */
7
- function resolvePath(data, path) {
8
- // Handle null or undefined data
9
- if (data === null || data === undefined) {
10
- return undefined
11
- }
12
-
13
- // Handle simple variable names (backwards compatibility)
14
- if (!/[.\[]/.test(path)) {
15
- return data[path]
16
- }
17
-
18
- // Split the path into parts, handling both dot notation and bracket notation
19
- // e.g., "images[0].src" -> ["images", "0", "src"]
20
- const parts = path
21
- .replace(/\[(\d+)\]/g, ".$1") // Convert [0] to .0
22
- .split(".")
23
- .filter(Boolean)
24
-
25
- let current = data
26
- for (const part of parts) {
27
- if (current === null || current === undefined) {
28
- return undefined
29
- }
30
- current = current[part]
31
- }
32
-
33
- return current
34
- }
35
-
36
- /**
37
- * Replace {variableName} placeholders in text with actual values from data
38
- * Supports:
39
- * - Simple variables: {name}
40
- * - Array indexing: {images[0]}
41
- * - Property access: {user.name}
42
- * - Combined: {images[0].src}
43
- * @param {string} text - Text containing variable placeholders
44
- * @param {Object} data - Data object with variable values
45
- * @returns {string|Array} - Text with variables replaced, or array if mixed content
46
- */
47
- function replaceVariables(text, data) {
48
- if (!text || typeof text !== "string") {
49
- return text
50
- }
51
-
52
- if (!data || typeof data !== "object") {
53
- return text
54
- }
55
-
56
- // Check if text contains any variables
57
- if (!text.includes("{") || !text.includes("}")) {
58
- return text
59
- }
60
-
61
- const result = []
62
- let i = 0
63
- let lastIndex = 0
64
-
65
- while (i < text.length) {
66
- if (text[i] === "\\" && text[i + 1] === "{") {
67
- // Escaped opening brace
68
- result.push(text.substring(lastIndex, i))
69
- result.push("{")
70
- i += 2
71
- lastIndex = i
72
- continue
73
- }
74
-
75
- if (text[i] === "{") {
76
- const closeIndex = text.indexOf("}", i + 1)
77
-
78
- if (closeIndex !== -1) {
79
- // Found a variable placeholder
80
- const variablePath = text.substring(i + 1, closeIndex).trim()
81
-
82
- if (variablePath) {
83
- // Add text before the variable
84
- if (i > lastIndex) {
85
- result.push(text.substring(lastIndex, i))
86
- }
87
-
88
- // Resolve the variable value (supports paths like "images[0].src")
89
- const value = resolvePath(data, variablePath)
90
- if (value !== undefined && value !== null) {
91
- result.push(String(value))
92
- } else {
93
- // Variable not found, keep the placeholder
94
- result.push(text.substring(i, closeIndex + 1))
95
- }
96
-
97
- i = closeIndex + 1
98
- lastIndex = i
99
- continue
100
- }
101
- }
102
- }
103
-
104
- i++
105
- }
106
-
107
- // Add remaining text
108
- if (lastIndex < text.length) {
109
- result.push(text.substring(lastIndex))
110
- }
111
-
112
- // If no substitutions were made, return original text
113
- if (result.length === 0) {
114
- return text
115
- }
116
-
117
- return result.join("")
118
- }
119
-
120
- module.exports = { replaceVariables, resolvePath }
File without changes
File without changes