1 | 'use strict';
|
2 |
|
3 | var PATH = require('./path'),
|
4 | FS = require('fs'),
|
5 | INHERIT = require('inherit'),
|
6 | createTech = require('./tech').createTech,
|
7 | U = require('util'),
|
8 | bemUtil = require('./util'),
|
9 | LOGGER = require('./logger'),
|
10 | isRequireable = bemUtil.isRequireable,
|
11 |
|
12 | BEM,
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | getBem = function() {
|
18 | if (!BEM) BEM = require('..');
|
19 |
|
20 | return BEM;
|
21 | },
|
22 |
|
23 | getLevelClass = function(path, optional, stack) {
|
24 | stack = stack || [];
|
25 |
|
26 | var level;
|
27 | try {
|
28 | level = optional && !isRequireable(path) ? {} : requireLevel(path);
|
29 | } catch(error) {
|
30 | throw new Error(U.format(
|
31 | 'level module %s can not be found %s %s%s\n',
|
32 | path,
|
33 | stack.length > 0? 'but required by the level': '',
|
34 | stack.length > 1? 'inheritance tree:\n\t': '',
|
35 | stack.join('\n\t')));
|
36 | }
|
37 |
|
38 | stack.push(path);
|
39 |
|
40 | if (typeof level === 'function') level = level(getBem());
|
41 |
|
42 | if (level.Level) return level.Level;
|
43 |
|
44 | var baseLevelPath = (level.baseLevelName?
|
45 | PATH.join(__dirname, 'levels', level.baseLevelName):
|
46 | null) || level.baseLevelPath;
|
47 |
|
48 | return INHERIT(baseLevelPath? getLevelClass(baseLevelPath, false, stack) : Level, level);
|
49 | },
|
50 |
|
51 | requireLevel = function(path) {
|
52 | return bemUtil.requireWrapper(require)(path, true);
|
53 | },
|
54 |
|
55 | checkedLevels = {},
|
56 |
|
57 | checkLevel = function(path) {
|
58 | if (checkedLevels[path]) {
|
59 | return;
|
60 | }
|
61 | checkedLevels[path] = true;
|
62 | if (!bemUtil.isDirectory(path)) {
|
63 | LOGGER.fwarn('Level at %s is not a directory', path);
|
64 | return;
|
65 | }
|
66 | if (!bemUtil.isDirectory(PATH.join(path, '.bem'))) {
|
67 | LOGGER.fwarn('Level at %s does not contain .bem subdirectory', path);
|
68 | }
|
69 | },
|
70 |
|
71 | levelCache = {},
|
72 | useCache = false,
|
73 | exceptLevels = [],
|
74 |
|
75 | allRe = /(?:^([^_.\/]+)\/__([^_.\/]+)\/(?:_([^_.\/]+)\/\1__\2_\3(?:_([^_.\/]+))?|\1__\2)(.*?)$|^([^_.\/]+)\/(?:(?:\6)|(?:_([^_.\/]+)\/\6_\7(?:_([^_.\/]+))?))(.*?)$)/,
|
76 | elemAllRe = /^([^_.\/]+)\/__([^_.\/]+)\/(?:_([^_.\/]+)\/\1__\2_\3(?:_([^_.\/]+))?|\1__\2)(.*?)$/,
|
77 | blockAllRe = /^([^_.\/]+)\/(?:(?:\1)|(?:_([^_.\/]+)\/\1_\2(?:_([^_.\/]+))?))(.*?)$/;
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 | exports.createLevel = function(level, opts) {
|
87 |
|
88 |
|
89 | var path = level.path || level;
|
90 |
|
91 | opts = opts || {};
|
92 |
|
93 | checkLevel(level);
|
94 |
|
95 | if (!opts.noCache && levelCache[path]) return levelCache[path];
|
96 | level = new (getLevelClass(PATH.resolve(path, '.bem', 'level.js'), true))(level, opts);
|
97 | levelCache[path] = level;
|
98 |
|
99 | return level;
|
100 | };
|
101 |
|
102 | exports.setCachePolicy = function(useCacheByDefault, except) {
|
103 | useCache = useCacheByDefault;
|
104 | exceptLevels = except || [];
|
105 | };
|
106 |
|
107 | exports.resetLevelsCache = function(all) {
|
108 | for(var l in levelCache) {
|
109 | var level = levelCache[l];
|
110 |
|
111 | if (!level.cache || all) level.files = null;
|
112 | }
|
113 | };
|
114 |
|
115 |
|
116 | var Level = exports.Level = INHERIT({
|
117 |
|
118 | |
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 | __constructor: function(path, opts) {
|
127 | opts = opts || {};
|
128 | this.dir = PATH.resolve(path.path || path);
|
129 | this.projectRoot = opts.projectRoot || PATH.resolve('');
|
130 |
|
131 |
|
132 | this.path = this.bemDir = PATH.join(this.dir, '.bem');
|
133 |
|
134 | path = PATH.relative(this.projectRoot, this.dir);
|
135 | this.cache = useCache;
|
136 | for(var e in exceptLevels) {
|
137 | var except = exceptLevels[e];
|
138 | if (path.substr(0, except.length) === except) {
|
139 | this.cache = !this.cache;
|
140 | break;
|
141 | }
|
142 | }
|
143 |
|
144 |
|
145 | this._techsCache = {};
|
146 | },
|
147 |
|
148 | |
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 | getTypes: function() {
|
156 | return ['level'];
|
157 | },
|
158 |
|
159 | |
160 |
|
161 |
|
162 |
|
163 |
|
164 | getConfig: function() {
|
165 | return {};
|
166 | },
|
167 |
|
168 | |
169 |
|
170 |
|
171 |
|
172 |
|
173 | getTechs: function() {
|
174 |
|
175 | return this.techs || {};
|
176 | },
|
177 |
|
178 | |
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 | getTech: function(name, path) {
|
192 | if(!this._techsCache.hasOwnProperty(name)) {
|
193 | this._techsCache[name] = this.createTech(name, path || name);
|
194 | }
|
195 | return this._techsCache[name];
|
196 | },
|
197 |
|
198 | |
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 | createTech: function(name, path) {
|
206 | return createTech(this.resolveTech(path || name), name, this);
|
207 | },
|
208 |
|
209 | |
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 | resolveTech: function(techIdent, opts) {
|
222 | if (typeof opts === 'boolean') {
|
223 |
|
224 | opts = {force: opts};
|
225 | }
|
226 | opts = opts || {};
|
227 | if(bemUtil.isPath(techIdent)) {
|
228 | return this.resolveTechPath(techIdent);
|
229 | }
|
230 | if(!opts.force && this.getTechs().hasOwnProperty(techIdent)) {
|
231 | return this.resolveTechName(techIdent);
|
232 | }
|
233 | return bemUtil.getBemTechPath(techIdent, opts);
|
234 | },
|
235 |
|
236 | |
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 | resolveTechName: function(techName) {
|
243 | var p = this.getTechs()[techName];
|
244 | return typeof p !== 'undefined'? this.resolveTech(p, {force: true}) : null;
|
245 | },
|
246 |
|
247 | |
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 | resolveTechPath: function(techPath) {
|
255 |
|
256 |
|
257 | if(techPath.substring(0, 1) === '.') {
|
258 |
|
259 | techPath = PATH.join(this.bemDir, techPath);
|
260 |
|
261 |
|
262 | if(!isRequireable(techPath)) {
|
263 | throw new Error("Tech module on path '" + techPath + "' not found");
|
264 | }
|
265 |
|
266 |
|
267 | return techPath;
|
268 | }
|
269 |
|
270 |
|
271 | if(isRequireable(techPath)) {
|
272 | return techPath;
|
273 | }
|
274 |
|
275 |
|
276 |
|
277 | try {
|
278 | return require.resolve('./' + PATH.join('./techs', techPath));
|
279 | } catch (err) {
|
280 | throw new Error("Tech module with path '" + techPath + "' not found on require search paths");
|
281 | }
|
282 |
|
283 | },
|
284 |
|
285 | |
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 | getDefaultTechs: function() {
|
295 | return this.defaultTechs || Object.keys(this.getTechs());
|
296 | },
|
297 |
|
298 | |
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 | resolvePaths: function(paths) {
|
309 |
|
310 |
|
311 | if (Array.isArray(paths)) {
|
312 | return paths.map(function(path) {
|
313 | return this.resolvePath(path);
|
314 | }, this);
|
315 | }
|
316 |
|
317 |
|
318 | var resolved = {};
|
319 | Object.keys(paths).forEach(function(key) {
|
320 | resolved[key] = this.resolvePath(paths[key]);
|
321 | }, this);
|
322 |
|
323 | return resolved;
|
324 |
|
325 | },
|
326 |
|
327 | |
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 | resolvePath: function(path) {
|
337 | return PATH.resolve(this.path, path);
|
338 | },
|
339 |
|
340 | |
341 |
|
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 | getPath: function(prefix, tech) {
|
349 | return this.getTech(tech).getPath(prefix);
|
350 | },
|
351 |
|
352 | getPaths: function(prefix, tech) {
|
353 | return (typeof tech === 'string'? this.getTech(tech): tech).getPaths(prefix);
|
354 | },
|
355 |
|
356 | |
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 | getPathByObj: function(item, tech) {
|
369 | return PATH.join(this.dir, this.getRelPathByObj(item, tech));
|
370 | },
|
371 |
|
372 | |
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 |
|
383 |
|
384 | getRelPathByObj: function(item, tech) {
|
385 | return this.getPath(this.getRelByObj(item), tech);
|
386 | },
|
387 |
|
388 | getFileByObjIfExists: function(item, tech) {
|
389 | if (!this.files) return;
|
390 |
|
391 | var blocks = this.files.tree,
|
392 | block = blocks[item.block];
|
393 |
|
394 | if (!block) return [];
|
395 |
|
396 | if (item.mod && !item.elem) {
|
397 | block = block.mods[item.mod];
|
398 | if (block && item.val) block = block.vals[item.val];
|
399 |
|
400 | } else if (item.elem) {
|
401 | block = block.elems[item.elem];
|
402 | if (block && item.mod) {
|
403 | block = block.mods[item.mod];
|
404 | if (block && item.val) block = block.vals[item.val];
|
405 | }
|
406 | }
|
407 |
|
408 |
|
409 | var files = block? block.files: null;
|
410 |
|
411 | if (!files || files.length === 0) return [];
|
412 |
|
413 | var suffixes = tech.getSuffixes(),
|
414 | res = [];
|
415 |
|
416 | for(var i = 0; i < suffixes.length; i++) {
|
417 | var suffix = suffixes[i],
|
418 | filesBySuffix = files[suffix];
|
419 |
|
420 | if (filesBySuffix) res = res.concat(filesBySuffix);
|
421 |
|
422 | }
|
423 |
|
424 |
|
425 | return res;
|
426 | },
|
427 |
|
428 | |
429 |
|
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 | getByObj: function(item) {
|
440 | return PATH.join(this.dir, this.getRelByObj(item));
|
441 | },
|
442 |
|
443 | |
444 |
|
445 |
|
446 |
|
447 |
|
448 |
|
449 |
|
450 |
|
451 |
|
452 |
|
453 |
|
454 |
|
455 | getRelByObj: function(item) {
|
456 | var getter, args;
|
457 | if (item.block) {
|
458 | getter = 'block';
|
459 | args = [item.block];
|
460 | if (item.elem) {
|
461 | getter = 'elem';
|
462 | args.push(item.elem);
|
463 | }
|
464 | if (item.mod) {
|
465 | getter += '-mod';
|
466 | args.push(item.mod);
|
467 | if (item.val) {
|
468 | getter += '-val';
|
469 | args.push(item.val);
|
470 | }
|
471 | }
|
472 | return this.getRel(getter, args);
|
473 | }
|
474 | return '';
|
475 | },
|
476 |
|
477 | |
478 |
|
479 |
|
480 |
|
481 |
|
482 |
|
483 |
|
484 |
|
485 | get: function(what, args) {
|
486 | return PATH.join(this.dir, this.getRel(what, args));
|
487 | },
|
488 |
|
489 | |
490 |
|
491 |
|
492 |
|
493 |
|
494 |
|
495 |
|
496 |
|
497 |
|
498 | getRel: function(what, args) {
|
499 | return this['get-' + what].apply(this, args);
|
500 | },
|
501 |
|
502 | |
503 |
|
504 |
|
505 |
|
506 |
|
507 |
|
508 | 'get-block': function(block) {
|
509 | return PATH.join.apply(null, [block, block]);
|
510 | },
|
511 |
|
512 | |
513 |
|
514 |
|
515 |
|
516 |
|
517 |
|
518 |
|
519 | 'get-block-mod': function(block, mod) {
|
520 | return PATH.join.apply(null,
|
521 | [block,
|
522 | '_' + mod,
|
523 | block + '_' + mod]);
|
524 | },
|
525 |
|
526 | |
527 |
|
528 |
|
529 |
|
530 |
|
531 |
|
532 |
|
533 |
|
534 | 'get-block-mod-val': function(block, mod, val) {
|
535 | return PATH.join.apply(null,
|
536 | [block,
|
537 | '_' + mod,
|
538 | block + '_' + mod + '_' + val]);
|
539 | },
|
540 |
|
541 | |
542 |
|
543 |
|
544 |
|
545 |
|
546 |
|
547 |
|
548 | 'get-elem': function(block, elem) {
|
549 | return PATH.join.apply(null,
|
550 | [block,
|
551 | '__' + elem,
|
552 | block + '__' + elem]);
|
553 | },
|
554 |
|
555 | |
556 |
|
557 |
|
558 |
|
559 |
|
560 |
|
561 |
|
562 |
|
563 | 'get-elem-mod': function(block, elem, mod) {
|
564 | return PATH.join.apply(null,
|
565 | [block,
|
566 | '__' + elem,
|
567 | '_' + mod,
|
568 | block + '__' + elem + '_' + mod]);
|
569 | },
|
570 |
|
571 | |
572 |
|
573 |
|
574 |
|
575 |
|
576 |
|
577 |
|
578 |
|
579 |
|
580 | 'get-elem-mod-val': function(block, elem, mod, val) {
|
581 | return PATH.join.apply(null,
|
582 | [block,
|
583 | '__' + elem,
|
584 | '_' + mod,
|
585 | block + '__' + elem + '_' + mod + '_' + val]);
|
586 | },
|
587 |
|
588 | |
589 |
|
590 |
|
591 |
|
592 |
|
593 |
|
594 | matchRe: function() {
|
595 | return '[^_.' + PATH.dirSepRe + ']+';
|
596 | },
|
597 |
|
598 | |
599 |
|
600 |
|
601 |
|
602 |
|
603 | matchOrder: function() {
|
604 |
|
605 | return ['elem-all', 'block-all', 'elem-mod-val', 'elem-mod', 'block-mod-val',
|
606 | 'block-mod', 'elem', 'block'];
|
607 | },
|
608 |
|
609 | |
610 |
|
611 |
|
612 |
|
613 |
|
614 | matchTechsOrder: function() {
|
615 | return Object.keys(this.getTechs());
|
616 | },
|
617 |
|
618 | |
619 |
|
620 |
|
621 |
|
622 |
|
623 |
|
624 |
|
625 |
|
626 |
|
627 |
|
628 | matchAny: function(path) {
|
629 | if (PATH.isAbsolute(path)) path = PATH.relative(this.dir, path);
|
630 |
|
631 | var matchTechs = this.matchTechsOrder().map(function(t) {
|
632 | return this.getTech(t);
|
633 | }, this);
|
634 |
|
635 | return this.matchOrder().reduce(function(match, matcher) {
|
636 |
|
637 |
|
638 | if (match) return match;
|
639 |
|
640 |
|
641 | match = this.match(matcher, path);
|
642 |
|
643 |
|
644 | if (!match) return false;
|
645 |
|
646 |
|
647 | match.tech = matchTechs.reduce(function(tech, t) {
|
648 | if (tech || !t.matchSuffix(match.suffix)) return tech;
|
649 | return t.getTechName();
|
650 | }, match.tech);
|
651 |
|
652 | return match;
|
653 |
|
654 | }.bind(this), false);
|
655 | },
|
656 |
|
657 | |
658 |
|
659 |
|
660 |
|
661 |
|
662 |
|
663 |
|
664 |
|
665 |
|
666 |
|
667 |
|
668 | match: function(what, path) {
|
669 | return this['match-' + what].call(this, path);
|
670 | },
|
671 |
|
672 | |
673 |
|
674 |
|
675 |
|
676 |
|
677 |
|
678 |
|
679 |
|
680 | 'match-block': function(path) {
|
681 | var match = new RegExp(['^(' + this.matchRe() + ')',
|
682 | '\\1(.*?)$'].join(PATH.dirSepRe)).exec(path);
|
683 |
|
684 | if (!match) return false;
|
685 | return {
|
686 | block: match[1],
|
687 | suffix: match[2]
|
688 | };
|
689 | },
|
690 |
|
691 | |
692 |
|
693 |
|
694 |
|
695 |
|
696 |
|
697 |
|
698 |
|
699 | 'match-block-mod': function(path) {
|
700 | var m = this.matchRe(),
|
701 | match = new RegExp(['^(' + m + ')',
|
702 | '_(' + m + ')',
|
703 | '\\1_\\2(.*?)$'].join(PATH.dirSepRe)).exec(path);
|
704 |
|
705 | if (!match) return false;
|
706 | return {
|
707 | block: match[1],
|
708 | mod: match[2],
|
709 | suffix: match[3]
|
710 | };
|
711 | },
|
712 |
|
713 | |
714 |
|
715 |
|
716 |
|
717 |
|
718 |
|
719 |
|
720 |
|
721 | 'match-block-mod-val': function(path) {
|
722 | var m = this.matchRe(),
|
723 | match = new RegExp(['^(' + m + ')',
|
724 | '_(' + m + ')',
|
725 | '\\1_\\2_(' + m + ')(.*?)$'].join(PATH.dirSepRe)).exec(path);
|
726 |
|
727 | if (!match) return false;
|
728 | return {
|
729 | block: match[1],
|
730 | mod: match[2],
|
731 | val: match[3],
|
732 | suffix: match[4]
|
733 | };
|
734 | },
|
735 |
|
736 | 'match-block-all': function(path) {
|
737 | var match = blockAllRe.exec(path);
|
738 | if (!match) return false;
|
739 |
|
740 | var res = {
|
741 | block: match[1]
|
742 | };
|
743 |
|
744 | if (match[2]) {
|
745 | res.mod = match[2];
|
746 |
|
747 | if (match[3]) res.val = match[3];
|
748 | }
|
749 |
|
750 | if (match[4]) res.suffix = match[4];
|
751 |
|
752 | return res;
|
753 | },
|
754 |
|
755 | 'get-block-all': function() {
|
756 |
|
757 | },
|
758 |
|
759 | 'get-elem-all': function() {
|
760 |
|
761 | },
|
762 |
|
763 | |
764 |
|
765 |
|
766 |
|
767 |
|
768 |
|
769 |
|
770 |
|
771 | 'match-elem': function(path) {
|
772 | var m = this.matchRe(),
|
773 | match = new RegExp(['^(' + m + ')',
|
774 | '__(' + m + ')',
|
775 | '\\1__\\2(.*?)$'].join(PATH.dirSepRe)).exec(path);
|
776 |
|
777 | if (!match) return false;
|
778 | return {
|
779 | block: match[1],
|
780 | elem: match[2],
|
781 | suffix: match[3]
|
782 | };
|
783 | },
|
784 |
|
785 | |
786 |
|
787 |
|
788 |
|
789 |
|
790 |
|
791 |
|
792 |
|
793 | 'match-elem-mod': function(path) {
|
794 | var m = this.matchRe(),
|
795 | match = new RegExp(['^(' + m + ')',
|
796 | '__(' + m + ')',
|
797 | '_(' + m + ')',
|
798 | '\\1__\\2_\\3(.*?)$'].join(PATH.dirSepRe)).exec(path);
|
799 |
|
800 |
|
801 | if (!match) return false;
|
802 | return {
|
803 | block: match[1],
|
804 | elem: match[2],
|
805 | mod: match[3],
|
806 | suffix: match[4]
|
807 | };
|
808 | },
|
809 |
|
810 | |
811 |
|
812 |
|
813 |
|
814 |
|
815 |
|
816 |
|
817 |
|
818 | 'match-elem-mod-val': function(path) {
|
819 | var m = this.matchRe(),
|
820 | match = new RegExp(['^(' + m + ')',
|
821 | '__(' + m + ')',
|
822 | '_(' + m + ')',
|
823 | '\\1__\\2_\\3_(' + m + ')(.*?)$'].join(PATH.dirSepRe)).exec(path);
|
824 |
|
825 | if (!match) return false;
|
826 | return {
|
827 | block: match[1],
|
828 | elem: match[2],
|
829 | mod: match[3],
|
830 | val: match[4],
|
831 | suffix: match[5]
|
832 | };
|
833 | },
|
834 |
|
835 | 'match-elem-all': function(path) {
|
836 | var match = elemAllRe.exec(path);
|
837 | if (!match) return false;
|
838 |
|
839 | var res = {
|
840 | block: match[1],
|
841 | elem: match[2]
|
842 | };
|
843 |
|
844 | if (match[3]) res.mod = match[3];
|
845 | if (match[4]) res.val = match[4];
|
846 | if (match[5]) res.suffix = match[5];
|
847 |
|
848 | return res;
|
849 | },
|
850 |
|
851 | 'match-all': function(path) {
|
852 | var match = allRe.exec(path);
|
853 | if (!match) return false;
|
854 |
|
855 | var res = {};
|
856 |
|
857 | if (match[1]) {
|
858 | res.block = match[1];
|
859 | res.elem = match[2];
|
860 |
|
861 | if (match[3]) res.mod = match[3];
|
862 | if (match[4]) res.val = match[4];
|
863 | if (match[5]) res.suffix = match[5];
|
864 | } else if (match[6]) {
|
865 |
|
866 | res.block = match[6];
|
867 |
|
868 | if (match[7]) {
|
869 | res.mod = match[7];
|
870 |
|
871 | if (match[8]) res.val = match[8];
|
872 | }
|
873 |
|
874 | if (match[9]) res.suffix = match[9];
|
875 | }
|
876 |
|
877 |
|
878 | return res;
|
879 | },
|
880 |
|
881 | |
882 |
|
883 |
|
884 |
|
885 |
|
886 |
|
887 | getBlockByIntrospection: function(blockName) {
|
888 |
|
889 |
|
890 | var decl = this.getDeclByIntrospection(PATH.dirname(this.get('block', [blockName])));
|
891 | return decl.length? decl.shift() : {};
|
892 | },
|
893 |
|
894 | |
895 |
|
896 |
|
897 |
|
898 |
|
899 |
|
900 | getDeclByIntrospection: function(from) {
|
901 |
|
902 | this._declIntrospector || (this._declIntrospector = this.createIntrospector({
|
903 |
|
904 | creator: function(res, match) {
|
905 | if (match && match.tech) {
|
906 | return this._mergeMatchToDecl(match, res);
|
907 | }
|
908 | return res;
|
909 | }
|
910 |
|
911 | }));
|
912 |
|
913 | return this._declIntrospector(from);
|
914 | },
|
915 |
|
916 | |
917 |
|
918 |
|
919 |
|
920 |
|
921 |
|
922 | getItemsByIntrospection: function(from) {
|
923 |
|
924 | this._itemsIntrospector || (this._itemsIntrospector = this.createIntrospector());
|
925 | return this._itemsIntrospector(from);
|
926 |
|
927 | },
|
928 |
|
929 | scanFiles: function(force) {
|
930 | var list = {},
|
931 | blocks = {},
|
932 | flat = [],
|
933 | files = {
|
934 | files: list,
|
935 | tree: blocks,
|
936 | blocks: flat
|
937 | },
|
938 | items = {
|
939 | push: function(file, item) {
|
940 | file.suffix = item.suffix[0] === '.'?item.suffix.substr(1):item.suffix;
|
941 | (list[file.suffix] || (list[file.suffix] = [])).push(file);
|
942 | flat.push(item);
|
943 |
|
944 | var block = blocks[item.block] || (blocks[item.block] = {elems: {}, mods: {}, files: {}});
|
945 | if (item.mod && !item.elem) {
|
946 | block = block.mods[item.mod] || (block.mods[item.mod] = {vals: {}, files: {}});
|
947 |
|
948 | if (item.val) block = block.vals[item.val] || (block.vals[item.val] = {files: {}});
|
949 | }
|
950 |
|
951 | if (item.elem) {
|
952 | block = block.elems[item.elem] || (block.elems[item.elem] = {mods: {}, files: {}});
|
953 |
|
954 | if (item.mod) block = block.mods[item.mod] || (block.mods[item.mod] = {vals: {}, files: {}});
|
955 | if (item.val) block = block.vals[item.val] || (block.vals[item.val] = {files: {}});
|
956 | }
|
957 |
|
958 | (block.files[file.suffix] || (block.files[file.suffix] = [])).push(file);
|
959 | }
|
960 | };
|
961 |
|
962 | if (this.files && !force) return this.files;
|
963 |
|
964 | var _this = this,
|
965 | cachePath = PATH.join(this.projectRoot, '.bem', 'cache', PATH.relative(this.projectRoot, this.dir));
|
966 |
|
967 | if (this.cache) {
|
968 | try {
|
969 | _this.files = JSON.parse(FS.readFileSync(PATH.join(cachePath, 'files.json')));
|
970 | return _this.files;
|
971 |
|
972 | } catch(err) {
|
973 | LOGGER.fdebug('cache for level not found', _this.dir);
|
974 | }
|
975 | }
|
976 |
|
977 | _this.scan(items);
|
978 | _this.files = files;
|
979 |
|
980 |
|
981 | return bemUtil
|
982 | .mkdirp(cachePath)
|
983 | .then(function() {
|
984 | return bemUtil.writeFile(PATH.join(cachePath, 'files.json'), JSON.stringify(files));
|
985 | })
|
986 | .then(function() {
|
987 | return files;
|
988 | });
|
989 | },
|
990 |
|
991 | scan: function(items) {
|
992 |
|
993 | if (!bemUtil.isDirectory(this.dir)) return;
|
994 |
|
995 | LOGGER.time('scan ' + this.dir);
|
996 |
|
997 | var _this = this;
|
998 |
|
999 | this.suffixToTech = {};
|
1000 | Object.keys(this.getTechs()).forEach(function(tech) {
|
1001 | try {
|
1002 | tech = this.getTech(tech);
|
1003 |
|
1004 | tech.getSuffixes().forEach(function(s) {
|
1005 | this.suffixToTech['.' + s] = tech.getTechName();
|
1006 | }, this);
|
1007 | } catch(err) {
|
1008 | LOGGER.fwarn(err.message);
|
1009 | }
|
1010 |
|
1011 | }, this);
|
1012 |
|
1013 | _this.scanBlocks(_this.dir, items);
|
1014 |
|
1015 | LOGGER.timeEndLevel('debug', 'scan ' + _this.dir);
|
1016 | },
|
1017 |
|
1018 | scanBlocks: function(path, items) {
|
1019 | var dirs = [],
|
1020 | _this = this;
|
1021 |
|
1022 | bemUtil.getDirsFilesSync(path, dirs);
|
1023 |
|
1024 | return dirs
|
1025 | .filter(function(dir) {
|
1026 | dir = dir.file;
|
1027 | return dir[0] !== '_' && dir[0] !== '.';
|
1028 | })
|
1029 | .forEach(function(block) {
|
1030 | return _this.scanBlock(_this.dir, block.file, items);
|
1031 | });
|
1032 | },
|
1033 |
|
1034 | scanBlock: function(path, block, items) {
|
1035 | var _this = this,
|
1036 | dirs = [], files = [];
|
1037 |
|
1038 | bemUtil.getDirsFilesSync(PATH.join(path, block), dirs, files);
|
1039 |
|
1040 | var blockPart = block + '.',
|
1041 | blockPartL = blockPart.length;
|
1042 |
|
1043 | files.forEach(function(f) {
|
1044 | var file = f.file;
|
1045 | if (file.substr(0, blockPartL) !== blockPart) return;
|
1046 |
|
1047 | var suffix = file.substr(blockPartL - 1);
|
1048 |
|
1049 | items.push(f, {
|
1050 | block: block,
|
1051 | suffix: suffix,
|
1052 | tech: _this.suffixToTech[suffix]
|
1053 | });
|
1054 | });
|
1055 |
|
1056 | dirs.forEach(function(d) {
|
1057 | var dir = d.file;
|
1058 | if (_this.isElemDir(dir)) return _this.scanElem(path, block, dir, items);
|
1059 | if (_this.isModDir(dir)) return _this.scanMod(path, block, null, dir, items);
|
1060 | if (dir.substr(0, blockPartL) !== blockPart) return;
|
1061 |
|
1062 | var suffix = dir.substr(blockPartL - 1);
|
1063 |
|
1064 | items.push(d, {
|
1065 | block: block,
|
1066 | suffix: suffix,
|
1067 | tech: _this.suffixToTech[suffix]
|
1068 | });
|
1069 |
|
1070 | files = [];
|
1071 | bemUtil.getDirsFilesSync(PATH.join(path, block, dir), files, files);
|
1072 |
|
1073 | files.forEach(function(file) {
|
1074 | var suffix = (dir + PATH.dirSep + file.file).substr(blockPartL - 1);
|
1075 |
|
1076 | items.push(file, {
|
1077 | block: block,
|
1078 | suffix: suffix,
|
1079 | tech: _this.suffixToTech[suffix]
|
1080 | });
|
1081 | });
|
1082 | });
|
1083 | },
|
1084 |
|
1085 | isElemDir: function(dir) {
|
1086 | return dir[0] === '_' && dir[1] === '_' && !~dir.indexOf('.');
|
1087 | },
|
1088 |
|
1089 | blockElemFileSeparator: '__',
|
1090 | elemDirPrefix: '__',
|
1091 |
|
1092 | scanElem: function(path, block, elem, items) {
|
1093 | var _this = this,
|
1094 | dir = path + PATH.dirSep + block + PATH.dirSep + elem,
|
1095 | dirs = [], files = [];
|
1096 |
|
1097 | bemUtil.getDirsFilesSync(dir, dirs, files);
|
1098 |
|
1099 | var blockPart = block + _this.blockElemFileSeparator + elem.substr(_this.elemDirPrefix.length) + '.',
|
1100 | blockPartL = blockPart.length,
|
1101 | prefixLen = _this.elemDirPrefix.length;
|
1102 |
|
1103 | files.forEach(function(f) {
|
1104 | var file = f.file;
|
1105 | if (file.substr(0, blockPartL) !== blockPart) return;
|
1106 |
|
1107 | var suffix = file.substr(blockPartL - 1);
|
1108 |
|
1109 | items.push(f, {
|
1110 | block: block,
|
1111 | elem: elem.substr(prefixLen),
|
1112 | suffix: suffix,
|
1113 | tech: _this.suffixToTech[suffix]
|
1114 | });
|
1115 | });
|
1116 |
|
1117 | dirs.forEach(function(d) {
|
1118 | if (_this.isModDir(d.file)) return _this.scanMod(path, block, elem, d.file, items);
|
1119 | if (d.file.substr(0, blockPartL) !== blockPart) return;
|
1120 |
|
1121 | var suffix = d.file.substr(blockPartL - 1);
|
1122 |
|
1123 | items.push(d, {
|
1124 | block: block,
|
1125 | elem: elem.substr(prefixLen),
|
1126 | suffix: suffix,
|
1127 | tech: _this.suffixToTech[suffix]
|
1128 | });
|
1129 |
|
1130 | files = [];
|
1131 |
|
1132 | bemUtil.getDirsFilesSync(PATH.join(dir, d.file), null, files);
|
1133 |
|
1134 | files.forEach(function(file) {
|
1135 | var suffix = (d.file + PATH.dirSep + file.file).substr(blockPartL - 1);
|
1136 |
|
1137 | items.push(file, {
|
1138 | block: block,
|
1139 | elem: elem.substr(prefixLen),
|
1140 | suffix: suffix,
|
1141 | tech: _this.suffixToTech[suffix]
|
1142 | });
|
1143 | });
|
1144 | });
|
1145 | },
|
1146 |
|
1147 | isModDir: function(dir) {
|
1148 | return dir[0] === '_' && dir[1] !== '_';
|
1149 | },
|
1150 |
|
1151 | scanMod: function(path, block, elem, mod, items) {
|
1152 | var _this = this,
|
1153 | dir = path + PATH.dirSep + block + PATH.dirSep + (elem?elem+PATH.dirSep:'') + mod,
|
1154 | dirs = [], files = [];
|
1155 |
|
1156 | bemUtil.getDirsFilesSync(dir, dirs, files);
|
1157 |
|
1158 | var blockPart = block + (elem?_this.blockElemFileSeparator + elem.substr(_this.elemDirPrefix.length):'') + mod,
|
1159 | blockPartL = blockPart.length;
|
1160 |
|
1161 | files.forEach(function(f) {
|
1162 | var file = f.file;
|
1163 | if (file.substr(0, blockPartL) !== blockPart) return;
|
1164 |
|
1165 | var val,
|
1166 | modval = file.substr(blockPartL);
|
1167 |
|
1168 | if (modval[0] === '_') val = modval.substr(1);
|
1169 | else if (modval[0] !== '.') return;
|
1170 |
|
1171 | var suffix = modval.substr(modval.indexOf('.')),
|
1172 | item = {
|
1173 | block: block,
|
1174 | mod: mod.substr(1),
|
1175 | suffix: suffix,
|
1176 | tech: _this.suffixToTech[suffix]
|
1177 | };
|
1178 |
|
1179 | if (elem) item.elem = elem.substr(_this.elemDirPrefix.length);
|
1180 | if (val) item.val = val.substr(0, val.indexOf('.'));
|
1181 |
|
1182 | items.push(f, item);
|
1183 | });
|
1184 |
|
1185 | dirs.forEach(function(d) {
|
1186 | if (d.file.substr(0, blockPartL) !== blockPart) return;
|
1187 |
|
1188 | var val,
|
1189 | modval = d.file.substr(blockPartL);
|
1190 |
|
1191 | if (modval[0] === '_') val = modval.substr(1);
|
1192 | else if (modval[0] !== '.') return;
|
1193 |
|
1194 | var suffix = modval.substr(modval.indexOf('.')),
|
1195 | item = {
|
1196 | block: block,
|
1197 | mod: mod.substr(1),
|
1198 | suffix: suffix,
|
1199 | tech: _this.suffixToTech[suffix]
|
1200 | };
|
1201 |
|
1202 | if (elem) item.elem = elem.substr(_this.elemDirPrefix.length);
|
1203 | if (val) item.val = val.substr(0, val.indexOf('.'));
|
1204 |
|
1205 | items.push(d, item);
|
1206 |
|
1207 | files = [];
|
1208 |
|
1209 | bemUtil.getDirsFilesSync(PATH.join(dir, d.file), null, files);
|
1210 |
|
1211 | files.forEach(function(file) {
|
1212 | var suffix = modval.substr(modval.indexOf('.')) + PATH.dirSep + file.file,
|
1213 | item = {
|
1214 | block: block,
|
1215 | mod: mod.substr(1),
|
1216 | suffix: suffix,
|
1217 | tech: _this.suffixToTech[suffix]
|
1218 | };
|
1219 |
|
1220 | if (elem) item.elem = elem.substr(_this.elemDirPrefix.length);
|
1221 | if (val) item.val = val.substr(0, val.indexOf('.'));
|
1222 |
|
1223 | items.push(file, item);
|
1224 | });
|
1225 | });
|
1226 | },
|
1227 |
|
1228 | |
1229 |
|
1230 |
|
1231 |
|
1232 |
|
1233 |
|
1234 |
|
1235 |
|
1236 |
|
1237 |
|
1238 |
|
1239 | createIntrospector: function(opts) {
|
1240 |
|
1241 | var level = this;
|
1242 |
|
1243 | if (!opts) opts = {
|
1244 | opts: false
|
1245 | };
|
1246 |
|
1247 |
|
1248 | opts = bemUtil.extend({}, opts);
|
1249 |
|
1250 |
|
1251 | opts.from || (opts.from = '.');
|
1252 |
|
1253 |
|
1254 | opts.init || (opts.init = function() {
|
1255 | return [];
|
1256 | });
|
1257 |
|
1258 |
|
1259 | opts.filter || (opts.filter = function(path) {
|
1260 | return !this.isIgnorablePath(path);
|
1261 | });
|
1262 |
|
1263 |
|
1264 | opts.matcher || (opts.matcher = function(path) {
|
1265 | return this.matchAny(path);
|
1266 | });
|
1267 |
|
1268 |
|
1269 | opts.creator || (opts.creator = function(res, match) {
|
1270 | if (match && match.tech) res.push(match);
|
1271 | return res;
|
1272 | });
|
1273 |
|
1274 | |
1275 |
|
1276 |
|
1277 |
|
1278 |
|
1279 |
|
1280 |
|
1281 | return function(from, res) {
|
1282 |
|
1283 | if (opts.opts === false) {
|
1284 | level.scanFiles();
|
1285 | return level.files.blocks;
|
1286 | }
|
1287 |
|
1288 | from = PATH.resolve(level.dir, from || opts.from);
|
1289 | res || (res = opts.init.call(level));
|
1290 |
|
1291 | bemUtil.fsWalkTree(from, function(path) {
|
1292 | res = opts.creator.call(level, res, opts.matcher.call(level, path));
|
1293 | },
|
1294 | opts.filter,
|
1295 | level);
|
1296 |
|
1297 | return res;
|
1298 |
|
1299 | };
|
1300 |
|
1301 | },
|
1302 |
|
1303 | _ignorePathRe: /\.(svn|git)$/,
|
1304 |
|
1305 | |
1306 |
|
1307 |
|
1308 |
|
1309 |
|
1310 |
|
1311 | isIgnorablePath: function(path) {
|
1312 | return this._ignorePathRe.test(path);
|
1313 | },
|
1314 |
|
1315 | _mergeMatchToDecl: function(match, decl) {
|
1316 | var blocks, elems, mods, vals,
|
1317 | techAdded = false,
|
1318 | addTech = function(o) {
|
1319 | if(!techAdded && match.tech) {
|
1320 | o.techs = [{ name: match.tech }];
|
1321 | techAdded = true;
|
1322 | }
|
1323 | return o;
|
1324 | };
|
1325 |
|
1326 | match.val &&
|
1327 | (vals = [addTech({name: match.val})]);
|
1328 | match.mod && match.val &&
|
1329 | (mods = [addTech({name: match.mod, vals: vals})]);
|
1330 | match.mod && !match.val &&
|
1331 | (mods = [addTech({name: match.mod})]);
|
1332 | match.elem &&
|
1333 | (elems = [addTech({name: match.elem, mods: mods})]) &&
|
1334 | (blocks = [addTech({name: match.block, elems: elems})]);
|
1335 | !match.elem &&
|
1336 | (blocks = [addTech({name: match.block, mods: mods})]);
|
1337 |
|
1338 | return bemUtil.mergeDecls(decl, blocks);
|
1339 | }
|
1340 |
|
1341 | });
|