UNPKG

4.46 kBJavaScriptView Raw
1const { fs, logger, path } = require('@vuepress/shared-utils')
2
3function dedent (text) {
4 const wRegexp = /^([ \t]*)(.*)\n/gm
5 let match; let minIndentLength = null
6
7 while ((match = wRegexp.exec(text)) !== null) {
8 const [indentation, content] = match.slice(1)
9 if (!content) continue
10
11 const indentLength = indentation.length
12 if (indentLength > 0) {
13 minIndentLength
14 = minIndentLength !== null
15 ? Math.min(minIndentLength, indentLength)
16 : indentLength
17 } else break
18 }
19
20 if (minIndentLength) {
21 text = text.replace(
22 new RegExp(`^[ \t]{${minIndentLength}}(.*)`, 'gm'),
23 '$1'
24 )
25 }
26
27 return text
28}
29
30function testLine (line, regexp, regionName, end = false) {
31 const [full, tag, name] = regexp.exec(line.trim()) || []
32
33 return (
34 full
35 && tag
36 && name === regionName
37 && tag.match(end ? /^[Ee]nd ?[rR]egion$/ : /^[rR]egion$/)
38 )
39}
40
41function findRegion (lines, regionName) {
42 const regionRegexps = [
43 /^\/\/ ?#?((?:end)?region) ([\w*-]+)$/, // javascript, typescript, java
44 /^\/\* ?#((?:end)?region) ([\w*-]+) ?\*\/$/, // css, less, scss
45 /^#pragma ((?:end)?region) ([\w*-]+)$/, // C, C++
46 /^<!-- #?((?:end)?region) ([\w*-]+) -->$/, // HTML, markdown
47 /^#((?:End )Region) ([\w*-]+)$/, // Visual Basic
48 /^::#((?:end)region) ([\w*-]+)$/, // Bat
49 /^# ?((?:end)?region) ([\w*-]+)$/ // C#, PHP, Powershell, Python, perl & misc
50 ]
51
52 let regexp = null
53 let start = -1
54
55 for (const [lineId, line] of lines.entries()) {
56 if (regexp === null) {
57 for (const reg of regionRegexps) {
58 if (testLine(line, reg, regionName)) {
59 start = lineId + 1
60 regexp = reg
61 break
62 }
63 }
64 } else if (testLine(line, regexp, regionName, true)) {
65 return { start, end: lineId, regexp }
66 }
67 }
68
69 return null
70}
71
72module.exports = function snippet (md, options = {}) {
73 const fence = md.renderer.rules.fence
74 const root = options.root || process.cwd()
75
76 md.renderer.rules.fence = (...args) => {
77 const [tokens, idx, , { loader }] = args
78 const token = tokens[idx]
79 const [src, regionName] = token.src ? token.src.split('#') : ['']
80 if (src) {
81 if (loader) {
82 loader.addDependency(src)
83 }
84 const isAFile = fs.lstatSync(src).isFile()
85 if (fs.existsSync(src) && isAFile) {
86 let content = fs.readFileSync(src, 'utf8')
87
88 if (regionName) {
89 const lines = content.split(/\r?\n/)
90 const region = findRegion(lines, regionName)
91
92 if (region) {
93 content = dedent(
94 lines
95 .slice(region.start, region.end)
96 .filter(line => !region.regexp.test(line.trim()))
97 .join('\n')
98 )
99 }
100 }
101
102 token.content = content
103 } else {
104 token.content = isAFile ? `Code snippet path not found: ${src}` : `Invalid code snippet option`
105 token.info = ''
106 logger.error(token.content)
107 }
108 }
109 return fence(...args)
110 }
111
112 function parser (state, startLine, endLine, silent) {
113 const CH = '<'.charCodeAt(0)
114 const pos = state.bMarks[startLine] + state.tShift[startLine]
115 const max = state.eMarks[startLine]
116
117 // if it's indented more than 3 spaces, it should be a code block
118 if (state.sCount[startLine] - state.blkIndent >= 4) {
119 return false
120 }
121
122 for (let i = 0; i < 3; ++i) {
123 const ch = state.src.charCodeAt(pos + i)
124 if (ch !== CH || pos + i >= max) return false
125 }
126
127 if (silent) {
128 return true
129 }
130
131 const start = pos + 3
132 const end = state.skipSpacesBack(max, pos)
133
134 /**
135 * raw path format: "/path/to/file.extension#region {meta}"
136 * where #region and {meta} are optionnal
137 *
138 * captures: ['/path/to/file.extension', 'extension', '#region', '{meta}']
139 */
140 const rawPathRegexp = /^(.+?(?:\.([a-z]+))?)(?:(#[\w-]+))?(?: ?({\d+(?:[,-]\d+)*}))?$/
141
142 const rawPath = state.src.slice(start, end).trim().replace(/^@/, root).trim()
143 const [filename = '', extension = '', region = '', meta = ''] = (rawPathRegexp.exec(rawPath) || []).slice(1)
144
145 state.line = startLine + 1
146
147 const token = state.push('fence', 'code', 0)
148 token.info = extension + meta
149 token.src = path.resolve(filename) + region
150 token.markup = '```'
151 token.map = [startLine, startLine + 1]
152
153 return true
154 }
155
156 md.block.ruler.before('fence', 'snippet', parser)
157}
158
\No newline at end of file