markdown-magic 2.6.1 → 3.0.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/README.md +6 -10
- package/cli.js +5 -82
- package/lib/block-parser-js.test.js +179 -0
- package/lib/block-parser.js +389 -0
- package/lib/{utils/new-parser.test.js → block-parser.test.js} +168 -50
- package/lib/cli.js +234 -0
- package/lib/cli.test.js +409 -0
- package/lib/defaults.js +12 -0
- package/lib/index.js +319 -184
- package/lib/index.test.js +11 -0
- package/lib/options-parser.js +498 -0
- package/lib/options-parser.test.js +1237 -0
- package/lib/process-contents.js +330 -0
- package/lib/process-file.js +34 -0
- package/lib/transforms/code.js +67 -22
- package/lib/transforms/file.js +13 -10
- package/lib/transforms/remote.js +9 -6
- package/lib/transforms/toc.js +136 -64
- package/lib/transforms/wordCount.js +5 -0
- package/lib/utils/fs.js +340 -0
- package/lib/utils/fs.test.js +268 -0
- package/lib/utils/html-to-json/compat.js +42 -0
- package/lib/utils/html-to-json/format.js +64 -0
- package/lib/utils/html-to-json/index.js +37 -0
- package/lib/utils/html-to-json/lexer.js +345 -0
- package/lib/utils/html-to-json/parser.js +146 -0
- package/lib/utils/html-to-json/stringify.js +37 -0
- package/lib/utils/html-to-json/tags.js +171 -0
- package/lib/utils/index.js +19 -0
- package/{cli-utils.js → lib/utils/load-config.js} +2 -6
- package/lib/utils/logs.js +89 -0
- package/lib/utils/md/filters.js +20 -0
- package/lib/utils/md/find-code-blocks.js +80 -0
- package/lib/utils/md/find-date.js +32 -0
- package/lib/utils/md/find-frontmatter.js +94 -0
- package/lib/utils/md/find-frontmatter.test.js +17 -0
- package/lib/utils/md/find-html-tags.js +105 -0
- package/lib/utils/md/find-images.js +102 -0
- package/lib/utils/md/find-links.js +202 -0
- package/lib/utils/md/find-unmatched-html-tags.js +33 -0
- package/lib/utils/md/fixtures/2022-01-22-date-in-filename.md +14 -0
- package/lib/utils/md/fixtures/file-with-frontmatter.md +32 -0
- package/lib/utils/md/fixtures/file-with-links.md +143 -0
- package/lib/utils/md/md.test.js +37 -0
- package/lib/utils/md/parse.js +122 -0
- package/lib/utils/md/utils.js +19 -0
- package/lib/utils/regex-timeout.js +83 -0
- package/lib/utils/regex.js +38 -5
- package/lib/utils/remoteRequest.js +54 -0
- package/lib/utils/syntax.js +79 -0
- package/lib/utils/text.js +260 -0
- package/lib/utils/text.test.js +311 -0
- package/package.json +25 -25
- package/index.js +0 -46
- package/lib/processFile.js +0 -154
- package/lib/transforms/index.js +0 -114
- package/lib/updateContents.js +0 -125
- package/lib/utils/_md.test.js +0 -63
- package/lib/utils/new-parser.js +0 -412
- package/lib/utils/weird-parse.js +0 -230
- package/lib/utils/weird-parse.test.js +0 -217
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
|
|
2
|
+
const { optionsParse } = require('./options-parser')
|
|
3
|
+
const { getSyntaxInfo } = require('./utils/syntax')
|
|
4
|
+
const {
|
|
5
|
+
getFirstCharacter,
|
|
6
|
+
getLastCharacter,
|
|
7
|
+
getTextBetweenChars,
|
|
8
|
+
stripIndent,
|
|
9
|
+
findMinIndent
|
|
10
|
+
} = require('./utils/text')
|
|
11
|
+
const { OPEN_WORD, CLOSE_WORD, SYNTAX } = require('./defaults')
|
|
12
|
+
// Alt parser https://github.com/LesterLyu/fast-formula-parser/blob/master/grammar/lexing.js
|
|
13
|
+
|
|
14
|
+
const defaultOptions = {
|
|
15
|
+
syntax: SYNTAX,
|
|
16
|
+
open: OPEN_WORD,
|
|
17
|
+
close: CLOSE_WORD,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseBlocks(contents, opts = {}) {
|
|
21
|
+
const blocks = []
|
|
22
|
+
const options = Object.assign({}, defaultOptions, opts)
|
|
23
|
+
const { syntax, open, close } = options
|
|
24
|
+
if (!open) {
|
|
25
|
+
throw new Error('Missing options.open')
|
|
26
|
+
}
|
|
27
|
+
if (!close) {
|
|
28
|
+
throw new Error('Missing options.close')
|
|
29
|
+
}
|
|
30
|
+
if (!syntax) {
|
|
31
|
+
throw new Error('Missing options.syntax')
|
|
32
|
+
}
|
|
33
|
+
const syntaxInfo = getSyntaxInfo(syntax)
|
|
34
|
+
if (!syntaxInfo.pattern) {
|
|
35
|
+
throw new Error(`Unknown syntax "${syntax}"`)
|
|
36
|
+
}
|
|
37
|
+
const [ openComment, closeComment ] = syntaxInfo.pattern
|
|
38
|
+
const regexToUse = getBlockRegex({
|
|
39
|
+
openComment,
|
|
40
|
+
closeComment,
|
|
41
|
+
openText: open,
|
|
42
|
+
closeText: close
|
|
43
|
+
})
|
|
44
|
+
// console.log('regexToUse', regexToUse)
|
|
45
|
+
const paramsRegex = new RegExp(`([\\s\\S]*?)${closeComment}`, 'gm')
|
|
46
|
+
//console.log('paramsRegex', paramsRegex)
|
|
47
|
+
const trimRegex = new RegExp(`${closeComment}$`)
|
|
48
|
+
// ([ \t]*)(?:\/\*(?:.*|\r?|\n?|\s*)XYZ:START\s*([(\[\{]*[A-Za-z0-9_$-]*[)\]\}]*)\s*)((?:.|\r?\n)*?)\/\*(?:.*|\r?|\n?|\s*)XYZ:END(?:.|\r?\n)*?\*\/
|
|
49
|
+
|
|
50
|
+
let openTagRegex = getOpenCommentRegex(open, openComment, closeComment)
|
|
51
|
+
let closeTagRegex = getClosingCommentRegex(close, openComment, closeComment)
|
|
52
|
+
// console.log('openTagRegex', openTagRegex)
|
|
53
|
+
// console.log('closeTagRegex', closeTagRegex)
|
|
54
|
+
|
|
55
|
+
/* Verify comment blocks aren't broken (redos) */
|
|
56
|
+
const { isBalanced, openCount, closeCount } = verifyTagsBalanced(contents, openTagRegex, closeTagRegex)
|
|
57
|
+
const balanced = (closeCount > openCount) ? true : isBalanced
|
|
58
|
+
if (!balanced) {
|
|
59
|
+
throw new Error(`Blocks are unbalanced.
|
|
60
|
+
${openCount} "${open}" open tags.
|
|
61
|
+
${closeCount} "${close}" close tags.
|
|
62
|
+
`)
|
|
63
|
+
}
|
|
64
|
+
let index = 0
|
|
65
|
+
while ((commentMatches = regexToUse.exec(contents)) !== null) {
|
|
66
|
+
index++
|
|
67
|
+
let props = {}
|
|
68
|
+
let paramString = ''
|
|
69
|
+
const [ block, spaces, __type, params ] = commentMatches
|
|
70
|
+
const isMultiline = block.indexOf('\n') > -1
|
|
71
|
+
let context = {
|
|
72
|
+
isMultiline,
|
|
73
|
+
}
|
|
74
|
+
// console.log('commentMatches', commentMatches)
|
|
75
|
+
const indentation = spaces || ''
|
|
76
|
+
/* Remove trailing -- if no params */
|
|
77
|
+
const type = __type.replace(/-*$/, '')
|
|
78
|
+
/*
|
|
79
|
+
console.log('index', commentMatches.index)
|
|
80
|
+
console.log('block', block)
|
|
81
|
+
console.log('type', type)
|
|
82
|
+
console.log('params', params)
|
|
83
|
+
console.log('spaces', `"${spaces}"`)
|
|
84
|
+
/** */
|
|
85
|
+
// This is necessary to avoid infinite loops
|
|
86
|
+
if (commentMatches.index === regexToUse.lastIndex) {
|
|
87
|
+
regexToUse.lastIndex++
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
openTagRegex = getOpenCommentRegex(open, openComment, closeComment)
|
|
91
|
+
// console.log('openTagRegex', openTagRegex)
|
|
92
|
+
const openingTag = getOpeningTags(block, {
|
|
93
|
+
pattern: openTagRegex,
|
|
94
|
+
open: openComment,
|
|
95
|
+
close: closeComment
|
|
96
|
+
})
|
|
97
|
+
closeTagRegex = getClosingCommentRegex(close, openComment, closeComment)
|
|
98
|
+
// console.log('closeTagRegex', closeTagRegex)
|
|
99
|
+
const closingTag = getClosingTags(block, {
|
|
100
|
+
pattern: closeTagRegex
|
|
101
|
+
})
|
|
102
|
+
/*
|
|
103
|
+
console.log('openingTag', openingTag)
|
|
104
|
+
console.log('closingTag', closingTag)
|
|
105
|
+
/** */
|
|
106
|
+
if (!openingTag || !closingTag) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const openingTagLength = openingTag.length //+ indentation.length
|
|
111
|
+
const contentEndPosition = block.indexOf(closingTag.tag, openingTagLength)
|
|
112
|
+
const content = getTextBetweenChars(block, openingTagLength, contentEndPosition)
|
|
113
|
+
// console.log('content', content)
|
|
114
|
+
let originalContent = content
|
|
115
|
+
const contentEndsWithNewLine = getLastCharacter(originalContent) === '\n'
|
|
116
|
+
const openEndsWithNewLine = getLastCharacter(openingTag.tag) === '\n'
|
|
117
|
+
|
|
118
|
+
const closeTag = (contentEndsWithNewLine) ? `\n${closingTag.tag}` : closingTag.tag
|
|
119
|
+
|
|
120
|
+
// Move new line to beginning of closing tag
|
|
121
|
+
// if (originalContent.match(/\n$/)) {
|
|
122
|
+
if (contentEndsWithNewLine) {
|
|
123
|
+
// originalContent = originalContent.replace(/\n$/, '')
|
|
124
|
+
originalContent = originalContent.slice(0, -1)
|
|
125
|
+
}
|
|
126
|
+
/* Strip indentation */
|
|
127
|
+
originalContent = stripIndent(originalContent, indentation.length)
|
|
128
|
+
// originalContent = originalContent.replace(/^\s+|\s+$/g, '')
|
|
129
|
+
/*
|
|
130
|
+
console.log('originalContent')
|
|
131
|
+
console.log(`"${originalContent}"`)
|
|
132
|
+
/** */
|
|
133
|
+
|
|
134
|
+
/* strip brackets (functionName) or [functionName] or {functionName} */
|
|
135
|
+
const cleanType = stripBrackets(type)
|
|
136
|
+
const shift = (openEndsWithNewLine) ? 1 : 0
|
|
137
|
+
const lineOpen = contents.substr(0, commentMatches.index).split('\n').length
|
|
138
|
+
const lineClose = contents.substr(0, regexToUse.lastIndex).split('\n').length
|
|
139
|
+
const contentStart = commentMatches.index + openingTag.tag.length - shift //+ indentation.length
|
|
140
|
+
/* If single line comment block, remove indentation */
|
|
141
|
+
const finIndentation = (lineOpen === lineClose) ? '' : indentation
|
|
142
|
+
const contentEnd = contentStart + content.length + finIndentation.length + shift
|
|
143
|
+
|
|
144
|
+
// if (cleanType && !cleanType.match(/^-+/)) {
|
|
145
|
+
if (cleanType && getFirstCharacter(cleanType) !== '-') {
|
|
146
|
+
// console.log('params', params)
|
|
147
|
+
// const paramValue = params.match(/([\s\S]*?)-->/gm)
|
|
148
|
+
const paramValue = params.match(paramsRegex)
|
|
149
|
+
// console.log('paramValue', paramValue)
|
|
150
|
+
if (paramValue) {
|
|
151
|
+
// paramString = paramValue[0].replace(/-*>$/, '').trim()
|
|
152
|
+
paramString = paramValue[0].replace(trimRegex, '').trim()
|
|
153
|
+
// console.log('clean', `${cleanType}`)
|
|
154
|
+
// console.log('param', `${paramString}`)
|
|
155
|
+
// console.log('type ', `${__type}`)
|
|
156
|
+
// console.log('──────────────────────')
|
|
157
|
+
// console.log(`${cleanType} "${paramString}" "${__type}"`)
|
|
158
|
+
if (paramString) {
|
|
159
|
+
// console.log('paramString', `"${paramString}"`)
|
|
160
|
+
// Legacy v1 options parser
|
|
161
|
+
if (getFirstCharacter(paramString) === ':' || getFirstCharacter(paramString) === '?') {
|
|
162
|
+
context.isLegacy = true
|
|
163
|
+
// paramString = paramString.replace(/\s?\)\s*$/, '').substring(1)
|
|
164
|
+
paramString = paramString.split(')')[0].substring(1)
|
|
165
|
+
// console.log('fixed paramString', paramString)
|
|
166
|
+
props = legacyParseOptions(paramString)
|
|
167
|
+
} else {
|
|
168
|
+
if (type.startsWith('(') && paramString.endsWith(')')) {
|
|
169
|
+
paramString = paramString.replace(/\)$/, '')
|
|
170
|
+
}
|
|
171
|
+
props = optionsParse(paramString)
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
} else if (!paramString && __type.match(/^\(.*\)$/)) {
|
|
175
|
+
context.isLegacy = true
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/*
|
|
179
|
+
console.log(regexToUse)
|
|
180
|
+
console.log(`cleanType "${cleanType}" at ${regexToUse.lastIndex} using props:`)
|
|
181
|
+
console.log(props)
|
|
182
|
+
console.log('───────────────────────')
|
|
183
|
+
/** */
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* Add found block */
|
|
187
|
+
blocks.push({
|
|
188
|
+
index: index,
|
|
189
|
+
type: cleanType,
|
|
190
|
+
options: props,
|
|
191
|
+
context,
|
|
192
|
+
/* Open Tag */
|
|
193
|
+
open: {
|
|
194
|
+
value: openingTag.tag,
|
|
195
|
+
start: commentMatches.index,
|
|
196
|
+
end: contentStart
|
|
197
|
+
},
|
|
198
|
+
/* Inner Content */
|
|
199
|
+
content: {
|
|
200
|
+
value: originalContent,
|
|
201
|
+
start: contentStart,
|
|
202
|
+
end: contentEnd,
|
|
203
|
+
indentation: findMinIndent(originalContent),
|
|
204
|
+
},
|
|
205
|
+
/* Close Tag */
|
|
206
|
+
close: {
|
|
207
|
+
value: closeTag,
|
|
208
|
+
start: contentEnd,
|
|
209
|
+
end: regexToUse.lastIndex
|
|
210
|
+
},
|
|
211
|
+
/* Full Block */
|
|
212
|
+
block: {
|
|
213
|
+
indentation: finIndentation,
|
|
214
|
+
lines: [lineOpen, lineClose],
|
|
215
|
+
start: commentMatches.index,
|
|
216
|
+
end: regexToUse.lastIndex,
|
|
217
|
+
// position: [ commentMatches.index, regexToUse.lastIndex ],
|
|
218
|
+
rawType: (context.isLegacy) ? type.replace(/^\s?\(/, '') : type,
|
|
219
|
+
rawArgs: paramString,
|
|
220
|
+
rawContent: getTextBetweenChars(contents, contentStart, contentEnd),
|
|
221
|
+
value: block,
|
|
222
|
+
},
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
// Close but no single line newPattern: newGetBlockRegex({ openComment, commentClose, start: START, ending: END }),
|
|
227
|
+
pattern: regexToUse,
|
|
228
|
+
COMMENT_OPEN_REGEX: openTagRegex,
|
|
229
|
+
COMMENT_CLOSE_REGEX: closeTagRegex,
|
|
230
|
+
blocks
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function verifyTagsBalanced(str, open, close) {
|
|
235
|
+
const openCount = (str.match(open) || []).length
|
|
236
|
+
const closeCount = (str.match(close) || []).length
|
|
237
|
+
return {
|
|
238
|
+
isBalanced: openCount === closeCount,
|
|
239
|
+
openCount,
|
|
240
|
+
closeCount
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function getOpeningTags(block, {
|
|
245
|
+
pattern,
|
|
246
|
+
open,
|
|
247
|
+
close
|
|
248
|
+
}) {
|
|
249
|
+
// console.log(block.match(/^\/\*+(.*)\*\//))
|
|
250
|
+
// console.log('openTagRegex', pattern)
|
|
251
|
+
let matches
|
|
252
|
+
while ((matches = pattern.exec(block)) !== null) {
|
|
253
|
+
if (matches.index === pattern.lastIndex) {
|
|
254
|
+
pattern.lastIndex++ // avoid infinite loops with zero-width matches
|
|
255
|
+
}
|
|
256
|
+
const [ tag, spaces, tagStart, tagEnd ] = matches
|
|
257
|
+
/*
|
|
258
|
+
console.log('FULL Open Tag >>>>>', tag)
|
|
259
|
+
console.log('openTag Start', "'"+tagStart+"'");
|
|
260
|
+
console.log('openTag End', "'"+tagEnd+"'");
|
|
261
|
+
/**/
|
|
262
|
+
return {
|
|
263
|
+
tag,
|
|
264
|
+
spaces: spaces || '',
|
|
265
|
+
length: tag.length,
|
|
266
|
+
tagStart,
|
|
267
|
+
tagEnd,
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// // Fallthrough
|
|
271
|
+
// // const fallbackRegex = new RegExp(`^([ \\t]*)(${open}([\\s\\S]*?)${close})\\n?`)
|
|
272
|
+
// const fallbackRegex = new RegExp(`^([ \\t]*)(\\b${open}\\b([\\s\\S]*?)${close})\\n?`)
|
|
273
|
+
// // const xyz = block.match(/^([ \t]*)(\/\*+([\s\S]*?)\*+\/)/)
|
|
274
|
+
// const fallbackMatch= block.match(fallbackRegex)
|
|
275
|
+
// if (fallbackMatch) {
|
|
276
|
+
// /*
|
|
277
|
+
// console.log('fallbackRegex', fallbackRegex)
|
|
278
|
+
// console.log('fall through', `"${block}"`)
|
|
279
|
+
// console.log('xyz', xyz)
|
|
280
|
+
// /** */
|
|
281
|
+
// return {
|
|
282
|
+
// fallthrough: true,
|
|
283
|
+
// tag: fallbackMatch[0],
|
|
284
|
+
// spaces: fallbackMatch[1] || '',
|
|
285
|
+
// length: fallbackMatch[0].length,
|
|
286
|
+
// }
|
|
287
|
+
// }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function getClosingTags(block, {
|
|
291
|
+
pattern,
|
|
292
|
+
// open,
|
|
293
|
+
// close
|
|
294
|
+
}) {
|
|
295
|
+
// console.log('closeTagRegex', closeTagRegex)
|
|
296
|
+
let matches
|
|
297
|
+
while ((matches = pattern.exec(block)) !== null) {
|
|
298
|
+
if (matches.index === pattern.lastIndex) {
|
|
299
|
+
pattern.lastIndex++ // avoid infinite loops with zero-width matches
|
|
300
|
+
}
|
|
301
|
+
const [ _tag, spaces, tagStart, tagEnd] = matches
|
|
302
|
+
/*
|
|
303
|
+
console.log('FULL CLOSE Tag >>>>>', matches[0])
|
|
304
|
+
console.log('closeTag Start', "'"+matches[1]+"'");
|
|
305
|
+
console.log('closeTag End', "'"+matches[2]+"'");
|
|
306
|
+
/**/
|
|
307
|
+
const tag = spaces + tagStart + tagEnd
|
|
308
|
+
return {
|
|
309
|
+
tag: tag,
|
|
310
|
+
length: tag.length,
|
|
311
|
+
spaces: spaces || '',
|
|
312
|
+
tagStart,
|
|
313
|
+
tagEnd
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
const EMOJI = '\\u00a9|\\u00ae|[\\u2000-\\u3300]|\\ud83c[\\ud000-\\udfff]|\\ud83d[\\ud000-\\udfff]|\\ud83e[\\ud000-\\udfff]'
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Get Regex pattern to match block
|
|
323
|
+
* @param {object} options
|
|
324
|
+
* @param {string} [options.openComment] - comment syntax open
|
|
325
|
+
* @param {string} [options.closeComment] - comment syntax open
|
|
326
|
+
* @param {string} [options.openText] - comment open text
|
|
327
|
+
* @param {string} [options.closeText] - comment close text
|
|
328
|
+
* @returns {RegExp}
|
|
329
|
+
*/
|
|
330
|
+
function getBlockRegex({ openComment, closeComment, openText, closeText }) {
|
|
331
|
+
return new RegExp(
|
|
332
|
+
`([ \\t]*)(?:${openComment}(?:.*|\\r?|\\n?|\\s*)${openText}\\s*([(\\[\\{]*[A-Za-z0-9_$-]*[)\\]\\}]*)\\s*)((?:.*?|.*?\\r?\\n?)*?)${openComment}(?:.*|\\r?|\\n?|\\s*)${closeText}(?:.|\\r?\\n)*?${closeComment}`,
|
|
333
|
+
'gmi'
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* // Named matches
|
|
338
|
+
(?<leading>[ \t]*)(?:<!-{2,}(?:.*|\r?|\n?|\s*)MD-MAGIC-EXAMPLE:START\s*(?<key>[(\[\{]*[A-Za-z0-9_$-]*[)\]\}]*)\s*)([\s\S]*?)-->(?<content>(?:.*?|.*?\r?\n?)*?)<!-{2,}(?:.*|\r?|\n?|\s*)MD-MAGIC-EXAMPLE:END(?:.|\r?\n)*?-{2,}>
|
|
339
|
+
*/
|
|
340
|
+
function newGetBlockRegex({ commentOpen, commentClose, start, ending }) {
|
|
341
|
+
// https://regex101.com/r/C9WSk8/1 close but breaks on single line blocks. Maybe needs lookahead https://stackoverflow.com/questions/7124778/how-can-i-match-anything-up-until-this-sequence-of-characters-in-a-regular-exp
|
|
342
|
+
return new RegExp(
|
|
343
|
+
`([ \\t]*)(?:${commentOpen}(?:.*|\\r?|\\n?|\\s*)${start}\\s*([(\\[\\{]*[A-Za-z0-9_$-]*[)\\]\\}]*)\\s*)([\\s\\S]*?)${commentClose}((?:.*?|.*?\\r?\\n?)*?)${commentOpen}(?:.*|\\r?|\\n?|\\s*)${ending}(?:.|\\r?\\n)*?${commentClose}`,
|
|
344
|
+
'gmi'
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function getOpenCommentRegex(word, open, close) {
|
|
349
|
+
// console.log('open', open)
|
|
350
|
+
const boundary = word.indexOf('/') > -1 ? '' : '\\b'
|
|
351
|
+
// console.log('boundary', boundary)
|
|
352
|
+
// return new RegExp(`(\\<\\!--(?:.|\\r?\\n)*?${matchWord}:START)((?:.|\\r?\\n)*?--\\>)`, 'g')
|
|
353
|
+
return new RegExp(`([ \\t]*)(${open}(?:.|\r?|\n?|\\s*)${boundary}${word}${boundary})((?:.|\\r?\\n)*?${close}\n?)`, 'gi')
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function getClosingCommentRegex(word, open, close) {
|
|
357
|
+
const boundary = word.indexOf('/') > -1 ? '' : '\\b'
|
|
358
|
+
return new RegExp(`${close}(?:.|\\r?\\n)*?([ \t]*)((?:${open}(?:.*|\\r?\\n)(?:.*|\\r?\\n))*?${boundary}${word}${boundary})((?:.|\\r?\\n)*?${close})`, 'gi')
|
|
359
|
+
// return new RegExp(`--\\>(?:.|\\r?\\n)*?([ \t]*)((?:\\<\\!--(?:.*|\\r?\\n)(?:.*|\\r?\\n))*?${word}:END)((?:.|\\r?\\n)*?--\\>)`, 'gi')
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Strip brackets from string (functionName) or [functionName] or {functionName}
|
|
364
|
+
* @param {string} str
|
|
365
|
+
* @returns {string}
|
|
366
|
+
*/
|
|
367
|
+
function stripBrackets(str) {
|
|
368
|
+
return str.replace(/[(\[\{]*([A-Z-a-z0-9_$-]*)[)\]\}]*/, '$1')
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function legacyParseOptions(options) {
|
|
372
|
+
const returnOptions = {}
|
|
373
|
+
if (!options) {
|
|
374
|
+
return returnOptions
|
|
375
|
+
}
|
|
376
|
+
options.split('&').map((opt, i) => { // eslint-disable-line
|
|
377
|
+
const getValues = opt.split(/=(.+)/)
|
|
378
|
+
if (getValues[0] && getValues[1]) {
|
|
379
|
+
returnOptions[getValues[0]] = getValues[1]
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
return returnOptions
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
module.exports = {
|
|
387
|
+
getBlockRegex,
|
|
388
|
+
parseBlocks,
|
|
389
|
+
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
const { inspect } = require('util')
|
|
2
1
|
const { test } = require('uvu')
|
|
3
2
|
const assert = require('uvu/assert')
|
|
4
|
-
const
|
|
5
|
-
const {
|
|
6
|
-
|
|
7
|
-
function deepLog(v) {
|
|
8
|
-
console.log(inspect(v, {showHidden: false, depth: null}))
|
|
9
|
-
}
|
|
3
|
+
const { parseBlocks } = require('./block-parser')
|
|
4
|
+
const { deepLog } = require('./utils/logs')
|
|
10
5
|
|
|
11
6
|
const md = `<h1 id="jdjdj">Netlify + FaunaDB
|
|
12
7
|
<a href="https://app.netlify.com/start/deploy?repository=https://github.com/netlify/netlify-faunadb-example&stack=fauna">
|
|
@@ -182,38 +177,65 @@ contents
|
|
|
182
177
|
Markdown magic uses comment blocks in markdown files to automatically sync or transform its contents. <img align="right" width="200" height="183" src="https://cloud.githubusercontent.com/assets/532272/21507867/3376e9fe-cc4a-11e6-9350-7ec4f680da36.gif" />
|
|
183
178
|
`
|
|
184
179
|
|
|
185
|
-
test('parser', () => {
|
|
186
|
-
const parsedValue = parseBlocks(md
|
|
180
|
+
test('verify parser', () => {
|
|
181
|
+
const parsedValue = parseBlocks(md, {
|
|
182
|
+
open: 'XYZ:START',
|
|
183
|
+
close: 'XYZ:END'
|
|
184
|
+
})
|
|
187
185
|
/*
|
|
188
186
|
console.log('parsedValue')
|
|
189
187
|
deepLog(parsedValue)
|
|
190
188
|
/** */
|
|
191
|
-
assert.equal(
|
|
189
|
+
assert.equal(typeof parsedValue, 'object')
|
|
190
|
+
assert.equal(parsedValue.blocks.length, 11)
|
|
192
191
|
})
|
|
193
192
|
|
|
194
|
-
const inlineOne = `<!--XYZ:START functionName foo={{ rad: 'bar' }}-->99<!--XYZ:END-->`
|
|
195
|
-
const inlineTwo = ` <!-- XYZ:START transformX foo=111 -->99<!-- XYZ:END -->`
|
|
196
193
|
test('inline parser', () => {
|
|
197
|
-
const
|
|
194
|
+
const inlineOne = `<!--XYZ:START functionName foo={{ rad: 'bar' }}-->99<!--XYZ:END-->`
|
|
195
|
+
const one = parseBlocks(inlineOne, {
|
|
196
|
+
open: 'XYZ:START',
|
|
197
|
+
close: 'XYZ:END'
|
|
198
|
+
})
|
|
198
199
|
/*
|
|
199
200
|
console.log('inline one')
|
|
200
201
|
deepLog(one)
|
|
201
202
|
/** */
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
203
|
+
const values = [
|
|
204
|
+
{
|
|
205
|
+
type: 'functionName',
|
|
206
|
+
options: { foo: { rad: 'bar' } },
|
|
207
|
+
location: 66
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
values.forEach((val, i) => {
|
|
211
|
+
const stub = val
|
|
212
|
+
const currentItem = one.blocks[i]
|
|
213
|
+
assert.equal(stub.type, currentItem.type, `${stub.type} ${i} transform`)
|
|
214
|
+
assert.equal(stub.options, currentItem.options, `${stub.type} ${i} options`)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
const inlineTwo = ` <!-- XYZ:START transformX foo=111 -->99<!-- XYZ:END -->`
|
|
218
|
+
const two = parseBlocks(inlineTwo, {
|
|
219
|
+
open: 'XYZ:START',
|
|
220
|
+
close: 'XYZ:END'
|
|
221
|
+
})
|
|
222
|
+
/*
|
|
211
223
|
console.log('inline two ───────────────────────')
|
|
212
224
|
deepLog(two)
|
|
213
225
|
/** */
|
|
214
|
-
|
|
215
|
-
{
|
|
216
|
-
|
|
226
|
+
const valuesTwo = [
|
|
227
|
+
{
|
|
228
|
+
type: 'transformX',
|
|
229
|
+
options: { foo: 111 },
|
|
230
|
+
location: 55
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
valuesTwo.forEach((val, i) => {
|
|
234
|
+
const stub = val
|
|
235
|
+
const currentItem = two.blocks[i]
|
|
236
|
+
assert.equal(stub.type, currentItem.type, `${stub.type} ${i} transform`)
|
|
237
|
+
assert.equal(stub.options, currentItem.options, `${stub.type} ${i} options`)
|
|
238
|
+
})
|
|
217
239
|
})
|
|
218
240
|
|
|
219
241
|
const fnBlocks = `
|
|
@@ -246,53 +268,67 @@ nice
|
|
|
246
268
|
<!-- XYZ:END -->
|
|
247
269
|
`
|
|
248
270
|
|
|
249
|
-
test('function names', () => {
|
|
250
|
-
const parsedValue = parseBlocks(fnBlocks
|
|
271
|
+
test('Handles function names', () => {
|
|
272
|
+
const parsedValue = parseBlocks(fnBlocks, {
|
|
273
|
+
open: 'XYZ:START',
|
|
274
|
+
close: 'XYZ:END'
|
|
275
|
+
})
|
|
251
276
|
/*
|
|
252
277
|
console.log('fn names')
|
|
253
278
|
deepLog(parsedValue)
|
|
254
279
|
/** */
|
|
255
|
-
assert.equal(parsedValue,
|
|
280
|
+
assert.equal(Array.isArray(parsedValue.blocks), true)
|
|
281
|
+
assert.equal(parsedValue.blocks.length, 7)
|
|
282
|
+
|
|
283
|
+
const values = [
|
|
256
284
|
{
|
|
257
|
-
|
|
258
|
-
|
|
285
|
+
type: 'functionName',
|
|
286
|
+
options: { foo: { rad: 'yellow' } },
|
|
259
287
|
location: 78
|
|
260
288
|
},
|
|
261
289
|
{
|
|
262
|
-
|
|
263
|
-
|
|
290
|
+
type: 'functionName',
|
|
291
|
+
options: { foo: { rad: 'blue' } },
|
|
264
292
|
location: 157
|
|
265
293
|
},
|
|
266
294
|
{
|
|
267
|
-
|
|
268
|
-
|
|
295
|
+
type: 'functionName',
|
|
296
|
+
options: { foo: { rad: 'red' } },
|
|
269
297
|
location: 235
|
|
270
298
|
},
|
|
271
299
|
{
|
|
272
|
-
|
|
273
|
-
|
|
300
|
+
type: 'functionName',
|
|
301
|
+
options: { foo: { rad: 'purple' } },
|
|
274
302
|
location: 316
|
|
275
303
|
},
|
|
276
304
|
{
|
|
277
|
-
|
|
278
|
-
|
|
305
|
+
type: 'functionName',
|
|
306
|
+
options: { foo: { rad: 'black' } },
|
|
279
307
|
location: 398
|
|
280
308
|
},
|
|
281
309
|
{
|
|
282
|
-
|
|
283
|
-
|
|
310
|
+
type: 'functionName',
|
|
311
|
+
options: { foo: { rad: 'white' } },
|
|
284
312
|
location: 480
|
|
285
313
|
},
|
|
286
314
|
{
|
|
287
|
-
|
|
288
|
-
|
|
315
|
+
type: 'functionName',
|
|
316
|
+
options: { foo: { rad: 'orange' } },
|
|
289
317
|
location: 563
|
|
290
318
|
}
|
|
291
|
-
]
|
|
319
|
+
]
|
|
320
|
+
|
|
321
|
+
values.forEach((val, i) => {
|
|
322
|
+
const stub = val
|
|
323
|
+
const currentItem = parsedValue.blocks[i]
|
|
324
|
+
assert.equal(stub.type, currentItem.type, `${stub.type} ${i} transform`)
|
|
325
|
+
assert.equal(stub.options, currentItem.options, `${stub.type} ${i} options`)
|
|
326
|
+
})
|
|
292
327
|
})
|
|
293
328
|
|
|
294
329
|
|
|
295
|
-
|
|
330
|
+
test('different function names', () => {
|
|
331
|
+
const backwardCompat = `
|
|
296
332
|
<!-- XYZ:START functionName foo={{ rad: 'yellow' }} -->
|
|
297
333
|
nice
|
|
298
334
|
<!-- XYZ:END -->
|
|
@@ -305,20 +341,102 @@ nice
|
|
|
305
341
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer vitae mauris arcu, eu pretium nisi. Praesent fringilla ornare ullamcorper. Pellentesque diam orci, sodales in blandit ut, placerat quis felis. Vestibulum at sem massa, in tempus nisi. Vivamus ut fermentum odio. Etiam porttitor faucibus volutpat. Vivamus vitae mi ligula, non hendrerit urna. Suspendisse potenti. Quisque eget massa a massa semper mollis.
|
|
306
342
|
<!-- XYZ:END -->
|
|
307
343
|
|
|
308
|
-
|
|
309
344
|
<!-- XYZ:START (CODE:src=./relative/path/to/code.js&lines=22-44) -->
|
|
310
345
|
This content will be dynamically replaced with code from the file lines 22 through 44
|
|
311
346
|
<!-- XYZ:END -->
|
|
312
347
|
`
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
348
|
+
const parsedValue = parseBlocks(backwardCompat, {
|
|
349
|
+
open: 'XYZ:START',
|
|
350
|
+
close: 'XYZ:END'
|
|
351
|
+
})
|
|
352
|
+
/*
|
|
317
353
|
console.log('backwardCompat')
|
|
318
354
|
deepLog(parsedValue)
|
|
319
355
|
/** */
|
|
320
|
-
|
|
356
|
+
const answers = [
|
|
357
|
+
{
|
|
358
|
+
type: 'functionName',
|
|
359
|
+
options: { foo: { rad: 'yellow' } },
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
type: 'lol',
|
|
363
|
+
options: {
|
|
364
|
+
width: 999,
|
|
365
|
+
height: 111,
|
|
366
|
+
numberAsString: '12345',
|
|
367
|
+
great: [ 'scoot', 'sco ot', 'scooo ttt' ],
|
|
368
|
+
nope: false
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
type: 'CODE',
|
|
373
|
+
options: { src: './relative/path/to/code.js', lines: '22-44' },
|
|
374
|
+
},
|
|
375
|
+
]
|
|
376
|
+
parsedValue.blocks.forEach((transform, i) => {
|
|
377
|
+
const stub = answers[i]
|
|
378
|
+
assert.equal(transform.type, stub.type, `type: ${stub.type} at index ${i}`)
|
|
379
|
+
assert.equal(transform.options, stub.options, `options: ${stub.type} at index ${i}`)
|
|
380
|
+
})
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
const defaultOpts = {
|
|
384
|
+
syntax: 'md',
|
|
385
|
+
open: 'DOCS:START',
|
|
386
|
+
close: 'DOCS:END',
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const mdText = `
|
|
390
|
+
Very nice
|
|
391
|
+
|
|
392
|
+
<!-- DOCS:START(TOC) foo={{ rad: 'orange' }} ------>
|
|
393
|
+
ok
|
|
394
|
+
<!-- DOCS:END -->`
|
|
395
|
+
|
|
396
|
+
test('Parse md blocks', () => {
|
|
397
|
+
const parsedValue = parseBlocks(mdText, defaultOpts)
|
|
398
|
+
deepLog(parsedValue)
|
|
399
|
+
assert.equal(parsedValue.blocks, [
|
|
400
|
+
{
|
|
401
|
+
index: 1,
|
|
402
|
+
type: 'TOC',
|
|
403
|
+
options: { foo: { rad: 'orange' } },
|
|
404
|
+
context: { isMultiline: true },
|
|
405
|
+
open: {
|
|
406
|
+
value: "<!-- DOCS:START(TOC) foo={{ rad: 'orange' }} ------>\n",
|
|
407
|
+
start: 12,
|
|
408
|
+
end: 64
|
|
409
|
+
},
|
|
410
|
+
content: { value: 'ok', start: 64, end: 68, indentation: 0 },
|
|
411
|
+
close: { value: '\n<!-- DOCS:END -->', start: 68, end: 85 },
|
|
412
|
+
block: {
|
|
413
|
+
indentation: '',
|
|
414
|
+
lines: [ 4, 6 ],
|
|
415
|
+
start: 12,
|
|
416
|
+
end: 85,
|
|
417
|
+
rawType: '(TOC)',
|
|
418
|
+
rawArgs: "foo={{ rad: 'orange' }}",
|
|
419
|
+
rawContent: '\nok\n',
|
|
420
|
+
value: "<!-- DOCS:START(TOC) foo={{ rad: 'orange' }} ------>\n" +
|
|
421
|
+
'ok\n' +
|
|
422
|
+
'<!-- DOCS:END -->'
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
], 'Array contains details')
|
|
321
426
|
})
|
|
322
427
|
|
|
428
|
+
test('Returns empty array', () => {
|
|
429
|
+
assert.equal(parseBlocks('', defaultOpts).blocks, [])
|
|
430
|
+
assert.equal(parseBlocks(' ', defaultOpts).blocks, [])
|
|
431
|
+
assert.equal(parseBlocks(`
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
`, defaultOpts).blocks, [])
|
|
435
|
+
assert.equal(parseBlocks(`
|
|
436
|
+
# No block in here
|
|
437
|
+
|
|
438
|
+
nope
|
|
439
|
+
`, defaultOpts).blocks, [])
|
|
440
|
+
})
|
|
323
441
|
|
|
324
442
|
test.run()
|