UNPKG

28 kBJavaScriptView Raw
1/* global toString: false */
2'use strict';
3
4var ASSERT = require('assert'),
5 Q = require('q'),
6 QFS = require('q-io/fs'),
7 PATH = require('./path'),
8 FS = require('fs'),
9 VM = require('vm'),
10 UTIL = require('util'),
11 ENV = require('./env'),
12 MKDIRP = require('mkdirp'),
13 _ = require('lodash/dist/lodash.underscore');
14
15exports.nodeVer = process.version.substr(1)
16 .split('.')
17 .map(function(v) {
18 return parseInt(v, 10);
19 });
20
21exports.oldNode = exports.nodeVer[0] === 0 && exports.nodeVer[1] < 10;
22
23exports.chdirOptParse = function() {
24 return this.opt()
25 .name('dir').short('C').long('chdir')
26 .title('change process working directory, cwd by default; to specify level use --level, -l option instead')
27 .def(process.cwd())
28 .val(function(d) {
29 return PATH.join(PATH.resolve(d), PATH.dirSep);
30 })
31 .act(function(opts) {
32 process.chdir(opts.dir);
33 })
34 .end();
35};
36
37exports.techsOptParse = function() {
38 return this
39 .opt()
40 .name('addTech').short('t').long('add-tech')
41 .title('add tech')
42 .arr()
43 .end()
44 .opt()
45 .name('forceTech').short('T').long('force-tech')
46 .title('use only specified tech')
47 .arr()
48 .end()
49 .opt()
50 .name('noTech').short('n').long('no-tech')
51 .title('exclude tech')
52 .arr()
53 .end();
54};
55
56exports.levelOptParse = function() {
57 var def = exports.findLevel(process.cwd()),
58 rel = PATH.relative(process.cwd(), def);
59 return this.opt()
60 .name('level').short('l').long('level')
61 .def(def)
62 .title(['level directory path, default: ',
63 !rel? '.' : rel, PATH.dirSep].join(''))
64 .val(function(l) {
65 return typeof l === 'string'? require('./level').createLevel(l) : l;
66 })
67 .end();
68};
69
70exports.mergeTechs = function(level, opts) {
71 // NOTE: если при создании блока/элемента/модификатора
72 // указали --force-tech <name> или --no-tech, и в level.js
73 // определена технология с таким именем/файлом на диске,
74 // нужно использовать именно её
75 var techs = opts.forceTech? {} : level.techs,
76 optsTechs = [];
77
78 opts.forceTech && optsTechs.push.apply(optsTechs, opts.forceTech);
79 opts.addTech && optsTechs.push.apply(optsTechs, opts.addTech);
80
81 optsTechs.forEach(function(t) {
82 var tech = level.getTech(t),
83 name = tech.getTechName();
84 techs[name] || (techs[name] = tech);
85 });
86
87 opts.noTech && opts.noTech.forEach(function(t) {
88 delete techs[level.getTech(t).getTechName()];
89 });
90
91 return techs;
92};
93
94/**
95 * Create symbolic link.
96 *
97 * If `force` is specified and is true, it will check
98 * for `link` to exist and remove it in case it is
99 * a symbolic link.
100 *
101 * Files and directories will be left untouched.
102 *
103 * @param {String} link Symbolic link name.
104 * @param {String} target Symbolic link target.
105 * @param {Boolean} [force] Force creating symplink in case it is already exist.
106 * @return {Promise * Undefined}
107 */
108exports.symbolicLink = function(link, target, force) {
109
110 return Q.resolve(force)
111 .then(function(force) {
112
113 if (!force) return;
114
115 return QFS.statLink(link)
116 .then(function(stat) {
117 if (stat.isSymbolicLink()) {
118 return QFS.remove(link);
119 }
120 })
121 .fail(function() {});
122
123 })
124 .then(function() {
125 // TODO: pass correct type based on target? (Windows)
126 return QFS.symbolicLink(link, target, 'file');
127 });
128
129};
130
131/**
132 * Remove path (file or directory) but not recursively.
133 *
134 * @param {String} path Path to remove
135 * @return {Promise * Undefined}
136 */
137exports.removePath = function(path) {
138
139 return QFS.stat(path)
140 .then(function(stat) {
141
142 if (stat.isDirectory()) return QFS.removeDirectory(path);
143 return QFS.remove(path);
144
145 });
146
147};
148
149exports.write = function(path, content) {
150 FS.writeFileSync(path, Array.isArray(content) ? content.join('') : content);
151};
152
153exports.writeFile = function(path, content) {
154 return Q.when(content, function(content) {
155 return QFS.write(path, Array.isArray(content) ? content.join('') : content, { charset: 'utf8' });
156 });
157};
158
159exports.writeFileIfDiffers = function(path, content) {
160 return QFS.exists(path)
161 .then(function(exists) {
162 if (!exists) return true;
163 return exports.readFile(path)
164 .then(function(current) {
165 return current !== content;
166 });
167 })
168 .then(function(rewrite) {
169 if (rewrite) return exports.writeFile(path, content);
170 });
171};
172
173exports.readFile = function(path) {
174 return QFS.read(path, { charset: 'utf8' });
175};
176
177exports.readBinary = function(path) {
178 return QFS.read(path, { charset: 'binary' });
179};
180
181/**
182 * Read and parse declaration module-like file,
183 * e.g. deps.js or bemdecl.js.
184 *
185 * @param {String} path Path to declaration file.
186 * @return {Promise * Object} Declaration object.
187 */
188exports.readDecl = function(path) {
189 return exports.readFile(path)
190 .then(function(c) {
191 var fn = VM.runInThisContext(declWrapper[0] + c + declWrapper[1], path),
192 decl = {},
193 module = { exports: decl };
194 return fn(decl, require, module, PATH.resolve(path), PATH.resolve(PATH.dirname(path)));
195 });
196};
197
198/**
199 * Declaration modules content wrapper for `readDecl()`.
200 *
201 * @type {String[]}
202 */
203var declWrapper = ['(function(exports, require, module, __filename, __dirname) {', ';return module.exports;})'];
204
205/**
206 * Read and parse JSON-JS file.
207 *
208 * @param {String} path Path to file to read.
209 * @return {Promise * Object|Array} Data read from file.
210 */
211exports.readJsonJs = function(path) {
212 return exports.readFile(path)
213 .then(function(c) {
214 return VM.runInThisContext(c, path);
215 });
216};
217
218exports.mkdir = function(path) {
219 try {
220 FS.mkdirSync(path, '0777');
221 } catch(ignore) {}
222};
223
224/**
225 * Create directories.
226 *
227 * @return {String} First directory being created.
228 */
229exports.mkdirs = MKDIRP.sync;
230
231/**
232 * Create directories.
233 *
234 * @return {Promise * String} First directory being created.
235 */
236exports.mkdirp = Q.nfbind(MKDIRP);
237
238exports.isExists = function(path) {
239 var d = Q.defer();
240 PATH.exists(path, function(res) {
241 d.resolve(res);
242 });
243 return d.promise;
244};
245
246exports.isFile = function(path) {
247 try {
248 return FS.statSync(path).isFile();
249 } catch(ignore) {}
250 return false;
251};
252
253/** @deprecated */
254exports.isFileP = QFS.isFile.bind(QFS);
255
256exports.isDirectory = function(path) {
257 try {
258 return FS.statSync(path).isDirectory();
259 } catch(ignore) {}
260 return false;
261};
262
263exports.isLevel = function(path) {
264 return exports.isDirectory(path) &&
265 exports.isFile(PATH.join(path, '.bem', 'level.js'));
266};
267
268/**
269 * Search for the nearest level recursivelt from the specified
270 * directory to the filesystem root.
271 *
272 * @param {String} path Path to start search from.
273 * @param {String[]|String|Undefined} [types] Level type to search.
274 * @param {String} [startPath]
275 * @return {String} Found level path or specified path if not found.
276 */
277exports.findLevel = function(path, types, startPath) {
278
279 var createLevel = require('./level').createLevel;
280
281 if (types && !Array.isArray(types)) types = [types];
282 startPath = startPath || path;
283
284 // Check for level and level type if applicable
285 if (exports.isLevel(path) &&
286 (!types || exports.containsAll(createLevel(path).getTypes(), types))) return path;
287
288 // Check for fs root
289 if (PATH.isRoot(path)) return startPath;
290
291 return exports.findLevel(PATH.dirname(path), types, startPath);
292
293};
294
295/**
296 * Filter out non-existent paths.
297 *
298 * @param {String[]} paths Paths to filter
299 * @return {Promise * String[]} Existent paths
300 */
301exports.filterPaths = function(paths) {
302
303 var d = Q.defer(),
304 res = [],
305 total = paths.length,
306 count = 0;
307
308 paths.forEach(function(path, index) {
309
310 PATH.exists(path, function(exists) {
311
312 count++;
313 res[index] = exists;
314
315 if (count < total) return;
316
317 d.resolve(paths.filter(function(path, index) {
318 return res[index];
319 }));
320
321 });
322
323 });
324
325 return d.promise;
326
327};
328
329exports.fsWalkTree = function(root, fileCb, filterCb, ctx) {
330 var files = FS.readdirSync(root);
331 files.sort();
332 while (files.length > 0) {
333 var path = PATH.join(root, files.shift());
334 if(filterCb && !filterCb.call(ctx, path)) continue;
335 fileCb.call(ctx, path);
336 if(exports.isDirectory(path)) exports.fsWalkTree(path, fileCb, filterCb, ctx);
337 }
338};
339
340exports.fsWalkTreeAsync = function(root, fileCb, filterCb, ctx) {
341 return QFS.list(root)
342 .then(function(files) {
343 return Q.all(files.map(function(file) {
344 var path = PATH.join(root, file);
345 if (!(filterCb && !filterCb.call(ctx, path, file))) {
346 //fileCb.call(ctx, path);
347 return QFS.isDirectory(path)
348 .then(function(isdir) {
349 if (isdir) return exports.fsWalkTreeAsync(path, fileCb, filterCb, ctx);
350 });
351 }
352 }));
353 });
354};
355
356exports.fsWalkTreeCb = function(root, fileCb) {
357
358 var d = Q.defer(),
359 results = {};
360
361 function done(err) {
362 if (err) d.reject(err);
363 d.resolve(results);
364 }
365
366 if (root[root.length-1] !== PATH.dirSep) root += PATH.dirSep;
367
368 walk(root, '', done);
369
370 function walk(path, relPath, cb) {
371 FS.readdir(path, function(err, list) {
372
373 if (err) return done(err);
374 var pending = list.length;
375 if (!pending) return cb(null, results);
376
377 list.forEach(function(file) {
378 var absPath = PATH.join(path, file);
379
380 FS.stat(absPath, function(err, stat) {
381 if (err) return done(err);
382
383 if (file[0] !== '.') {
384 if (stat && stat.isDirectory()) {
385 walk(absPath, PATH.join(relPath, file), function() {
386 if (!--pending) cb(null, results);
387 });
388
389 return;
390 }
391
392 var f = PATH.join(relPath, file);
393
394 process.nextTick(fileCb.bind(fileCb, {
395 file: file,
396 relPath: f,
397 absPath: absPath,
398 lastUpdated: stat.mtime.getTime()
399 }));
400 }
401
402 if (!--pending) cb(null, results);
403 });
404 });
405 });
406
407 }
408 return d.promise;
409};
410
411exports.getDirs = function(path) {
412 try {
413 return exports.isDirectory(path)?
414 FS.readdirSync(path)
415 .filter(function(d) {
416 return !(/^\.svn$/.test(d)) && exports.isDirectory(PATH.join(path, d));
417 })
418 .sort() :
419 [];
420 } catch (e) {
421 return [];
422 }
423};
424
425exports.getDirsAsync = function(path) {
426 return QFS.list(path).then(function(items) {
427 return Q.all(items.map(function(i) {
428 return QFS.isDirectory(PATH.join(path, i))
429 .then(function(isDir){
430 return {
431 name: i,
432 dir: isDir
433 };
434 }
435 );
436 }))
437 .then(function(items) {
438 return items
439 .filter(function(item) {
440 return item.dir;
441 })
442 .map(function(item) {
443 return item.name;
444 });
445 }
446 );
447 });
448};
449
450exports.getFilesAsync = function(path) {
451 return QFS.list(path).then(function(items) {
452 return Q.all(items.map(function(i) {
453 return QFS.isFile(PATH.join(path, i))
454 .then(function(isFile){
455 return {
456 name: i,
457 file: isFile
458 };
459 }
460 );
461 }))
462 .then(function(items) {
463 return items
464 .filter(function(item) {
465 return item.file;
466 })
467 .map(function(item) {
468 return item.name;
469 });
470 }
471 );
472 });
473};
474
475exports.getFiles = function(path) {
476 try {
477 return exports.isDirectory(path)?
478 FS.readdirSync(path)
479 .filter(function(f) {
480 return exports.isFile(PATH.join(path, f));
481 })
482 .sort() :
483 [];
484 } catch (e) {
485 return [];
486 }
487};
488
489exports.toUpperCaseFirst = function(str) {
490 return str.charAt(0).toUpperCase() + str.slice(1);
491};
492
493/* jshint -W098 */
494exports.isEmptyObject = function(obj) {
495 for(var i in obj) return false;
496 return true;
497};
498/* jshint +W098 */
499
500var errRe = /^Cannot find module/;
501exports.isRequireError = function(e) {
502 return errRe.test(e.message);
503};
504
505exports.isPath = function(str) {
506 return PATH.normalize(str).indexOf(PATH.dirSep) !== -1;
507};
508
509exports.isRequireable = function(path) {
510 try {
511 require.resolve(path);
512 return true;
513 } catch (e) {
514 if(! exports.isRequireError(e)) throw e;
515 return false;
516 }
517};
518
519exports.arrayUnique = function(arr) {
520 return arr.reduce(function(prev, cur) {
521 if(prev.indexOf(cur) + 1) return prev;
522 return prev.concat([cur]);
523 }, []);
524};
525
526exports.arrayReverse = function(arr) {
527 return arr.reduceRight(function(prev, cur) {
528 prev.push(cur);
529 return prev;
530 }, []);
531};
532
533exports.getBemTechPath = function(name, opts) {
534 opts = _.defaults(opts || {}, {
535 throwWhenUnresolved: false,
536 });
537
538 var bemLib = process.env.COVER? 'bem/lib-cov/' : 'bem/lib/',
539 v1Path = PATH.join(bemLib, 'techs', name),
540 v2Path = PATH.join(bemLib, 'techs', 'v2', name),
541 paths;
542
543 if (typeof opts.version === 'undefined') {
544 paths = [
545 v1Path,
546 v2Path
547 ];
548 } else if (opts.version === 1) {
549 paths = [
550 v1Path
551 ];
552 } else {
553 paths = [
554 v2Path
555 ];
556 }
557
558 for (var i=0, path; path = paths[i]; i++) {
559 if(exports.isRequireable(path + '.js')) return path + '.js';
560 if(exports.isRequireable(path)) return path;
561 }
562
563 if (!opts.throwWhenUnresolved) return PATH.join(bemLib, 'tech');
564
565 throw new Error('Unable to resolve "' + name + '" tech');
566};
567
568exports.stripModuleExt = function(path) {
569 var exts = Object.keys(require.extensions).map(function(v) {
570 return v.replace(/^\./, '');
571 });
572 return path.replace(new RegExp('\\.(' + exts.join('|') + ')$'), '');
573};
574
575exports.getNodePaths = function() {
576 return (process.env.NODE_PATH || '').split(PATH.pathSep);
577};
578
579exports.mergeDecls = function mergeDecls(d1, d2) {
580 var keys = {};
581 d1?
582 d1.forEach(function(o) {
583 keys[o.name || o] = o;
584 }) :
585 d1 = [];
586
587 d2.forEach(function(o2) {
588 var name = o2.name || o2;
589 if (keys.hasOwnProperty(name)) {
590 var o1 = keys[name];
591 o2.elems && (o1.elems = mergeDecls(o1.elems, o2.elems));
592 o2.mods && (o1.mods = mergeDecls(o1.mods, o2.mods));
593 o2.vals && (o1.vals = mergeDecls(o1.vals, o2.vals));
594 o2.techs && (o1.techs = mergeDecls(o1.techs, o2.techs));
595 } else {
596 d1.push(o2);
597 keys[name] = o2;
598 }
599 });
600
601 return d1;
602};
603
604exports.declForEach = function(decl, cb) {
605
606 var forItemWithMods = function(block, elem) {
607 var item = elem || block,
608 type = elem? 'elem' : 'block',
609 args = elem? [block.name, elem.name] : [block.name];
610
611 // for block and element
612 cb(type, args, item);
613
614 // for each modifier
615 item.mods && item.mods.forEach(function(mod) {
616
617 // for modifier
618 cb(type + '-mod', args.concat(mod.name), mod);
619
620 // for each modifier value
621 mod.vals && mod.vals.forEach(function(val, i) {
622 if (!val.name) {
623 val = { name: val };
624 mod.vals[i] = val;
625 }
626 cb(type + '-mod-val', args.concat(mod.name, val.name), val);
627 });
628
629 });
630 },
631 forBlockDecl = function(block) {
632 // for block
633 forItemWithMods(block);
634
635 // for each block element
636 block.elems && block.elems.forEach(function(elem) {
637 forItemWithMods(block, elem);
638 });
639 },
640 forBlocksDecl = function(blocks) {
641 // for each block in declaration
642 blocks.forEach(forBlockDecl);
643 };
644
645 decl.name && forBlockDecl(decl);
646 decl.blocks && forBlocksDecl(decl.blocks);
647
648};
649
650/**
651 * Constructs BEM entity key from entity properties.
652 *
653 * @param {Object} item BEM entity object.
654 * @param {String} item.block Block name.
655 * @param {String} [item.elem] Element name.
656 * @param {String} [item.mod] Modifier name.
657 * @param {String} [item.val] Modifier value.
658 * @return {String}
659 */
660exports.bemKey = function(item) {
661
662 var key = '';
663
664 if (item.block) {
665 key += item.block;
666
667 item.elem && (key += '__' + item.elem);
668
669 if (item.mod) {
670 key += '_' + item.mod;
671 item.val && (key += '_' + item.val);
672 }
673 }
674
675 return key;
676
677};
678
679/**
680 * Constructs BEM entity full key from entity properties plus tech name.
681 *
682 * @param {Object} item BEM entity object.
683 * @param {String} item.block Block name.
684 * @param {String} [item.elem] Element name.
685 * @param {String} [item.mod] Modifier name.
686 * @param {String} [item.val] Modifier value.
687 * @param {String} [item.tech] Tech name.
688 * @return {String}
689 */
690exports.bemFullKey = function(item) {
691 return exports.bemKey(item) + (item.tech? '.' + item.tech : '');
692};
693
694/**
695 * Return BEM entity type by describing object.
696 *
697 * @param {Object} item BEM entity object.
698 * @param {String} item.block Block name.
699 * @param {String} [item.elem] Element name.
700 * @param {String} [item.mod] Modifier name.
701 * @param {String} [item.val] Modifier value.
702 * @return {String}
703 */
704exports.bemType = function(item) {
705
706 var type = item.elem? 'elem' : 'block';
707
708 if (item.mod) {
709 type += '-mod';
710 item.val && (type += '-val');
711 }
712
713 return type;
714
715};
716
717var bemItemRe = '([^_.]+)',
718 bemKeyRe = new RegExp('^' + bemItemRe +
719 '(?:__' + bemItemRe + ')?(?:_' + bemItemRe + '(?:_' + bemItemRe + ')?)?' +
720 '(?:\\.([^_]+))?$');
721
722/**
723 * Parse BEM-entity key into BEM-entity object.
724 *
725 * @param {String} key Key to parse.
726 * @return {Object} BEM-entity object.
727 */
728exports.bemParseKey = function(key) {
729
730 var m = bemKeyRe.exec(key),
731 item = { block: m[1] };
732
733 m[2] && (item.elem = m[2]);
734 m[3] && (item.mod = m[3]);
735 m[4] && (item.val = m[4]);
736 m[5] && (item.tech = m[5]);
737
738 return item;
739
740};
741
742/* jshint -W106 */
743/**
744 * Adopted from jquery's extend method. Under the terms of MIT License.
745 *
746 * http://code.jquery.com/jquery-1.4.2.js
747 *
748 * Modified by mscdex to use Array.isArray instead of the custom isArray method
749 */
750var extend = exports.extend = function() {
751 // copy reference to target object
752 var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy;
753
754 // Handle a deep copy situation
755 if (typeof target === 'boolean') {
756 deep = target;
757 target = arguments[1] || {};
758 // skip the boolean and the target
759 i = 2;
760 }
761
762 // Handle case when target is a string or something (possible in deep copy)
763 if (typeof target !== 'object' && typeof target !== 'function')
764 target = {};
765
766 var isPlainObject = function(obj) {
767 // Must be an Object.
768 // Because of IE, we also have to check the presence of the constructor property.
769 // Make sure that DOM nodes and window objects don't pass through, as well
770 if (!obj || toString.call(obj) !== '[object Object]' || obj.nodeType || obj.setInterval)
771 return false;
772
773 var has_own_constructor = hasOwnProperty.call(obj, 'constructor');
774 var has_is_property_of_method = hasOwnProperty.call(obj.constructor.prototype, 'isPrototypeOf');
775 // Not own constructor property must be Object
776 if (obj.constructor && !has_own_constructor && !has_is_property_of_method)
777 return false;
778
779 // Own properties are enumerated firstly, so to speed up,
780 // if last one is own, then all properties are own.
781
782 var key, last_key;
783 for (key in obj)
784 last_key = key;
785
786 return typeof last_key === 'undefined' || hasOwnProperty.call(obj, last_key);
787 };
788
789
790 for (; i < length; i++) {
791 // Only deal with non-null/undefined values
792 if ((options = arguments[i]) !== null) {
793 // Extend the base object
794 for (name in options) {
795 if (!options.hasOwnProperty(name))
796 continue;
797 src = target[name];
798 copy = options[name];
799
800 // Prevent never-ending loop
801 if (target === copy)
802 continue;
803
804 // Recurse if we're merging object literal values or arrays
805 if (deep && copy && (isPlainObject(copy) || Array.isArray(copy))) {
806 var clone = src && (isPlainObject(src) || Array.isArray(src)) ? src : Array.isArray(copy) ? [] : {};
807
808 // Never move original objects, clone them
809 target[name] = extend(deep, clone, copy);
810
811 // Don't bring in undefined values
812 } else if (typeof copy !== 'undefined')
813 target[name] = copy;
814 }
815 }
816 }
817
818 // Return the modified object
819 return target;
820};
821/* jshint +W106 */
822
823exports.requireWrapper = function(wrappedRequire) {
824 var func = function(module, noCache) {
825 if (noCache) delete wrappedRequire.cache[wrappedRequire.resolve(module)];
826 return wrappedRequire(module);
827 };
828
829 ['resolve', 'cache', 'extensions', 'registerExtension'].forEach(function(key) {
830 func[key] = wrappedRequire[key];
831 });
832
833 return func;
834};
835
836exports.removeFromArray = function(arr, o) {
837 var i = arr.indexOf(o);
838 return i >= 0 ?
839 (arr.splice(i, 1), true) :
840 false;
841};
842
843/**
844 * Return true if all of `needles` are found in `arr`.
845 *
846 * @param {Array} arr Array to search.
847 * @param {String[]|String} needles Needles to search.
848 * @return {Boolean}
849 */
850exports.containsAll = function(arr, needles) {
851
852 Array.isArray(needles) || (needles = [needles]);
853
854 return _.all(needles, function(i) {
855 return _.contains(arr, i);
856 });
857
858};
859
860var getNodePrefix = exports.getNodePrefix = function(level, item) {
861 return PATH.join(
862 PATH.relative(ENV.getEnv('root'), level.dir),
863 level.getRelByObj(item));
864};
865
866exports.getNodeTechPath = function(level, item, tech) {
867 return level.getPath(getNodePrefix(level, item), tech);
868};
869
870exports.setEnv = function(opts) {
871 ENV.setEnv('root', opts.root);
872 ENV.setEnv('verbose', opts.verbose);
873 ENV.setEnv('force', opts.force);
874};
875
876exports.pad = exports.lpad = function(n, desiredLength, padWith) {
877 n = '' + n;
878 if (n.length < desiredLength) n = new Array(desiredLength - n.length + 1).join(padWith) + n;
879
880 return n;
881};
882
883/**
884 * Implementation `rsplit` from Python.
885 *
886 * See http://docs.python.org/library/stdtypes.html#str.rsplit
887 *
888 * @param {String} string String to split
889 * @param {String} [sep] Separator
890 * @param {Number} [maxsplit] Max chunks
891 *
892 * @return {Array}
893 */
894exports.rsplit = function(string, sep, maxsplit) {
895 var arr = string.split(sep || /s+/);
896 return maxsplit ? [arr.slice(0, -maxsplit).join(sep)].concat(arr.slice(-maxsplit)) : arr;
897};
898
899exports.snapshotArch = function(arch, filename) {
900 ASSERT.ok(arch, 'argument is not an object');
901 ASSERT.ok(filename, 'string is expected');
902 ASSERT.ok(arch.toJson, 'object has no toJson method');
903
904 var path = PATH.dirname(filename);
905
906 return QFS.exists(path)
907 .then(function(exists) {
908 if (!exists) return QFS.makeDirectory(path);
909 })
910 .then(function() {
911 return exports.writeFile(filename, arch.toJson());
912 });
913};
914
915exports.getDirsFiles = function(path, dirs, files) {
916 return QFS.list(path).then(function(list) {
917 return Q.all(list
918 .map(function(i) {
919 return QFS.isDirectory(PATH.join(path, i))
920 .then(function(isDir) {
921 (isDir ? dirs : files).push(i);
922 });
923 }));
924 });
925};
926
927exports.getDirsFilesSync = function(path, dirs, files) {
928
929 var items = FS.readdirSync(path);
930
931 items.forEach(function(item) {
932
933 if (item[0] === '.') return;
934
935 var stat = FS.lstatSync(path + PATH.dirSep + item),
936 file = {
937 file: item,
938 absPath: path + PATH.dirSep + item,
939 lastUpdated: stat.mtime.getTime()
940 };
941
942 if (stat.isDirectory()) dirs && dirs.push(file);
943 else files && files.push(file);
944 });
945};
946
947/**
948 * Executes specified command with options.
949 *
950 * @param {String} cmd Command to execute.
951 * @param {Object} options Options to `child_process.exec()` function.
952 * @param {Boolean} resolveWithOutput Resolve returned promise with command output if true.
953 * @return {Promise * String | Undefined}
954 */
955exports.exec = function(cmd, options, resolveWithOutput) {
956
957 var cp = require('child_process').exec(cmd, options),
958 d = Q.defer(),
959 output = '';
960
961 cp.on('exit', function(code) {
962 if (code === 0) return d.resolve(resolveWithOutput && output ? output : null);
963 d.reject(new Error(UTIL.format('%s failed: %s', cmd, output)));
964 });
965
966 cp.stderr.on('data', function(data) {
967 output += data;
968 });
969
970 cp.stdout.on('data', function(data) {
971 output += data;
972 });
973
974 return d.promise;
975
976};
977
978var os = require('os'),
979 hits = {};
980
981/**
982 * Output deprecate message only once based on the value of
983 * the `methodName` argument.
984 *
985 * Based on the code of `deprecate` module:
986 * https://github.com/brianc/node-deprecate
987 *
988 * @type {Function}
989 * @param {String} methodName
990 * @param {String} message
991 */
992var deprecate = exports.deprecate = function(methodName, message) {
993 if (deprecate.silence) return;
994 if (hits[methodName]) return;
995
996 hits[methodName] = true;
997
998 deprecate.stream.write(os.EOL);
999
1000 if (deprecate.color) {
1001 deprecate.stream.write(deprecate.color);
1002 }
1003
1004 deprecate.stream.write('WARNING!' + os.EOL);
1005
1006 for (var i = 0; i < arguments.length; i++) {
1007 deprecate.stream.write(arguments[i] + os.EOL);
1008 }
1009
1010 if (deprecate.color) {
1011 deprecate.stream.write('\x1b[0m');
1012 }
1013
1014 deprecate.stream.write(os.EOL);
1015};
1016
1017deprecate.stream = process.stderr;
1018deprecate.silence = !!process.env.BEM_NO_DEPRECATION;
1019deprecate.color = '\x1b[31;1m';