source: trip-planner-front/node_modules/ignore-walk/index.js@ 6c1585f

Last change on this file since 6c1585f was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 6.9 KB
Line 
1'use strict'
2
3const fs = require('fs')
4const path = require('path')
5const EE = require('events').EventEmitter
6const Minimatch = require('minimatch').Minimatch
7
8class Walker extends EE {
9 constructor (opts) {
10 opts = opts || {}
11 super(opts)
12 this.path = opts.path || process.cwd()
13 this.basename = path.basename(this.path)
14 this.ignoreFiles = opts.ignoreFiles || [ '.ignore' ]
15 this.ignoreRules = {}
16 this.parent = opts.parent || null
17 this.includeEmpty = !!opts.includeEmpty
18 this.root = this.parent ? this.parent.root : this.path
19 this.follow = !!opts.follow
20 this.result = this.parent ? this.parent.result : new Set()
21 this.entries = null
22 this.sawError = false
23 }
24
25 sort (a, b) {
26 return a.localeCompare(b, 'en')
27 }
28
29 emit (ev, data) {
30 let ret = false
31 if (!(this.sawError && ev === 'error')) {
32 if (ev === 'error')
33 this.sawError = true
34 else if (ev === 'done' && !this.parent) {
35 data = Array.from(data)
36 .map(e => /^@/.test(e) ? `./${e}` : e).sort(this.sort)
37 this.result = data
38 }
39
40 if (ev === 'error' && this.parent)
41 ret = this.parent.emit('error', data)
42 else
43 ret = super.emit(ev, data)
44 }
45 return ret
46 }
47
48 start () {
49 fs.readdir(this.path, (er, entries) =>
50 er ? this.emit('error', er) : this.onReaddir(entries))
51 return this
52 }
53
54 isIgnoreFile (e) {
55 return e !== "." &&
56 e !== ".." &&
57 -1 !== this.ignoreFiles.indexOf(e)
58 }
59
60 onReaddir (entries) {
61 this.entries = entries
62 if (entries.length === 0) {
63 if (this.includeEmpty)
64 this.result.add(this.path.substr(this.root.length + 1))
65 this.emit('done', this.result)
66 } else {
67 const hasIg = this.entries.some(e =>
68 this.isIgnoreFile(e))
69
70 if (hasIg)
71 this.addIgnoreFiles()
72 else
73 this.filterEntries()
74 }
75 }
76
77 addIgnoreFiles () {
78 const newIg = this.entries
79 .filter(e => this.isIgnoreFile(e))
80
81 let igCount = newIg.length
82 const then = _ => {
83 if (--igCount === 0)
84 this.filterEntries()
85 }
86
87 newIg.forEach(e => this.addIgnoreFile(e, then))
88 }
89
90 addIgnoreFile (file, then) {
91 const ig = path.resolve(this.path, file)
92 fs.readFile(ig, 'utf8', (er, data) =>
93 er ? this.emit('error', er) : this.onReadIgnoreFile(file, data, then))
94 }
95
96 onReadIgnoreFile (file, data, then) {
97 const mmopt = {
98 matchBase: true,
99 dot: true,
100 flipNegate: true,
101 nocase: true
102 }
103 const rules = data.split(/\r?\n/)
104 .filter(line => !/^#|^$/.test(line.trim()))
105 .map(r => new Minimatch(r, mmopt))
106
107 this.ignoreRules[file] = rules
108
109 then()
110 }
111
112 filterEntries () {
113 // at this point we either have ignore rules, or just inheriting
114 // this exclusion is at the point where we know the list of
115 // entries in the dir, but don't know what they are. since
116 // some of them *might* be directories, we have to run the
117 // match in dir-mode as well, so that we'll pick up partials
118 // of files that will be included later. Anything included
119 // at this point will be checked again later once we know
120 // what it is.
121 const filtered = this.entries.map(entry => {
122 // at this point, we don't know if it's a dir or not.
123 const passFile = this.filterEntry(entry)
124 const passDir = this.filterEntry(entry, true)
125 return (passFile || passDir) ? [entry, passFile, passDir] : false
126 }).filter(e => e)
127
128 // now we stat them all
129 // if it's a dir, and passes as a dir, then recurse
130 // if it's not a dir, but passes as a file, add to set
131 let entryCount = filtered.length
132 if (entryCount === 0) {
133 this.emit('done', this.result)
134 } else {
135 const then = _ => {
136 if (-- entryCount === 0)
137 this.emit('done', this.result)
138 }
139 filtered.forEach(filt => {
140 const entry = filt[0]
141 const file = filt[1]
142 const dir = filt[2]
143 this.stat(entry, file, dir, then)
144 })
145 }
146 }
147
148 onstat (st, entry, file, dir, then) {
149 const abs = this.path + '/' + entry
150 if (!st.isDirectory()) {
151 if (file)
152 this.result.add(abs.substr(this.root.length + 1))
153 then()
154 } else {
155 // is a directory
156 if (dir)
157 this.walker(entry, then)
158 else
159 then()
160 }
161 }
162
163 stat (entry, file, dir, then) {
164 const abs = this.path + '/' + entry
165 fs[this.follow ? 'stat' : 'lstat'](abs, (er, st) => {
166 if (er)
167 this.emit('error', er)
168 else
169 this.onstat(st, entry, file, dir, then)
170 })
171 }
172
173 walkerOpt (entry) {
174 return {
175 path: this.path + '/' + entry,
176 parent: this,
177 ignoreFiles: this.ignoreFiles,
178 follow: this.follow,
179 includeEmpty: this.includeEmpty
180 }
181 }
182
183 walker (entry, then) {
184 new Walker(this.walkerOpt(entry)).on('done', then).start()
185 }
186
187 filterEntry (entry, partial) {
188 let included = true
189
190 // this = /a/b/c
191 // entry = d
192 // parent /a/b sees c/d
193 if (this.parent && this.parent.filterEntry) {
194 var pt = this.basename + "/" + entry
195 included = this.parent.filterEntry(pt, partial)
196 }
197
198 this.ignoreFiles.forEach(f => {
199 if (this.ignoreRules[f]) {
200 this.ignoreRules[f].forEach(rule => {
201 // negation means inclusion
202 // so if it's negated, and already included, no need to check
203 // likewise if it's neither negated nor included
204 if (rule.negate !== included) {
205 // first, match against /foo/bar
206 // then, against foo/bar
207 // then, in the case of partials, match with a /
208 const match = rule.match('/' + entry) ||
209 rule.match(entry) ||
210 (!!partial && (
211 rule.match('/' + entry + '/') ||
212 rule.match(entry + '/'))) ||
213 (!!partial && rule.negate && (
214 rule.match('/' + entry, true) ||
215 rule.match(entry, true)))
216
217 if (match)
218 included = rule.negate
219 }
220 })
221 }
222 })
223
224 return included
225 }
226}
227
228class WalkerSync extends Walker {
229 constructor (opt) {
230 super(opt)
231 }
232
233 start () {
234 this.onReaddir(fs.readdirSync(this.path))
235 return this
236 }
237
238 addIgnoreFile (file, then) {
239 const ig = path.resolve(this.path, file)
240 this.onReadIgnoreFile(file, fs.readFileSync(ig, 'utf8'), then)
241 }
242
243 stat (entry, file, dir, then) {
244 const abs = this.path + '/' + entry
245 const st = fs[this.follow ? 'statSync' : 'lstatSync'](abs)
246 this.onstat(st, entry, file, dir, then)
247 }
248
249 walker (entry, then) {
250 new WalkerSync(this.walkerOpt(entry)).start()
251 then()
252 }
253}
254
255const walk = (options, callback) => {
256 const p = new Promise((resolve, reject) => {
257 new Walker(options).on('done', resolve).on('error', reject).start()
258 })
259 return callback ? p.then(res => callback(null, res), callback) : p
260}
261
262const walkSync = options => {
263 return new WalkerSync(options).start().result
264}
265
266module.exports = walk
267walk.sync = walkSync
268walk.Walker = Walker
269walk.WalkerSync = WalkerSync
Note: See TracBrowser for help on using the repository browser.