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/transforms/remote.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
const regexUtils = require('../utils/regex')
|
|
2
1
|
const remoteRequest = require('../utils/remoteRequest')
|
|
3
2
|
|
|
4
|
-
module.exports = function REMOTE(
|
|
5
|
-
|
|
3
|
+
module.exports = function REMOTE(api) {
|
|
4
|
+
// console.log('REMOTE api', api)
|
|
5
|
+
const { options, content, settings } = api
|
|
6
|
+
const { regex } = settings
|
|
7
|
+
// console.log('MAKE REMOTE REQUEST')
|
|
8
|
+
const remoteContent = remoteRequest(options.url)
|
|
6
9
|
if (!remoteContent) {
|
|
7
10
|
return content
|
|
8
11
|
}
|
|
9
12
|
if (options.keepComments) {
|
|
10
13
|
return remoteContent
|
|
11
14
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return remoteContent.replace(openTagRegex, '').replace(closeTagRegex, '')
|
|
15
|
+
// console.log('REMOTE', remoteContent)
|
|
16
|
+
return remoteContent.replace(regex.open, '').replace(regex.close, '')
|
|
15
17
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const { generateToc } = require('../utils/toc')
|
|
2
|
+
|
|
3
|
+
module.exports = async function sectionToc(api) {
|
|
4
|
+
// console.log("sectionToc", api)
|
|
5
|
+
const { options, fileContent, srcPath, getBlockDetails } = api
|
|
6
|
+
const opts = options || {}
|
|
7
|
+
const subToc = await generateToc({
|
|
8
|
+
options: {
|
|
9
|
+
...opts,
|
|
10
|
+
// Set sub table of contents
|
|
11
|
+
sub: true
|
|
12
|
+
},
|
|
13
|
+
fileContent,
|
|
14
|
+
srcPath,
|
|
15
|
+
getBlockDetails: getBlockDetails
|
|
16
|
+
})
|
|
17
|
+
return subToc
|
|
18
|
+
}
|
package/lib/transforms/toc.js
CHANGED
|
@@ -1,266 +1,13 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// console.log('config', config.originalPath)
|
|
12
|
-
// debugFileMatch = config.originalPath.match(/packages\/analytics\/README\.md/)
|
|
13
|
-
|
|
14
|
-
// https://www.npmjs.com/package/@technote-space/toc-generator
|
|
15
|
-
const t = await transform(contents, {
|
|
16
|
-
// mode: 'github.com', // github.com | bitbucket.org | gitlab.com | nodejs.org | ghost.org (default: github.com)
|
|
17
|
-
isNotitle: true,
|
|
18
|
-
//isCustomMode: true,
|
|
19
|
-
openingComment: '',
|
|
20
|
-
closingComment: '',
|
|
21
|
-
maxHeaderLevel: (opts.maxDepth) ? Number(opts.maxDepth) : 4
|
|
22
|
-
// maxHeaderLevel: 2, // default: 4
|
|
23
|
-
// title: '**Table of Contents**',
|
|
24
|
-
// isNotitle: true,
|
|
25
|
-
// isFolding: true,
|
|
26
|
-
// entryPrefix: '*',
|
|
27
|
-
// processAll: true,
|
|
28
|
-
// updateOnly: true,
|
|
29
|
-
// openingComment: '<!-- toc -->',
|
|
30
|
-
// closingComment: '<!-- tocstop --> ',
|
|
31
|
-
// checkOpeningComments: ['<!-- toc '],
|
|
32
|
-
// checkClosingComments: ['<!-- tocstop '],
|
|
33
|
-
// isCustomMode: false,
|
|
34
|
-
// customTemplate: '<p align="center">${ITEMS}</p>',
|
|
35
|
-
// itemTemplate: '<a href="${LINK}">${TEXT}</a>',
|
|
36
|
-
// separator: '<span>|</span>',
|
|
37
|
-
// footer: '',
|
|
1
|
+
const { generateToc } = require('../utils/toc')
|
|
2
|
+
|
|
3
|
+
module.exports = async function TOC(api) {
|
|
4
|
+
// console.log("TOC API", api)
|
|
5
|
+
const { options, fileContent, srcPath, getBlockDetails } = api
|
|
6
|
+
const toc = await generateToc({
|
|
7
|
+
options: options || {},
|
|
8
|
+
fileContent,
|
|
9
|
+
srcPath,
|
|
10
|
+
getBlockDetails: getBlockDetails
|
|
38
11
|
})
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (debugFileMatch) {
|
|
42
|
-
console.log('before firsth1 removal', output)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/* remove first h1 */
|
|
46
|
-
if (!opts.firsth1) {
|
|
47
|
-
// console.log('output', output)
|
|
48
|
-
const lines = output.split('\n')
|
|
49
|
-
let firstH1
|
|
50
|
-
let firstLine
|
|
51
|
-
const countOfH1s = lines.reduce((acc, line, index) => {
|
|
52
|
-
if (line !== '' && !firstLine) {
|
|
53
|
-
firstLine = index
|
|
54
|
-
}
|
|
55
|
-
if (line.match(/^-\s+/)) {
|
|
56
|
-
if (!firstH1) {
|
|
57
|
-
const rawText = line.match(/\[(.*)\]/)[1]
|
|
58
|
-
firstH1 = [line, index, firstLine === index, rawText]
|
|
59
|
-
}
|
|
60
|
-
acc = acc + 1
|
|
61
|
-
}
|
|
62
|
-
return acc
|
|
63
|
-
}, 0)
|
|
64
|
-
|
|
65
|
-
const firstHeadingData = (firstH1 && Array.isArray(firstH1)) ? firstH1 : []
|
|
66
|
-
const [ lineText, lineIndex, isFirst, matchText ] = firstHeadingData
|
|
67
|
-
|
|
68
|
-
// verify its h1
|
|
69
|
-
const matchTextEscaped = escapeStringRegexp(matchText)
|
|
70
|
-
// console.log('matchTextEscaped', matchTextEscaped)
|
|
71
|
-
// /^#{1}\s+(.*)/
|
|
72
|
-
const FIRST_H1_REGEX = new RegExp(`^#\\s*\\[?${matchTextEscaped}\\]?(?:.*)?`, 'gim')
|
|
73
|
-
// /^<h1\b[^>]*>([\s\S]*?)<\/h1>/
|
|
74
|
-
const FIRST_HTML_H1_REGEX = new RegExp(`^<h1\\b[^>]*>[\\s]*?(${matchTextEscaped})[\\s]*?<\\/h1>`, 'gim')
|
|
75
|
-
// /^(.*)\n={3,}/
|
|
76
|
-
const FIRST_UNDERSCORE_H1 = new RegExp(`^(${matchTextEscaped})\n={3,}`, 'gim')
|
|
77
|
-
|
|
78
|
-
let docHasHeading = false
|
|
79
|
-
if (contents.match(FIRST_H1_REGEX)) {
|
|
80
|
-
docHasHeading = matchText
|
|
81
|
-
} else if (contents.match(FIRST_HTML_H1_REGEX)) {
|
|
82
|
-
docHasHeading = matchText
|
|
83
|
-
} else if (contents.match(FIRST_UNDERSCORE_H1)) {
|
|
84
|
-
docHasHeading = matchText
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (debugFileMatch) {
|
|
88
|
-
console.log('matchText', matchText)
|
|
89
|
-
if (docHasHeading) {
|
|
90
|
-
console.log('Found heading 1', docHasHeading)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (debugFileMatch) {
|
|
95
|
-
console.log('top level toc item count', countOfH1s)
|
|
96
|
-
if (docHasHeading) {
|
|
97
|
-
console.log('Found heading 1', docHasHeading)
|
|
98
|
-
console.log('firstH1', firstH1)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
let firstItemWithContent
|
|
103
|
-
let foundFirstH1
|
|
104
|
-
|
|
105
|
-
if (docHasHeading) {
|
|
106
|
-
output = lines.reduce((acc, line, i) => {
|
|
107
|
-
const isLineEmpty = line === ''
|
|
108
|
-
if (!isLineEmpty && !firstItemWithContent) {
|
|
109
|
-
firstItemWithContent = i
|
|
110
|
-
}
|
|
111
|
-
if (!foundFirstH1 && (firstItemWithContent === i) && line.match(/^-\s+/)) {
|
|
112
|
-
foundFirstH1 = true
|
|
113
|
-
return acc
|
|
114
|
-
}
|
|
115
|
-
// Add back line and remove level
|
|
116
|
-
let newLineValue = line
|
|
117
|
-
if (lineText) {
|
|
118
|
-
// const untilFirstH1 = i < lineIndex
|
|
119
|
-
/* @TODO make option? flatten all non first h1 tags */
|
|
120
|
-
if (countOfH1s > 0 && !isLineEmpty && newLineValue.match(/^\s+-/)) {
|
|
121
|
-
newLineValue = line.substring(2)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
acc = acc.concat(newLineValue)
|
|
126
|
-
return acc
|
|
127
|
-
}, []).join('\n')
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// console.log('output', output)
|
|
131
|
-
if (debugFileMatch) {
|
|
132
|
-
console.log('after firsth1 removal', output)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// console.log(t)
|
|
136
|
-
// Fix <h1> with multiple lines
|
|
137
|
-
const spaces = output.match(/\[\n([\s\S]*?)\]/gm)
|
|
138
|
-
if (spaces) {
|
|
139
|
-
const fixed = spaces[0]
|
|
140
|
-
// all new lines
|
|
141
|
-
.replace(/\n/g, '')
|
|
142
|
-
// leading spaces
|
|
143
|
-
.replace(/\[\s+/, '[')
|
|
144
|
-
.trim()
|
|
145
|
-
output = output.replace(spaces[0], fixed)
|
|
146
|
-
}
|
|
147
|
-
// console.log(output)
|
|
148
|
-
output = output
|
|
149
|
-
.replace(/(.*)\[Table of Contents\]\(.*\)\n?/i, '')
|
|
150
|
-
.replace(/(.*)\[toc\]\(.*\)\n?/i, '')
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (opts.excludeText) {
|
|
154
|
-
// (\s+)?-(.*)\[Usage\]\(.*\)
|
|
155
|
-
const regex = new RegExp(`\(\\s+\)?-(.*)\\[${opts.excludeText}\\]\\(.*\\)`, 'i')
|
|
156
|
-
// /(\s+)?-(.*)\[Usage\]\(.*\)(\n\s+(.*))+/im
|
|
157
|
-
const nestedRegex = new RegExp(`\(\\s+\)?-(.*)\\[${opts.excludeText}\\]\\(.*\\)\(\\n\\s+\(.*\)\)+`, 'i')
|
|
158
|
-
|
|
159
|
-
const hasNested = nestedRegex.exec(output)
|
|
160
|
-
if (hasNested) {
|
|
161
|
-
// Count indentation of spaces
|
|
162
|
-
const numberOfSpaces = hasNested[1].replace(/\n/g, '').length
|
|
163
|
-
const subItems = numberOfSpaces + 1
|
|
164
|
-
// Update regex to only remove sub items
|
|
165
|
-
const nestedRegexSpaces = new RegExp(`\(\\s+\)?-(.*)\\[${opts.excludeText}\\]\\(.*\\)\(\\n\\s{${subItems},}\(.*\)\)+`, 'i')
|
|
166
|
-
// console.log('nestedRegexSpaces', nestedRegexSpaces)
|
|
167
|
-
// If exclude value has nested sections remove them as well.
|
|
168
|
-
output = output.replace(nestedRegexSpaces, '')
|
|
169
|
-
output = output.replace(regex, '')
|
|
170
|
-
} else {
|
|
171
|
-
output = output.replace(regex, '')
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// If collapse wrap in <details>
|
|
176
|
-
if (opts.collapse) {
|
|
177
|
-
const text = (opts.collapseText) ? opts.collapseText : 'Table of Contents'
|
|
178
|
-
return `<details>
|
|
179
|
-
<summary>${text}</summary>
|
|
180
|
-
${output
|
|
181
|
-
// Replace leading double spaces
|
|
182
|
-
.replace(/^\n\n/, '\n')
|
|
183
|
-
// Replace trailing double spaces
|
|
184
|
-
.replace(/\n\n$/, '\n')}
|
|
185
|
-
</details>`
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return output.replace(removeLeadingAndTrailingLineBreaks, '')
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const HTML_TAG = /<([a-zA-Z1-6]+)([^<]+)*(?:>(.*)<\/\1>|\s+\/>)/gim
|
|
192
|
-
|
|
193
|
-
function parseHtmlProps(mdContents) {
|
|
194
|
-
const htmlTags = mdContents
|
|
195
|
-
/* Fix non terminating <tags> */
|
|
196
|
-
.replace(/(['"`]<(.*)>['"`])/gm, '_$2_')
|
|
197
|
-
.match(HTML_TAG)
|
|
198
|
-
//console.log('htmlTags', htmlTags)
|
|
199
|
-
|
|
200
|
-
let tags = []
|
|
201
|
-
if (htmlTags) {
|
|
202
|
-
let propsValues = {}
|
|
203
|
-
var regexSingleTag = /<([a-zA-Z1-6]+)([^<]+)*(?:>(.*)<\/\1>|\s+\/>)/
|
|
204
|
-
for (var i = 0; i < htmlTags.length; i++) {
|
|
205
|
-
var tagMatches = regexSingleTag.exec(htmlTags[i])
|
|
206
|
-
// console.log('tagMatches', tagMatches)
|
|
207
|
-
var [ match, tag, props ] = tagMatches
|
|
208
|
-
// console.log(`Tag #${i} ${tag}`)
|
|
209
|
-
if (props) {
|
|
210
|
-
const cleanProps = props
|
|
211
|
-
// Remove new lines and tabs
|
|
212
|
-
.replace(/\n\t/g, '')
|
|
213
|
-
// Remove extra spaces
|
|
214
|
-
.replace(/\s\s+/g, ' ')
|
|
215
|
-
.trim()
|
|
216
|
-
|
|
217
|
-
propsValues = cleanProps.split(" ").reduce((acc, curr) => {
|
|
218
|
-
const hasQuotes = curr.match(/=['"]/)
|
|
219
|
-
// Check key="value" | key='value' | key={value}
|
|
220
|
-
const propWithValue = /([A-Za-z-_$]+)=['{"](.*)['}"]/g.exec(curr)
|
|
221
|
-
if (propWithValue) {
|
|
222
|
-
return {
|
|
223
|
-
...acc,
|
|
224
|
-
[`${propWithValue[1]}`]: (hasQuotes) ? propWithValue[2] : convert(propWithValue[2])
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
// Check isLoading boolean props
|
|
228
|
-
const booleanProp = curr.match(/([A-Za-z]*)/)
|
|
229
|
-
if (booleanProp) {
|
|
230
|
-
return {
|
|
231
|
-
...acc,
|
|
232
|
-
[`${booleanProp[1]}`]: true
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return acc
|
|
236
|
-
}, {})
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
tags.push({
|
|
240
|
-
tag: tag,
|
|
241
|
-
props: propsValues,
|
|
242
|
-
raw: match
|
|
243
|
-
})
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return tags
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function escapeStringRegexp(string) {
|
|
250
|
-
if (typeof string !== 'string') {
|
|
251
|
-
throw new TypeError('Expected a string');
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Escape characters with special meaning either inside or outside character sets.
|
|
255
|
-
// Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar.
|
|
256
|
-
return string
|
|
257
|
-
.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
|
|
258
|
-
.replace(/-/g, '\\x2d');
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function removeUndefined(output) {
|
|
262
|
-
// remove undefined from new line and start of string if first H1 is missing
|
|
263
|
-
return output.replace(/\nundefined/g, '\n-').replace(/^undefined/g, '-')
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Alt https://github.com/thlorenz/doctoc
|
|
12
|
+
return toc
|
|
13
|
+
}
|
package/lib/types.js
ADDED
package/lib/utils/fs.js
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
const fs = require('fs').promises
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const globrex = require('globrex')
|
|
4
|
+
const isGlob = require('is-glob')
|
|
5
|
+
const isLocalPath = require('is-local-path')
|
|
6
|
+
const { REGEX_REGEX, escapeRegexString } = require('./regex')
|
|
7
|
+
const { dirname, resolve, join } = require('path')
|
|
8
|
+
const { readdir, stat, readFile } = fs
|
|
9
|
+
|
|
10
|
+
const IS_HIDDEN_FILE = /(^|[\\\/])\.[^\\\/\.]/g
|
|
11
|
+
|
|
12
|
+
function isRegex(thing) {
|
|
13
|
+
return (thing instanceof RegExp)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function writeFile(filePath, content) {
|
|
17
|
+
try {
|
|
18
|
+
await fs.writeFile(filePath, content)
|
|
19
|
+
} catch(e) {
|
|
20
|
+
const dirName = path.dirname(filePath)
|
|
21
|
+
await fs.mkdir(dirName, { recursive: true })
|
|
22
|
+
await fs.writeFile(filePath, content)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function findUp(start, fileName) {
|
|
27
|
+
const file = await escalade(start, (dir, relativePaths) => {
|
|
28
|
+
// console.log('~> dir:', dir);
|
|
29
|
+
// console.log('~> relativePaths:', relativePaths);
|
|
30
|
+
// console.log('---')
|
|
31
|
+
if (typeof fileName === 'string' && relativePaths.includes(fileName)) {
|
|
32
|
+
// will be resolved into absolute
|
|
33
|
+
return fileName
|
|
34
|
+
}
|
|
35
|
+
if (fileName instanceof RegExp) {
|
|
36
|
+
const found = relativePaths.find((relativePath) => relativePath.match(fileName))
|
|
37
|
+
if (found) return found
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
return file
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// https://github.com/lukeed/escalade
|
|
44
|
+
async function escalade(start, callback) {
|
|
45
|
+
let dir = resolve('.', start)
|
|
46
|
+
let tmp, stats = await stat(dir)
|
|
47
|
+
|
|
48
|
+
if (!stats.isDirectory()) {
|
|
49
|
+
dir = dirname(dir)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
while (true) {
|
|
53
|
+
tmp = await callback(dir, await readdir(dir))
|
|
54
|
+
if (tmp) return resolve(dir, tmp)
|
|
55
|
+
dir = dirname(tmp = dir)
|
|
56
|
+
if (tmp === dir) break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// https://github.com/lukeed/totalist
|
|
61
|
+
async function totalist(dir, callback, pre='') {
|
|
62
|
+
dir = resolve('.', dir)
|
|
63
|
+
await readdir(dir).then(arr => {
|
|
64
|
+
return Promise.all(arr.map((str) => {
|
|
65
|
+
let abs = join(dir, str)
|
|
66
|
+
return stat(abs).then(stats => {
|
|
67
|
+
return stats.isDirectory() ? totalist(abs, callback, join(pre, str)) : callback(join(pre, str), abs, stats)
|
|
68
|
+
})
|
|
69
|
+
}))
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function combineRegexes(patterns = []) {
|
|
74
|
+
const string = patterns.map((pat) => {
|
|
75
|
+
if (isRegex(pat)) {
|
|
76
|
+
return pat.source
|
|
77
|
+
} else if (typeof pat === 'string' && REGEX_REGEX.test(pat)) {
|
|
78
|
+
const regexInfo = pat.match(REGEX_REGEX)
|
|
79
|
+
console.log('regexInfo', regexInfo)
|
|
80
|
+
if (regexInfo && regexInfo[1]) {
|
|
81
|
+
// escapeRegexString
|
|
82
|
+
return regexInfo[1]
|
|
83
|
+
}
|
|
84
|
+
} else if (isGlob(pat)) {
|
|
85
|
+
console.log('pat', pat)
|
|
86
|
+
const result = globrex(pat, { globstar: true, extended: true })
|
|
87
|
+
console.log('result', result)
|
|
88
|
+
return result.regex.source
|
|
89
|
+
}
|
|
90
|
+
return pat
|
|
91
|
+
}).join('|')
|
|
92
|
+
console.log('xxxstring', string)
|
|
93
|
+
return new RegExp(string)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// alt https://github.com/duniul/clean-modules/blob/33b66bcfb7825e1fa1eb1e296e523ddba374f1ae/src/utils/filesystem.ts#L92
|
|
97
|
+
// Alt https://github.com/AvianFlu/ncp
|
|
98
|
+
async function getFilePaths(dirName, {
|
|
99
|
+
patterns = [],
|
|
100
|
+
ignore = [],
|
|
101
|
+
excludeGitIgnore = false,
|
|
102
|
+
excludeHidden = false
|
|
103
|
+
}) {
|
|
104
|
+
let findPattern
|
|
105
|
+
let ignorePattern
|
|
106
|
+
let filePaths = []
|
|
107
|
+
let gitIgnoreFiles = []
|
|
108
|
+
let gitIgnoreGlobs = []
|
|
109
|
+
|
|
110
|
+
if (patterns && patterns.length) {
|
|
111
|
+
findPattern = combineRegexes(patterns)
|
|
112
|
+
console.log('findPatternfindPatternfindPattern', patterns)
|
|
113
|
+
}
|
|
114
|
+
if (ignore && ignore.length) {
|
|
115
|
+
ignorePattern = combineRegexes(ignore)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (excludeGitIgnore) {
|
|
119
|
+
const gitIgnoreContents = await getGitignoreContents()
|
|
120
|
+
for (let index = 0; index < gitIgnoreContents.length; index++) {
|
|
121
|
+
const ignoreItem = gitIgnoreContents[index]
|
|
122
|
+
// console.log('ignoreItem', ignoreItem)
|
|
123
|
+
if (isGlob(ignoreItem)) {
|
|
124
|
+
gitIgnoreGlobs.push(ignoreItem)
|
|
125
|
+
} else {
|
|
126
|
+
gitIgnoreFiles.push(
|
|
127
|
+
ignoreItem.replace(/^\.\//, '') // normalize relative paths
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
//*
|
|
134
|
+
console.log('findPattern', findPattern)
|
|
135
|
+
console.log('ignorePattern', ignorePattern)
|
|
136
|
+
console.log('gitIgnoreFiles', gitIgnoreFiles)
|
|
137
|
+
console.log('gitIgnoreGlobs', gitIgnoreGlobs)
|
|
138
|
+
// process.exit(1)
|
|
139
|
+
/** */
|
|
140
|
+
|
|
141
|
+
await totalist(dirName, (relativePath, abs, stats) => {
|
|
142
|
+
const absolutePath = `${dirName}/${relativePath}`
|
|
143
|
+
const topLevelDir = relativePath.substring(0, relativePath.indexOf('/'))
|
|
144
|
+
//console.log('absolutePath', absolutePath)
|
|
145
|
+
// console.log('relativePath', relativePath)
|
|
146
|
+
// console.log('topLevelDir', topLevelDir)
|
|
147
|
+
|
|
148
|
+
/* Remove hidden files */
|
|
149
|
+
if (excludeHidden && IS_HIDDEN_FILE.test(relativePath)) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Remove files in git ignore */
|
|
154
|
+
if (excludeGitIgnore && gitIgnoreFiles.length) {
|
|
155
|
+
if (gitIgnoreFiles.includes(relativePath)) return
|
|
156
|
+
if (gitIgnoreFiles.includes(path.basename(relativePath))) return
|
|
157
|
+
//* // slow
|
|
158
|
+
if (gitIgnoreFiles.some((ignore) => {
|
|
159
|
+
// console.log('ignore', ignore)
|
|
160
|
+
// return relativePath.indexOf(ignore) > -1
|
|
161
|
+
// return relativePath.split('/')[0] === ignore
|
|
162
|
+
return topLevelDir === ignore || relativePath === ignore
|
|
163
|
+
})) {
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
/** */
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* Remove files in ignore array */
|
|
170
|
+
if (ignorePattern && ignorePattern.test(relativePath)) {
|
|
171
|
+
// Alt checker https://github.com/axtgr/wildcard-match
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* If no patterns supplied add all files */
|
|
176
|
+
if (!findPattern) {
|
|
177
|
+
filePaths.push(absolutePath)
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* If pattern match add file! */
|
|
182
|
+
// Alt match https://github.com/micromatch/picomatch
|
|
183
|
+
if (findPattern.test(absolutePath)) {
|
|
184
|
+
// console.log('Match absolutePath', absolutePath)
|
|
185
|
+
filePaths.push(absolutePath)
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* If pattern match add file! */
|
|
190
|
+
if (findPattern.test(relativePath)) {
|
|
191
|
+
// console.log('Match relativePath', relativePath)
|
|
192
|
+
filePaths.push(absolutePath)
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/*
|
|
197
|
+
let ignored = false
|
|
198
|
+
for (let index = 0; index < ignore.length; index++) {
|
|
199
|
+
const pattern = ignore[index]
|
|
200
|
+
if (pattern.test(absolutePath)) {
|
|
201
|
+
ignored = true
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (!ignored) {
|
|
205
|
+
filePaths.push(absolutePath)
|
|
206
|
+
}
|
|
207
|
+
/** */
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
if (gitIgnoreGlobs && gitIgnoreGlobs.length) {
|
|
211
|
+
console.log('gitIgnoreGlobs', gitIgnoreGlobs)
|
|
212
|
+
let removeFiles = []
|
|
213
|
+
for (let index = 0; index < gitIgnoreGlobs.length; index++) {
|
|
214
|
+
const glob = gitIgnoreGlobs[index]
|
|
215
|
+
const result = globrex(glob) // alt lib https://github.com/axtgr/wildcard-match
|
|
216
|
+
// console.log('result', result)
|
|
217
|
+
for (let n = 0; n < filePaths.length; n++) {
|
|
218
|
+
const file = filePaths[n]
|
|
219
|
+
if (result.regex.test(file)) {
|
|
220
|
+
removeFiles.push(file)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/* Remove files that match glob pattern */
|
|
225
|
+
if (removeFiles.length) {
|
|
226
|
+
filePaths = filePaths.filter(function(el) {
|
|
227
|
+
return removeFiles.indexOf(el) < 0
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return filePaths
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function convertToRelativePath(file, cwd) {
|
|
236
|
+
return file.replace(cwd, '').replace(/^\//, '')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function getGitignoreContents(filePath = '.gitignore') {
|
|
240
|
+
try {
|
|
241
|
+
const gitIgnoreContent = await readFile(filePath, { encoding: 'utf8' })
|
|
242
|
+
return gitIgnoreContent
|
|
243
|
+
.split(/\r?\n/)
|
|
244
|
+
.filter((line) => !/^\s*$/.test(line) && !/^\s*#/.test(line))
|
|
245
|
+
.map((line) => line.trim().replace(/^\/+|\/+$/g, ''))
|
|
246
|
+
} catch (_a) {
|
|
247
|
+
return []
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// slash at the beginning of a filename
|
|
252
|
+
const leadingPathSeparator = new RegExp(`^${escapeRegexString(path.sep)}`)
|
|
253
|
+
const windowsLeadingPathSeparator = new RegExp('^/')
|
|
254
|
+
|
|
255
|
+
// all slashes in the filename. path.sep is OS agnostic (windows, mac, etc)
|
|
256
|
+
const pathSeparator = new RegExp(escapeRegexString(path.sep), 'g')
|
|
257
|
+
const windowsPathSeparator = new RegExp('/', 'g')
|
|
258
|
+
|
|
259
|
+
// handle MS Windows style double-backslashed filenames
|
|
260
|
+
const windowsDoubleSlashSeparator = new RegExp('\\\\', 'g')
|
|
261
|
+
|
|
262
|
+
// derive `foo.bar.baz` object key from `foo/bar/baz.yml` filename
|
|
263
|
+
function fileNameToKey(filename) {
|
|
264
|
+
// const extension = new RegExp(`${path.extname(filename)}$`)
|
|
265
|
+
const key = filename
|
|
266
|
+
// .replace(extension, '')
|
|
267
|
+
.replace(leadingPathSeparator, '')
|
|
268
|
+
.replace(windowsLeadingPathSeparator, '')
|
|
269
|
+
.replace(pathSeparator, '.')
|
|
270
|
+
.replace(windowsPathSeparator, '.')
|
|
271
|
+
.replace(windowsDoubleSlashSeparator, '.')
|
|
272
|
+
|
|
273
|
+
return key
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// https://github.com/regexhq/unc-path-regex/blob/master/index.js
|
|
277
|
+
function isUncPath(filepath) {
|
|
278
|
+
return /^[\\\/]{2,}[^\\\/]+[\\\/]+[^\\\/]+/.test(filepath)
|
|
279
|
+
}
|
|
280
|
+
function isRelative(filepath) {
|
|
281
|
+
const isRel = !isUncPath(filepath) && !/^([a-z]:)?[\\\/]/i.test(filepath)
|
|
282
|
+
// console.log(`isRel ${filepath}`, isRel)
|
|
283
|
+
return isRel
|
|
284
|
+
}
|
|
285
|
+
/* Find common parts of 2 paths */
|
|
286
|
+
function resolveCommonParent(mainDir = '', fileDir = '') {
|
|
287
|
+
const parts = mainDir.split('/')
|
|
288
|
+
let acc = ''
|
|
289
|
+
let value = ''
|
|
290
|
+
for (let i = 0; i < parts.length; i++) {
|
|
291
|
+
const element = parts[i];
|
|
292
|
+
acc+= ((i) ? '/' : '') + element
|
|
293
|
+
if (fileDir.startsWith(acc)) {
|
|
294
|
+
value = acc
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return value
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function resolveOutputPath(cwd, outputDir, file) {
|
|
301
|
+
// console.log('file', file)
|
|
302
|
+
const fileCommon = resolveCommonParent(cwd, file)
|
|
303
|
+
// console.log('fileCommon', fileCommon)
|
|
304
|
+
const remove = resolveCommonParent(outputDir, file)
|
|
305
|
+
const fileName = file.replace(remove, '').replace(fileCommon, '')
|
|
306
|
+
let outputFilePath = path.join(outputDir, fileName)
|
|
307
|
+
if (isRelative(outputDir)) {
|
|
308
|
+
outputFilePath = path.join(cwd, outputDir, fileName)
|
|
309
|
+
}
|
|
310
|
+
// console.log('isRelative(outputDir)', isRelative(outputDir))
|
|
311
|
+
// console.log('outputDir', outputDir)
|
|
312
|
+
// console.log('fileName', fileName)
|
|
313
|
+
// console.log('remove', remove)
|
|
314
|
+
return outputFilePath
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function resolveFlatPath(cwd, outputDir, file) {
|
|
318
|
+
/* old setup */
|
|
319
|
+
const fileName = path.basename(file)
|
|
320
|
+
let outputFilePath = path.join(outputDir, fileName)
|
|
321
|
+
if (isRelative(outputDir)) {
|
|
322
|
+
outputFilePath = path.join(cwd, outputDir, fileName)
|
|
323
|
+
}
|
|
324
|
+
return outputFilePath
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function depth(string) {
|
|
328
|
+
return path.normalize(string).split(path.sep).length - 1;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
module.exports = {
|
|
332
|
+
isLocalPath,
|
|
333
|
+
writeFile,
|
|
334
|
+
readFile,
|
|
335
|
+
findUp,
|
|
336
|
+
getFilePaths,
|
|
337
|
+
resolveOutputPath,
|
|
338
|
+
resolveFlatPath,
|
|
339
|
+
resolveCommonParent,
|
|
340
|
+
getGitignoreContents,
|
|
341
|
+
convertToRelativePath
|
|
342
|
+
}
|