markdown-magic 3.0.0 → 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.
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
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "markdown-magic",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "Automatically update markdown files with content from external sources",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -15,6 +15,8 @@
15
15
  "/lib"
16
16
  ],
17
17
  "scripts": {
18
+ "types": "tsc",
19
+ "emit-types": "tsc --noEmit false --emitDeclarationOnly true",
18
20
  "docs": "node examples/generate-readme.js",
19
21
  "test": "npm run test:lib && npm run test:test && echo 'tests done'",
20
22
  "test:lib": "uvu lib '.test.([mc]js|[jt]sx?)$'",
@@ -48,17 +50,19 @@
48
50
  "is-glob": "^4.0.3",
49
51
  "is-local-path": "^0.1.6",
50
52
  "is-valid-path": "^0.1.1",
51
- "json-alexander": "^0.1.8",
53
+ "micro-mdx-parser": "^1.1.0",
52
54
  "mri": "^1.2.0",
55
+ "oparser": "^2.1.1",
53
56
  "smart-glob": "^1.0.2",
54
57
  "sync-request": "^6.1.0"
55
58
  },
56
59
  "devDependencies": {
57
60
  "ansi-styles": "^4.2.1",
58
61
  "concordance": "^5.0.1",
59
- "doxxx": "^1.0.0",
62
+ "doxxx": "^2.0.1",
60
63
  "rimraf": "^3.0.2",
61
64
  "safe-chalk": "^1.0.0",
65
+ "typescript": "^5.0.2",
62
66
  "uvu": "^0.5.1"
63
67
  }
64
68
  }