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
@@ -49,6 +49,10 @@ authors:
49
49
 
50
50
  ![](https://avatars2.githubusercontent.com/u/532272?v=3&s=400)
51
51
 
52
+ This is the weird markdown link syntax <https://foooooooooooo.com>
53
+
54
+ this is less than sign
55
+
52
56
  Hear Bob Bobster discuss the evolution of Serverless and the launch of ABC at the AWS Serverless Community Day.
53
57
 
54
58
  <iframe width="560" height="315" src="https://www.youtube.com/embed/KX7tj3giizI" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen></iframe>
@@ -61,6 +65,12 @@ Hear Bob Bobster discuss the evolution of Serverless and the launch of ABC at th
61
65
  <img src="/img/deploy/button.svg">
62
66
  </a>
63
67
 
68
+
69
+ <a href="https://www.yoursite.com/pricing?utm_source=active%20users&utm_medium=email&utm_campaign=feature%20launch&utm_content=bottom%20cta%20button">
70
+ Utm params link
71
+ </a>
72
+
73
+
64
74
  Title xyz
65
75
  ============================
66
76
 
@@ -3,10 +3,9 @@ const path = require('path')
3
3
  const { test } = require('uvu')
4
4
  const assert = require('uvu/assert')
5
5
  const { deepLog } = require('../logs')
6
- const { stringify } = require('../html-to-json')
7
6
  const { parseMarkdown } = require('./parse')
8
7
  const { findLinks } = require('./find-links')
9
- const { findFrontmatter } = require('./find-frontmatter')
8
+ const { parseFrontmatter } = require('./find-frontmatter')
10
9
 
11
10
  const FILE_PATH = path.join(__dirname, 'fixtures/file-with-links.md')
12
11
  // const FILE_PATH = path.join(__dirname, 'fixtures/2022-01-22-date-in-filename.md')
@@ -14,7 +13,6 @@ const fileContents = fs.readFileSync(FILE_PATH, 'utf-8')
14
13
 
15
14
  test('parseMarkdown API', async () => {
16
15
  const res = parseMarkdown(fileContents, { filePath: FILE_PATH })
17
- // deepLog(res)
18
16
  assert.is(typeof res, 'object')
19
17
  assert.is(typeof res.ast, 'object')
20
18
  assert.is(typeof res.data, 'object')
@@ -23,8 +21,78 @@ test('parseMarkdown API', async () => {
23
21
  assert.is(Array.isArray(res.errors), true)
24
22
  })
25
23
 
24
+ test('Verify contents', async () => {
25
+ const res = parseMarkdown(fileContents, {
26
+ filePath: FILE_PATH
27
+ })
28
+ deepLog('Results:', res)
29
+ assert.equal(res.links, [
30
+ 'https://funky-frontmatter.com',
31
+ 'https://www.front.com/blog/open-beta-changes',
32
+ 'https://youtu.be/A1bL4pHuivU',
33
+ 'https://foooooooooooo.com',
34
+ 'https://www.youtube.com/embed/KX7tj3giizI',
35
+ 'https://app.netlify.com/start/deploy',
36
+ 'https://www.yoursite.com/pricing?utm_source=active%20users&utm_medium=email&utm_campaign=feature%20launch&utm_content=bottom%20cta%20button',
37
+ 'https://ABC.com/sign-up',
38
+ 'http://jobs.ABC.net',
39
+ '/foobar'
40
+ ])
41
+ assert.equal(res.images, [
42
+ '/assets/images/lol-frontmatter.jpg',
43
+ '/assets/images/lol.jpg',
44
+ 'assets/images/san-juan-mountains.jpg',
45
+ 'https://res.cloudinary.com/ABC/image/upload/f_auto,q_auto/c_fill,w_1200/v1668114635/what-you-can-build_p8uape.png',
46
+ 'https://avatars2.githubusercontent.com/u/532272?v=3&s=400',
47
+ 'https://dope-frontmatter.com/img/deploy/button.svg',
48
+ 'https://frontmatter.com/img/deploy/button.svg',
49
+ '/img/in-nested-frontmatter/button.svg',
50
+ 'https://www.netlify.com/img/deploy/button.svg',
51
+ 'https://fooo.com/img/deploy/button.svg',
52
+ '/img/deploy/button.svg',
53
+ 'img/deploy/button.svg',
54
+ '../img/deploy/button.svg'
55
+ ])
56
+ })
57
+
58
+ test('opts - includeAst false', async () => {
59
+ const res = parseMarkdown(fileContents, {
60
+ filePath: FILE_PATH,
61
+ })
62
+ // deepLog('Results:', res)
63
+ assert.ok(Array.isArray(res.ast))
64
+ /* Disable AST */
65
+ const resTwo = parseMarkdown(fileContents, {
66
+ filePath: FILE_PATH,
67
+ includeAst: false,
68
+ })
69
+ // deepLog('Results two:', resTwo)
70
+ assert.ok(typeof resTwo.ast === 'undefined')
71
+ })
72
+
73
+ test('opts - includeImages false', async () => {
74
+ const res = parseMarkdown(fileContents, {
75
+ filePath: FILE_PATH,
76
+ // includeAst: false,
77
+ includeImages: false,
78
+ })
79
+ deepLog('Results:', res)
80
+ assert.ok(typeof res.links === 'object')
81
+ assert.ok(typeof res.images === 'undefined')
82
+ })
83
+
84
+ test('opts - includeLinks false', async () => {
85
+ const res = parseMarkdown(fileContents, {
86
+ filePath: FILE_PATH,
87
+ includeLinks: false,
88
+ })
89
+ deepLog('Results:', res)
90
+ assert.ok(typeof res.links === 'undefined')
91
+ assert.ok(typeof res.images === 'object')
92
+ })
93
+
26
94
  // test('File have correct extensions', async () => {
27
- // const { data } = findFrontmatter(fileWithLinks, FILE_PATH)
95
+ // const { data } = parseFrontmatter(fileWithLinks, FILE_PATH)
28
96
  // console.log('frontmatter data', data)
29
97
  // const links = findLinks(fileWithLinks, { frontmatter: data })
30
98
  // // console.log('links', links)
@@ -1,54 +1,74 @@
1
- // const { validateHtml } = require('./validate-html')
2
- const { findFrontmatter } = require('./find-frontmatter')
3
- const { parse } = require('../html-to-json')
4
- const { getLineCount } = require('./utils')
1
+ const { parse } = require('micro-mdx-parser')
2
+ const { parseFrontmatter } = require('./find-frontmatter')
5
3
  const { findUnmatchedHtmlTags } = require('./find-unmatched-html-tags')
6
4
  const { findLinks } = require('./find-links')
7
5
  const { findDate } = require('./find-date')
8
6
  const { findCodeBlocks, REMOVE_CODE_BLOCK_REGEX } = require('./find-code-blocks')
9
- // const { findImages, findLiveImages } = require('./find-images')
10
- // const { findHtmlTags } = require('./find-html-tags')
7
+ const { getLineCount } = require('./utils')
11
8
 
12
9
  function parseMarkdown(text, opts = {}) {
13
- const { filePath, validateHtml } = opts
10
+ const {
11
+ filePath,
12
+ validator,
13
+ astParser,
14
+ includeAst = true,
15
+ includeLinks = true,
16
+ includeImages = true,
17
+ includeCodeBlocks = true,
18
+ includePositions = false,
19
+ includeRawFrontmatter = false,
20
+ } = opts
14
21
  let errors = []
15
- let frontmatter = {}
22
+ let result = {}
16
23
  let alreadySetError = false
17
24
  try {
18
- frontmatter = findFrontmatter(text, filePath)
25
+ result = parseFrontmatter(text)
19
26
  } catch (err) {
20
27
  console.log(`Broken frontmatter in ${filePath}...`)
21
28
  errors.push(err.message)
22
29
  alreadySetError = true
23
30
  }
24
- const { data, content = '', rawFrontMatter = '' } = frontmatter
31
+ const { data, content = '', frontMatterRaw = '' } = result
25
32
  if (!data || !Object.keys(data).length) {
26
33
  if (!alreadySetError) {
27
34
  errors.push(`Missing or broken frontmatter in ${filePath}. Double check file for --- frontmatter tags`)
28
35
  }
29
36
  }
30
37
 
31
- // const imagesInYml = findLinksInFrontMatter(data, findLiveImages)
32
- // console.log('linksInYml', linksInYml)
33
- // const links = findLiveLinks(text)
34
- //console.log(`links ${filePath}`, links)
35
- // const relativeLinks = findRelativeLinks(text)
36
- // console.log(`relativeLinks ${filePath}`, relativeLinks)
37
- /* gets all images in file */
38
- // const images = findLiveImages(text) // findImages(text)
39
- // console.log(`images ${filePath}`, images)
40
- // const htmlTags = findHtmlTags(text)
41
- const linkData = findLinks(text)
42
-
43
- const html = parse(content, {
44
- includePositions: true,
45
- offset: {
46
- lineOffset: getLineCount(rawFrontMatter),
47
- charOffset: rawFrontMatter.length
38
+ let links
39
+ let images
40
+ if (includeLinks || includeImages) {
41
+ const linkData = findLinks(text, {
42
+ frontmatter: data
43
+ })
44
+ links = linkData.links
45
+ images = linkData.images
46
+ }
47
+
48
+ let ast = []
49
+ if (includeAst) {
50
+ /* If custom parser supplied */
51
+ if (astParser) {
52
+ ast = astParser(content, opts)
53
+ } else {
54
+ /* Default parser */
55
+ ast = parse(content, {
56
+ includePositions,
57
+ // offset: {
58
+ // lineOffset: getLineCount(frontMatterRaw),
59
+ // charOffset: frontMatterRaw.length
60
+ // }
61
+ })
48
62
  }
49
- })
63
+ }
64
+
65
+ // console.log('html', html)
50
66
  // console.log(`htmlTags ${filePath}`, htmlTags)
51
- const codeBlocks = findCodeBlocks(text, filePath)
67
+ let codeBlocks
68
+ if (includeCodeBlocks) {
69
+ codeBlocks = findCodeBlocks(text, { filePath, includePositions })
70
+ }
71
+
52
72
  // console.log(`codeBlocks ${filePath}`, codeBlocks)
53
73
  const tagsErrors = findUnmatchedHtmlTags(text, filePath)
54
74
 
@@ -58,9 +78,9 @@ function parseMarkdown(text, opts = {}) {
58
78
  // }
59
79
 
60
80
  let htmlValidation = []
61
- if (validateHtml) {
62
- const contentsNoCodeBlocks = content.replace(REMOVE_CODE_BLOCK_REGEX, '')
63
- htmlValidation = validateHtml(contentsNoCodeBlocks, filePath)
81
+ if (typeof validator === 'function') {
82
+ // const contentsNoCodeBlocks = content.replace(REMOVE_CODE_BLOCK_REGEX, '')
83
+ htmlValidation = validator(content, filePath)
64
84
  }
65
85
 
66
86
  if (htmlValidation && htmlValidation.length) {
@@ -76,45 +96,49 @@ function parseMarkdown(text, opts = {}) {
76
96
  errors = errors.concat(codeBlocks.errors)
77
97
  }
78
98
 
79
- const frontMatterData = data || {}
99
+ const frontmatter = data || {}
80
100
  const date = findDate({
81
- frontmatter: frontMatterData,
101
+ frontmatter,
82
102
  filePath
83
103
  })
84
104
 
85
- const markdownInfo = {
86
- ...(filePath) ? { filePath } : {},
87
- ...(date) ? { date } : {},
88
- ast: html,
89
- data: frontMatterData,
90
- links: linkData.links,
91
- images: linkData.images,
92
- codeBlocks,
93
- content,
94
- errors,
95
- // frontMatterRaw: rawFrontMatter,
96
- // ...frontmatter
97
- }
98
-
99
- /* // Debugger
100
- const path = require('path')
101
- if (path.basename(filePath)=== '2020-06-30-welcome-to-vendia.md') {
102
- // console.log('text', text)
103
- // console.log('Frontmatter')
104
- // console.log(data)
105
- // console.log('findLinks')
106
- // deepLog(links)
107
- // console.log('components')
108
- // deepLog(components)
109
- console.log('markdownInfo')
110
- deepLog(markdownInfo)
111
-
112
- deepLog(stringify(html))
113
- process.exit(1)
114
- }
115
- /** */
116
-
117
- return markdownInfo
105
+ const parseResult = {}
106
+
107
+ if (filePath) {
108
+ parseResult.filePath = filePath
109
+ }
110
+
111
+ if (date) {
112
+ parseResult.date = date
113
+ }
114
+
115
+ if (includeAst) {
116
+ parseResult.ast = ast
117
+ }
118
+
119
+ /* Include frontmatter as data object */
120
+ parseResult.data = frontmatter
121
+
122
+ if (includeRawFrontmatter) {
123
+ parseResult.frontMatterRaw = frontMatterRaw
124
+ }
125
+
126
+ if (includeLinks) {
127
+ parseResult.links = links
128
+ }
129
+
130
+ if (includeImages) {
131
+ parseResult.images = images
132
+ }
133
+
134
+ if (includeCodeBlocks) {
135
+ parseResult.codeBlocks = codeBlocks.blocks
136
+ }
137
+
138
+ parseResult.content = content
139
+ parseResult.errors = errors
140
+
141
+ return parseResult
118
142
  }
119
143
 
120
144
  module.exports = {
@@ -33,7 +33,8 @@ This is normal text in markdown. Keep it.
33
33
  // var pattern = /([ \t]*)(?:<!-{2,}(?:.*|\r?|\n?|\s*)docs-start\s*([(\[\{]*[A-Za-z0-9_$-]*[)\]\}]*)\s*)((?:.*?|.*?\r?\n?)*?)<!-{2,}(?:.*|\r?|\n?|\s*)docs-end(?:.|\r?\n)*?-{2,}>/gim
34
34
 
35
35
  function safeRegex(str) {
36
- const syntaxInfo = getSyntaxInfo('md')
36
+ const syntax = 'md'
37
+ const syntaxInfo = getSyntaxInfo(syntax)
37
38
  if (!syntaxInfo.pattern) {
38
39
  throw new Error(`Unknown syntax "${syntax}"`)
39
40
  }
@@ -2,6 +2,7 @@
2
2
  // REGEX to look for regex
3
3
  // https://github.com/kgryte/regex-regex/blob/master/lib/index.js
4
4
  const REGEX_REGEX = /^\/((?:\\\/|[^\/])+)\/([imgy]*)$/
5
+ const LINEBREAK_MATCHER = /\r\n|[\r\n\u2028\u2029]/;
5
6
 
6
7
  function escapeRegexString(string) {
7
8
  if (typeof string !== 'string') {
@@ -54,7 +55,7 @@ function cleanStepMatches(matches) {
54
55
  /**
55
56
  * Match comment steps in files
56
57
  */
57
- function getSteps() {
58
+ function getSteps(matches) {
58
59
  const steps = cleanStepMatches(matches)
59
60
 
60
61
  const sortedSteps = steps.reduce((accumulator, currentValue, currentIndex, array) => {
@@ -97,7 +98,7 @@ const TABEL_ROW_REGEX = /^\s*\|.*?\|\s*$/
97
98
  * @param {string} text The text to validate.
98
99
  * @returns {boolean}
99
100
  */
100
- const isTableRow = (text) => text.match(TABEL_ROW_REGEX);
101
+ const isTableRow = (text) => Boolean(text.match(TABEL_ROW_REGEX))
101
102
 
102
103
  module.exports = {
103
104
  REGEX_REGEX,
@@ -3,6 +3,7 @@ const request = require('sync-request')
3
3
  module.exports = function remoteRequest(url) {
4
4
  let body
5
5
  try {
6
+ // @ts-expect-error
6
7
  const res = request('GET', url)
7
8
  body = res.getBody('utf8')
8
9
  } catch (e) {
@@ -69,6 +69,9 @@ const syntaxMap = {
69
69
  yml: yaml
70
70
  }
71
71
 
72
+ // Additional comment syntaxes for future
73
+ // https://github.com/apidoc/apidoc/tree/27566b48a9e8bad2fb3920c02473940917eabe70#supported-programming-languages
74
+
72
75
  function getSyntaxInfo(syntax = '') {
73
76
  return syntaxMap[syntax.toLowerCase()] || {}
74
77
  }
package/lib/utils/text.js CHANGED
@@ -37,6 +37,25 @@ function getTextBetweenChars(text, start, end) {
37
37
  return text.slice(start, end)
38
38
  }
39
39
 
40
+ /**
41
+ * Get text between two words
42
+ * @param {string} s
43
+ * @param {string} prefix
44
+ * @param {string} suffix
45
+ * @returns {string}
46
+ */
47
+ function getTextBetweenWords(s, prefix, suffix) {
48
+ let i = s.indexOf(prefix)
49
+ if (i === -1) return ''
50
+ s = s.substring(i + prefix.length)
51
+ if (suffix) {
52
+ i = s.indexOf(suffix)
53
+ if (i === -1) return ''
54
+ s = s.substring(0, i)
55
+ }
56
+ return s
57
+ }
58
+
40
59
  function replaceTextBetweenChars(str = '', start, end, newStr) {
41
60
  return str.substring(0, start) + newStr + str.substring(end)
42
61
  }
@@ -77,7 +96,7 @@ function stripIndent(string, indentation) {
77
96
 
78
97
  /**
79
98
  * Trim leading & trailing spaces/line breaks in code and keeps the indentation of the first non-empty line
80
- * @param {string} str
99
+ * @param {string|number} str
81
100
  * @returns string
82
101
  */
83
102
  function trimString(str = '') {
@@ -149,10 +168,10 @@ function stripCommentBlockOld(str, syntax = 'md') {
149
168
  /**
150
169
  * Strip out comment blocks
151
170
  * @param {string} str
152
- * @param {'md' | 'js'} syntax
171
+ * @param {typeof import('../types')['syntaxType']} syntax
153
172
  * @returns {string} clean commentless string
154
173
  */
155
- function stripComments(str, { syntax = 'md' }) {
174
+ function stripComments(str, syntax = 'md') {
156
175
  const syntaxData = syntaxMap[syntax]
157
176
  const [ openPattern, closePattern ] = syntaxData.pattern
158
177
  const OR = (syntaxData.singleLine) ? `|\\s?[ \\t]*${syntaxData.singleLine}` : ''
@@ -236,7 +255,55 @@ function convertCommentSyntax(str, { from, to }) {
236
255
  return str.replace(regexToUse, newText)
237
256
  }
238
257
 
258
+ /**
259
+ * capitalize first letter
260
+ * @param {string} str
261
+ * @returns
262
+ */
263
+ function capitalizeFirstLetter(str) {
264
+ return capitalize(str.charAt(0)) + str.slice(1)
265
+ }
266
+
267
+ /**
268
+ * capitalize string
269
+ * @param {string} str
270
+ * @returns
271
+ */
272
+ function capitalize(str = '') {
273
+ return str.toUpperCase()
274
+ }
275
+
276
+ function camelCase(str = '') {
277
+ return str.replace(/[-_ ](\w)/g, (_, c) => c.toUpperCase())
278
+ }
279
+
280
+ function kebabCase(str = '') {
281
+ return str.replace(/\B([A-Z])/g, '-$1').toLowerCase()
282
+ }
283
+
284
+ const smallWords = /^(a|an|and|as|at|but|by|en|for|if|in|nor|of|on|or|per|the|to|vs?\.?|via)$/i;
285
+
286
+ function toTitleCase(str = '') {
287
+ return str.replace(/[A-Za-z0-9\u00C0-\u00FF]+[^\s-]*/g, (match, index, title) => {
288
+ if (index > 0
289
+ && index + match.length !== title.length
290
+ && match.search(smallWords) > -1
291
+ && title.charAt(index - 2) !== ':'
292
+ && (title.charAt(index + match.length) !== '-' || title.charAt(index - 1) === '-')
293
+ && title.charAt(index - 1).search(/[^\s-]/) < 0) {
294
+ return match.toLowerCase();
295
+ }
296
+
297
+ if (match.substr(1).search(/[A-Z]|\../) > -1) {
298
+ return match;
299
+ }
300
+
301
+ return match.charAt(0).toUpperCase() + match.substr(1);
302
+ })
303
+ }
304
+
239
305
  module.exports = {
306
+ toTitleCase,
240
307
  getLines,
241
308
  getLineCount,
242
309
  getWordCount,
@@ -245,6 +312,7 @@ module.exports = {
245
312
  getLastCharacter,
246
313
  getRowAndColumnFromCharPos,
247
314
  getTextBetweenChars,
315
+ getTextBetweenWords,
248
316
  getTextBetweenLines,
249
317
  replaceTextBetweenChars,
250
318
  stripIndent,
@@ -88,9 +88,7 @@ nice
88
88
  `
89
89
 
90
90
  test('Remove Markdown comments', () => {
91
- const parsedValue = stripComments(md, {
92
- syntax: 'md'
93
- })
91
+ const parsedValue = stripComments(md, 'md')
94
92
  /*
95
93
  console.log('parsedValue')
96
94
  logOutput(parsedValue)
@@ -162,9 +160,7 @@ console.log('inline') /* inline klsjlkajdsalkjd *****/
162
160
 
163
161
  /* inline */
164
162
  console.log('nice')
165
- `, {
166
- syntax: 'js'
167
- })
163
+ `, 'js')
168
164
  /*
169
165
  console.log('parsedValue')
170
166
  logOutput(parsedValue)
@@ -195,9 +191,7 @@ test('Remove YAML comments', () => {
195
191
  steve: 'cool' # inline comment
196
192
  foo:
197
193
  bar: lol
198
- `, {
199
- syntax: 'yml'
200
- })
194
+ `, 'yml')
201
195
 
202
196
  /*
203
197
  console.log('yml', dedentString(parsedValue))