1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | var colors = require('colors');
|
11 | var util = require('util');
|
12 | var path = require('path');
|
13 | var mkdirp = require('mkdirp');
|
14 | var jsets = require('jsets');
|
15 | var cbml = require('cbml');
|
16 | var fs = require('fs');
|
17 | var url = require('url');
|
18 | var minimatch = require('minimatch');
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | function invalidFilename(name) {
|
26 | if (!name) {
|
27 | return;
|
28 | }
|
29 | return /[<>^|"'?*\t\r\f\v\n\x00-\x06\x0e-\x0f]/.test(name);
|
30 | }
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | var guid = 0;
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | function buildProcessor(body) {
|
44 | if (/\bmodule\.exports\s*=/.test(body)) {
|
45 | var module = {
|
46 | exports: {}
|
47 | };
|
48 |
|
49 | new Function('require', 'module', 'exports', body)(
|
50 | require, module, module.exports
|
51 | );
|
52 | return module.exports;
|
53 | }
|
54 | else {
|
55 |
|
56 | return new Function('require', 'return (' + body + ');')(require);
|
57 | }
|
58 | }
|
59 | exports.buildProcessor = buildProcessor;
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 | function create(options) {
|
75 | options = options || {};
|
76 | var filename = path.resolve('', options.filename || '');
|
77 | var tags = options.tags || {};
|
78 | var clean = options.clean;
|
79 | var removeList = options.removeList || [];
|
80 | var fromString = options.fromString;
|
81 | var contentString = options.content;
|
82 | var instance = {};
|
83 | var argv = options.argv || {};
|
84 | var env = options.env || global.process.env;
|
85 | var rootScope = options.rootScope || instance;
|
86 | var scopes = options.scopes || {};
|
87 | var processors = options.processors || {};
|
88 | var variants = options.variants || {};
|
89 | var cacheKeys = options.cacheKeys || {};
|
90 | var tokens = options.tokens;
|
91 | var excludeList = options.excludeList || [];
|
92 | |
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 | function cleanContent(content) {
|
99 | if (!clean) {
|
100 | return content;
|
101 | }
|
102 | return String(content).replace(/^[^\n\S]*$/gm, '')
|
103 | .replace(/\n{2,}/gm, '\n');
|
104 | }
|
105 | |
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 | function cleanDefine(content) {
|
112 | return String(content).replace(/^[^\S\n]*\n|\n[^\S\n]*$/g, '');
|
113 | }
|
114 | |
115 |
|
116 |
|
117 | function init() {
|
118 | if (tokens) {
|
119 | return;
|
120 | }
|
121 | if (fromString) {
|
122 | tokens = cbml.parse(contentString);
|
123 | }
|
124 | else {
|
125 | tokens = cbml.parse(fs.readFileSync(filename));
|
126 | }
|
127 | |
128 |
|
129 |
|
130 | }
|
131 | scopes[filename] = instance;
|
132 | |
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 | function compile(content) {
|
151 | return buildBlock(cbml.parse(content));
|
152 | }
|
153 | instance.compile = compile;
|
154 | |
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 | var getArgument = jsets.createGetter(instance, function (name) {
|
161 | return argv[name];
|
162 | }, false);
|
163 | instance.getArgument = getArgument;
|
164 | |
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 | var getEnvironment = jsets.createGetter(instance, function (name) {
|
171 | return env[name];
|
172 | }, false);
|
173 | instance.getEnvironment = getEnvironment;
|
174 | |
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 | var getVariant = jsets.createGetter(instance, function (name) {
|
181 | if (!(name in variants)) {
|
182 | console.warn(
|
183 | colors.blue(
|
184 | 'Variant "%s" is not set.'
|
185 | ), name
|
186 | );
|
187 | }
|
188 | return variants[name];
|
189 | }, true);
|
190 | instance.getVariant = getVariant;
|
191 | |
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 | var setVariant = jsets.createSetter(instance, function (name, value) {
|
199 | if (variants[name] !== value) {
|
200 | variants[name] = value;
|
201 | }
|
202 | }, true);
|
203 | instance.setVariant = setVariant;
|
204 | |
205 |
|
206 |
|
207 |
|
208 |
|
209 | instance.getRootScope = function () {
|
210 | return rootScope;
|
211 | };
|
212 | |
213 |
|
214 |
|
215 |
|
216 |
|
217 | function getDirname() {
|
218 | return path.dirname(filename);
|
219 | }
|
220 | instance.getDirname = getDirname;
|
221 | |
222 |
|
223 |
|
224 |
|
225 |
|
226 | function getFilename() {
|
227 | return url.format(path.relative('', filename));
|
228 | }
|
229 | instance.getFilename = getFilename;
|
230 | |
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 | function fileScope(filename) {
|
237 | filename = path.resolve('', filename || '');
|
238 | var result = scopes[filename];
|
239 | if (!result) {
|
240 | result = create({
|
241 | clean: clean,
|
242 | removeList: removeList,
|
243 | excludeList: excludeList,
|
244 | rootScope: rootScope,
|
245 | filename: filename,
|
246 | argv: argv,
|
247 | scopes: scopes,
|
248 | processors: processors,
|
249 | variants: variants,
|
250 | cacheKeys: cacheKeys,
|
251 | tags: tags
|
252 | });
|
253 | }
|
254 | return result;
|
255 | }
|
256 | instance.fileScope = fileScope;
|
257 | instance.getScope = fileScope;
|
258 | |
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 | function contentScope(content, file) {
|
266 | if (file) {
|
267 | file = path.resolve(getDirname(), file);
|
268 | } else {
|
269 | file = filename;
|
270 | }
|
271 | return create({
|
272 | fromString: true,
|
273 | content: content,
|
274 | clean: clean,
|
275 | removeList: removeList,
|
276 | excludeList: excludeList,
|
277 | rootScope: rootScope,
|
278 | filename: file,
|
279 | argv: argv,
|
280 | scopes: scopes,
|
281 | processors: processors,
|
282 | variants: variants,
|
283 | cacheKeys: cacheKeys,
|
284 | tags: tags
|
285 | });
|
286 | }
|
287 | instance.contentScope = contentScope;
|
288 | |
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 | function process(content, encoding, attrs, node) {
|
297 | if (typeof content === 'undefined') {
|
298 | console.error(
|
299 | colors.red('process() : Undefined "content" parameters.')
|
300 | );
|
301 | return '';
|
302 | }
|
303 | if (!encoding || encoding === 'original') {
|
304 | return content;
|
305 | }
|
306 | var items = encoding.split(/\s*,\s*/);
|
307 | if (items.length > 1) {
|
308 | items.forEach(function (item) {
|
309 | content = process(content, item, attrs, node);
|
310 | });
|
311 | return content;
|
312 | }
|
313 | var processor = getProcessor(encoding);
|
314 | if (!processor) {
|
315 | console.error(
|
316 | colors.red('The "%s" processor does not exist.'), encoding
|
317 | );
|
318 | return content;
|
319 | }
|
320 | return processor(content, attrs, instance, node);
|
321 | }
|
322 | instance.process = process;
|
323 | |
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 | function getProcessor(encoding) {
|
331 | var result;
|
332 | if (/^[\w-_]+$/.test(encoding)) {
|
333 | result = processors[encoding];
|
334 | if (result) {
|
335 | return result;
|
336 | }
|
337 | var file = path.join(__dirname, '../processor-extend', 'processor-' + encoding + '.js');
|
338 | if (fs.existsSync(file)) {
|
339 | processors[encoding] = require(file);
|
340 | return processors[encoding];
|
341 | }
|
342 | console.warn(
|
343 | colors.blue(
|
344 | 'Processor "%s" is not registered.'
|
345 | ), encoding
|
346 | );
|
347 | return;
|
348 | }
|
349 | var item = processors[encoding];
|
350 | if (item) {
|
351 | if (item.cacheKey === cacheKeys[encoding]) {
|
352 | return item.processor;
|
353 | }
|
354 | else {
|
355 | processors[encoding] = null;
|
356 | }
|
357 | }
|
358 | var body = execImport(encoding);
|
359 | if (!body || body.indexOf('function') < 0) {
|
360 | console.error(colors.red('Invalid encoding %j.'), encoding);
|
361 | return function (content) {
|
362 | return content;
|
363 | };
|
364 | }
|
365 | result = buildProcessor(body);
|
366 | if (/^[#@]/.test(encoding)) {
|
367 | cacheKeys[encoding] = guid++;
|
368 | processors[encoding] = {
|
369 | cacheKey: cacheKeys[encoding],
|
370 | processor: result
|
371 | };
|
372 | }
|
373 | return result;
|
374 | }
|
375 | |
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 | function querySelector(selector) {
|
382 | init();
|
383 | if (!selector) {
|
384 | return tokens;
|
385 | }
|
386 | var match = selector.match(
|
387 | /^\s*([\w_-]*)((\s*\[[\w_-]+\s*=\s*("([^\\"]*(\\.)*)*"|'([^\\']*(\\.)*)*'|[^\[\]]*)\])*)\s*(\*?)$/
|
388 | );
|
389 | if (!match) {
|
390 | console.warn(colors.blue('Invalid selector expressions %j.'), selector);
|
391 | return;
|
392 | }
|
393 | var tag = match[1];
|
394 | var attributes = [];
|
395 | var all = match[9] === '*';
|
396 | match[2].replace(/\s*\[([\w_-]+)\s*=\s*("([^\\"]*(\\.)*)*"|'([^\\']*(\\.)*)*'|[^\[\]]*)\]/g,
|
397 | function (match, name, value) {
|
398 | if (/^['"]/.test(value)) {
|
399 |
|
400 | value = new Function('return (' + value + ');')();
|
401 | }
|
402 | attributes.push({
|
403 | name: name,
|
404 | value: value
|
405 | });
|
406 | }
|
407 | );
|
408 | function check(node) {
|
409 | if (!node || !node.attrs) {
|
410 | return;
|
411 | }
|
412 | var flag;
|
413 | if (!tag || node.tag === tag) {
|
414 | flag = true;
|
415 | attributes.every(function (item) {
|
416 | if (item.value !== node.attrs[item.name]) {
|
417 | flag = false;
|
418 | }
|
419 | return flag;
|
420 | });
|
421 | }
|
422 | return flag;
|
423 | }
|
424 | var items;
|
425 | function scan(node) {
|
426 | if (check(node)) {
|
427 | if (items) {
|
428 | items.push(node);
|
429 | }
|
430 | return node;
|
431 | }
|
432 | var result;
|
433 | if (node.nodes) {
|
434 | node.nodes.every(function (item) {
|
435 | result = scan(item);
|
436 | return items || !result;
|
437 | });
|
438 | }
|
439 | return result;
|
440 | }
|
441 | if (all) {
|
442 | items = [];
|
443 | scan(tokens);
|
444 | return items;
|
445 | }
|
446 | else {
|
447 | return scan(tokens);
|
448 | }
|
449 | }
|
450 | instance.querySelector = querySelector;
|
451 | |
452 |
|
453 |
|
454 |
|
455 |
|
456 |
|
457 | function execTrigger(trigger) {
|
458 | if (!trigger) {
|
459 | return true;
|
460 | }
|
461 |
|
462 | if (/^([\w-_]+)(,[\w-_]+)*$/.test(trigger)) {
|
463 | var a1 = String(getArgument('trigger')).split(',');
|
464 | var a2 = trigger.split(',');
|
465 | var flag = false;
|
466 | a1.every(function (item) {
|
467 | if (a2.indexOf(item) >= 0) {
|
468 | flag = true;
|
469 | }
|
470 | return !flag;
|
471 | });
|
472 | return flag;
|
473 | }
|
474 |
|
475 |
|
476 |
|
477 |
|
478 | return new Function('return (' +
|
479 | trigger.replace(/(@|#|:)([\w-_]+)/g, function (all, flag, name) {
|
480 | if (flag === '@') {
|
481 | return JSON.stringify(getArgument(name));
|
482 | }
|
483 | else if (flag === ':') {
|
484 | return JSON.stringify(getEnvironment(name));
|
485 | }
|
486 | else {
|
487 | return JSON.stringify(getVariant(name));
|
488 | }
|
489 | }) +
|
490 | ')'
|
491 | )();
|
492 | }
|
493 | instance.execTrigger = execTrigger;
|
494 | |
495 |
|
496 |
|
497 |
|
498 |
|
499 |
|
500 | function execExclude(file) {
|
501 | var result = false;
|
502 | excludeList.every(function (item) {
|
503 | if (minimatch(file, item)) {
|
504 | result = true;
|
505 | }
|
506 | return !result;
|
507 | });
|
508 | return result;
|
509 | }
|
510 | |
511 |
|
512 |
|
513 |
|
514 |
|
515 |
|
516 |
|
517 | function execImport(importation, froms) {
|
518 | if (!importation) {
|
519 | return importation;
|
520 | }
|
521 | if (importation.indexOf('#') === 0) {
|
522 | if (froms instanceof Array && froms.indexOf('variant') < 0) {
|
523 | return importation;
|
524 | }
|
525 | return getVariant(importation.slice(1));
|
526 | }
|
527 | if (importation.indexOf('@') === 0) {
|
528 | if (froms instanceof Array && froms.indexOf('argv') < 0) {
|
529 | return importation;
|
530 | }
|
531 | return getArgument(importation.slice(1));
|
532 | }
|
533 | if (importation.indexOf(':') === 0) {
|
534 | if (froms instanceof Array && froms.indexOf('env') < 0) {
|
535 | return importation;
|
536 | }
|
537 | return getEnvironment(importation.slice(1));
|
538 | }
|
539 | if (/^'([^\\']*(\\.)*)*'$/.test(importation)) {
|
540 |
|
541 | return new Function('return (' + importation + ');')();
|
542 | }
|
543 | if (/^[\[\{"]/.test(importation)) {
|
544 | return importation;
|
545 | }
|
546 | if (froms instanceof Array && froms.indexOf('file') < 0) {
|
547 | return importation;
|
548 | }
|
549 |
|
550 | var items = importation.split('?');
|
551 | var name = items[0];
|
552 | if (invalidFilename(name)) {
|
553 | return importation;
|
554 | }
|
555 | var selector = items[1];
|
556 | var scope;
|
557 | if (!name) {
|
558 | scope = instance;
|
559 | }
|
560 | else {
|
561 | var file = path.resolve(getDirname(), name);
|
562 | if (!fs.existsSync(file)) {
|
563 | return importation;
|
564 | }
|
565 | if (execExclude(file)) {
|
566 | if (!selector) {
|
567 | return fs.readFileSync(file);
|
568 | }
|
569 | else {
|
570 | console.error(
|
571 | colors.red('File "%s" has been ruled exclude, unable to code block import.'), file
|
572 | );
|
573 | return '';
|
574 | }
|
575 | }
|
576 | scope = fileScope(file);
|
577 | }
|
578 | if (selector) {
|
579 | var node = scope.querySelector(selector);
|
580 | if (!node) {
|
581 | console.error(
|
582 | colors.red('Selector "%s" is no matching nodes.'), selector
|
583 | );
|
584 | return '';
|
585 | }
|
586 | if (node instanceof Array) {
|
587 | return node.map(function (item) {
|
588 | return scope.buildBlock(item, true);
|
589 | }).join('\n');
|
590 | }
|
591 | return scope.buildBlock(node, true);
|
592 | }
|
593 | else {
|
594 | if (instance === scope) {
|
595 | console.error(
|
596 | colors.red('Cannot reference himself.')
|
597 | );
|
598 | return '';
|
599 | }
|
600 | return scope.build(instance);
|
601 | }
|
602 | }
|
603 | instance.execImport = execImport;
|
604 | |
605 |
|
606 |
|
607 |
|
608 |
|
609 |
|
610 | function execExport(exportation, content) {
|
611 | if (!exportation) {
|
612 | return;
|
613 | }
|
614 | if (exportation.indexOf('@') === 0) {
|
615 | console.error(colors.red('Argv is readonly.'));
|
616 | return;
|
617 | }
|
618 | if (exportation.indexOf(':') === 0) {
|
619 | console.error(colors.red('Env is readonly.'));
|
620 | return;
|
621 | }
|
622 | if (exportation.indexOf('#') === 0) {
|
623 | setVariant(exportation.slice(1), content);
|
624 | cacheKeys[exportation] = null;
|
625 | return true;
|
626 | }
|
627 | else if (!invalidFilename(exportation)) {
|
628 | var name = path.resolve(getDirname(), exportation);
|
629 | cacheKeys[name] = null;
|
630 | if (fs.existsSync(name)) {
|
631 | console.warn(
|
632 | colors.blue('File "%s" overwrite.'), name
|
633 | );
|
634 | }
|
635 | else {
|
636 | mkdirp.sync(path.dirname(name));
|
637 | }
|
638 | fs.writeFileSync(name, content);
|
639 | scopes[name] = null;
|
640 | return true;
|
641 | }
|
642 | else {
|
643 | console.error(colors.red('Export %j invalid.'), exportation);
|
644 | return;
|
645 | }
|
646 | }
|
647 | instance.execExport = execExport;
|
648 | function isYes(text) {
|
649 | return /^(true|on|yes|ok)$/i.test(text);
|
650 | }
|
651 | instance.isYes = isYes;
|
652 | function isNo(text) {
|
653 | return /^(false|off|no)$/i.test(text);
|
654 | }
|
655 | instance.isNo = isNo;
|
656 | |
657 |
|
658 |
|
659 |
|
660 |
|
661 |
|
662 |
|
663 | function buildBlock(node, isImport) {
|
664 | if (!node) {
|
665 | return '';
|
666 | }
|
667 | init();
|
668 | if (node.pending) {
|
669 | var error = util.format('A circular reference. (%s:%d:%d)',
|
670 | filename, node.line || 0, node.col || 0
|
671 | );
|
672 | console.error(colors.red(error));
|
673 | throw error;
|
674 | }
|
675 | if (node.fixed) {
|
676 | if (isImport) {
|
677 | return node.content;
|
678 | }
|
679 | return node.value;
|
680 | }
|
681 | if (node.type === 'text') {
|
682 | return node.value;
|
683 | }
|
684 | node.pending = true;
|
685 | var value = '';
|
686 | var fixed = true;
|
687 | if (node.attrs && isYes(node.attrs.important)) {
|
688 | value = node.content;
|
689 | } else if (node.nodes) {
|
690 | node.nodes.forEach(function (item) {
|
691 | var text = buildBlock(item);
|
692 | if (!item.fixed && item.type !== 'text') {
|
693 | fixed = false;
|
694 | }
|
695 | if (item.altered) {
|
696 | value = value.replace(/[^\S\n]*$/, '');
|
697 | }
|
698 | value += text;
|
699 | });
|
700 | }
|
701 | var tagInfo = tags[node.tag];
|
702 | var isTrigger = true;
|
703 | node.altered = false;
|
704 | if (node.attrs && node.attrs.trigger) {
|
705 | isTrigger = execTrigger(node.attrs.trigger);
|
706 | }
|
707 | if (tagInfo && isTrigger) {
|
708 | if (node.attrs.import && node.attrs.import !== '&') {
|
709 | value = execImport(node.attrs.import);
|
710 | if (!/^[@:]/.test(node.attrs.import)) {
|
711 | fixed = false;
|
712 | }
|
713 | }
|
714 | else {
|
715 | value = cleanDefine(value);
|
716 | }
|
717 | value = process(value, node.attrs.encoding || tagInfo.encoding,
|
718 | node.attrs, node);
|
719 | if (/^\s+/.test(value)) {
|
720 | node.altered = true;
|
721 | }
|
722 | if (typeof value === 'object') {
|
723 | value = JSON.stringify(value);
|
724 | } else {
|
725 | value = String(value);
|
726 | }
|
727 | node.content = value;
|
728 | if (node.attrs.export && node.attrs.export !== '&') {
|
729 | execExport(node.attrs.export, value);
|
730 | value = '';
|
731 | fixed = false;
|
732 | }
|
733 | else if (removeList.indexOf(node.tag) >= 0) {
|
734 | value = '';
|
735 | }
|
736 | }
|
737 | else if (node.tag) {
|
738 | node.content = cleanDefine(value);
|
739 | if (isTrigger && removeList.indexOf(node.tag) >= 0) {
|
740 | node.altered = false;
|
741 | value = '';
|
742 | }
|
743 | else if (node.type !== 'single') {
|
744 | value = node.prefix + value + node.suffix;
|
745 | }
|
746 | else {
|
747 | value = node.value;
|
748 | }
|
749 | }
|
750 | else {
|
751 | value = cleanContent(value);
|
752 | }
|
753 | node.pending = false;
|
754 | node.value = value;
|
755 | node.fixed = fixed;
|
756 | if (isImport) {
|
757 | return node.content;
|
758 | }
|
759 | return value;
|
760 | }
|
761 | instance.buildBlock = buildBlock;
|
762 | |
763 |
|
764 |
|
765 |
|
766 |
|
767 | function build() {
|
768 | init();
|
769 | if (tokens.fixed) {
|
770 | return tokens.value;
|
771 | }
|
772 | return buildBlock(tokens);
|
773 | }
|
774 | instance.build = build;
|
775 | return instance;
|
776 | }
|
777 | exports.create = create; |
\ | No newline at end of file |