UNPKG

4.83 kBJavaScriptView Raw
1'use strict';
2
3var concat = require('concat-stream'),
4 es = require('event-stream'),
5 gutil = require('gulp-util'),
6 path = require('path'),
7 fs = require('fs');
8
9module.exports = function(options) {
10 var prefix, basepath, filters, context;
11
12 if (typeof options === 'object') {
13 basepath = options.basepath || '@file';
14 prefix = options.prefix || '@@';
15 context = options.context || {};
16 filters = options.filters;
17 } else {
18 prefix = options || '@@';
19 basepath = '@file';
20 context = {};
21 }
22
23 var includeRegExp = new RegExp(prefix + 'include\\s*\\([^)]*["\'](.*?)["\'](,\\s*({[\\s\\S]*?})){0,1}\\s*\\)+');
24
25 function fileInclude(file) {
26 var self = this;
27
28 if (file.isNull()) {
29 self.emit('data', file);
30 } else if (file.isStream()) {
31 file.contents.pipe(concat(function(data) {
32 var text = String(data);
33 text = stripCommentedIncludes(text);
34 text = parseConditionalIncludes(text);
35
36 try {
37 self.emit('data', include(file, text));
38 } catch (e) {
39 self.emit('error', new gutil.PluginError('gulp-file-include', e.message));
40 }
41 }));
42 } else if (file.isBuffer()) {
43 try {
44 var text = String(file.contents);
45 text = stripCommentedIncludes(text);
46 text = parseConditionalIncludes(text);
47
48 self.emit('data', include(file, text));
49 } catch (e) {
50 self.emit('error', new gutil.PluginError('gulp-file-include', e.message));
51 }
52 }
53 }
54
55 return es.through(fileInclude);
56
57 /**
58 * utils
59 */
60 function stripCommentedIncludes(content) {
61 // remove single line html comments that use the format: <!-- @@include() -->
62 var regex = new RegExp('<\!--(.*)' + prefix + 'include([\\s\\S]*?)-->', 'g');
63 return content.replace(regex, '');
64 }
65
66 function parseConditionalIncludes(content) {
67 // parse @@if (something) { include('...') }
68 var regexp = new RegExp(prefix + 'if.*\\{[^{}]*\\}\\s*'),
69 matches = regexp.exec(content),
70 included = false;
71
72 context.content = content;
73
74 while (matches) {
75 var match = matches[0],
76 includeExps = /\{([^{}]*)\}/.exec(match)[1];
77
78 // jshint ignore: start
79 var exp = /if(.*)\{/.exec(match)[1];
80 included = new Function('var context = this; return ' + exp + ';').call(context);
81 // jshint ignore: end
82
83 if (included) {
84 content = content.replace(match, includeExps);
85 } else {
86 content = content.replace(match, '');
87 }
88
89 matches = regexp.exec(content);
90 }
91
92 return content;
93 }
94
95 function include(file, text) {
96 var matches = includeRegExp.exec(text);
97
98 switch (basepath) {
99 case '@file':
100 basepath = path.dirname(file.path);
101 break;
102 case '@root':
103 basepath = process.cwd();
104 break;
105 default:
106 break;
107 }
108
109 basepath = path.resolve(process.cwd(), basepath);
110
111 // for checking if we are not including the current file again
112 var currentFilename = path.resolve(file.base, file.path);
113
114 while (matches) {
115 var match = matches[0];
116 var includePath = path.resolve(basepath, matches[1]);
117
118 if (currentFilename.toLowerCase() === includePath.toLowerCase()) {
119 throw new Error('recursion detected in file: ' + currentFilename);
120 }
121
122 var includeContent = fs.readFileSync(includePath);
123
124 // strip utf-8 BOM https://github.com/joyent/node/issues/1918
125 includeContent = includeContent.toString('utf-8').replace(/\uFEFF/, '');
126
127 // need to double each `$` to escape it in the `replace` function
128 includeContent = includeContent.replace(/\$/gi, '$$$$');
129
130 // apply filters on include content
131 if (typeof filters === 'object') {
132 includeContent = applyFilters(includeContent, match);
133 }
134
135 text = text.replace(match, includeContent);
136
137 if (matches[3]) {
138 // replace variables
139 var data = JSON.parse(matches[3]);
140 for (var k in data) {
141 text = text.replace(new RegExp(prefix + k, 'g'), data[k]);
142 }
143 }
144
145 matches = includeRegExp.exec(text);
146 }
147
148 file.contents = new Buffer(text);
149 return file;
150 }
151
152 function applyFilters(includeContent, match) {
153 if (match.match(/\)+$/)[0].length === 1) {
154 // nothing to filter return unchanged
155 return includeContent;
156 }
157
158 // now get the ordered list of filters
159 var filterlist = match.split('(').slice(1, -1);
160 filterlist = filterlist.map(function(str) {
161 return filters[str.trim()];
162 });
163
164 // compose them together into one function
165 var filter = filterlist.reduce(compose);
166
167 // and apply the composed function to the stringified content
168 return filter(String(includeContent));
169 }
170};
171
172function compose(f, g) {
173 return function(x) {
174 return f(g(x));
175 };
176}