1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | var Minimatch = require("minimatch").Minimatch
|
7 | , fstream = require("fstream")
|
8 | , DirReader = fstream.DirReader
|
9 | , inherits = require("inherits")
|
10 | , path = require("path")
|
11 | , fs = require("fs")
|
12 |
|
13 | module.exports = IgnoreReader
|
14 |
|
15 | inherits(IgnoreReader, DirReader)
|
16 |
|
17 | function IgnoreReader (props) {
|
18 | if (!(this instanceof IgnoreReader)) {
|
19 | return new IgnoreReader(props)
|
20 | }
|
21 |
|
22 |
|
23 | if (typeof props === "string") {
|
24 | props = { path: path.resolve(props) }
|
25 | }
|
26 |
|
27 | props.type = "Directory"
|
28 | props.Directory = true
|
29 |
|
30 | if (!props.ignoreFiles) props.ignoreFiles = [".ignore"]
|
31 | this.ignoreFiles = props.ignoreFiles
|
32 |
|
33 | this.ignoreRules = null
|
34 |
|
35 |
|
36 |
|
37 |
|
38 | if (props.sort) {
|
39 | this._sort = props.sort === "alpha" ? alphasort : props.sort
|
40 | props.sort = null
|
41 | }
|
42 |
|
43 | this.on("entries", function () {
|
44 |
|
45 |
|
46 |
|
47 |
|
48 | var hasIg = this.entries.some(this.isIgnoreFile, this)
|
49 |
|
50 | if (!hasIg) return this.filterEntries()
|
51 |
|
52 | this.addIgnoreFiles()
|
53 | })
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 | this.on("_entryStat", function (entry, props) {
|
60 | var t = entry.basename
|
61 | if (!this.applyIgnores(entry.basename,
|
62 | entry.type === "Directory",
|
63 | entry)) {
|
64 | entry.abort()
|
65 | }
|
66 | }.bind(this))
|
67 |
|
68 | DirReader.call(this, props)
|
69 | }
|
70 |
|
71 |
|
72 | IgnoreReader.prototype.addIgnoreFiles = function () {
|
73 | if (this._paused) {
|
74 | this.once("resume", this.addIgnoreFiles)
|
75 | return
|
76 | }
|
77 | if (this._ignoreFilesAdded) return
|
78 | this._ignoreFilesAdded = true
|
79 |
|
80 | var newIg = this.entries.filter(this.isIgnoreFile, this)
|
81 | , count = newIg.length
|
82 | , errState = null
|
83 |
|
84 | if (!count) return
|
85 |
|
86 | this.pause()
|
87 |
|
88 | var then = function then (er) {
|
89 | if (errState) return
|
90 | if (er) return this.emit("error", errState = er)
|
91 | if (-- count === 0) {
|
92 | this.filterEntries()
|
93 | this.resume()
|
94 | }
|
95 | }.bind(this)
|
96 |
|
97 | newIg.forEach(function (ig) {
|
98 | this.addIgnoreFile(ig, then)
|
99 | }, this)
|
100 | }
|
101 |
|
102 |
|
103 | IgnoreReader.prototype.isIgnoreFile = function (e) {
|
104 | return e !== "." &&
|
105 | e !== ".." &&
|
106 | -1 !== this.ignoreFiles.indexOf(e)
|
107 | }
|
108 |
|
109 |
|
110 | IgnoreReader.prototype.getChildProps = function (stat) {
|
111 | var props = DirReader.prototype.getChildProps.call(this, stat)
|
112 | props.ignoreFiles = this.ignoreFiles
|
113 |
|
114 |
|
115 |
|
116 | if (stat.isDirectory()) {
|
117 | props.type = this.constructor
|
118 | }
|
119 | return props
|
120 | }
|
121 |
|
122 |
|
123 | IgnoreReader.prototype.addIgnoreFile = function (e, cb) {
|
124 |
|
125 |
|
126 |
|
127 | var ig = path.resolve(this.path, e)
|
128 | fs.readFile(ig, function (er, data) {
|
129 | if (er) return cb(er)
|
130 |
|
131 | this.emit("ignoreFile", e, data)
|
132 | var rules = this.readRules(data, e)
|
133 | this.addIgnoreRules(rules, e)
|
134 | cb()
|
135 | }.bind(this))
|
136 | }
|
137 |
|
138 |
|
139 | IgnoreReader.prototype.readRules = function (buf, e) {
|
140 | return buf.toString().split(/\r?\n/)
|
141 | }
|
142 |
|
143 |
|
144 |
|
145 |
|
146 | IgnoreReader.prototype.addIgnoreRules = function (set, e) {
|
147 |
|
148 | set = set.filter(function (s) {
|
149 | s = s.trim()
|
150 | return s && !s.match(/^#/)
|
151 | })
|
152 |
|
153 |
|
154 | if (!set.length) return
|
155 |
|
156 | // now get a minimatch object for each one of these.
|
157 | // Note that we need to allow dot files by default, and
|
158 | // not switch the meaning of their exclusion
|
159 | var mmopt = { matchBase: true, dot: true, flipNegate: true }
|
160 | , mm = set.map(function (s) {
|
161 | var m = new Minimatch(s, mmopt)
|
162 | m.ignoreFile = e
|
163 | return m
|
164 | })
|
165 |
|
166 | if (!this.ignoreRules) this.ignoreRules = []
|
167 | this.ignoreRules.push.apply(this.ignoreRules, mm)
|
168 | }
|
169 |
|
170 |
|
171 | IgnoreReader.prototype.filterEntries = function () {
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 | this.entries = this.entries.filter(function (entry) {
|
180 |
|
181 | return this.applyIgnores(entry) || this.applyIgnores(entry, true)
|
182 | }, this)
|
183 | }
|
184 |
|
185 |
|
186 | IgnoreReader.prototype.applyIgnores = function (entry, partial, obj) {
|
187 | var included = true
|
188 |
|
189 |
|
190 |
|
191 |
|
192 | if (this.parent && this.parent.applyIgnores) {
|
193 | var pt = this.basename + "/" + entry
|
194 | included = this.parent.applyIgnores(pt, partial)
|
195 | }
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 | if (!this.ignoreRules) {
|
211 | return included
|
212 | }
|
213 |
|
214 | this.ignoreRules.forEach(function (rule) {
|
215 |
|
216 | if (rule.negate && included ||
|
217 | !rule.negate && !included) {
|
218 |
|
219 | return
|
220 | }
|
221 |
|
222 |
|
223 | var match = rule.match("/" + entry)
|
224 |
|
225 | if (!match) {
|
226 |
|
227 |
|
228 | match = rule.match(entry)
|
229 | }
|
230 |
|
231 |
|
232 |
|
233 | if (!match && partial) {
|
234 | match = rule.match("/" + entry + "/") ||
|
235 | rule.match(entry + "/")
|
236 | }
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 | if (!match && rule.negate && partial) {
|
243 | match = rule.match("/" + entry, true) ||
|
244 | rule.match(entry, true)
|
245 | }
|
246 |
|
247 | if (match) {
|
248 | included = rule.negate
|
249 | }
|
250 | }, this)
|
251 |
|
252 | return included
|
253 | }
|
254 |
|
255 |
|
256 | IgnoreReader.prototype.sort = function (a, b) {
|
257 | var aig = this.ignoreFiles.indexOf(a) !== -1
|
258 | , big = this.ignoreFiles.indexOf(b) !== -1
|
259 |
|
260 | if (aig && !big) return -1
|
261 | if (big && !aig) return 1
|
262 | return this._sort(a, b)
|
263 | }
|
264 |
|
265 | IgnoreReader.prototype._sort = function (a, b) {
|
266 | return 0
|
267 | }
|
268 |
|
269 | function alphasort (a, b) {
|
270 | return a === b ? 0
|
271 | : a.toLowerCase() > b.toLowerCase() ? 1
|
272 | : a.toLowerCase() < b.toLowerCase() ? -1
|
273 | : a > b ? 1
|
274 | : -1
|
275 | }
|