markdown-magic 2.6.1 → 3.0.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 +47 -37
- package/cli.js +5 -82
- package/lib/block-parser-js.test.js +171 -0
- package/lib/block-parser.js +382 -0
- package/lib/block-parser.test.js +479 -0
- package/lib/cli.js +245 -0
- package/lib/cli.test.js +409 -0
- package/lib/defaults.js +12 -0
- package/lib/globals.d.ts +66 -0
- package/lib/index.js +353 -184
- package/lib/index.test.js +11 -0
- package/lib/process-contents.js +371 -0
- package/lib/process-file.js +37 -0
- package/lib/transforms/code.js +67 -28
- package/lib/transforms/file.js +17 -17
- package/lib/transforms/index.js +0 -114
- package/lib/transforms/remote.js +8 -6
- package/lib/transforms/sectionToc.js +18 -0
- package/lib/transforms/toc.js +12 -265
- package/lib/transforms/wordCount.js +5 -0
- package/lib/types.js +11 -0
- package/lib/utils/fs.js +342 -0
- package/lib/utils/fs.test.js +267 -0
- package/lib/utils/index.js +19 -0
- package/{cli-utils.js → lib/utils/load-config.js} +2 -6
- package/lib/utils/logs.js +94 -0
- package/lib/utils/md/filters.js +20 -0
- package/lib/utils/md/find-code-blocks.js +88 -0
- package/lib/utils/md/find-date.js +32 -0
- package/lib/utils/md/find-frontmatter.js +92 -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-md.js +27 -0
- package/lib/utils/md/find-images.js +107 -0
- package/lib/utils/md/find-links.js +220 -0
- package/lib/utils/md/find-unmatched-html-tags.js +32 -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 +153 -0
- package/lib/utils/md/md.test.js +105 -0
- package/lib/utils/md/parse.js +146 -0
- package/lib/utils/md/utils.js +19 -0
- package/lib/utils/regex-timeout.js +84 -0
- package/lib/utils/regex.js +40 -6
- package/lib/utils/remoteRequest.js +55 -0
- package/lib/utils/syntax.js +82 -0
- package/lib/utils/text.js +328 -0
- package/lib/utils/text.test.js +305 -0
- package/lib/utils/toc.js +315 -0
- package/package.json +30 -26
- package/index.js +0 -46
- package/lib/processFile.js +0 -154
- 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/new-parser.test.js +0 -324
- package/lib/utils/weird-parse.js +0 -230
- package/lib/utils/weird-parse.test.js +0 -217
package/lib/utils/toc.js
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
const { transform } = require('@technote-space/doctoc')
|
|
2
|
+
const { removeLeadingAndTrailingLineBreaks, escapeRegexString } = require('./regex')
|
|
3
|
+
const { findMinIndent } = require('./text')
|
|
4
|
+
const { readFile } = require('./fs')
|
|
5
|
+
|
|
6
|
+
// Alt https://github.com/thlorenz/doctoc
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} TocOptions
|
|
10
|
+
* @property {boolean} [collapse = false] - Collapse toc in <details> pane
|
|
11
|
+
* @property {string} [collapseText] - Text in expand pane
|
|
12
|
+
* @property {string} [excludeText] - Text to exclude from toc
|
|
13
|
+
* @property {boolean} [firsth1 = true] - Exclude first heading from toc
|
|
14
|
+
* @property {boolean} [sub = false] - Mark as sub section table of contents
|
|
15
|
+
* @property {number} [maxDepth = 4] - Max depth of headings to add to toc.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {object} TocAPI
|
|
20
|
+
* @property {TocOptions} options - Toc generation options
|
|
21
|
+
* @property {string} fileContent - Markdown contents
|
|
22
|
+
* @property {string} srcPath - Path to markdown file
|
|
23
|
+
* @property {function} getBlockDetails - Get block details for sub tables
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate a table of contents
|
|
28
|
+
* @param {TocAPI} api
|
|
29
|
+
* @returns
|
|
30
|
+
*/
|
|
31
|
+
async function generateToc({
|
|
32
|
+
options = {},
|
|
33
|
+
fileContent,
|
|
34
|
+
srcPath,
|
|
35
|
+
getBlockDetails
|
|
36
|
+
}) {
|
|
37
|
+
// const currentBlock = api.getCurrentBlock()
|
|
38
|
+
// console.log('originalBlock', originalBlock)
|
|
39
|
+
|
|
40
|
+
// process.exit(1)
|
|
41
|
+
const opts = options || {}
|
|
42
|
+
const isSub = opts.sub
|
|
43
|
+
opts.firsth1 = (opts.firsth1) ? true : false
|
|
44
|
+
let contents = fileContent
|
|
45
|
+
// console.log('contents', contents)
|
|
46
|
+
|
|
47
|
+
let collapseText = opts.collapseText
|
|
48
|
+
|
|
49
|
+
let debugFileMatch
|
|
50
|
+
// console.log('config', config.originalPath)
|
|
51
|
+
// debugFileMatch = config.originalPath.match(/packages\/analytics\/README\.md/)
|
|
52
|
+
|
|
53
|
+
// https://www.npmjs.com/package/@technote-space/toc-generator
|
|
54
|
+
const tocOptions = {
|
|
55
|
+
// mode: 'github.com', // github.com | bitbucket.org | gitlab.com | nodejs.org | ghost.org (default: github.com)
|
|
56
|
+
isNotitle: true,
|
|
57
|
+
//isCustomMode: true,
|
|
58
|
+
openingComment: '',
|
|
59
|
+
closingComment: '',
|
|
60
|
+
maxHeaderLevel: (opts.maxDepth) ? Number(opts.maxDepth) : 4,
|
|
61
|
+
// maxHeaderLevel: 2, // default: 4
|
|
62
|
+
// title: '**Table of Contents**',
|
|
63
|
+
// isNotitle: true,
|
|
64
|
+
// isFolding: true,
|
|
65
|
+
// entryPrefix: '*',
|
|
66
|
+
// processAll: true,
|
|
67
|
+
// updateOnly: true,
|
|
68
|
+
// openingComment: '<!-- toc -->',
|
|
69
|
+
// closingComment: '<!-- tocstop --> ',
|
|
70
|
+
// checkOpeningComments: ['<!-- toc '],
|
|
71
|
+
// checkClosingComments: ['<!-- tocstop '],
|
|
72
|
+
// isCustomMode: false,
|
|
73
|
+
// customTemplate: '<p align="center">${ITEMS}</p>',
|
|
74
|
+
// itemTemplate: '<a href="${LINK}">${TEXT}</a>',
|
|
75
|
+
// separator: '<span>|</span>',
|
|
76
|
+
// footer: 'end',
|
|
77
|
+
}
|
|
78
|
+
const t = await transform(contents, tocOptions)
|
|
79
|
+
let outputText = t.wrappedToc || ''
|
|
80
|
+
|
|
81
|
+
if (debugFileMatch) {
|
|
82
|
+
console.log('before firsth1 removal', outputText)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* remove first h1 */
|
|
86
|
+
if (!opts.firsth1) {
|
|
87
|
+
// console.log('outputText', outputText)
|
|
88
|
+
const lines = outputText.split('\n')
|
|
89
|
+
let firstH1
|
|
90
|
+
let firstLine
|
|
91
|
+
const countOfH1s = lines.reduce((acc, line, index) => {
|
|
92
|
+
if (line !== '' && !firstLine) {
|
|
93
|
+
firstLine = index
|
|
94
|
+
}
|
|
95
|
+
if (line.match(/^-\s+/)) {
|
|
96
|
+
if (!firstH1) {
|
|
97
|
+
const headingText = line.match(/\[(.*)\]/)
|
|
98
|
+
if (headingText) {
|
|
99
|
+
const rawText = headingText[1]
|
|
100
|
+
firstH1 = [line, index, firstLine === index, rawText]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
acc = acc + 1
|
|
104
|
+
}
|
|
105
|
+
return acc
|
|
106
|
+
}, 0)
|
|
107
|
+
|
|
108
|
+
const firstHeadingData = (firstH1 && Array.isArray(firstH1)) ? firstH1 : []
|
|
109
|
+
const [ lineText, lineIndex, isFirst, matchText ] = firstHeadingData
|
|
110
|
+
|
|
111
|
+
let docHasHeading = false
|
|
112
|
+
if (matchText) {
|
|
113
|
+
// verify its h1
|
|
114
|
+
const matchTextEscaped = escapeRegexString(matchText)
|
|
115
|
+
// console.log('matchTextEscaped', matchTextEscaped)
|
|
116
|
+
// /^#{1}\s+(.*)/
|
|
117
|
+
const FIRST_H1_REGEX = new RegExp(`^#\\s*\\[?${matchTextEscaped}\\]?(?:.*)?`, 'gim')
|
|
118
|
+
// /^<h1\b[^>]*>([\s\S]*?)<\/h1>/
|
|
119
|
+
const FIRST_HTML_H1_REGEX = new RegExp(`^<h1\\b[^>]*>[\\s]*?(${matchTextEscaped})[\\s]*?<\\/h1>`, 'gim')
|
|
120
|
+
// /^(.*)\n={3,}/
|
|
121
|
+
const FIRST_UNDERSCORE_H1 = new RegExp(`^(${matchTextEscaped})\n={3,}`, 'gim')
|
|
122
|
+
|
|
123
|
+
if (contents.match(FIRST_H1_REGEX)) {
|
|
124
|
+
docHasHeading = matchText
|
|
125
|
+
} else if (contents.match(FIRST_HTML_H1_REGEX)) {
|
|
126
|
+
docHasHeading = matchText
|
|
127
|
+
} else if (contents.match(FIRST_UNDERSCORE_H1)) {
|
|
128
|
+
docHasHeading = matchText
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (debugFileMatch) {
|
|
133
|
+
console.log('matchText', matchText)
|
|
134
|
+
if (docHasHeading) {
|
|
135
|
+
console.log('Found heading 1', docHasHeading)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (debugFileMatch) {
|
|
140
|
+
console.log('top level toc item count', countOfH1s)
|
|
141
|
+
if (docHasHeading) {
|
|
142
|
+
console.log('Found heading 1', docHasHeading)
|
|
143
|
+
console.log('firstH1', firstH1)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let firstItemWithContent
|
|
148
|
+
let foundFirstH1
|
|
149
|
+
|
|
150
|
+
if (docHasHeading) {
|
|
151
|
+
outputText = lines.reduce((acc, line, i) => {
|
|
152
|
+
const isLineEmpty = line === ''
|
|
153
|
+
if (!isLineEmpty && !firstItemWithContent) {
|
|
154
|
+
firstItemWithContent = i
|
|
155
|
+
}
|
|
156
|
+
if (!foundFirstH1 && (firstItemWithContent === i) && line.match(/^-\s+/)) {
|
|
157
|
+
foundFirstH1 = true
|
|
158
|
+
return acc
|
|
159
|
+
}
|
|
160
|
+
// Add back line and remove level
|
|
161
|
+
let newLineValue = line
|
|
162
|
+
if (lineText) {
|
|
163
|
+
// const untilFirstH1 = i < lineIndex
|
|
164
|
+
/* @TODO make option? flatten all non first h1 tags */
|
|
165
|
+
if (countOfH1s > 0 && !isLineEmpty && newLineValue.match(/^\s+-/)) {
|
|
166
|
+
newLineValue = line.substring(2)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
acc = acc.concat(newLineValue)
|
|
171
|
+
return acc
|
|
172
|
+
}, []).join('\n')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// console.log('outputText', outputText)
|
|
176
|
+
if (debugFileMatch) {
|
|
177
|
+
console.log('after firsth1 removal', outputText)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// console.log(t)
|
|
181
|
+
// Fix <h1> with multiple lines
|
|
182
|
+
const spaces = outputText.match(/\[\n([\s\S]*?)\]/gm)
|
|
183
|
+
if (spaces) {
|
|
184
|
+
const fixed = spaces[0]
|
|
185
|
+
// all new lines
|
|
186
|
+
.replace(/\n/g, '')
|
|
187
|
+
// leading spaces
|
|
188
|
+
.replace(/\[\s+/, '[')
|
|
189
|
+
.trim()
|
|
190
|
+
outputText = outputText.replace(spaces[0], fixed)
|
|
191
|
+
}
|
|
192
|
+
// console.log(outputText)
|
|
193
|
+
outputText = outputText
|
|
194
|
+
.replace(/(.*)\[Table of Contents\]\(.*\)\n?/i, '')
|
|
195
|
+
.replace(/(.*)\[toc\]\(.*\)\n?/i, '')
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
if (opts.excludeText) {
|
|
199
|
+
|
|
200
|
+
outputText = excludeTocItem(outputText, opts.excludeText)
|
|
201
|
+
// // (\s+)?-(.*)\[Usage\]\(.*\)
|
|
202
|
+
// const regex = new RegExp(`\(\\s+\)?-(.*)\\[${opts.excludeText}\\]\\(.*\\)`, 'i')
|
|
203
|
+
// // /(\s+)?-(.*)\[Usage\]\(.*\)(\n\s+(.*))+/im
|
|
204
|
+
// const nestedRegex = new RegExp(`\(\\s+\)?-(.*)\\[${opts.excludeText}\\]\\(.*\\)\(\\n\\s+\(.*\)\)+`, 'i')
|
|
205
|
+
|
|
206
|
+
// const hasNested = nestedRegex.exec(outputText)
|
|
207
|
+
// if (hasNested) {
|
|
208
|
+
// // Count indentation of spaces
|
|
209
|
+
// const numberOfSpaces = hasNested[1].replace(/\n/g, '').length
|
|
210
|
+
// const subItems = numberOfSpaces + 1
|
|
211
|
+
// // Update regex to only remove sub items
|
|
212
|
+
// const nestedRegexSpaces = new RegExp(`\(\\s+\)?-(.*)\\[${opts.excludeText}\\]\\(.*\\)\(\\n\\s{${subItems},}\(.*\)\)+`, 'i')
|
|
213
|
+
// // console.log('nestedRegexSpaces', nestedRegexSpaces)
|
|
214
|
+
// // If exclude value has nested sections remove them as well.
|
|
215
|
+
// outputText = outputText.replace(nestedRegexSpaces, '')
|
|
216
|
+
// outputText = outputText.replace(regex, '')
|
|
217
|
+
// } else {
|
|
218
|
+
// outputText = outputText.replace(regex, '')
|
|
219
|
+
// }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* Sub table of contents */
|
|
223
|
+
if (isSub) {
|
|
224
|
+
// const start = fileContent.indexOf(open.value)
|
|
225
|
+
// const linesBefore = fileContent.substr(0, start).split('\n')
|
|
226
|
+
// const closestHeading = linesBefore.reverse().find((line) => {
|
|
227
|
+
// return line.match((/^#+/))
|
|
228
|
+
// })
|
|
229
|
+
const originalContents = await readFile(srcPath, { encoding: 'utf8' })
|
|
230
|
+
const originalBlock = getBlockDetails(originalContents)
|
|
231
|
+
const linesBefore = originalContents.substr(0, originalBlock.block.start).split('\n')
|
|
232
|
+
const closestHeading = linesBefore.reverse().find((line) => {
|
|
233
|
+
return line.match((/^#+/))
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
if (closestHeading) {
|
|
237
|
+
const headingText = closestHeading.replace(/^#*\s*/, '')
|
|
238
|
+
// console.log('BEFORE', linesBefore)
|
|
239
|
+
// console.log('closest parent heading', closestHeading)
|
|
240
|
+
// console.log('headingText', headingText)
|
|
241
|
+
collapseText = collapseText || `${headingText} contents`
|
|
242
|
+
// https://regex101.com/r/MB85zm/1
|
|
243
|
+
|
|
244
|
+
const findSubToc = new RegExp(`^\(\\s+\)?-(.*)\\[${headingText}\\]\\(.*\\)(\\n\\s.*)+`, 'gm')
|
|
245
|
+
// console.log('findSubToc', findSubToc)
|
|
246
|
+
const single = singleLinePattern(headingText)
|
|
247
|
+
// console.log('single', single)
|
|
248
|
+
const subItems = outputText.match(findSubToc)
|
|
249
|
+
if (subItems) {
|
|
250
|
+
const items = subItems[0].replace(single, '').split('\n')
|
|
251
|
+
.filter(Boolean)
|
|
252
|
+
// console.log('items', items)
|
|
253
|
+
const finalItems = items // .slice(1, items.length)
|
|
254
|
+
if (finalItems) {
|
|
255
|
+
const indent = findMinIndent(finalItems.join('\n'))
|
|
256
|
+
// console.log('min indent', indent)
|
|
257
|
+
// console.log('finalItems', finalItems)
|
|
258
|
+
outputText = finalItems.map((thing) => thing.substring(indent)).join('\n')
|
|
259
|
+
} else {
|
|
260
|
+
// console.log('No sub items')
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// If collapse wrap in <details>
|
|
267
|
+
if (opts.collapse) {
|
|
268
|
+
return `<details>
|
|
269
|
+
<summary>${collapseText || 'Table of Contents'}</summary>
|
|
270
|
+
|
|
271
|
+
${outputText
|
|
272
|
+
// Replace leading double spaces
|
|
273
|
+
.replace(/^\n*/g, '')
|
|
274
|
+
// Replace trailing double spaces
|
|
275
|
+
.replace(/\n*$/g, '')
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
</details>`
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return outputText.replace(removeLeadingAndTrailingLineBreaks, '')
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function singleLinePattern(text) {
|
|
285
|
+
/* (\s+)?-(.*)\[Usage\]\(.*\) */
|
|
286
|
+
return new RegExp(`\(\\s+\)?-(.*)\\[${text}\\]\\(.*\\)`, 'i')
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function excludeTocItem(str, excludeText) {
|
|
290
|
+
const matchTextEscaped = escapeRegexString(excludeText)
|
|
291
|
+
/* (\s+)?-(.*)\[Usage\]\(.*\) */
|
|
292
|
+
const regex = singleLinePattern(matchTextEscaped) // new RegExp(`\(\\s+\)?-(.*)\\[${matchTextEscaped}\\]\\(.*\\)`, 'i')
|
|
293
|
+
/* /(\s+)?-(.*)\[Usage\]\(.*\)(\n\s+(.*))+/im */
|
|
294
|
+
const nestedRegex = new RegExp(`\(\\s+\)?-(.*)\\[${matchTextEscaped}\\]\\(.*\\)\(\\n\\s+\(.*\)\)+`, 'i')
|
|
295
|
+
|
|
296
|
+
const hasNested = nestedRegex.exec(str)
|
|
297
|
+
if (hasNested) {
|
|
298
|
+
// Count indentation of spaces
|
|
299
|
+
const numberOfSpaces = (hasNested[1] || '').replace(/\n/g, '').length
|
|
300
|
+
const subItems = numberOfSpaces + 1
|
|
301
|
+
// Update regex to only remove sub items
|
|
302
|
+
const nestedRegexSpaces = new RegExp(`\(\\s+\)?-(.*)\\[${matchTextEscaped}\\]\\(.*\\)\(\\n\\s{${subItems},}\(.*\)\)+`, 'i')
|
|
303
|
+
// console.log('nestedRegexSpaces', nestedRegexSpaces)
|
|
304
|
+
// If exclude value has nested sections remove them as well.
|
|
305
|
+
str = str.replace(nestedRegexSpaces, '')
|
|
306
|
+
str = str.replace(regex, '')
|
|
307
|
+
} else {
|
|
308
|
+
str = str.replace(regex, '')
|
|
309
|
+
}
|
|
310
|
+
return str
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
module.exports = {
|
|
314
|
+
generateToc
|
|
315
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "markdown-magic",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Automatically update markdown files with content from external sources",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"markdown": "./cli.js",
|
|
8
8
|
"md-magic": "./cli.js"
|
|
@@ -10,18 +10,27 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"README.md",
|
|
12
12
|
"package.json",
|
|
13
|
-
"
|
|
13
|
+
"package-lock.json",
|
|
14
14
|
"cli.js",
|
|
15
|
-
"cli-utils.js",
|
|
16
15
|
"/lib"
|
|
17
16
|
],
|
|
18
17
|
"scripts": {
|
|
18
|
+
"types": "tsc",
|
|
19
|
+
"emit-types": "tsc --noEmit false --emitDeclarationOnly true",
|
|
19
20
|
"docs": "node examples/generate-readme.js",
|
|
20
|
-
"
|
|
21
|
-
"test": "
|
|
22
|
-
"test:
|
|
21
|
+
"test": "npm run test:lib && npm run test:test && echo 'tests done'",
|
|
22
|
+
"test:lib": "uvu lib '.test.([mc]js|[jt]sx?)$'",
|
|
23
|
+
"test:test": "uvu test '.test.([mc]js|[jt]sx?)$'",
|
|
24
|
+
"test:options": "uvu lib 'options-parser.test.([mc]js|[jt]sx?)$'",
|
|
25
|
+
"test:block": "uvu lib 'block-parser.test.([mc]js|[jt]sx?)$'",
|
|
26
|
+
"test:cli": "uvu lib 'cli.test.([mc]js|[jt]sx?)$'",
|
|
27
|
+
"test:md": "uvu lib 'md.test.([mc]js|[jt]sx?)$'",
|
|
28
|
+
"test:fs": "uvu lib 'fs.test.([mc]js|[jt]sx?)$'",
|
|
29
|
+
"test:js": "uvu lib 'block-parser-js.test.([mc]js|[jt]sx?)$'",
|
|
30
|
+
"test:errors": "uvu test 'errors.test.([mc]js|[jt]sx?)$'",
|
|
31
|
+
"test:transforms": "uvu test 'transforms.test.([mc]js|[jt]sx?)$'",
|
|
32
|
+
"test:text": "uvu lib 'text.test.([mc]js|[jt]sx?)$'",
|
|
23
33
|
"cli": "node ./cli.js --path 'README.md' --config ./markdown.config.js",
|
|
24
|
-
"lint": "eslint .",
|
|
25
34
|
"publish": "git push origin && git push origin --tags",
|
|
26
35
|
"release:patch": "npm version patch && npm publish",
|
|
27
36
|
"release:minor": "npm version minor && npm publish",
|
|
@@ -36,29 +45,24 @@
|
|
|
36
45
|
},
|
|
37
46
|
"dependencies": {
|
|
38
47
|
"@technote-space/doctoc": "2.4.7",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"globby": "^10.0.2",
|
|
48
|
+
"globrex": "^0.1.2",
|
|
49
|
+
"gray-matter": "^4.0.3",
|
|
50
|
+
"is-glob": "^4.0.3",
|
|
43
51
|
"is-local-path": "^0.1.6",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
52
|
+
"is-valid-path": "^0.1.1",
|
|
53
|
+
"micro-mdx-parser": "^1.1.0",
|
|
54
|
+
"mri": "^1.2.0",
|
|
55
|
+
"oparser": "^2.1.1",
|
|
56
|
+
"smart-glob": "^1.0.2",
|
|
46
57
|
"sync-request": "^6.1.0"
|
|
47
58
|
},
|
|
48
59
|
"devDependencies": {
|
|
49
|
-
"ava": "^3.15.0",
|
|
50
|
-
"doxxx": "^1.0.0",
|
|
51
|
-
"rimraf": "^3.0.2",
|
|
52
|
-
"sinon": "^11.1.1",
|
|
53
|
-
"uvu": "^0.5.1",
|
|
54
60
|
"ansi-styles": "^4.2.1",
|
|
55
61
|
"concordance": "^5.0.1",
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
],
|
|
62
|
-
"verbose": true
|
|
62
|
+
"doxxx": "^2.0.1",
|
|
63
|
+
"rimraf": "^3.0.2",
|
|
64
|
+
"safe-chalk": "^1.0.0",
|
|
65
|
+
"typescript": "^5.0.2",
|
|
66
|
+
"uvu": "^0.5.1"
|
|
63
67
|
}
|
|
64
68
|
}
|
package/index.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
const globby = require('globby')
|
|
2
|
-
const processFile = require('./lib/processFile')
|
|
3
|
-
/**
|
|
4
|
-
* ### API
|
|
5
|
-
* ```js
|
|
6
|
-
* markdownMagic(filePath, config, callback)
|
|
7
|
-
* ```
|
|
8
|
-
* - `filePaths` - *String or Array* - Path or glob pattern. Uses [globby patterns](https://github.com/sindresorhus/multimatch/blob/master/test.js)
|
|
9
|
-
* - `config` - See configuration options below
|
|
10
|
-
* - `callback` - callback to run after markdown updates
|
|
11
|
-
*
|
|
12
|
-
* @param {string} filePath - Path to markdown file
|
|
13
|
-
* @param {object} [config] - configuration object
|
|
14
|
-
* @param {Function} [callback] - callback function with updated contents
|
|
15
|
-
*/
|
|
16
|
-
module.exports = async function markdownMagic(filePaths, config, callback) {
|
|
17
|
-
const files = globby.sync(filePaths)
|
|
18
|
-
const configuration = config || {}
|
|
19
|
-
if (!callback && typeof configuration === 'function') {
|
|
20
|
-
callback = configuration // eslint-disable-line
|
|
21
|
-
} else if (typeof config === 'object' && config.callback) {
|
|
22
|
-
// set callback in config for CLI usage
|
|
23
|
-
callback = config.callback // eslint-disable-line
|
|
24
|
-
}
|
|
25
|
-
if (!files.length) {
|
|
26
|
-
callback && callback('No files matched')
|
|
27
|
-
console.log('No files matched pattern', filePaths)
|
|
28
|
-
return false
|
|
29
|
-
}
|
|
30
|
-
configuration.originalFilePaths = files
|
|
31
|
-
|
|
32
|
-
const data = files.map(async (file) => {
|
|
33
|
-
const x = await processFile(file, configuration)
|
|
34
|
-
return x
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
const values = await Promise.all(data)
|
|
38
|
-
|
|
39
|
-
if (callback) {
|
|
40
|
-
callback(null, values)
|
|
41
|
-
}
|
|
42
|
-
return values
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// expose globby for use in plugins
|
|
46
|
-
module.exports.globby = globby
|
package/lib/processFile.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
const mkdirp = require('mkdirp')
|
|
3
|
-
const path = require('path')
|
|
4
|
-
const merge = require('deepmerge')
|
|
5
|
-
const transforms = require('./transforms')
|
|
6
|
-
const regexUtils = require('./utils/regex')
|
|
7
|
-
const pluginSortOrder = require('./utils/sortOrder')
|
|
8
|
-
const updateContents = require('./updateContents')
|
|
9
|
-
const cwd = process.cwd()
|
|
10
|
-
|
|
11
|
-
module.exports = async function processFile(filePath, config) {
|
|
12
|
-
let content
|
|
13
|
-
try {
|
|
14
|
-
content = fs.readFileSync(filePath, 'utf8')
|
|
15
|
-
} catch (e) {
|
|
16
|
-
console.log(`FILE NOT FOUND ${filePath}`)
|
|
17
|
-
throw e
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* ### Configuration Options
|
|
21
|
-
*/
|
|
22
|
-
const defaultConfig = {
|
|
23
|
-
/**
|
|
24
|
-
* - `transforms` - *object* - (optional) Custom commands to transform block contents, see transforms & custom transforms sections below.
|
|
25
|
-
* @type {Object}
|
|
26
|
-
*/
|
|
27
|
-
transforms: transforms,
|
|
28
|
-
/**
|
|
29
|
-
* - `outputDir` - *string* - (optional) Change output path of new content. Default behavior is replacing the original file
|
|
30
|
-
* @type {string}
|
|
31
|
-
*/
|
|
32
|
-
outputDir: path.dirname(filePath),
|
|
33
|
-
/**
|
|
34
|
-
* - `matchWord` - *string* - (optional) Comment pattern to look for & replace inner contents. Default `AUTO-GENERATED-CONTENT`
|
|
35
|
-
* @type {string}
|
|
36
|
-
* @default [AUTO-GENERATED-CONTENT]
|
|
37
|
-
*/
|
|
38
|
-
matchWord: 'AUTO-GENERATED-CONTENT',
|
|
39
|
-
/**
|
|
40
|
-
* - `DEBUG` - *Boolean* - (optional) set debug flag to `true` to inspect the process
|
|
41
|
-
* @type {boolean}
|
|
42
|
-
*/
|
|
43
|
-
DEBUG: false,
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const userConfig = (config && typeof config === 'object') ? config : {}
|
|
47
|
-
const mergedConfig = merge(defaultConfig, userConfig)
|
|
48
|
-
|
|
49
|
-
const registeredTransforms = Object.keys(mergedConfig.transforms)
|
|
50
|
-
// Set originalPath constant
|
|
51
|
-
mergedConfig.originalPath = filePath
|
|
52
|
-
// contents of original MD file
|
|
53
|
-
mergedConfig.originalContent = content
|
|
54
|
-
// set default outputContents for first pass for single commands
|
|
55
|
-
mergedConfig.outputContent = content
|
|
56
|
-
|
|
57
|
-
const regex = regexUtils.matchCommentBlock(mergedConfig.matchWord)
|
|
58
|
-
// console.log(regex)
|
|
59
|
-
const match = content.match(regex)
|
|
60
|
-
const transformsFound = []
|
|
61
|
-
|
|
62
|
-
if (match) {
|
|
63
|
-
let commentMatches
|
|
64
|
-
let matchIndex = 0
|
|
65
|
-
while ((commentMatches = regex.exec(content)) !== null) {
|
|
66
|
-
if (commentMatches.index === regex.lastIndex) {
|
|
67
|
-
regex.lastIndex++ // This is necessary to avoid infinite loops
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// console.log('commentMatches', commentMatches)
|
|
71
|
-
// const command = `Transform ${commentMatches[2]}`
|
|
72
|
-
// console.log(command)
|
|
73
|
-
transformsFound.push({
|
|
74
|
-
spaces: commentMatches[1], // Preserve indentation
|
|
75
|
-
transform: commentMatches[2],
|
|
76
|
-
match: match[matchIndex]
|
|
77
|
-
})
|
|
78
|
-
// wait
|
|
79
|
-
matchIndex++
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// console.log('registeredTransforms', registeredTransforms)
|
|
83
|
-
|
|
84
|
-
const transformsToRun = pluginSortOrder(registeredTransforms, transformsFound)
|
|
85
|
-
if (mergedConfig.DEBUG) {
|
|
86
|
-
console.log('↓ transformsToRun')
|
|
87
|
-
console.log(transformsToRun)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const fileName = path.basename(filePath)
|
|
91
|
-
const outputFilePath = path.join(mergedConfig.outputDir, fileName)
|
|
92
|
-
|
|
93
|
-
// create folder path if doesnt exist
|
|
94
|
-
mkdirp.sync(mergedConfig.outputDir)
|
|
95
|
-
|
|
96
|
-
// run sort
|
|
97
|
-
let transformMsg = ''
|
|
98
|
-
for (const element of transformsToRun) {
|
|
99
|
-
// console.log('element', element)
|
|
100
|
-
transformMsg += ` ⁕ ${element.transform} \n`
|
|
101
|
-
// console.log('order', element.transform)
|
|
102
|
-
const newContent = await updateContents(element.match, mergedConfig)
|
|
103
|
-
const firstLineIndentation = element.spaces
|
|
104
|
-
const contentWithIndentation = newContent.split('\n').join(`\n` + element.spaces)
|
|
105
|
-
const preserveTabs = `${firstLineIndentation}${contentWithIndentation}`
|
|
106
|
-
|
|
107
|
-
content = content.replace(element.match, preserveTabs)
|
|
108
|
-
mergedConfig.outputContent = content
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
let notFoundNotice
|
|
112
|
-
if (!transformMsg) {
|
|
113
|
-
// console.log('config', config)
|
|
114
|
-
const notFound = transformsFound.map((x) => {
|
|
115
|
-
return `"${x.transform}"`
|
|
116
|
-
})
|
|
117
|
-
notFoundNotice = notFound
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// console.log('transformMsg', transformMsg)
|
|
121
|
-
|
|
122
|
-
const msg = outputFilePath.replace(cwd, '')
|
|
123
|
-
|
|
124
|
-
if (transformMsg) {
|
|
125
|
-
console.log(`✔ ${msg} Updated`)
|
|
126
|
-
// update file contents
|
|
127
|
-
fs.writeFileSync(outputFilePath, content)
|
|
128
|
-
console.log(` Transforms run`)
|
|
129
|
-
console.log(transformMsg)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (notFoundNotice) {
|
|
133
|
-
const word = notFoundNotice.length > 1 ? 'transforms' : 'transform'
|
|
134
|
-
console.log(`ℹ Notice:`)
|
|
135
|
-
console.log(` Missing ${word} ${notFoundNotice.join(',')} in ${msg}`)
|
|
136
|
-
console.log()
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// set return values
|
|
141
|
-
mergedConfig.outputFilePath = outputFilePath
|
|
142
|
-
mergedConfig.outputContent = content
|
|
143
|
-
|
|
144
|
-
return mergedConfig
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (mergedConfig.DEBUG) {
|
|
148
|
-
console.log(`↓ ${filePath}`)
|
|
149
|
-
console.log(`[No match] <!-- ${mergedConfig.matchWord} --> comment found`)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// no match return original contents
|
|
153
|
-
return mergedConfig
|
|
154
|
-
}
|