markdown-magic 2.5.1 → 2.6.1
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 +20 -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/updateContents.js +8 -2
- 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 +17 -3
- 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 -37
- 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/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 -319
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ This `README.md` is generated with `markdown-magic` [view the raw file](https://
|
|
|
34
34
|
- [> CODE](#-code)
|
|
35
35
|
- [> FILE](#-file)
|
|
36
36
|
- [> REMOTE](#-remote)
|
|
37
|
+
- [Inline transforms](#inline-transforms)
|
|
37
38
|
- [🔌 Markdown magic plugins](#-markdown-magic-plugins)
|
|
38
39
|
- [Adding Custom Transforms](#adding-custom-transforms)
|
|
39
40
|
- [Plugin Example](#plugin-example)
|
|
@@ -227,6 +228,17 @@ Default `MATCHWORD` is `AUTO-GENERATED-CONTENT`
|
|
|
227
228
|
---
|
|
228
229
|
<!-- ⛔️ MD-MAGIC-EXAMPLE:END - Do not remove or modify this section -->
|
|
229
230
|
|
|
231
|
+
## Inline transforms
|
|
232
|
+
|
|
233
|
+
Any transform, including custom transforms can be used inline as well to insert content into paragraphs and other places.
|
|
234
|
+
|
|
235
|
+
The face symbol 👉 <!-- MD-MAGIC-EXAMPLE:START (INLINE_EXAMPLE) -->**⊂◉‿◉つ**<!-- MD-MAGIC-EXAMPLE:END --> is auto generated inline.
|
|
236
|
+
|
|
237
|
+
**Example:**
|
|
238
|
+
```md
|
|
239
|
+
<!-- AUTO-GENERATED-CONTENT:START (FILE:src=./path/to/file) -->xyz<!-- AUTO-GENERATED-CONTENT:END -->
|
|
240
|
+
```
|
|
241
|
+
|
|
230
242
|
## 🔌 Markdown magic plugins
|
|
231
243
|
|
|
232
244
|
* [wordcount](https://github.com/DavidWells/markdown-magic-wordcount/) - Add wordcount to markdown files
|
|
@@ -285,6 +297,9 @@ const config = {
|
|
|
285
297
|
})
|
|
286
298
|
return updatedContent.replace(/^\s+|\s+$/g, '')
|
|
287
299
|
},
|
|
300
|
+
INLINE_EXAMPLE: () => {
|
|
301
|
+
return '**⊂◉‿◉つ**'
|
|
302
|
+
},
|
|
288
303
|
/* Match <!-- AUTO-GENERATED-CONTENT:START (pluginExample) --> */
|
|
289
304
|
pluginExample: require('./plugin-example')({ addNewLine: true }),
|
|
290
305
|
/* Include plugins from NPM */
|
|
@@ -378,3 +393,8 @@ This section was generated by the cli config markdown.config.js file
|
|
|
378
393
|
|
|
379
394
|
- [Project using markdown-magic](https://github.com/search?o=desc&q=filename%3Apackage.json+%22markdown-magic%22&s=indexed&type=Code)
|
|
380
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
|
package/lib/updateContents.js
CHANGED
|
@@ -42,12 +42,18 @@ module.exports = async function updateContents(block, config) {
|
|
|
42
42
|
if (!newContent) {
|
|
43
43
|
newContent = originalContent
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
|
|
46
|
+
// If original block or new content contains a new line, preserve it
|
|
47
|
+
if (block.indexOf('\n') > -1 || newContent.indexOf('\n') > -1) {
|
|
48
|
+
return `${openingTag.openTag}
|
|
47
49
|
${newContent}
|
|
48
50
|
${closingTag.closeTag}`
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
// Block has no newline, inline contents
|
|
54
|
+
return `${openingTag.openTag}${newContent}${closingTag.closeTag}`
|
|
55
|
+
}
|
|
56
|
+
|
|
51
57
|
function parseOptions(options) {
|
|
52
58
|
if (!options) {
|
|
53
59
|
return null
|
|
@@ -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()
|