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
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
const { test } = require('uvu')
|
|
2
|
+
const assert = require('uvu/assert')
|
|
3
|
+
const { parseBlocks } = require('./block-parser')
|
|
4
|
+
const { deepLog } = require('./utils/logs')
|
|
5
|
+
|
|
6
|
+
const md = `<h1 id="jdjdj">Netlify + FaunaDB
|
|
7
|
+
<a href="https://app.netlify.com/start/deploy?repository=https://github.com/netlify/netlify-faunadb-example&stack=fauna">
|
|
8
|
+
<img src="https://www.netlify.com/img/deploy/button.svg">
|
|
9
|
+
</a>
|
|
10
|
+
</h1>
|
|
11
|
+
|
|
12
|
+
<\!-- XYZ:START functionName foo={{ rad: 'yellow' }} -->
|
|
13
|
+
nice
|
|
14
|
+
<\!-- XYZ:END -->
|
|
15
|
+
|
|
16
|
+
<\!-- XYZ:START {functionName} foo={{ rad: 'blue' }} -->
|
|
17
|
+
nice
|
|
18
|
+
<\!-- XYZ:END -->
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
<\!-- XYZ:START {functionName} foo={{ rad: 'red' }} -->
|
|
22
|
+
nice
|
|
23
|
+
<\!-- XYZ:END -->
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
<\!-- XYZ:START [wootName] foo=['one', 'two'] -->
|
|
27
|
+
nice
|
|
28
|
+
<\!-- XYZ:END -->
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
<\!-- XYZ:START -->
|
|
32
|
+
lol
|
|
33
|
+
<\!-- XYZ:END -->
|
|
34
|
+
|
|
35
|
+
<!-- XYZ:START funky-key-here kldskjfjslfjs -->
|
|
36
|
+
lol
|
|
37
|
+
<!-- XYZ:END -->
|
|
38
|
+
|
|
39
|
+
<\!-- xyz:start (lowerCase) foo=['one', 'two'] heading=false -->
|
|
40
|
+
nice
|
|
41
|
+
<\!-- XYZ:END -->
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
<\!-- XYZ:START(cool)
|
|
45
|
+
width={999}
|
|
46
|
+
height={{111}}
|
|
47
|
+
numberAsString="12345"
|
|
48
|
+
great={["scoot", "sco ot", 'scooo ttt']}
|
|
49
|
+
nice={{ value: nice, cool: "true" }}
|
|
50
|
+
soclose=[jdjdjd, hdhfhfhffh]
|
|
51
|
+
rad="boss"
|
|
52
|
+
cool=true notCool=false
|
|
53
|
+
nooooo={[one, two, 3, 4]}
|
|
54
|
+
numberZero=0,
|
|
55
|
+
xyz=999,
|
|
56
|
+
nope=false,
|
|
57
|
+
// comment
|
|
58
|
+
yes={true} -->
|
|
59
|
+
|
|
60
|
+
actual content
|
|
61
|
+
|
|
62
|
+
<\!-- XYZ:END -->
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
<img src="https://www.netlify.com/img/deploy/button.svg"/>
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
<img src="https://www.hehhehehehe.com/img/deploy/button.svg" />
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
<\!-- XYZ:START(cool) xxx
|
|
72
|
+
hhddh=cool -->
|
|
73
|
+
wowow
|
|
74
|
+
whatever we want
|
|
75
|
+
<\!-- XYZ:END -->
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
<\!-- XYZ:START(hhh) -->
|
|
79
|
+
xyz
|
|
80
|
+
<\!-- XYZ:END -->
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
<\!-- XYZ:START(cool) isCool -->
|
|
84
|
+
nice
|
|
85
|
+
<\!-- XYZ:END -->
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
<button
|
|
89
|
+
great={[one, two, 3, 4]}
|
|
90
|
+
>
|
|
91
|
+
wow
|
|
92
|
+
</button>
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
<\!-- XYZ:START(niceeeee)
|
|
96
|
+
xxx
|
|
97
|
+
// comment here
|
|
98
|
+
hhddh=cool
|
|
99
|
+
-->
|
|
100
|
+
contents
|
|
101
|
+
<\!-- XYZ:END -->
|
|
102
|
+
|
|
103
|
+
<button
|
|
104
|
+
width={999}
|
|
105
|
+
great={["scoot", "scoot"]}
|
|
106
|
+
nice={{ value: nice, cool: true }}
|
|
107
|
+
rad="boss"
|
|
108
|
+
cool=true
|
|
109
|
+
nope=false
|
|
110
|
+
what='xnxnx'
|
|
111
|
+
isLoading
|
|
112
|
+
src="https://user-images.githubusercontent.com/532272/123136878-46f1a300-d408-11eb-82f2-ad452498457b.jpg"
|
|
113
|
+
>
|
|
114
|
+
coooooll
|
|
115
|
+
</button>
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
<hr />
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
<br />
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
<ReactComponent>lolol</ReactComponent>
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
<ReactComponent width={123} lol={["no", "cool"]}>
|
|
128
|
+
lolol
|
|
129
|
+
</ReactComponent>
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
<OtherComponent width={123} lol={["no", "cool"]} nice={{ value: "nice", cool: true }}>
|
|
133
|
+
lolol
|
|
134
|
+
</OtherComponent>
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
<table style="width:100%">
|
|
138
|
+
<tr>
|
|
139
|
+
<th>Firstname</th>
|
|
140
|
+
<th>Lastname</th>
|
|
141
|
+
<th>Age</th>
|
|
142
|
+
</tr>
|
|
143
|
+
<tr>
|
|
144
|
+
<td>Jill</td>
|
|
145
|
+
<td>Smith</td>
|
|
146
|
+
<td>50</td>
|
|
147
|
+
</tr>
|
|
148
|
+
<tr>
|
|
149
|
+
<td>Eve</td>
|
|
150
|
+
<td>Jackson</td>
|
|
151
|
+
<td>94</td>
|
|
152
|
+
</tr>
|
|
153
|
+
</table>
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
<div>
|
|
157
|
+
<p>
|
|
158
|
+
<img align="right" isLoading={false} width="250" src="https://user-images.githubusercontent.com/532272/123136878-46f1a300-d408-11eb-82f2-ad452498457b.jpg" />
|
|
159
|
+
</p>
|
|
160
|
+
<p>
|
|
161
|
+
cool
|
|
162
|
+
</p>
|
|
163
|
+
<div>
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
<p>
|
|
167
|
+
<img align="left" width="250" src="https://user-images.githubusercontent.com/532272/123136889-4953fd00-d408-11eb-8a3e-f82f1d073298.jpg" />
|
|
168
|
+
</p>
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
Add a little magic to your markdown
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
## About
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
<img align="right" width="200" height="183" src="https://cloud.githubusercontent.com/assets/532272/21507867/3376e9fe-cc4a-11e6-9350-7ec4f680da36.gif" />Markdown magic uses comment blocks in markdown files to automatically sync or transform its contents.
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
Markdown magic uses comment blocks in markdown files to automatically sync or transform its contents. <img align="right" width="200" height="183" src="https://cloud.githubusercontent.com/assets/532272/21507867/3376e9fe-cc4a-11e6-9350-7ec4f680da36.gif" />
|
|
181
|
+
`
|
|
182
|
+
|
|
183
|
+
test('verify parser', () => {
|
|
184
|
+
const parsedValue = parseBlocks(md, {
|
|
185
|
+
open: 'XYZ:START',
|
|
186
|
+
close: 'XYZ:END'
|
|
187
|
+
})
|
|
188
|
+
/*
|
|
189
|
+
console.log('parsedValue')
|
|
190
|
+
deepLog(parsedValue)
|
|
191
|
+
// process.exit(1)
|
|
192
|
+
/** */
|
|
193
|
+
assert.equal(typeof parsedValue, 'object')
|
|
194
|
+
assert.equal(parsedValue.blocks.length, 11)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test('inline parser', () => {
|
|
198
|
+
const inlineOne = `<!--XYZ:START functionName foo={{ rad: 'bar' }}-->99<!--XYZ:END-->`
|
|
199
|
+
const one = parseBlocks(inlineOne, {
|
|
200
|
+
open: 'XYZ:START',
|
|
201
|
+
close: 'XYZ:END'
|
|
202
|
+
})
|
|
203
|
+
/*
|
|
204
|
+
console.log('inline one')
|
|
205
|
+
deepLog(one)
|
|
206
|
+
/** */
|
|
207
|
+
const values = [
|
|
208
|
+
{
|
|
209
|
+
type: 'functionName',
|
|
210
|
+
options: { foo: { rad: 'bar' } },
|
|
211
|
+
location: 66
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
values.forEach((val, i) => {
|
|
215
|
+
const stub = val
|
|
216
|
+
const currentItem = one.blocks[i]
|
|
217
|
+
assert.equal(stub.type, currentItem.type, `${stub.type} ${i} transform`)
|
|
218
|
+
assert.equal(stub.options, currentItem.options, `${stub.type} ${i} options`)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
const inlineTwo = ` <!-- XYZ:START transformX foo=111 -->99<!-- XYZ:END -->`
|
|
222
|
+
const two = parseBlocks(inlineTwo, {
|
|
223
|
+
open: 'XYZ:START',
|
|
224
|
+
close: 'XYZ:END'
|
|
225
|
+
})
|
|
226
|
+
/*
|
|
227
|
+
console.log('inline two ───────────────────────')
|
|
228
|
+
deepLog(two)
|
|
229
|
+
/** */
|
|
230
|
+
const valuesTwo = [
|
|
231
|
+
{
|
|
232
|
+
type: 'transformX',
|
|
233
|
+
options: { foo: 111 },
|
|
234
|
+
location: 55
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
valuesTwo.forEach((val, i) => {
|
|
238
|
+
const stub = val
|
|
239
|
+
const currentItem = two.blocks[i]
|
|
240
|
+
assert.equal(stub.type, currentItem.type, `${stub.type} ${i} transform`)
|
|
241
|
+
assert.equal(stub.options, currentItem.options, `${stub.type} ${i} options`)
|
|
242
|
+
})
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
const fnBlocks = `
|
|
246
|
+
<!-- XYZ:START functionName foo={{ rad: 'yellow' }} -->
|
|
247
|
+
nice
|
|
248
|
+
<!-- XYZ:END -->
|
|
249
|
+
|
|
250
|
+
<!-- XYZ:START {functionName} foo={{ rad: 'blue' }} -->
|
|
251
|
+
nice
|
|
252
|
+
<!-- XYZ:END -->
|
|
253
|
+
|
|
254
|
+
<!-- XYZ:START (functionName) foo={{ rad: 'red' }} -->
|
|
255
|
+
nice
|
|
256
|
+
<!-- XYZ:END -->
|
|
257
|
+
|
|
258
|
+
<!-- XYZ:START [functionName] foo={{ rad: 'purple' }} -->
|
|
259
|
+
nice
|
|
260
|
+
<!-- XYZ:END -->
|
|
261
|
+
|
|
262
|
+
<!-- XYZ:START {{functionName}} foo={{ rad: 'black' }} -->
|
|
263
|
+
nice
|
|
264
|
+
<!-- XYZ:END -->
|
|
265
|
+
|
|
266
|
+
<!-- XYZ:START ((functionName)) foo={{ rad: 'white' }} -->
|
|
267
|
+
nice
|
|
268
|
+
<!-- XYZ:END -->
|
|
269
|
+
|
|
270
|
+
<!-- XYZ:START [[functionName]] foo={{ rad: 'orange' }} -->
|
|
271
|
+
nice
|
|
272
|
+
<!-- XYZ:END -->
|
|
273
|
+
`
|
|
274
|
+
|
|
275
|
+
test('Handles function names', () => {
|
|
276
|
+
const parsedValue = parseBlocks(fnBlocks, {
|
|
277
|
+
open: 'XYZ:START',
|
|
278
|
+
close: 'XYZ:END'
|
|
279
|
+
})
|
|
280
|
+
/*
|
|
281
|
+
console.log('fn names')
|
|
282
|
+
deepLog(parsedValue)
|
|
283
|
+
/** */
|
|
284
|
+
assert.equal(Array.isArray(parsedValue.blocks), true)
|
|
285
|
+
assert.equal(parsedValue.blocks.length, 7)
|
|
286
|
+
|
|
287
|
+
const values = [
|
|
288
|
+
{
|
|
289
|
+
type: 'functionName',
|
|
290
|
+
options: { foo: { rad: 'yellow' } },
|
|
291
|
+
location: 78
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
type: 'functionName',
|
|
295
|
+
options: { foo: { rad: 'blue' } },
|
|
296
|
+
location: 157
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
type: 'functionName',
|
|
300
|
+
options: { foo: { rad: 'red' } },
|
|
301
|
+
location: 235
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
type: 'functionName',
|
|
305
|
+
options: { foo: { rad: 'purple' } },
|
|
306
|
+
location: 316
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
type: 'functionName',
|
|
310
|
+
options: { foo: { rad: 'black' } },
|
|
311
|
+
location: 398
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
type: 'functionName',
|
|
315
|
+
options: { foo: { rad: 'white' } },
|
|
316
|
+
location: 480
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
type: 'functionName',
|
|
320
|
+
options: { foo: { rad: 'orange' } },
|
|
321
|
+
location: 563
|
|
322
|
+
}
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
values.forEach((val, i) => {
|
|
326
|
+
const stub = val
|
|
327
|
+
const currentItem = parsedValue.blocks[i]
|
|
328
|
+
assert.equal(stub.type, currentItem.type, `${stub.type} ${i} transform`)
|
|
329
|
+
assert.equal(stub.options, currentItem.options, `${stub.type} ${i} options`)
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
test('different function names', () => {
|
|
335
|
+
const backwardCompat = `
|
|
336
|
+
<!-- XYZ:START functionName foo={{ rad: 'yellow' }} -->
|
|
337
|
+
nice
|
|
338
|
+
<!-- XYZ:END -->
|
|
339
|
+
|
|
340
|
+
<!-- XYZ:START lol width={999}
|
|
341
|
+
height={{111}}
|
|
342
|
+
numberAsString="12345"
|
|
343
|
+
great={["scoot", "sco ot", 'scooo ttt']}
|
|
344
|
+
nope=false -->
|
|
345
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer vitae mauris arcu, eu pretium nisi. Praesent fringilla ornare ullamcorper. Pellentesque diam orci, sodales in blandit ut, placerat quis felis. Vestibulum at sem massa, in tempus nisi. Vivamus ut fermentum odio. Etiam porttitor faucibus volutpat. Vivamus vitae mi ligula, non hendrerit urna. Suspendisse potenti. Quisque eget massa a massa semper mollis.
|
|
346
|
+
<!-- XYZ:END -->
|
|
347
|
+
|
|
348
|
+
<!-- XYZ:START (CODE:src=./relative/path/to/code.js&lines=22-44) -->
|
|
349
|
+
This content will be dynamically replaced with code from the file lines 22 through 44
|
|
350
|
+
<!-- XYZ:END -->
|
|
351
|
+
`
|
|
352
|
+
const parsedValue = parseBlocks(backwardCompat, {
|
|
353
|
+
open: 'XYZ:START',
|
|
354
|
+
close: 'XYZ:END'
|
|
355
|
+
})
|
|
356
|
+
/*
|
|
357
|
+
console.log('backwardCompat')
|
|
358
|
+
deepLog(parsedValue)
|
|
359
|
+
/** */
|
|
360
|
+
const answers = [
|
|
361
|
+
{
|
|
362
|
+
type: 'functionName',
|
|
363
|
+
options: { foo: { rad: 'yellow' } },
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
type: 'lol',
|
|
367
|
+
options: {
|
|
368
|
+
width: 999,
|
|
369
|
+
height: 111,
|
|
370
|
+
numberAsString: '12345',
|
|
371
|
+
great: [ 'scoot', 'sco ot', 'scooo ttt' ],
|
|
372
|
+
nope: false
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
type: 'CODE',
|
|
377
|
+
options: { src: './relative/path/to/code.js', lines: '22-44' },
|
|
378
|
+
},
|
|
379
|
+
]
|
|
380
|
+
parsedValue.blocks.forEach((transform, i) => {
|
|
381
|
+
const stub = answers[i]
|
|
382
|
+
assert.equal(transform.type, stub.type, `type: ${stub.type} at index ${i}`)
|
|
383
|
+
assert.equal(transform.options, stub.options, `options: ${stub.type} at index ${i}`)
|
|
384
|
+
})
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
const defaultOpts = {
|
|
388
|
+
syntax: 'md',
|
|
389
|
+
open: 'DOCS:START',
|
|
390
|
+
close: 'DOCS:END',
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const mdText = `
|
|
394
|
+
Very nice
|
|
395
|
+
|
|
396
|
+
<!-- DOCS:START(TOC) foo={{ rad: 'orange' }} ------>
|
|
397
|
+
ok
|
|
398
|
+
<!-- DOCS:END -->
|
|
399
|
+
|
|
400
|
+
<!-- DOCS:START (CODE:src=./relative/path/to/code.js&lines=22-44) -->
|
|
401
|
+
This content will be dynamically replaced with code from the file lines 22 through 44
|
|
402
|
+
<!-- DOCS:END -->
|
|
403
|
+
`
|
|
404
|
+
|
|
405
|
+
test('Parse md blocks', () => {
|
|
406
|
+
const parsedValue = parseBlocks(mdText, defaultOpts)
|
|
407
|
+
deepLog(parsedValue)
|
|
408
|
+
assert.equal(parsedValue.blocks, [
|
|
409
|
+
{
|
|
410
|
+
index: 1,
|
|
411
|
+
type: 'TOC',
|
|
412
|
+
options: { foo: { rad: 'orange' } },
|
|
413
|
+
context: { isMultiline: true },
|
|
414
|
+
open: {
|
|
415
|
+
value: "<!-- DOCS:START(TOC) foo={{ rad: 'orange' }} ------>\n",
|
|
416
|
+
start: 12,
|
|
417
|
+
end: 65
|
|
418
|
+
},
|
|
419
|
+
content: { value: 'ok', start: 65, end: 67, indentation: 0 },
|
|
420
|
+
close: { value: '\n<!-- DOCS:END -->', start: 67, end: 85 },
|
|
421
|
+
block: {
|
|
422
|
+
indentation: '',
|
|
423
|
+
lines: [ 4, 6 ],
|
|
424
|
+
start: 12,
|
|
425
|
+
end: 85,
|
|
426
|
+
rawArgs: "foo={{ rad: 'orange' }}",
|
|
427
|
+
rawContent: 'ok',
|
|
428
|
+
value: "<!-- DOCS:START(TOC) foo={{ rad: 'orange' }} ------>\n" +
|
|
429
|
+
'ok\n' +
|
|
430
|
+
'<!-- DOCS:END -->'
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
index: 2,
|
|
435
|
+
type: 'CODE',
|
|
436
|
+
options: { src: './relative/path/to/code.js', lines: '22-44' },
|
|
437
|
+
context: { isMultiline: true, isLegacy: true },
|
|
438
|
+
open: {
|
|
439
|
+
value: '<!-- DOCS:START (CODE:src=./relative/path/to/code.js&lines=22-44) -->\n',
|
|
440
|
+
start: 87,
|
|
441
|
+
end: 157
|
|
442
|
+
},
|
|
443
|
+
content: {
|
|
444
|
+
value: 'This content will be dynamically replaced with code from the file lines 22 through 44',
|
|
445
|
+
start: 157,
|
|
446
|
+
end: 242,
|
|
447
|
+
indentation: 0
|
|
448
|
+
},
|
|
449
|
+
close: { value: '\n<!-- DOCS:END -->', start: 242, end: 260 },
|
|
450
|
+
block: {
|
|
451
|
+
indentation: '',
|
|
452
|
+
lines: [ 8, 10 ],
|
|
453
|
+
start: 87,
|
|
454
|
+
end: 260,
|
|
455
|
+
rawArgs: 'src=./relative/path/to/code.js&lines=22-44',
|
|
456
|
+
rawContent: 'This content will be dynamically replaced with code from the file lines 22 through 44',
|
|
457
|
+
value: '<!-- DOCS:START (CODE:src=./relative/path/to/code.js&lines=22-44) -->\n' +
|
|
458
|
+
'This content will be dynamically replaced with code from the file lines 22 through 44\n' +
|
|
459
|
+
'<!-- DOCS:END -->'
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
], 'Array contains details')
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
test('Returns empty array', () => {
|
|
466
|
+
assert.equal(parseBlocks('', defaultOpts).blocks, [])
|
|
467
|
+
assert.equal(parseBlocks(' ', defaultOpts).blocks, [])
|
|
468
|
+
assert.equal(parseBlocks(`
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
`, defaultOpts).blocks, [])
|
|
472
|
+
assert.equal(parseBlocks(`
|
|
473
|
+
# No block in here
|
|
474
|
+
|
|
475
|
+
nope
|
|
476
|
+
`, defaultOpts).blocks, [])
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
test.run()
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const isGlob = require('is-glob')
|
|
4
|
+
const isValidFilePath = require('is-valid-path')
|
|
5
|
+
const { loadConfig } = require('./utils/load-config')
|
|
6
|
+
const { findUp } = require('./utils/fs')
|
|
7
|
+
const { markdownMagic } = require('./')
|
|
8
|
+
const { parse } = require('oparser')
|
|
9
|
+
const { deepLog } = require('./utils/logs')
|
|
10
|
+
const { REGEX_REGEX, escapeRegexString } = require('./utils/regex')
|
|
11
|
+
const { getFirstCharacter, isUpperCase } = require('./utils/text')
|
|
12
|
+
const argv = process.argv.slice(2)
|
|
13
|
+
const cwd = process.cwd()
|
|
14
|
+
const defaultConfigPath = 'md.config.js'
|
|
15
|
+
|
|
16
|
+
const ARRAY_REGEX = /^\[(.*)\]$/
|
|
17
|
+
|
|
18
|
+
/** @type {BUILD_VERSION} */
|
|
19
|
+
const x = 'f'
|
|
20
|
+
|
|
21
|
+
function isArrayLike(str) {
|
|
22
|
+
if (typeof str !== 'string') return false
|
|
23
|
+
return Boolean(ARRAY_REGEX.test(str))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function getBaseDir(opts = {}) {
|
|
27
|
+
const { currentDir = cwd } = opts
|
|
28
|
+
const gitDir = await findUp(currentDir, '.git')
|
|
29
|
+
return (gitDir) ? path.dirname(gitDir) : currentDir
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function stringLooksLikeFile(value) {
|
|
33
|
+
return !value.match(/^-+/) // isn't option looking
|
|
34
|
+
&& isValidFilePath(value)
|
|
35
|
+
&& (
|
|
36
|
+
path.basename(value).indexOf('.') > -1 // has period
|
|
37
|
+
|| getFirstCharacter(value) === '_' // starts with _
|
|
38
|
+
// || isUpperCase(value) // is all caps
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getValue(val) {
|
|
43
|
+
return (val[1] !== '=') ? val[1] : val[0]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function trimLeadingDashes(value) {
|
|
47
|
+
return value.replace(/^-+/, '')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function removeNodeModules(value = '') {
|
|
51
|
+
if (typeof value !== 'string') {
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
54
|
+
return !value.match(/node_modules\//)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function coerceStringToRegex(str) {
|
|
58
|
+
const isRegex = str.match(REGEX_REGEX)
|
|
59
|
+
if (!isRegex) {
|
|
60
|
+
return str
|
|
61
|
+
}
|
|
62
|
+
const [ _match, pattern, flags ] = isRegex
|
|
63
|
+
return new RegExp(escapeRegexString(pattern), flags)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function convertToArray(str = '') {
|
|
67
|
+
const { fixedArray } = parse(`fixedArray=${str}`)
|
|
68
|
+
return (fixedArray || [])
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function addValue(value, currentCollection) {
|
|
72
|
+
if (isArrayLike(value)) {
|
|
73
|
+
const array = convertToArray(value)
|
|
74
|
+
// uniquify returned array
|
|
75
|
+
return Array.from(new Set(currentCollection.concat(array)))
|
|
76
|
+
}
|
|
77
|
+
if (currentCollection.indexOf(value) > -1) {
|
|
78
|
+
return currentCollection
|
|
79
|
+
}
|
|
80
|
+
return currentCollection.concat(value)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/*
|
|
84
|
+
node ./cli.js test/fixtures/md/**.md debug --transform test/fixtures/md/transform-**.md 1233535235 = hahah funky=hi --ignore test/fixtures/output/**.md --lol --whatever test/fixtures/md/syntax-**.md --foo=bar --fun lol.md what=no.md x 'xno.md' what
|
|
85
|
+
*/
|
|
86
|
+
function getGlobGroupsFromArgs(args, opts = {}) {
|
|
87
|
+
const globKeys = opts.globKeys || []
|
|
88
|
+
let preceding = ['', '']
|
|
89
|
+
let collection = []
|
|
90
|
+
let globGroups = []
|
|
91
|
+
let reserved = []
|
|
92
|
+
let otherOpts = []
|
|
93
|
+
let /** @type {string|undefined} */ cacheKey = ''
|
|
94
|
+
for (let i = 0; i < args.length; i++) {
|
|
95
|
+
const isLastArg = (args.length - 1) === i
|
|
96
|
+
const arg = args[i]
|
|
97
|
+
const prevArg = args[i - 1]
|
|
98
|
+
const looksLikeFile = stringLooksLikeFile(arg) // @TODO verify file exists?
|
|
99
|
+
// console.log('looksLikeFile', looksLikeFile)
|
|
100
|
+
// console.log('cacheKey', cacheKey)
|
|
101
|
+
// console.log('arg', arg)
|
|
102
|
+
|
|
103
|
+
if (looksLikeFile && typeof cacheKey !== 'undefined') {
|
|
104
|
+
// @TODO verify file exists?
|
|
105
|
+
collection.push(arg)
|
|
106
|
+
} else if (arg.match(/^-+/)) {
|
|
107
|
+
cacheKey = arg
|
|
108
|
+
if (collection.length) {
|
|
109
|
+
const val = getValue(preceding)
|
|
110
|
+
reserved.push(val)
|
|
111
|
+
globGroups.push({
|
|
112
|
+
key: trimLeadingDashes(val),
|
|
113
|
+
raw: val,
|
|
114
|
+
values: collection.filter(removeNodeModules).map((x) => coerceStringToRegex(x))
|
|
115
|
+
})
|
|
116
|
+
collection = []
|
|
117
|
+
}
|
|
118
|
+
preceding = [ prevArg, arg ]
|
|
119
|
+
} else if (cacheKey && arg.match(/=/)) {
|
|
120
|
+
// console.log('FOOO', arg)
|
|
121
|
+
cacheKey = undefined
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const cleanKey = trimLeadingDashes(cacheKey || '')
|
|
125
|
+
const isDefinedGlobKey = globKeys.includes(cleanKey) || globKeys.includes(cacheKey)
|
|
126
|
+
// console.log(`isDefinedGlobKey: ${cleanKey}`, isDefinedGlobKey)
|
|
127
|
+
if (isDefinedGlobKey && (isGlob(arg) || looksLikeFile)) {
|
|
128
|
+
/*
|
|
129
|
+
console.log('ADD collection.push', arg)
|
|
130
|
+
/** */
|
|
131
|
+
collection = addValue(arg, collection)
|
|
132
|
+
} else {
|
|
133
|
+
if (!cacheKey && isGlob(arg)) {
|
|
134
|
+
collection = addValue(arg, collection)
|
|
135
|
+
} else if (!looksLikeFile) {
|
|
136
|
+
/* Pass through non matching args */
|
|
137
|
+
/*
|
|
138
|
+
console.log('ADD otherOpts', arg)
|
|
139
|
+
/** */
|
|
140
|
+
otherOpts.push(arg)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (isLastArg && collection.length) {
|
|
145
|
+
const val = getValue(preceding)
|
|
146
|
+
reserved.push(val)
|
|
147
|
+
globGroups.push({
|
|
148
|
+
key: trimLeadingDashes(val),
|
|
149
|
+
raw: val,
|
|
150
|
+
values: collection.filter(removeNodeModules).map((x) => coerceStringToRegex(x))
|
|
151
|
+
})
|
|
152
|
+
collection = []
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
globGroups,
|
|
158
|
+
otherOpts: otherOpts.filter((opt) => !reserved.includes(opt))
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function runCli(options = {}) {
|
|
163
|
+
let configFile
|
|
164
|
+
let opts = {}
|
|
165
|
+
/*
|
|
166
|
+
console.log('argv', argv)
|
|
167
|
+
console.log('options', options)
|
|
168
|
+
/** */
|
|
169
|
+
/* If raw args found, process them further */
|
|
170
|
+
if (argv.length && (options._ && options._.length || (options.file || options.files))) {
|
|
171
|
+
// if (isGlob(argv[0])) {
|
|
172
|
+
// console.log('glob', argv[0])
|
|
173
|
+
// options.glob = argv[0]
|
|
174
|
+
// }
|
|
175
|
+
const globParse = getGlobGroupsFromArgs(argv, {
|
|
176
|
+
/* CLI args that should be glob keys */
|
|
177
|
+
globKeys: ['files', 'file']
|
|
178
|
+
})
|
|
179
|
+
const { globGroups, otherOpts } = globParse
|
|
180
|
+
console.log('globGroups', globGroups)
|
|
181
|
+
// deepLog(globParse)
|
|
182
|
+
// console.log('globParse', globParse)
|
|
183
|
+
if (globGroups.length) {
|
|
184
|
+
const globGroupByKey = globGroups.reduce((acc, curr, i) => {
|
|
185
|
+
acc[curr.key] = globGroups[i]
|
|
186
|
+
return acc
|
|
187
|
+
}, {})
|
|
188
|
+
// console.log('globGroupByKey', globGroupByKey)
|
|
189
|
+
|
|
190
|
+
if (globGroupByKey.file) {
|
|
191
|
+
options.glob = globGroupByKey.file.values
|
|
192
|
+
} else if (globGroupByKey['']) {
|
|
193
|
+
options.glob = globGroupByKey[''].values
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (globGroupByKey.ignore) {
|
|
197
|
+
options.ignore = globGroupByKey.ignore.values
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/*
|
|
201
|
+
deepLog(options)
|
|
202
|
+
/** */
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* Parse for weird CLI inputs */
|
|
206
|
+
const extraParse = parse(otherOpts.join(' '))
|
|
207
|
+
|
|
208
|
+
/*
|
|
209
|
+
// console.log('otherOpts', otherOpts)
|
|
210
|
+
console.log('nicely handed CLI args')
|
|
211
|
+
console.log(extraParse)
|
|
212
|
+
process.exit(1)
|
|
213
|
+
/** */
|
|
214
|
+
|
|
215
|
+
delete options._
|
|
216
|
+
opts = {
|
|
217
|
+
...options,
|
|
218
|
+
...extraParse
|
|
219
|
+
}
|
|
220
|
+
// console.log('opts', opts)
|
|
221
|
+
}
|
|
222
|
+
if (opts.config) {
|
|
223
|
+
configFile = opts.config
|
|
224
|
+
} else {
|
|
225
|
+
const baseDir = await getBaseDir()
|
|
226
|
+
configFile = await findUp(baseDir, defaultConfigPath)
|
|
227
|
+
}
|
|
228
|
+
const config = (configFile) ? loadConfig(configFile) : {}
|
|
229
|
+
const mergedConfig = {
|
|
230
|
+
...config,
|
|
231
|
+
...opts,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
mergedConfig.outputDir = mergedConfig.output || mergedConfig.outputDir
|
|
235
|
+
/*
|
|
236
|
+
console.log('mergedConfig', mergedConfig)
|
|
237
|
+
// return
|
|
238
|
+
/** */
|
|
239
|
+
await markdownMagic(mergedConfig)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
module.exports = {
|
|
243
|
+
getGlobGroupsFromArgs,
|
|
244
|
+
runCli
|
|
245
|
+
}
|