UNPKG

9.31 kBJavaScriptView Raw
1/**
2 * @fileoverview Strengthen the ability of file system
3 * @author wliao <wliao@Ctrip.com>
4 */
5var fs = require('fs');
6var util = require('utils-extend');
7var path = require('path');
8var fileMatch = require('file-match');
9
10function checkCbAndOpts(options, callback) {
11 if (util.isFunction(options)) {
12 return {
13 options: null,
14 callback: options
15 };
16 } else if (util.isObject(options)) {
17 return {
18 options: options,
19 callback: callback
20 };
21 } else {
22 return {
23 options: null,
24 callback: util.noop
25 };
26 }
27}
28
29function getExists(filepath) {
30 var exists = fs.existsSync(filepath);
31
32 if (exists) {
33 return filepath;
34 } else {
35 return getExists(path.dirname(filepath));
36 }
37}
38
39util.extend(exports, fs);
40
41/**
42 * @description
43 * Assign node origin methods to fs
44 */
45exports.fs = fs;
46
47exports.fileMatch = fileMatch;
48
49/**
50 * @description
51 * Create dir, if dir exist, it will only invoke callback.
52 *
53 * @example
54 * ```js
55 * fs.mkdir('1/2/3/4/5', 511);
56 * fs.mkdir('path/2/3', function() {});
57 * ```
58 */
59exports.mkdir = function(filepath, mode, callback) {
60 var root = getExists(filepath);
61 var children = path.relative(root, filepath);
62
63 if (util.isFunction(mode)) {
64 callback = mode;
65 mode = null;
66 }
67
68 if (!util.isFunction(callback)) {
69 callback = util.noop;
70 }
71
72 mode = mode || 511;
73
74 if (!children) return callback();
75
76 children = children.split(path.sep);
77
78 function create(filepath) {
79 if (create.count === children.length) {
80 return callback();
81 }
82
83 filepath = path.join(filepath, children[create.count]);
84
85 fs.mkdir(filepath, mode, function(err) {
86 create.count++;
87 create(filepath);
88 });
89 }
90
91 create.count = 0;
92 create(root);
93};
94
95/**
96 * @description
97 * Same as mkdir, but it is synchronous
98 */
99exports.mkdirSync = function(filepath, mode) {
100 var root = getExists(filepath);
101 var children = path.relative(root, filepath);
102
103 if (!children) return;
104
105 children = children.split(path.sep);
106
107 children.forEach(function(item) {
108 root = path.join(root, item);
109 fs.mkdirSync(root, mode);
110 });
111};
112
113/**
114 * @description
115 * Create file, if path don't exists, it will not throw error.
116 * And will mkdir for path, it is asynchronous
117 *
118 * @example
119 * ```js
120 * fs.writeFile('path/filename.txt', 'something')
121 * fs.writeFile('path/filename.txt', 'something', {})
122 * ```
123 */
124exports.writeFile = function(filename, data, options, callback) {
125 var result = checkCbAndOpts(options, callback);
126 var dirname = path.dirname(filename);
127 options = result.options;
128 callback = result.callback;
129
130 // Create dir first
131 exports.mkdir(dirname, function() {
132 fs.writeFile(filename, data, options, callback);
133 });
134};
135
136/**
137 * @description
138 * Same as writeFile, but it is synchronous
139 */
140exports.writeFileSync = function(filename, data, options) {
141 var dirname = path.dirname(filename);
142
143 exports.mkdirSync(dirname);
144 fs.writeFileSync(filename, data, options);
145};
146
147/**
148 * @description
149 * Asynchronously copy a file
150 * @example
151 * file.copyFile('demo.txt', 'demo.dest.txt', { done: function(err) { }})
152 */
153exports.copyFile = function(srcpath, destpath, options) {
154 options = util.extend({
155 encoding: 'utf8',
156 done: util.noop
157 }, options || {});
158
159 if (!options.process) {
160 options.encoding = null;
161 }
162
163 fs.readFile(srcpath, {
164 encoding: options.encoding
165 }, function(err, contents) {
166 if (err) return options.done(err);
167
168 if (options.process) {
169 contents = options.process(contents);
170 }
171
172 exports.writeFile(destpath, contents, options, options.done);
173 });
174};
175
176/**
177 * @description
178 * Copy file to dest, if no process options, it will only copy file to dest
179 * @example
180 * file.copyFileSync('demo.txt', 'demo.dest.txt' { process: function(contents) { }});
181 * file.copyFileSync('demo.png', 'dest.png');
182 */
183exports.copyFileSync = function(srcpath, destpath, options) {
184 options = util.extend({
185 encoding: 'utf8'
186 }, options || {});
187 var contents;
188
189 if (options.process) {
190 contents = fs.readFileSync(srcpath, options);
191 contents = options.process(contents);
192 exports.writeFileSync(destpath, contents, options);
193 } else {
194 contents = fs.readFileSync(srcpath);
195 exports.writeFileSync(destpath, contents);
196 }
197};
198
199/**
200 * @description
201 * Recurse into a directory, executing callback for each file and folder
202 * if the filename is undefiend, the callback is for folder, otherwise for file.
203 * and it is asynchronous
204 * @example
205 * file.recurse('path', function(filepath, filename) { });
206 * file.recurse('path', ['*.js', 'path/**\/*.html'], function(filepath, filename) { });
207 */
208exports.recurse = function(dirpath, filter, callback) {
209 if (util.isFunction(filter)) {
210 callback = filter;
211 filter = null;
212 }
213 var filterCb = fileMatch(filter);
214 var rootpath = dirpath;
215
216 function recurse(dirpath) {
217 fs.readdir(dirpath, function(err, files) {
218 if (err) return callback(err);
219
220 files.forEach(function(filename) {
221 var filepath = path.join(dirpath, filename);
222
223 fs.stat(filepath, function(err, stats) {
224 var relative = path.relative(rootpath, filepath);
225 var flag = filterCb(relative);
226
227 if (stats.isDirectory()) {
228 recurse(filepath);
229 if (flag) callback(filepath);
230 } else {
231 if (flag) callback(filepath, filename);
232 }
233 });
234 });
235 });
236 }
237
238 recurse(dirpath);
239};
240
241/**
242 * @description
243 * Same as recurse, but it is synchronous
244 * @example
245 * file.recurseSync('path', function(filepath, filename) {});
246 * file.recurseSync('path', ['*.js', 'path/**\/*.html'], function(filepath, filename) {});
247 */
248exports.recurseSync = function(dirpath, filter, callback) {
249 if (util.isFunction(filter)) {
250 callback = filter;
251 filter = null;
252 }
253 var filterCb = fileMatch(filter);
254 var rootpath = dirpath;
255
256 function recurse(dirpath) {
257 // permission bug
258 try {
259 fs.readdirSync(dirpath).forEach(function(filename) {
260 var filepath = path.join(dirpath, filename);
261 var stats = fs.statSync(filepath);
262 var relative = path.relative(rootpath, filepath);
263 var flag = filterCb(relative);
264
265 if (stats.isDirectory()) {
266 recurse(filepath);
267 if (flag) callback(filepath);
268 } else {
269 if (flag) callback(filepath, filename);
270 }
271 });
272 } catch(e) {
273 fs.chmodSync(dirpath, 511);
274 recurse(dirpath);
275 }
276 }
277
278 recurse(dirpath);
279};
280
281/**
282 * @description
283 * Remove folder and files in folder, but it's synchronous
284 * @example
285 * file.rmdirSync('path');
286 */
287exports.rmdirSync = function(dirpath) {
288 exports.recurseSync(dirpath, function(filepath, filename) {
289 // it is file, otherwise it's folder
290 if (filename) {
291 fs.unlinkSync(filepath);
292 } else {
293 fs.rmdirSync(filepath);
294 }
295 });
296
297 fs.rmdirSync(dirpath);
298};
299
300/**
301 * @description
302 * Copy dirpath to destpath, pass process callback for each file hanlder
303 * if you want to change the dest filepath, process callback return { contents: '', filepath: ''}
304 * otherwise only change contents
305 * @example
306 * file.copySync('path', 'dest');
307 * file.copySync('src', 'dest/src');
308 * file.copySync('path', 'dest', { process: function(contents, filepath) {} });
309 * file.copySync('path', 'dest', { process: function(contents, filepath) {} }, noProcess: ['']);
310 */
311exports.copySync = function(dirpath, destpath, options) {
312 options = util.extend({
313 encoding: 'utf8',
314 filter: null,
315 noProcess: ''
316 }, options || {});
317 var files = [];
318 var folders = [];
319
320 exports.recurseSync(dirpath, options.filter, function(filepath, filename) {
321 if (!filename) return;
322 files.push(filepath);
323 folders.push(path.dirname(filepath));
324 });
325
326 var length = files.length;
327 var noProcessCb = fileMatch(options.noProcess);
328
329 // Make sure dest root
330 exports.mkdirSync(destpath);
331 // First create folder for file
332 folders.forEach(function(item, index) {
333 var isCreate = true;
334 var relative, newpath;
335
336 while(index++ < length) {
337 if (folders[index] === item) {
338 isCreate = false;
339 break;
340 }
341 }
342
343 if (isCreate) {
344 relative = path.relative(dirpath, item);
345 if (relative) {
346 newpath = path.join(destpath, relative);
347 exports.mkdirSync(newpath);
348 }
349 }
350 });
351
352 function copy(oldpath, newpath, options) {
353 var result;
354 if (options.process) {
355 var encoding = {
356 encoding: options.encoding
357 };
358 result = fs.readFileSync(oldpath, encoding);
359 result = options.process(result, oldpath);
360
361 if (util.isObject(result) && result.filepath) {
362 fs.writeFileSync(result.filepath, result.contents, encoding);
363 } else {
364 fs.writeFileSync(newpath, result, encoding);
365 }
366 } else {
367 result = fs.readFileSync(oldpath);
368 fs.writeFileSync(newpath, result);
369 }
370 }
371
372 // Copy file
373 files.forEach(function(item) {
374 var relative = path.relative(dirpath, item);
375 var newpath = path.join(destpath, relative);
376
377 if (options.process) {
378 if (noProcessCb(relative)) {
379 copy(item, newpath, {});
380 } else {
381 copy(item, newpath, options);
382 }
383 } else {
384 copy(item, newpath, {});
385 }
386 });
387};
\No newline at end of file