markdown-magic 2.5.2 → 2.6.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 +5 -0
- package/cli.js +0 -0
- package/lib/index.js +219 -0
- package/lib/processFile.js +2 -1
- package/lib/transforms/toc.js +5 -3
- package/lib/utils/_md.test.js +63 -0
- package/lib/utils/new-parser.js +412 -0
- package/lib/utils/new-parser.test.js +324 -0
- package/lib/utils/regex.js +20 -6
- package/lib/utils/weird-parse.js +230 -0
- package/lib/utils/weird-parse.test.js +217 -0
- package/package.json +16 -2
- package/.github/FUNDING.yml +0 -1
- package/.github/workflows/test.yml +0 -40
- package/.travis.yml +0 -10
- package/examples/basic-usage.js +0 -5
- package/examples/generate-readme.js +0 -40
- package/examples/package.json +0 -14
- package/examples/plugin-example.js +0 -16
- package/markdown.config.js +0 -13
- package/test/fixtures/CODE-test.md +0 -21
- package/test/fixtures/CUSTOM-async.md +0 -9
- package/test/fixtures/CUSTOM-test.md +0 -9
- package/test/fixtures/FILE-test.md +0 -11
- package/test/fixtures/INLINE-test.md +0 -13
- package/test/fixtures/REMOTE-test.md +0 -12
- package/test/fixtures/TOC-test.md +0 -39
- package/test/fixtures/custom-match-word-test.md +0 -9
- package/test/fixtures/local-code-file-lines.js +0 -6
- package/test/fixtures/local-code-file.js +0 -6
- package/test/fixtures/nested/file.md +0 -9
- package/test/fixtures/test.md +0 -432
- package/test/main.test.js +0 -343
package/README.md
CHANGED
|
@@ -393,3 +393,8 @@ This section was generated by the cli config markdown.config.js file
|
|
|
393
393
|
|
|
394
394
|
- [Project using markdown-magic](https://github.com/search?o=desc&q=filename%3Apackage.json+%22markdown-magic%22&s=indexed&type=Code)
|
|
395
395
|
- [Examples in md](https://github.com/search?l=Markdown&o=desc&q=AUTO-GENERATED-CONTENT&s=indexed&type=Code)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
## Misc Markdown helpers
|
|
399
|
+
|
|
400
|
+
- https://github.com/azu/markdown-function
|
package/cli.js
CHANGED
|
File without changes
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const fs = require('fs').promises
|
|
3
|
+
const { getTextBetween, parseBlocks } = require('./utils/new-parser')
|
|
4
|
+
|
|
5
|
+
async function processContents({
|
|
6
|
+
filePath,
|
|
7
|
+
syntax,
|
|
8
|
+
open,
|
|
9
|
+
close,
|
|
10
|
+
transforms,
|
|
11
|
+
beforeMiddelwares,
|
|
12
|
+
afterMiddelwares,
|
|
13
|
+
DEBUG = false
|
|
14
|
+
}) {
|
|
15
|
+
const syntaxType = syntax || path.extname(filePath).replace(/^\./, '')
|
|
16
|
+
const originalContents = await fs.readFile(filePath, 'utf8')
|
|
17
|
+
|
|
18
|
+
const foundBlocks = parseBlocks(originalContents, {
|
|
19
|
+
syntax: syntaxType,
|
|
20
|
+
open,
|
|
21
|
+
close,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
if (DEBUG) {
|
|
25
|
+
console.log('foundBlocks')
|
|
26
|
+
console.log(foundBlocks)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const transformsToRun = sortPlugins(foundBlocks.transforms)
|
|
30
|
+
// console.log('transformsToRun', transformsToRun)
|
|
31
|
+
|
|
32
|
+
// if (!transformsToRun.length) {
|
|
33
|
+
// process.exit(1)
|
|
34
|
+
// }
|
|
35
|
+
|
|
36
|
+
let missingTransforms = []
|
|
37
|
+
const updatedContents = await transformsToRun.reduce(async (updatedContent, ogmatch) => {
|
|
38
|
+
const md = await updatedContent
|
|
39
|
+
/* Apply Before middleware to all transforms */
|
|
40
|
+
const match = await applyMiddleware(ogmatch, md, beforeMiddelwares)
|
|
41
|
+
const { block, raw, transform, args } = match
|
|
42
|
+
const { openTag, closeTag, content, contentStart, contentEnd, start, end } = block
|
|
43
|
+
|
|
44
|
+
/* Run transform plugins */
|
|
45
|
+
let tempContent = content
|
|
46
|
+
if (transforms[transform]) {
|
|
47
|
+
tempContent = await transforms[transform](match, md)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Apply After middleware to all transforms */
|
|
51
|
+
const afterContent = await applyMiddleware({
|
|
52
|
+
...match,
|
|
53
|
+
...{
|
|
54
|
+
block: {
|
|
55
|
+
...match.block,
|
|
56
|
+
content: tempContent
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}, md, afterMiddelwares)
|
|
60
|
+
if (DEBUG) {
|
|
61
|
+
console.log('afterContent', afterContent)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!transforms[transform]) {
|
|
65
|
+
missingTransforms.push(afterContent)
|
|
66
|
+
console.log(`Missing "${transform}" transform`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const newContent = afterContent.block.content
|
|
70
|
+
const formattedNewContent = (args.noTrim) ? newContent : smartTrim(newContent)
|
|
71
|
+
/* Remove any conflicting imported comments */
|
|
72
|
+
// const fix = removeConflictingComments(formattedNewContent, foundBlocks.commentOpen, foundBlocks.commentClose)
|
|
73
|
+
const fix = stripAllComments(formattedNewContent, foundBlocks.commentOpen, foundBlocks.commentClose)
|
|
74
|
+
// console.log('foundBlocks.commentClose', foundBlocks.commentClose)
|
|
75
|
+
// console.log('formattedNewContent', formattedNewContent)
|
|
76
|
+
// console.log('fix', fix)
|
|
77
|
+
const preserveIndent = (true || args.preserveIndent) ? block.indentation.length + block.contentIndent : block.indentation.length
|
|
78
|
+
const indent = indentString(fix, preserveIndent)
|
|
79
|
+
const newCont = `${openTag}${indent}${closeTag}`
|
|
80
|
+
/* Replace original contents */
|
|
81
|
+
const newContents = md.replace(raw.block, newCont)
|
|
82
|
+
return Promise.resolve(newContents)
|
|
83
|
+
}, Promise.resolve(originalContents))
|
|
84
|
+
|
|
85
|
+
if (DEBUG) {
|
|
86
|
+
console.log('Output Markdown')
|
|
87
|
+
console.log(updatedContents)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
if (missingTransforms.length) {
|
|
92
|
+
console.log('missingTransforms', missingTransforms)
|
|
93
|
+
let matchOne = missingTransforms[1]
|
|
94
|
+
matchOne = missingTransforms[missingTransforms.length - 1]
|
|
95
|
+
// console.log('matchOne', matchOne)
|
|
96
|
+
const { block, transform, args } = matchOne
|
|
97
|
+
const { openTag, closeTag, content, contentStart, contentEnd, start, end} = block
|
|
98
|
+
|
|
99
|
+
// console.log('contentStart', contentStart)
|
|
100
|
+
// console.log('contentEnd', contentEnd)
|
|
101
|
+
// console.log('original text between', `"${getTextBetween(md, contentStart, contentEnd)}"`)
|
|
102
|
+
// console.log('original block between', `"${getTextBetween(md, start, end)}"`)
|
|
103
|
+
}
|
|
104
|
+
/** */
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
filePath,
|
|
108
|
+
transforms: transformsToRun,
|
|
109
|
+
missingTransforms,
|
|
110
|
+
originalContents,
|
|
111
|
+
updatedContents
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function applyMiddleware(data, md, middlewares) {
|
|
116
|
+
return middlewares.reduce(async (acc, curr) => {
|
|
117
|
+
const realAcc = await acc
|
|
118
|
+
// console.log(`Running "${curr.name}" Middleware on "${realAcc.transform}" block`)
|
|
119
|
+
const updatedContent = await curr.transform(realAcc, md)
|
|
120
|
+
// realAcc.block.content = updatedContent
|
|
121
|
+
return Promise.resolve({
|
|
122
|
+
...realAcc,
|
|
123
|
+
...{
|
|
124
|
+
block: {
|
|
125
|
+
...realAcc.block,
|
|
126
|
+
content: updatedContent
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}, Promise.resolve(data))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Trim leading & trailing spaces/line breaks in code and keeps the indentation of the first non-empty line
|
|
135
|
+
* @param {string} str
|
|
136
|
+
* @returns string
|
|
137
|
+
*/
|
|
138
|
+
function smartTrim(str) {
|
|
139
|
+
return str.replace(/^(?:[\t ]*(?:\r?\n|\r))+|\s+$/g, '')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function stripAllComments(block) {
|
|
143
|
+
const pattern = new RegExp(`([^\\s]*)?([ \\t]*)?(<!-+\\s?([\\s\\S]*?)?-+>)([^\\s<]*)?(\n{1,2})?`, 'gi')
|
|
144
|
+
|
|
145
|
+
// console.log('closeTagRegex', closeTagRegex)
|
|
146
|
+
let matches
|
|
147
|
+
let remove = []
|
|
148
|
+
while ((matches = pattern.exec(block)) !== null) {
|
|
149
|
+
if (matches.index === pattern.lastIndex) {
|
|
150
|
+
pattern.lastIndex++ // avoid infinite loops with zero-width matches
|
|
151
|
+
}
|
|
152
|
+
const [ match, leadingText, leadingSpace, comment, insideComment, trailingText, trailingNewLine ] = matches
|
|
153
|
+
/*
|
|
154
|
+
console.log('match', match)
|
|
155
|
+
console.log('leadingText', leadingText)
|
|
156
|
+
console.log('leadingSpace', leadingSpace)
|
|
157
|
+
console.log('comment', comment)
|
|
158
|
+
console.log('insideComment', insideComment)
|
|
159
|
+
console.log('trailingText', trailingText)
|
|
160
|
+
console.log('trailingNewLine', trailingNewLine)
|
|
161
|
+
/** */
|
|
162
|
+
const newLineCount = (trailingNewLine || '').length
|
|
163
|
+
const trailing = (!trailingText && newLineCount > 1) ? `${trailingNewLine || ''}` : ''
|
|
164
|
+
const leading = (leadingSpace) ? leadingSpace.slice(1) : ''
|
|
165
|
+
remove.push(`${leading}${comment}${trailing}`)
|
|
166
|
+
}
|
|
167
|
+
return remove.reduce((acc, curr) => {
|
|
168
|
+
return acc.replaceAll(curr, '')
|
|
169
|
+
}, block)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Remove conflicting comments that might have been inserted from transforms
|
|
174
|
+
* @param {*} content
|
|
175
|
+
* @param {*} openPattern
|
|
176
|
+
* @param {*} closePattern
|
|
177
|
+
* @returns
|
|
178
|
+
*/
|
|
179
|
+
function removeConflictingComments(content, openPattern, closePattern) {
|
|
180
|
+
const removeOpen = content.replace(openPattern, '')
|
|
181
|
+
// TODO this probably needs to be a loop for larger blocks
|
|
182
|
+
closePattern.lastIndex = 0; // reset regex
|
|
183
|
+
const hasClose = closePattern.exec(content)
|
|
184
|
+
// console.log('closePattern', closePattern)
|
|
185
|
+
// console.log('has', content)
|
|
186
|
+
// console.log('hasClose', hasClose)
|
|
187
|
+
if (!hasClose) {
|
|
188
|
+
return removeOpen
|
|
189
|
+
}
|
|
190
|
+
const closeTag = `${hasClose[2]}${hasClose[3] || ''}`
|
|
191
|
+
// console.log('closeTag', closeTag)
|
|
192
|
+
return removeOpen
|
|
193
|
+
.replaceAll(closeTag, '')
|
|
194
|
+
/* Trailing new line */
|
|
195
|
+
.replace(/\n$/, '')
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function sortPlugins(data) {
|
|
199
|
+
return data.sort((a, b) => {
|
|
200
|
+
// put table of contents (TOC) at end of tranforms
|
|
201
|
+
if (a.transform === 'TOC') return 1
|
|
202
|
+
if (b.transform === 'TOC') return -1
|
|
203
|
+
return 0
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function indentString(string, count = 1, options = {}) {
|
|
208
|
+
const {
|
|
209
|
+
indent = ' ',
|
|
210
|
+
includeEmptyLines = false
|
|
211
|
+
} = options;
|
|
212
|
+
if (count === 0) return string
|
|
213
|
+
const regex = includeEmptyLines ? /^/gm : /^(?!\s*$)/gm
|
|
214
|
+
return string.replace(regex, indent.repeat(count))
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = {
|
|
218
|
+
processContents
|
|
219
|
+
}
|
package/lib/processFile.js
CHANGED
|
@@ -38,7 +38,7 @@ module.exports = async function processFile(filePath, config) {
|
|
|
38
38
|
matchWord: 'AUTO-GENERATED-CONTENT',
|
|
39
39
|
/**
|
|
40
40
|
* - `DEBUG` - *Boolean* - (optional) set debug flag to `true` to inspect the process
|
|
41
|
-
* @type {
|
|
41
|
+
* @type {boolean}
|
|
42
42
|
*/
|
|
43
43
|
DEBUG: false,
|
|
44
44
|
}
|
|
@@ -55,6 +55,7 @@ module.exports = async function processFile(filePath, config) {
|
|
|
55
55
|
mergedConfig.outputContent = content
|
|
56
56
|
|
|
57
57
|
const regex = regexUtils.matchCommentBlock(mergedConfig.matchWord)
|
|
58
|
+
// console.log(regex)
|
|
58
59
|
const match = content.match(regex)
|
|
59
60
|
const transformsFound = []
|
|
60
61
|
|
package/lib/transforms/toc.js
CHANGED
|
@@ -69,11 +69,11 @@ module.exports = async function TOC(content, options, config) {
|
|
|
69
69
|
const matchTextEscaped = escapeStringRegexp(matchText)
|
|
70
70
|
// console.log('matchTextEscaped', matchTextEscaped)
|
|
71
71
|
// /^#{1}\s+(.*)/
|
|
72
|
-
const FIRST_H1_REGEX = new RegExp(`^#\\s
|
|
72
|
+
const FIRST_H1_REGEX = new RegExp(`^#\\s*\\[?${matchTextEscaped}\\]?(?:.*)?`, 'gim')
|
|
73
73
|
// /^<h1\b[^>]*>([\s\S]*?)<\/h1>/
|
|
74
|
-
const FIRST_HTML_H1_REGEX = new RegExp(`^<h1\\b[^>]*>[\\s]*?(${matchTextEscaped})[\\s]*?<\\/h1>`, 'gim')
|
|
74
|
+
const FIRST_HTML_H1_REGEX = new RegExp(`^<h1\\b[^>]*>[\\s]*?(${matchTextEscaped})[\\s]*?<\\/h1>`, 'gim')
|
|
75
75
|
// /^(.*)\n={3,}/
|
|
76
|
-
const FIRST_UNDERSCORE_H1 = new RegExp(`^(${matchTextEscaped})\n={3,}`, 'gim')
|
|
76
|
+
const FIRST_UNDERSCORE_H1 = new RegExp(`^(${matchTextEscaped})\n={3,}`, 'gim')
|
|
77
77
|
|
|
78
78
|
let docHasHeading = false
|
|
79
79
|
if (contents.match(FIRST_H1_REGEX)) {
|
|
@@ -262,3 +262,5 @@ function removeUndefined(output) {
|
|
|
262
262
|
// remove undefined from new line and start of string if first H1 is missing
|
|
263
263
|
return output.replace(/\nundefined/g, '\n-').replace(/^undefined/g, '-')
|
|
264
264
|
}
|
|
265
|
+
|
|
266
|
+
// Alt https://github.com/thlorenz/doctoc
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const { test } = require('uvu')
|
|
2
|
+
const assert = require('uvu/assert')
|
|
3
|
+
const { parseBlocks, replaceContent } = require('./new-parser')
|
|
4
|
+
|
|
5
|
+
const defaultOpts = {
|
|
6
|
+
syntax: 'md',
|
|
7
|
+
open: 'DOCS:START',
|
|
8
|
+
close: 'DOCS:END',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
test('Returns empty array', () => {
|
|
12
|
+
assert.equal(parseBlocks('', defaultOpts).transforms, [])
|
|
13
|
+
assert.equal(parseBlocks(' ', defaultOpts).transforms, [])
|
|
14
|
+
assert.equal(parseBlocks(`
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
`, defaultOpts).transforms, [])
|
|
18
|
+
assert.equal(parseBlocks(`
|
|
19
|
+
# No block in here
|
|
20
|
+
|
|
21
|
+
nope
|
|
22
|
+
`, defaultOpts).transforms, [])
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const md = `
|
|
26
|
+
Very nice
|
|
27
|
+
|
|
28
|
+
<!-- DOCS:START(TOC) foo={{ rad: 'orange' }} ------>
|
|
29
|
+
ok
|
|
30
|
+
<!-- DOCS:END -->`
|
|
31
|
+
|
|
32
|
+
test('Parse md blocks', () => {
|
|
33
|
+
const parsedValue = parseBlocks(md, defaultOpts)
|
|
34
|
+
console.log('parsedValue', parsedValue)
|
|
35
|
+
assert.equal(parsedValue.transforms, [
|
|
36
|
+
{
|
|
37
|
+
transform: 'TOC',
|
|
38
|
+
args: { foo: { rad: 'orange' } },
|
|
39
|
+
block: {
|
|
40
|
+
indentation: '',
|
|
41
|
+
start: 12,
|
|
42
|
+
end: 85,
|
|
43
|
+
contentStart: 64,
|
|
44
|
+
contentEnd: 68,
|
|
45
|
+
contentIndent: 0,
|
|
46
|
+
openTag: "<!-- DOCS:START(TOC) foo={{ rad: 'orange' }} ------>\n",
|
|
47
|
+
content: 'ok',
|
|
48
|
+
closeTag: '\n<!-- DOCS:END -->'
|
|
49
|
+
},
|
|
50
|
+
raw: {
|
|
51
|
+
transform: '(TOC)',
|
|
52
|
+
args: "foo={{ rad: 'orange' }} ----",
|
|
53
|
+
content: '\nok\n',
|
|
54
|
+
block: "<!-- DOCS:START(TOC) foo={{ rad: 'orange' }} ------>\n" +
|
|
55
|
+
'ok\n' +
|
|
56
|
+
'<!-- DOCS:END -->'
|
|
57
|
+
},
|
|
58
|
+
meta: { isMultiline: true }
|
|
59
|
+
}
|
|
60
|
+
], '')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test.run()
|