@threadedstack/wmd-web 0.1.16 → 0.1.18

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.
@@ -0,0 +1,270 @@
1
+ // .dockerignore: a port of github.com/moby/patternmatcher (patternmatcher.go and ignorefile/ignorefile.go).
2
+ // BuildKit v0.33 pins patternmatcher v0.6.1 (third_party/buildkit/go.mod); this port was written against the
3
+ // source at github.com/moby/patternmatcher main (5a6d8429a19bb6948a372ff19e86fe83599a04b7), whose matching
4
+ // code is the same as v0.6.1's for the functions ported here (except the escape set, see compile()): New, MatchesOrParentMatches, Pattern.compile and
5
+ // Pattern.match, plus ReadAll. Only Unix separators exist here (the page has no Windows paths).
6
+ //
7
+ // Differences that cannot be matched exactly:
8
+ // - Go's regexp is RE2; patterns compile to JavaScript RegExp. RE2-only syntax is not translated: POSIX classes
9
+ // such as `[[:alpha:]]` and `\Q...\E` literal runs behave as JavaScript reads them, not as Go does.
10
+ // Pattern classes use the same '[a-z]' / '[^a-z]' / '\\x' forms in both, so the shared syntax behaves the same.
11
+ // - Go's `regexp` treats `\a`, `\f`, `\v` etc. as control characters; JavaScript's non-unicode mode treats an
12
+ // unknown escape as the literal character. Only reachable through a backslash before a letter.
13
+
14
+ /** filepath.Clean on '/' paths (Go's lexical Clean). */
15
+ export function cleanPath(p) {
16
+ if (p === '') return '.'
17
+ const rooted = p[0] === '/'
18
+ const out = []
19
+ for (const seg of p.split('/')) {
20
+ if (seg === '' || seg === '.') continue
21
+ if (seg === '..') {
22
+ if (out.length && out[out.length - 1] !== '..') out.pop()
23
+ else if (!rooted) out.push('..')
24
+ continue
25
+ }
26
+ out.push(seg)
27
+ }
28
+ const s = out.join('/')
29
+ if (rooted) return `/${s}`
30
+ return s === '' ? '.' : s
31
+ }
32
+
33
+ /** filepath.Dir on '/' paths. */
34
+ function dirOf(p) {
35
+ const i = p.lastIndexOf('/')
36
+ if (i < 0) return '.'
37
+ return cleanPath(p.slice(0, i + 1))
38
+ }
39
+
40
+ /** ignorefile.ReadAll: the pattern list of a .dockerignore text. */
41
+ export function parseDockerignore(text) {
42
+ const out = []
43
+ const lines = String(text).split('\n')
44
+ if (lines.length && lines[lines.length - 1] === '') lines.pop() // bufio.ScanLines: no phantom last line
45
+ lines.forEach((raw, idx) => {
46
+ let line = raw.endsWith('\r') ? raw.slice(0, -1) : raw
47
+ if (idx === 0 && line.charCodeAt(0) === 0xfeff) line = line.slice(1)
48
+ if (line.startsWith('#')) return
49
+ let pattern = goTrim(line)
50
+ if (pattern === '') return
51
+ const invert = pattern[0] === '!'
52
+ if (invert) pattern = goTrim(pattern.slice(1))
53
+ if (pattern.length > 0) {
54
+ pattern = cleanPath(pattern)
55
+ if (pattern.length > 1 && pattern[0] === '/') pattern = pattern.slice(1)
56
+ }
57
+ out.push(invert ? `!${pattern}` : pattern)
58
+ })
59
+ return out
60
+ }
61
+
62
+ // strings.TrimSpace: Unicode White_Space (JS trim() also strips U+FEFF, which Go does not).
63
+ function goTrim(s) {
64
+ const ws = (c) => {
65
+ const n = c.charCodeAt(0)
66
+ return (n >= 9 && n <= 13) || n === 32 || n === 0x85 || n === 0xa0 || n === 0x1680 || (n >= 0x2000 && n <= 0x200a) ||
67
+ n === 0x2028 || n === 0x2029 || n === 0x202f || n === 0x205f || n === 0x3000
68
+ }
69
+ let a = 0
70
+ let b = s.length
71
+ while (a < b && ws(s[a])) a++
72
+ while (b > a && ws(s[b - 1])) b--
73
+ return s.slice(a, b)
74
+ }
75
+
76
+ const BAD = 'syntax error in pattern'
77
+
78
+ /** The syntax check New does through filepath.Match(p, "."): throws on a malformed pattern. */
79
+ export function checkPatternSyntax(p) {
80
+ const cs = Array.from(p)
81
+ let i = 0
82
+ const getEsc = () => {
83
+ if (i >= cs.length || cs[i] === '-' || cs[i] === ']') throw new Error(BAD)
84
+ if (cs[i] === '\\') {
85
+ i++
86
+ if (i >= cs.length) throw new Error(BAD)
87
+ }
88
+ i++
89
+ }
90
+ while (i < cs.length) {
91
+ const c = cs[i]
92
+ if (c === '[') {
93
+ i++
94
+ if (cs[i] === '^') i++
95
+ let nrange = 0
96
+ for (;;) {
97
+ if (cs[i] === ']' && nrange > 0) {
98
+ i++
99
+ break
100
+ }
101
+ getEsc()
102
+ if (cs[i] === '-') {
103
+ i++
104
+ getEsc()
105
+ }
106
+ nrange++
107
+ if (i >= cs.length) throw new Error(BAD)
108
+ }
109
+ } else if (c === '\\') {
110
+ i++
111
+ if (i >= cs.length) throw new Error(BAD)
112
+ i++
113
+ } else i++
114
+ }
115
+ }
116
+
117
+ const EXACT = 1
118
+ const PREFIX = 2
119
+ const SUFFIX = 3
120
+ const REGEXP = 4
121
+ const SL = '/'
122
+
123
+ class Pattern {
124
+ constructor(cleaned, exclusion) {
125
+ this.cleanedPattern = cleaned
126
+ this.exclusion = exclusion
127
+ this.dirs = cleaned.split(SL)
128
+ this.matchType = 0
129
+ this.regexp = null
130
+ }
131
+ toString() {
132
+ return this.cleanedPattern
133
+ }
134
+ match(path) {
135
+ if (this.matchType === 0) this.compile()
136
+ switch (this.matchType) {
137
+ case EXACT:
138
+ return path === this.cleanedPattern
139
+ case PREFIX:
140
+ return path.startsWith(this.cleanedPattern.slice(0, -2))
141
+ case SUFFIX: {
142
+ const suffix = this.cleanedPattern.slice(2)
143
+ if (path.endsWith(suffix)) return true
144
+ return suffix[0] === SL && path === suffix.slice(1) // **/foo matches "foo"
145
+ }
146
+ case REGEXP:
147
+ if (!this.regexp) throw new Error(BAD)
148
+ return this.regexp.test(path)
149
+ default:
150
+ throw new Error(BAD)
151
+ }
152
+ }
153
+ compile() {
154
+ let reg = '^'
155
+ let type = EXACT
156
+ const cs = Array.from(this.cleanedPattern)
157
+ let p = 0
158
+ for (let i = 0; p < cs.length; i++) {
159
+ const ch = cs[p++]
160
+ if (ch === '*') {
161
+ if (cs[p] === '*') {
162
+ p++
163
+ if (cs[p] === SL) p++ // treat **/ as **
164
+ if (p >= cs.length) {
165
+ // "**EOF": accept all, as .gitignore
166
+ if (type === EXACT) type = PREFIX
167
+ else {
168
+ reg += '.*'
169
+ type = REGEXP
170
+ }
171
+ } else {
172
+ reg += `(.*${SL})?`
173
+ type = REGEXP
174
+ }
175
+ if (i === 0) type = SUFFIX
176
+ } else {
177
+ reg += `[^${SL}]*`
178
+ type = REGEXP
179
+ }
180
+ } else if (ch === '?') {
181
+ reg += `[^${SL}]`
182
+ type = REGEXP
183
+ } else if ('.+()$'.includes(ch)) {
184
+ // v0.6.1 escapes only these: its escapeBytes bitset is [8]byte indexed b%8 with bit 1<<(b/8), and for
185
+ // '|' (124), '{' (123) and '}' (125) b/8 is 15, which overflows the byte, so they are never escaped.
186
+ // Mirrored on purpose: `{1,2}` is a repeat and `|` an alternation, as in Go.
187
+ reg += `\\${ch}`
188
+ } else if (ch === '\\') {
189
+ if (p < cs.length) {
190
+ reg += `\\${cs[p++]}`
191
+ type = REGEXP
192
+ } else reg += '\\'
193
+ } else if (ch === '[' || ch === ']') {
194
+ reg += ch
195
+ type = REGEXP
196
+ } else reg += ch
197
+ }
198
+ if (type === REGEXP) {
199
+ reg += '$'
200
+ let re = null
201
+ try {
202
+ re = new RegExp(reg, 'u')
203
+ } catch {
204
+ try {
205
+ re = new RegExp(reg)
206
+ } catch {
207
+ throw new Error(BAD)
208
+ }
209
+ }
210
+ this.regexp = re
211
+ }
212
+ this.matchType = type
213
+ }
214
+ }
215
+
216
+ /**
217
+ * patternmatcher.New: build a matcher from the patterns ParseDockerignore returns (or any list; blank ones are
218
+ * skipped). Throws on '!' alone or a malformed pattern.
219
+ * `ignored(path, isDir)`: moby's MatchesOrParentMatches (a path is ignored when a pattern matches it or any
220
+ * parent directory, and no later '!' pattern re-includes it). isDir is accepted for callers' symmetry; moby does
221
+ * not use it. `hasExceptions` is moby's Exclusions(): when false, an ignored directory can be skipped whole.
222
+ */
223
+ export function matcher(patterns) {
224
+ const list = []
225
+ let hasExceptions = false
226
+ for (let raw of patterns) {
227
+ let p = goTrim(raw)
228
+ if (p === '') continue
229
+ p = cleanPath(p)
230
+ let exclusion = false
231
+ if (p[0] === '!') {
232
+ if (p.length === 1) throw new Error('illegal exclusion pattern: "!"')
233
+ exclusion = true
234
+ p = p.slice(1)
235
+ hasExceptions = true
236
+ }
237
+ checkPatternSyntax(p)
238
+ list.push(new Pattern(p, exclusion))
239
+ }
240
+ function matchesOrParentMatches(file) {
241
+ let matched = false
242
+ const parentPath = dirOf(file)
243
+ const parentDirs = parentPath.split(SL)
244
+ for (const pattern of list) {
245
+ if (pattern.exclusion !== matched) continue
246
+ let match = pattern.match(file)
247
+ if (!match && parentPath !== '.') {
248
+ for (let i = 0; i < parentDirs.length; i++) {
249
+ try {
250
+ match = pattern.match(parentDirs.slice(0, i + 1).join(SL))
251
+ } catch {
252
+ match = false
253
+ }
254
+ if (match) break
255
+ }
256
+ }
257
+ if (match) matched = !pattern.exclusion
258
+ }
259
+ return matched
260
+ }
261
+ return {
262
+ patterns: list,
263
+ hasExceptions,
264
+ ignored(path, _isDir) {
265
+ const f = cleanPath(path)
266
+ if (f === '.') return false // never exclude everything
267
+ return matchesOrParentMatches(f)
268
+ },
269
+ }
270
+ }