Skip to content
This repository was archived by the owner on Feb 21, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ npm-debug.log
node_modules/
.DS_Store
coverage/
.idea/
17 changes: 17 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ fileinclude('@@')
- filters: `object`, filters of include content
- context: `object`, context of `if` statement
- indent: `boolean`, default `false`
- plugins `array`, default `[]`

* options.basepath - type: `string`, it could be
- `@root`, include file relative to the dir where `gulp` running in
Expand Down Expand Up @@ -185,6 +186,22 @@ fileinclude({
</ul>
```

### add plugins

gulpfile.js
```js
var fileinclude = require('gulp-file-include'),
gulp = require('gulp');

gulp.task('fileinclude', function() {
gulp.src(['index.html'])
.pipe(fileinclude({
plugins: ['path/to/plugin', 'node_modules/plugin-name']
}))
.pipe(gulp.dest('./'));
});
```

### License
MIT

Expand Down
129 changes: 25 additions & 104 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
'use strict';

var replaceOperator = require('./replace-operator');
var replaceFunction = require('./replace-function');
var replaceVariable = require('./replace-variable');
var concat = require('concat-stream');
var setIndent = require('./indent');
var through = require('through2');
var gutil = require('gulp-util');
var extend = require('extend');
var path = require('path');
var fs = require('fs');

module.exports = function(opts) {
if (typeof opts === 'string') {
Expand All @@ -29,6 +24,30 @@ module.exports = function(opts) {
opts.basepath = opts.basepath === '@root' ? process.cwd() : path.resolve(opts.basepath);
}

// add plugins
opts.plugins = [].concat(
// list of inner plugins
[
'replace-operator',
'replace-operator-for',
'replace-variable',
'replace-function'
],
// list of external plugins
opts.plugins || []
).map(function(pluginOpts) {
var plugin;

try {
// inner plugins
plugin = require(`./plugins/${pluginOpts}`);
} catch (e) {
// external plugins
plugin = require(path.resolve(pluginOpts));
}
return plugin;
});

function fileInclude(file, enc, cb) {
if (file.isNull()) {
cb(null, file);
Expand Down Expand Up @@ -63,114 +82,16 @@ module.exports = function(opts) {
}

function include(file, text, data) {
var filebase = opts.basepath === '@file' ? path.dirname(file.path) : opts.basepath;
var currentFilename = path.resolve(file.base, file.path);

data = extend(true, {}, opts.context, data || {});
data.content = text;

text = stripCommentedIncludes(text, opts);
text = replaceOperator(text, {
prefix: opts.prefix,
suffix: opts.suffix,
name: 'if',
handler: conditionalHandler
});
text = replaceOperator(text, {
prefix: opts.prefix,
suffix: opts.suffix,
name: 'for',
handler: forHandler
});
text = replaceVariable(text, data, opts);
text = replaceFunction(text, {
prefix: opts.prefix,
suffix: opts.suffix,
name: 'include',
handler: includeHandler
});

function conditionalHandler(inst) {
// jshint ignore: start
var condition = new Function('var context = this; with (context) { return ' + inst.args + '; }').call(data);
// jshint ignore: end

return condition ? inst.body : '';
}

function forHandler(inst) {
var condition = 'var context = this; with (context) { var result=""; for' + inst.args + ' { result+=`' + inst.body + '`; } return result; }';
// jshint ignore: start
var result = new Function(condition).call(data);
// jshint ignore: end

return result;
}

function includeHandler(inst) {
var args = /[^)"\']*["\']([^"\']*)["\'](,\s*({[\s\S]*})){0,1}\s*/.exec(inst.args);

if (args) {
var includePath = path.resolve(filebase, args[1]);
// for checking if we are not including the current file again
if (currentFilename.toLowerCase() === includePath.toLowerCase()) {
throw new Error('recursion detected in file: ' + currentFilename);
}

var includeContent = fs.readFileSync(includePath, 'utf-8');

if (opts.indent) {
includeContent = setIndent(inst.before, inst.before.length, includeContent);
}

// need to double each `$` to escape it in the `replace` function
// includeContent = includeContent.replace(/\$/gi, '$$$$');

// apply filters on include content
if (typeof opts.filters === 'object') {
includeContent = applyFilters(includeContent, args.input);
}

var recFile = new gutil.File({
cwd: process.cwd(),
base: file.base,
path: includePath,
contents: new Buffer(includeContent)
});

recFile = include(recFile, includeContent, args[3] ? JSON.parse(args[3]) : {});

return String(recFile.contents);
}
}
opts.plugins.forEach(plugin => text = plugin(file, text, data, opts, include));

file.contents = new Buffer(text);

return file;
}

function applyFilters(includeContent, match) {
if (!match.match(/\)+$/)) {
// nothing to filter return unchanged
return includeContent;
}

// now get the ordered list of filters
var filterlist = match.split('(').slice(0, -1);
filterlist = filterlist.map(function(str) {
return opts.filters[str.trim()];
});

// compose them together into one function
var filter = filterlist.reduce(compose);

// and apply the composed function to the stringified content
return filter(String(includeContent));
}
};

function compose(f, g) {
return function(x) {
return f(g(x));
};
}
File renamed without changes.
124 changes: 124 additions & 0 deletions lib/plugins/replace-function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
var balanced = require('balanced-match');
var setIndent = require('./indent');
var gutil = require('gulp-util');
var path = require('path');
var fs = require('fs');

module.exports = function(file, text, data, opts, handleFn) {

opts.name = 'include';

return include(file, text, opts, handleFn);

};

function include(file, content, opts, handleFn) {
var result = '';
var reStart = new RegExp(opts.prefix + '[ ]*' + opts.name + '\\(');
var reEnd = new RegExp('^[ ]*' + opts.suffix);
var matchStart;
var matchArg;
var matchEnd;
var safeStart;
var before;
var replacement;

while (matchStart = reStart.exec(content)) {
safeStart = matchStart.index + matchStart[0].length - 1;

matchArg = balanced('(', ')', content.slice(safeStart));

if (matchArg && matchArg.start === 0) {
if (opts.suffix) {
matchEnd = reEnd.exec(matchArg.post);
}

matchEnd = matchEnd ? matchEnd.index + matchEnd[0].length : 0;

if (!opts.suffix || matchEnd) {
before = content.slice(0, matchStart.index);
replacement = handler(file, {
before: before,
args: matchArg.body,
}, opts, handleFn);

if (replacement !== undefined) {
result += before + replacement.toString();
content = content.slice(safeStart + matchArg.end + 1 + matchEnd);
continue;
}
}
}

result += content.slice(0, safeStart);
content = content.slice(safeStart);
}

result += content;

return result;
}

function handler(file, inst, opts, handleFn) {
var args = /[^)"\']*["\']([^"\']*)["\'](,\s*({[\s\S]*})){0,1}\s*/.exec(inst.args);
var filebase = opts.basepath === '@file' ? path.dirname(file.path) : opts.basepath;
var currentFilename = path.resolve(file.base, file.path);

if (args) {
var includePath = path.resolve(filebase, args[1]);
// for checking if we are not including the current file again
if (currentFilename.toLowerCase() === includePath.toLowerCase()) {
throw new Error('recursion detected in file: ' + currentFilename);
}

var includeContent = fs.readFileSync(includePath, 'utf-8');

if (opts.indent) {
includeContent = setIndent(inst.before, inst.before.length, includeContent);
}

// need to double each `$` to escape it in the `replace` function
// includeContent = includeContent.replace(/\$/gi, '$$$$');

// apply filters on include content
if (typeof opts.filters === 'object') {
includeContent = applyFilters(includeContent, args.input, opts);
}

var recFile = new gutil.File({
cwd: process.cwd(),
base: file.base,
path: includePath,
contents: new Buffer(includeContent)
});

recFile = handleFn(recFile, includeContent, args[3] ? JSON.parse(args[3]) : {});

return String(recFile.contents);
}
}

function applyFilters(includeContent, match, opts) {
if (!match.match(/\)+$/)) {
// nothing to filter return unchanged
return includeContent;
}

// now get the ordered list of filters
var filterlist = match.split('(').slice(0, -1);
filterlist = filterlist.map(function(str) {
return opts.filters[str.trim()];
});

// compose them together into one function
var filter = filterlist.reduce(compose);

// and apply the composed function to the stringified content
return filter(String(includeContent));
}

function compose(f, g) {
return function(x) {
return f(g(x));
};
}
61 changes: 61 additions & 0 deletions lib/plugins/replace-operator-for.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
var balanced = require('balanced-match');

module.exports = function(file, text, data, opts, handleFn) {

opts.name = 'for';

return parse(text, data, opts);

};

function parse(content, data, opts) {
var regexpStart = new RegExp(opts.prefix + '[ ]*' + opts.name + '([^{}]*)\\{');
var regexpEnd = opts.suffix ? new RegExp('^\\s*' + opts.suffix) : false;
var replacement;
var result = '';
var matchStart;
var matchBody;
var matchEnd;
var startEnd;
var before;

while (matchStart = regexpStart.exec(content)) {
startEnd = matchStart.index + matchStart[0].length;
matchBody = balanced('{', '}', content.slice(startEnd - 1));

if (matchBody && matchBody.start === 0) {
matchEnd = regexpEnd ? regexpEnd.exec(matchBody.post) : true;

if (matchEnd) {
before = content.slice(0, matchStart.index);
matchEnd = regexpEnd ? matchEnd[0].length : 0;
replacement = handler({
before: before,
args: matchStart[1],
body: matchBody.body
}, data);

if (replacement !== undefined) {
result += before + parse(replacement.toString(), data, opts);
content = content.slice(startEnd + matchBody.end + matchEnd);
continue;
}
}
}

result += content.slice(0, startEnd);
content = content.slice(startEnd);
}

result += content;

return result;
}

function handler(inst, data) {
var condition = 'var context = this; with (context) { var result=""; for' + inst.args + ' { result+=`' + inst.body + '`; } return result; }';
// jshint ignore: start
var result = new Function(condition).call(data);
// jshint ignore: end
return result;
}
Loading