markdown-magic 3.0.0 → 3.0.2

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 (49) hide show
  1. package/README.md +43 -29
  2. package/lib/block-parser-js.test.js +148 -156
  3. package/lib/block-parser.js +255 -262
  4. package/lib/block-parser.test.js +43 -6
  5. package/lib/cli.js +30 -19
  6. package/lib/cli.test.js +73 -73
  7. package/lib/globals.d.ts +66 -0
  8. package/lib/index.js +43 -9
  9. package/lib/process-contents.js +80 -39
  10. package/lib/process-file.js +4 -1
  11. package/lib/transforms/code.js +4 -10
  12. package/lib/transforms/file.js +7 -10
  13. package/lib/transforms/index.js +0 -0
  14. package/lib/transforms/remote.js +2 -3
  15. package/lib/transforms/sectionToc.js +18 -0
  16. package/lib/transforms/toc.js +10 -335
  17. package/lib/types.js +11 -0
  18. package/lib/utils/fs.js +21 -19
  19. package/lib/utils/fs.test.js +4 -5
  20. package/lib/utils/logs.js +7 -2
  21. package/lib/utils/md/filters.js +5 -5
  22. package/lib/utils/md/find-code-blocks.js +16 -8
  23. package/lib/utils/md/find-frontmatter.js +11 -13
  24. package/lib/utils/md/find-frontmatter.test.js +2 -2
  25. package/lib/utils/md/find-html-tags.js +1 -1
  26. package/lib/utils/md/find-images-md.js +27 -0
  27. package/lib/utils/md/find-images.js +39 -34
  28. package/lib/utils/md/find-links.js +72 -54
  29. package/lib/utils/md/find-unmatched-html-tags.js +1 -2
  30. package/lib/utils/md/fixtures/file-with-links.md +10 -0
  31. package/lib/utils/md/md.test.js +72 -4
  32. package/lib/utils/md/parse.js +91 -67
  33. package/lib/utils/regex-timeout.js +2 -1
  34. package/lib/utils/regex.js +3 -2
  35. package/lib/utils/remoteRequest.js +1 -0
  36. package/lib/utils/syntax.js +3 -0
  37. package/lib/utils/text.js +71 -3
  38. package/lib/utils/text.test.js +3 -9
  39. package/lib/utils/toc.js +315 -0
  40. package/package.json +7 -3
  41. package/lib/options-parser.js +0 -498
  42. package/lib/options-parser.test.js +0 -1237
  43. package/lib/utils/html-to-json/compat.js +0 -42
  44. package/lib/utils/html-to-json/format.js +0 -64
  45. package/lib/utils/html-to-json/index.js +0 -37
  46. package/lib/utils/html-to-json/lexer.js +0 -345
  47. package/lib/utils/html-to-json/parser.js +0 -146
  48. package/lib/utils/html-to-json/stringify.js +0 -37
  49. package/lib/utils/html-to-json/tags.js +0 -171
@@ -1,42 +0,0 @@
1
-
2
- function startsWith (str, searchString, position) {
3
- return str.substr(position || 0, searchString.length) === searchString
4
- }
5
-
6
- function endsWith (str, searchString, position) {
7
- const index = (position || str.length) - searchString.length
8
- const lastIndex = str.lastIndexOf(searchString, index)
9
- return lastIndex !== -1 && lastIndex === index
10
- }
11
-
12
- function stringIncludes (str, searchString, position) {
13
- return str.indexOf(searchString, position || 0) !== -1
14
- }
15
-
16
- function isRealNaN (x) {
17
- return typeof x === 'number' && isNaN(x)
18
- }
19
-
20
- function arrayIncludes (array, searchElement, position) {
21
- const len = array.length
22
- if (len === 0) return false
23
-
24
- const lookupIndex = position | 0
25
- const isNaNElement = isRealNaN(searchElement)
26
- let searchIndex = lookupIndex >= 0 ? lookupIndex : len + lookupIndex
27
- while (searchIndex < len) {
28
- const element = array[searchIndex++]
29
- if (element === searchElement) return true
30
- if (isNaNElement && isRealNaN(element)) return true
31
- }
32
-
33
- return false
34
- }
35
-
36
- module.exports = {
37
- startsWith,
38
- endsWith,
39
- stringIncludes,
40
- isRealNaN,
41
- arrayIncludes
42
- }
@@ -1,64 +0,0 @@
1
-
2
- function splitHead(str, sep) {
3
- const idx = str.indexOf(sep)
4
- if (idx === -1) return [str]
5
- return [str.slice(0, idx), str.slice(idx + sep.length)]
6
- }
7
-
8
- function unquote(str) {
9
- const car = str.charAt(0)
10
- const end = str.length - 1
11
- const isQuoteStart = car === '"' || car === "'"
12
- if (isQuoteStart && car === str.charAt(end)) {
13
- return str.slice(1, end)
14
- }
15
- return str
16
- }
17
-
18
- function format(nodes, options) {
19
- return nodes.map(node => {
20
- const type = node.type
21
- let outputNode = { type, content: node.content }
22
- if (type === 'element') {
23
- outputNode = {
24
- // TODO maybe harden with https://github.com/riot/dom-nodes
25
- type: (/^[A-Z]/.test(node.tagName)) ? 'component' : type,
26
- // isReactComponent: /^[A-Z]/.test(node.tagName),
27
- tagName: node.tagName,
28
- props: node.props,
29
- propsRaw: node.propsRaw,
30
- children: format(node.children, options)
31
- }
32
- }
33
- if (options.includePositions) {
34
- if (options.offset) {
35
- const { lineOffset, charOffset } = options.offset
36
- node.position.start.line = node.position.start.line + lineOffset
37
- node.position.start.index = node.position.start.index + charOffset
38
- node.position.end.line = node.position.end.line + lineOffset
39
- node.position.end.index = node.position.end.index + charOffset
40
- }
41
- outputNode.position = node.position
42
- }
43
- return outputNode
44
- })
45
- }
46
-
47
- // Old
48
- function formatAttributes (attributes) {
49
- return attributes.map(attribute => {
50
- const parts = splitHead(attribute.trim(), '=')
51
- const key = parts[0]
52
- const value = typeof parts[1] === 'string'
53
- ? unquote(parts[1])
54
- : null
55
- return {key, value}
56
- })
57
- }
58
-
59
- module.exports = {
60
- splitHead,
61
- unquote,
62
- format,
63
- formatAttributes
64
- }
@@ -1,37 +0,0 @@
1
- // Fork of https://github.com/andrejewski/himalaya with tweaks
2
- const { lexer } = require('./lexer')
3
- const { parser } = require('./parser')
4
- const { format } = require('./format')
5
- const { toHTML } = require('./stringify')
6
- const {
7
- voidTags,
8
- closingTags,
9
- childlessTags,
10
- closingTagAncestorBreakers
11
- } = require('./tags')
12
-
13
- const parseDefaults = {
14
- voidTags,
15
- closingTags,
16
- childlessTags,
17
- closingTagAncestorBreakers,
18
- includePositions: false
19
- }
20
-
21
- function parse(str, opts = {}) {
22
- const options = Object.assign(parseDefaults, opts)
23
- const tokens = lexer(str, options)
24
- const nodes = parser(tokens, options)
25
- return format(nodes, options)
26
- }
27
-
28
- function stringify(ast, opts = {}) {
29
- const options = Object.assign(parseDefaults, opts)
30
- return toHTML(ast, options)
31
- }
32
-
33
- module.exports = {
34
- parseDefaults,
35
- parse,
36
- stringify
37
- }
@@ -1,345 +0,0 @@
1
- const {
2
- startsWith,
3
- endsWith,
4
- stringIncludes,
5
- arrayIncludes
6
- } = require('./compat')
7
-
8
- function feedPosition(position, str, len) {
9
- const start = position.index
10
- const end = position.index = start + len
11
- for (let i = start; i < end; i++) {
12
- const char = str.charAt(i)
13
- if (char === '\n') {
14
- position.line++
15
- position.column = 0
16
- } else {
17
- position.column++
18
- }
19
- }
20
- }
21
-
22
- function jumpPosition (position, str, end) {
23
- const len = end - position.index
24
- return feedPosition(position, str, len)
25
- }
26
-
27
- function makeInitialPosition () {
28
- return {
29
- index: 0,
30
- column: 0,
31
- line: 1
32
- }
33
- }
34
-
35
- function copyPositionStart(position) {
36
- return {
37
- index: position.index,
38
- line: position.line,
39
- column: position.column + 1
40
- }
41
- }
42
-
43
- function copyPositionEnd(position) {
44
- return {
45
- index: position.index + 1,
46
- line: position.line,
47
- column: position.column + 1
48
- }
49
- }
50
-
51
- // Old offset was index/column was 1 off in some cases
52
- function copyPosition(position) {
53
- return {
54
- index: position.index,
55
- line: position.line,
56
- column: position.column
57
- }
58
- }
59
-
60
- function lexer (str, options) {
61
- const state = {
62
- str,
63
- options,
64
- position: makeInitialPosition(),
65
- tokens: []
66
- }
67
- lex(state)
68
- return state.tokens
69
- }
70
-
71
- function lex (state) {
72
- const {str, options: {childlessTags}} = state
73
- const len = str?.length || 0
74
- while (state.position.index < len) {
75
- const start = state.position.index
76
- lexText(state)
77
- if (state.position.index === start) {
78
- const isComment = startsWith(str, '!--', start + 1)
79
- if (isComment) {
80
- lexComment(state)
81
- } else {
82
- const tagName = lexTag(state)
83
- const safeTag = tagName.toLowerCase()
84
- if (arrayIncludes(childlessTags, safeTag)) {
85
- lexSkipTag(tagName, state)
86
- }
87
- }
88
- }
89
- }
90
- }
91
-
92
- const alphanumeric = /[A-Za-z0-9]/
93
- function findTextEnd (str, index) {
94
- while (true) {
95
- const textEnd = str.indexOf('<', index)
96
- if (textEnd === -1) {
97
- return textEnd
98
- }
99
- const char = str.charAt(textEnd + 1)
100
- if (char === '/' || char === '!' || alphanumeric.test(char)) {
101
- return textEnd
102
- }
103
- index = textEnd + 1
104
- }
105
- }
106
-
107
- function lexText (state) {
108
- const type = 'text'
109
- const {str, position} = state
110
- let textEnd = findTextEnd(str, position.index)
111
- if (textEnd === position.index) return
112
- if (textEnd === -1) {
113
- textEnd = str.length
114
- }
115
-
116
- const start = copyPositionStart(position)
117
- const content = str.slice(position.index, textEnd)
118
- jumpPosition(position, str, textEnd)
119
- const end = copyPositionEnd(position)
120
- state.tokens.push({type, content, position: {start, end}})
121
- }
122
-
123
- function lexComment (state) {
124
- const {str, position} = state
125
- const start = copyPositionStart(position)
126
- feedPosition(position, str, 4) // "<!--".length
127
- let contentEnd = str.indexOf('-->', position.index)
128
- let commentEnd = contentEnd + 3 // "-->".length
129
- if (contentEnd === -1) {
130
- contentEnd = commentEnd = str.length
131
- }
132
-
133
- const content = str.slice(position.index, contentEnd)
134
- jumpPosition(position, str, commentEnd)
135
- state.tokens.push({
136
- type: 'comment',
137
- content,
138
- position: {
139
- start,
140
- end: copyPositionEnd(position)
141
- }
142
- })
143
- }
144
-
145
- function lexTag (state) {
146
- const {str, position} = state
147
- {
148
- const secondChar = str.charAt(position.index + 1)
149
- const close = secondChar === '/'
150
- const start = copyPositionStart(position, 0)
151
- feedPosition(position, str, close ? 2 : 1)
152
- state.tokens.push({type: 'tag-start', close, position: {start}})
153
- }
154
- const tagName = lexTagName(state)
155
- lexTagAttributes(state)
156
- {
157
- const firstChar = str.charAt(position.index)
158
- const close = firstChar === '/'
159
- feedPosition(position, str, close ? 2 : 1)
160
- const end = copyPositionEnd(position)
161
- state.tokens.push({type: 'tag-end', close, position: {end}})
162
- }
163
- return tagName
164
- }
165
-
166
- // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#special-white-space
167
- const whitespace = /\s/
168
- function isWhitespaceChar (char) {
169
- return whitespace.test(char)
170
- }
171
-
172
- function lexTagName (state) {
173
- const {str, position} = state
174
- const len = str.length
175
- let start = position.index
176
- while (start < len) {
177
- const char = str.charAt(start)
178
- const isTagChar = !(isWhitespaceChar(char) || char === '/' || char === '>')
179
- if (isTagChar) break
180
- start++
181
- }
182
-
183
- let end = start + 1
184
- while (end < len) {
185
- const char = str.charAt(end)
186
- const isTagChar = !(isWhitespaceChar(char) || char === '/' || char === '>')
187
- if (!isTagChar) break
188
- end++
189
- }
190
-
191
- jumpPosition(position, str, end)
192
- const tagName = str.slice(start, end)
193
- state.tokens.push({
194
- type: 'tag',
195
- content: tagName
196
- })
197
- return tagName
198
- }
199
-
200
- function lexTagAttributes (state) {
201
- const {str, position, tokens} = state
202
- let cursor = position.index
203
- let quote = null // null, single-, or double-quote
204
- let wordBegin = cursor // index of word start
205
- const words = [] // "key", "key=value", "key='value'", etc
206
- const len = str.length
207
- while (cursor < len) {
208
- const char = str.charAt(cursor)
209
- if (quote) {
210
- const isQuoteEnd = char === quote
211
- if (isQuoteEnd) {
212
- quote = null
213
- }
214
- cursor++
215
- continue
216
- }
217
-
218
- const isTagEnd = char === '/' || char === '>'
219
- if (isTagEnd) {
220
- if (cursor !== wordBegin) {
221
- words.push(str.slice(wordBegin, cursor))
222
- }
223
- break
224
- }
225
-
226
- const isWordEnd = isWhitespaceChar(char)
227
- if (isWordEnd) {
228
- if (cursor !== wordBegin) {
229
- words.push(str.slice(wordBegin, cursor))
230
- }
231
- wordBegin = cursor + 1
232
- cursor++
233
- continue
234
- }
235
-
236
- const isQuoteStart = char === '\'' || char === '"'
237
- if (isQuoteStart) {
238
- quote = char
239
- cursor++
240
- continue
241
- }
242
-
243
- cursor++
244
- }
245
- jumpPosition(position, str, cursor)
246
-
247
- const wLen = words.length
248
- const type = 'attribute'
249
- for (let i = 0; i < wLen; i++) {
250
- const word = words[i]
251
- const isNotPair = word.indexOf('=') === -1
252
- if (isNotPair) {
253
- const secondWord = words[i + 1]
254
- if (secondWord && startsWith(secondWord, '=')) {
255
- if (secondWord.length > 1) {
256
- const newWord = word + secondWord
257
- tokens.push({type, content: newWord})
258
- i += 1
259
- continue
260
- }
261
- const thirdWord = words[i + 2]
262
- i += 1
263
- if (thirdWord) {
264
- const newWord = word + '=' + thirdWord
265
- tokens.push({type, content: newWord})
266
- i += 1
267
- continue
268
- }
269
- }
270
- }
271
- if (endsWith(word, '=')) {
272
- const secondWord = words[i + 1]
273
- if (secondWord && !stringIncludes(secondWord, '=')) {
274
- const newWord = word + secondWord
275
- tokens.push({type, content: newWord})
276
- i += 1
277
- continue
278
- }
279
-
280
- const newWord = word.slice(0, -1)
281
- tokens.push({type, content: newWord})
282
- continue
283
- }
284
-
285
- tokens.push({type, content: word})
286
- }
287
- }
288
-
289
- const push = [].push
290
-
291
- function lexSkipTag (tagName, state) {
292
- const {str, position, tokens} = state
293
- const safeTagName = tagName.toLowerCase()
294
- const len = str.length
295
- let index = position.index
296
- while (index < len) {
297
- const nextTag = str.indexOf('</', index)
298
- if (nextTag === -1) {
299
- lexText(state)
300
- break
301
- }
302
-
303
- const tagStartPosition = copyPositionStart(position)
304
- jumpPosition(tagStartPosition, str, nextTag)
305
- const tagState = {str, position: tagStartPosition, tokens: []}
306
- const name = lexTag(tagState)
307
- if (safeTagName !== name.toLowerCase()) {
308
- index = tagState.position.index
309
- continue
310
- }
311
-
312
- if (nextTag !== position.index) {
313
- const textStart = copyPositionStart(position)
314
- jumpPosition(position, str, nextTag)
315
- tokens.push({
316
- type: 'text',
317
- content: str.slice(textStart.index, nextTag),
318
- position: {
319
- start: textStart,
320
- end: copyPositionEnd(position)
321
- }
322
- })
323
- }
324
-
325
- push.apply(tokens, tagState.tokens)
326
- jumpPosition(position, str, tagState.position.index)
327
- break
328
- }
329
- }
330
-
331
- module.exports = {
332
- feedPosition,
333
- jumpPosition,
334
- makeInitialPosition,
335
- copyPosition,
336
- lexer,
337
- lex,
338
- findTextEnd,
339
- lexText,
340
- lexTag,
341
- isWhitespaceChar,
342
- lexTagName,
343
- lexTagAttributes,
344
- lexSkipTag,
345
- }
@@ -1,146 +0,0 @@
1
- const { optionsParse } = require('../../options-parser')
2
- const { arrayIncludes }= require('./compat')
3
-
4
- function parser (tokens, options) {
5
- const root = {tagName: null, children: []}
6
- const state = {tokens, options, cursor: 0, stack: [root]}
7
- parse(state)
8
- return root.children
9
- }
10
-
11
- function hasTerminalParent (tagName, stack, terminals) {
12
- const tagParents = terminals[tagName]
13
- if (tagParents) {
14
- let currentIndex = stack.length - 1
15
- while (currentIndex >= 0) {
16
- const parentTagName = stack[currentIndex].tagName
17
- if (parentTagName === tagName) {
18
- break
19
- }
20
- if (arrayIncludes(tagParents, parentTagName)) {
21
- return true
22
- }
23
- currentIndex--
24
- }
25
- }
26
- return false
27
- }
28
-
29
- function rewindStack (stack, newLength, childrenEndPosition, endPosition) {
30
- stack[newLength].position.end = endPosition
31
- for (let i = newLength + 1, len = stack.length; i < len; i++) {
32
- stack[i].position.end = childrenEndPosition
33
- }
34
- stack.splice(newLength)
35
- }
36
-
37
- function parse (state) {
38
- const {tokens, options} = state
39
- let {stack} = state
40
- let nodes = stack[stack.length - 1].children
41
- const len = tokens.length
42
- let {cursor} = state
43
- while (cursor < len) {
44
- const token = tokens[cursor]
45
- if (token.type !== 'tag-start') {
46
- nodes.push(token)
47
- cursor++
48
- continue
49
- }
50
-
51
- const tagToken = tokens[++cursor]
52
- cursor++
53
- const tagName = tagToken.content.toLowerCase()
54
- if (token.close) {
55
- let index = stack.length
56
- let shouldRewind = false
57
- while (--index > -1) {
58
- if (stack[index].tagName === tagName) {
59
- shouldRewind = true
60
- break
61
- }
62
- }
63
- while (cursor < len) {
64
- const endToken = tokens[cursor]
65
- if (endToken.type !== 'tag-end') break
66
- cursor++
67
- }
68
- if (shouldRewind) {
69
- rewindStack(stack, index, token.position.start, tokens[cursor - 1].position.end)
70
- break
71
- } else {
72
- continue
73
- }
74
- }
75
-
76
- const isClosingTag = arrayIncludes(options.closingTags, tagName)
77
- let shouldRewindToAutoClose = isClosingTag
78
- if (shouldRewindToAutoClose) {
79
- const { closingTagAncestorBreakers: terminals } = options
80
- shouldRewindToAutoClose = !hasTerminalParent(tagName, stack, terminals)
81
- }
82
-
83
- if (shouldRewindToAutoClose) {
84
- // rewind the stack to just above the previous
85
- // closing tag of the same name
86
- let currentIndex = stack.length - 1
87
- while (currentIndex > 0) {
88
- if (tagName === stack[currentIndex].tagName) {
89
- rewindStack(stack, currentIndex, token.position.start, token.position.start)
90
- const previousIndex = currentIndex - 1
91
- nodes = stack[previousIndex].children
92
- break
93
- }
94
- currentIndex = currentIndex - 1
95
- }
96
- }
97
-
98
- // let attributes = []
99
- let propsRaw = ''
100
- let attrToken
101
- while (cursor < len) {
102
- attrToken = tokens[cursor]
103
- if (attrToken.type === 'tag-end') break
104
- // attributes.push(attrToken.content)
105
- propsRaw+= ((propsRaw !== '') ? ' ' : '') + attrToken.content
106
- cursor++
107
- }
108
-
109
- cursor++
110
- const children = []
111
- const position = {
112
- start: token.position.start,
113
- end: attrToken.position.end
114
- }
115
- const elementNode = {
116
- type: 'element',
117
- tagName: tagToken.content,
118
- // attributes,
119
- props: (propsRaw) ? optionsParse(propsRaw) : {},
120
- propsRaw,
121
- children,
122
- position
123
- }
124
- nodes.push(elementNode)
125
-
126
- const hasChildren = !(attrToken.close || arrayIncludes(options.voidTags, tagName))
127
- if (hasChildren) {
128
- const size = stack.push({tagName, children, position})
129
- const innerState = {tokens, options, cursor, stack}
130
- parse(innerState)
131
- cursor = innerState.cursor
132
- const rewoundInElement = stack.length === size
133
- if (rewoundInElement) {
134
- elementNode.position.end = tokens[cursor - 1].position.end
135
- }
136
- }
137
- }
138
- state.cursor = cursor
139
- }
140
-
141
- module.exports = {
142
- parser,
143
- hasTerminalParent,
144
- rewindStack,
145
- parse,
146
- }
@@ -1,37 +0,0 @@
1
- const { arrayIncludes }= require('./compat')
2
-
3
- // Old
4
- function formatAttributes(attributes) {
5
- return attributes.reduce((attrs, attribute) => {
6
- const {key, value} = attribute
7
- if (value === null) {
8
- return `${attrs} ${key}`
9
- }
10
- const quoteEscape = value.indexOf('\'') !== -1
11
- const quote = quoteEscape ? '"' : '\''
12
- return `${attrs} ${key}=${quote}${value}${quote}`
13
- }, '')
14
- }
15
-
16
- function toHTML(tree, options) {
17
- return tree.map((node) => {
18
- if (node.type === 'text') {
19
- return node.content
20
- }
21
- if (node.type === 'comment') {
22
- return `<!--${node.content}-->`
23
- }
24
- const {tagName, propsRaw, children} = node
25
- // @TODO update prop parsing to keep new lines
26
- const propsString = (propsRaw) ? ` ${propsRaw}` : ''
27
- const isSelfClosing = arrayIncludes(options.voidTags, tagName.toLowerCase())
28
- return isSelfClosing
29
- ? `<${tagName}${propsString}>`
30
- : `<${tagName}${propsString}>${toHTML(children, options)}</${tagName}>`
31
- }).join('')
32
- }
33
-
34
- module.exports = {
35
- formatAttributes,
36
- toHTML
37
- }