UNPKG

5.92 kBJavaScriptView Raw
1var
2 when = require('when'),
3 fs = require('final-fs'),
4 _ = require('underscore'),
5 parseOptions = require('./parse_options'),
6 pathjs = require('path'),
7 join = require('path').join,
8 debugBase = require('debug'),
9 debug = debugBase('loaddir:directory'),
10 Directory
11 ;
12
13Directory = function(options) {
14 var self = this;
15
16 this.options = options;
17 this.children = [];
18
19 debug('Directory'.yellow, 'constructor'.blue, self.path);
20
21 // fixes issues where big files trigger watch before they're done writing
22 self._watchHandler = _.debounce( _.bind(self._watchHandler, self), 500);
23
24 options.watchedPaths = options.watchedPaths || [];
25 options.watchers = options.watchers || [];
26
27 parseOptions.call(this);
28
29 // One time use
30 _.each([ 'top', 'white_list'], function(key) {
31 self[key] = options[key]
32 delete options[key]
33 });
34
35 // First run through we don't add directories since top is not relative to anything
36 this.relativePath = this.relativePath == null ?
37 '' : join(this.relativePath, this.baseName);
38
39 when().then(function(){
40 if (self.destination)
41 return ensureDirectory(self.destination);
42 });
43
44 return this;
45
46};
47
48Directory.prototype.load = function() {
49 var
50 self = this,
51 opts = self.options;
52
53 debug('Directory'.yellow, '.load'.blue, this.path.green)
54
55 return fs.readdir(self.path)
56 .then(function(results) {
57 if (self.watch == true || (self.watch !== false && self.watch !== 'files'))
58 self._watch();
59 return when.map(results, _.bind(self.iterate, self));
60 })
61 .otherwise(function(er) {
62 debug('Directory'.yellow, 'not found'.red, er);
63
64 if (this.watcher) {
65 if (_.contains(self.watchers, self.watcher))
66 self.watchers.splice(_.indexOf(self.watchers, self.watcher), 1)
67
68 self.watcher.close();
69 }
70
71 if (self.asObject)
72 delete opts.parent.output[self.baseName];
73 else
74 delete opts.output[self.path];
75
76 });
77
78};
79
80// Processes contents of directory
81Directory.prototype.iterate = function(fileName){
82
83 var self = this;
84 debug('Directory'.yellow, 'iterate'.blue, fileName.green);
85
86 // Child options
87 var
88 Class,
89 path, baseName, options;
90
91 path = join(this.path, fileName);
92 baseName = pathjs.basename(fileName, pathjs.extname(fileName));
93
94 if ( self.exclude(baseName, path) ) return;
95
96 var black_list = self.buildBlackList(fileName),
97 options = _.extend( _.clone(self.options), {
98 path: path,
99 fileName: fileName,
100 parent: self,
101 black_list: black_list,
102 destination: self.destination,
103 relativePath: self.relativePath,
104 baseName: baseName,
105 existingManifest: self.options.existingManifest && self.options.existingManifest[path],
106 });
107
108 File = require('./file');
109
110 return fs.lstat( path )
111 .then(function(stats) {
112
113 if (stats.isDirectory()) {
114 if (self.asObject)
115 // nested objects reflect directory structure {topPath: {subPath: {file1: ...}}}
116 self.output[baseName] = options.output = {};
117 else
118 // all output goes into one object w/ paths { 'topPath/subPath/file1': ...}
119 options.output = self.output;
120
121 Class = Directory;
122 } else {
123 Class = require('./file');
124 };
125
126 var child = new Class(options);
127 child.stats = stats;
128
129 self.children.push(child);
130
131 return child.load();
132 });
133
134};
135
136Directory.prototype.buildBlackList = function(fileName) {
137
138 var self = this;
139 var output = [];
140
141 _.each(self.black_list, function(path) {
142 var parts = path.split('/')
143 if (parts[0] == fileName && parts.slice(1).length)
144 output.push(parts.slice(1).join('/'));
145 });
146
147 return output
148
149
150};
151
152// Determines which children to not include
153Directory.prototype.exclude = function(fileName, path) {
154
155 var self = this;
156 if (!self.asObject && self.output[path] || self.asObject && self.output[fileName])
157 debug(' Directory exclude. Already exists on output '.red,
158 !self.asObject && self.output[path],
159 self.asObject && self.output[fileName],
160 fileName, path);
161
162 // exists -- NOTE: I don't remember if this actually happens or why
163 return (!self.asObject && self.output[path]) || (self.asObject && self.output[fileName]) ||
164
165 // white / black list violation
166 (self.white_list && !_.include(self.white_list, fileName)) ||
167 (_.any(self.black_list, function(bl, key) {
168 // finding an exact match for a black_list item means it's done for
169 if (bl == fileName) {
170 delete self.black_list[key]
171 return true;
172 } else
173 return bl == '*';
174 }) ) ||
175
176 // tmp / git files
177 (fileName.charAt(0) == '.');
178
179};
180
181Directory.prototype._watch = function(){
182
183 var self = this;
184 if (self.watcher || _.include(self.watchedPaths, self.path))
185 return
186
187 debug('Directory'.yellow, 'start_watching'.blue, self.path.green)
188
189 self.watchedPaths.push(self.path);
190 self.watcher = fs.watch(self.path, _.bind(self._watchHandler, self))
191
192};
193
194Directory.prototype._watchHandler = function(){
195
196 debugBase('loaddir:watch')('Directory'.yellow, '_watchHandler'.blue, this.options.path.green);
197
198 if (this.watchHandler)
199 this.watchHandler();
200 else
201 this.load();
202
203};
204
205Directory.prototype.buildManifest = function(manifest){
206
207 var self = this;
208 // Only created for top
209 var manifest = manifest || {};
210
211 _.each(self.children, function(child) {
212
213 manifest[child.path] = (function(){
214 if (child instanceof Directory)
215 return {
216 isDir: true,
217 children: child.buildManifest(),
218 }
219 else
220
221 return {
222 isFile: true,
223 mtime: child.stats.mtime,
224 fileData: child.fileData,
225 fileContents: child.fileContents,
226 };
227 })();
228 });
229
230 return manifest;
231};
232
233function ensureDirectory(path) {
234
235 return fs.exists(path).then(function(exists) {
236 if (!exists) return fs.mkdir(path);
237 });
238};
239
240module.exports = Directory;