UNPKG

5.14 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 filebase = basepath === "@file" ? path.dirname(file.path) : basepath === "@root" ? process.cwd() : basepath;
97 var matches = includeRegExp.exec(text);
98
99 filebase = path.resolve(process.cwd(), filebase);
100
101 // for checking if we are not including the current file again
102 var currentFilename = path.resolve(file.base, file.path);
103
104 while (matches) {
105 var match = matches[0];
106 var includePath = path.resolve(filebase, matches[1]);
107
108 if (currentFilename.toLowerCase() === includePath.toLowerCase()) {
109 throw new Error('recursion detected in file: ' + currentFilename);
110 }
111
112 var includeContent = fs.readFileSync(includePath);
113
114 // strip utf-8 BOM https://github.com/joyent/node/issues/1918
115 includeContent = includeContent.toString('utf-8').replace(/\uFEFF/, '');
116
117 // need to double each `$` to escape it in the `replace` function
118 includeContent = includeContent.replace(/\$/gi, '$$$$');
119
120 // apply filters on include content
121 if (typeof filters === 'object') {
122 includeContent = applyFilters(includeContent, match);
123 }
124
125 var recMatches = includeRegExp.exec(includeContent);
126 if (recMatches && basepath == "@file") {
127 var recFile = new gutil.File({
128 cwd: process.cwd(),
129 base: file.base,
130 path: includePath,
131 contents: new Buffer(includeContent)
132 });
133 recFile = include(recFile, includeContent);
134 includeContent = String(recFile.contents);
135 }
136
137 text = text.replace(match, includeContent);
138
139 if (matches[3]) {
140 // replace variables
141 var data = JSON.parse(matches[3]);
142 for (var k in data) {
143 text = text.replace(new RegExp(prefix + k, 'g'), data[k]);
144 }
145 }
146
147 matches = includeRegExp.exec(text);
148 }
149
150 file.contents = new Buffer(text);
151 return file;
152 }
153
154 function applyFilters(includeContent, match) {
155 if (match.match(/\)+$/)[0].length === 1) {
156 // nothing to filter return unchanged
157 return includeContent;
158 }
159
160 // now get the ordered list of filters
161 var filterlist = match.split('(').slice(1, -1);
162 filterlist = filterlist.map(function(str) {
163 return filters[str.trim()];
164 });
165
166 // compose them together into one function
167 var filter = filterlist.reduce(compose);
168
169 // and apply the composed function to the stringified content
170 return filter(String(includeContent));
171 }
172};
173
174function compose(f, g) {
175 return function(x) {
176 return f(g(x));
177 };
178}