initial commit
This commit is contained in:
421
node_modules/preprocess/lib/preprocess.js
generated
vendored
Normal file
421
node_modules/preprocess/lib/preprocess.js
generated
vendored
Normal file
@@ -0,0 +1,421 @@
|
||||
/*
|
||||
* preprocess
|
||||
* https://github.com/onehealth/preprocess
|
||||
*
|
||||
* Copyright (c) 2012 OneHealth Solutions, Inc.
|
||||
* Written by Jarrod Overson - http://jarrodoverson.com/
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
exports.preprocess = preprocess;
|
||||
exports.preprocessFile = preprocessFile;
|
||||
exports.preprocessFileSync = preprocessFileSync;
|
||||
|
||||
var path = require('path'),
|
||||
fs = require('fs'),
|
||||
os = require('os'),
|
||||
delim = require('./regexrules'),
|
||||
XRegExp = require('xregexp');
|
||||
|
||||
function preprocessFile(srcFile, destFile, context, callback, options) {
|
||||
options = getOptionsForFile(srcFile, options);
|
||||
context.src = srcFile;
|
||||
|
||||
fs.readFile(srcFile, function (err, data) {
|
||||
if (err) return callback(err, data);
|
||||
var parsed = preprocess(data, context, options);
|
||||
fs.writeFile(destFile, parsed, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function preprocessFileSync(srcFile, destFile, context, options) {
|
||||
options = getOptionsForFile(srcFile, options);
|
||||
context.src = srcFile;
|
||||
|
||||
var data = fs.readFileSync(srcFile);
|
||||
var parsed = preprocess(data, context, options);
|
||||
return fs.writeFileSync(destFile, parsed);
|
||||
}
|
||||
|
||||
function getOptionsForFile(srcFile, options) {
|
||||
options = options || {};
|
||||
options.srcDir = options.srcDir || path.dirname(srcFile);
|
||||
options.type = options.type || getExtension(srcFile);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function getExtension(filename) {
|
||||
var ext = path.extname(filename||'').split('.');
|
||||
return ext[ext.length - 1];
|
||||
}
|
||||
|
||||
function preprocess(src, context, typeOrOptions) {
|
||||
src = src.toString();
|
||||
context = context || process.env;
|
||||
|
||||
// default values
|
||||
var options = {
|
||||
fileNotFoundSilentFail: false,
|
||||
srcDir: process.cwd(),
|
||||
srcEol: getEolType(src),
|
||||
type: delim['html']
|
||||
};
|
||||
|
||||
// needed for backward compatibility with 2.x.x series
|
||||
if (typeof typeOrOptions === 'string') {
|
||||
typeOrOptions = {
|
||||
type: typeOrOptions
|
||||
};
|
||||
}
|
||||
|
||||
// needed for backward compatibility with 2.x.x series
|
||||
if (typeof context.srcDir === "string") {
|
||||
typeOrOptions = typeOrOptions || {};
|
||||
typeOrOptions.srcDir = context.srcDir;
|
||||
}
|
||||
|
||||
if (typeOrOptions && typeof typeOrOptions === 'object') {
|
||||
options.srcDir = typeOrOptions.srcDir || options.srcDir;
|
||||
options.fileNotFoundSilentFail = typeOrOptions.fileNotFoundSilentFail || options.fileNotFoundSilentFail;
|
||||
options.srcEol = typeOrOptions.srcEol || options.srcEol;
|
||||
options.type = delim[typeOrOptions.type] || options.type;
|
||||
}
|
||||
|
||||
context = copy(context);
|
||||
|
||||
return preprocessor(src, context, options);
|
||||
}
|
||||
|
||||
function preprocessor(src, context, opts, noRestoreEol) {
|
||||
src = normalizeEol(src);
|
||||
|
||||
var rv = src;
|
||||
|
||||
rv = replace(rv, opts.type.include, processIncludeDirective.bind(null, false, context, opts));
|
||||
|
||||
if (opts.type.extend) {
|
||||
rv = replaceRecursive(rv, opts.type.extend, function(startMatches, endMatches, include, recurse) {
|
||||
var file = (startMatches[1] || '').trim();
|
||||
var extendedContext = copy(context);
|
||||
var extendedOpts = copy(opts);
|
||||
extendedContext.src = path.join(opts.srcDir, file);
|
||||
extendedOpts.srcDir = path.dirname(extendedContext.src);
|
||||
|
||||
var fileContents = getFileContents(extendedContext.src, opts.fileNotFoundSilentFail, context.src);
|
||||
if (fileContents.error) {
|
||||
return fileContents.contents;
|
||||
}
|
||||
|
||||
var extendedSource = preprocessor(fileContents.contents, extendedContext, extendedOpts, true).trim();
|
||||
|
||||
if (extendedSource) {
|
||||
include = include.replace(/^\n?|\n?$/g, '');
|
||||
return replace(extendedSource, opts.type.extendable, recurse(include));
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.type.foreach) {
|
||||
rv = replaceRecursive(rv, opts.type.foreach, function(startMatches, endMatches, include, recurse) {
|
||||
var variable = (startMatches[1] || '').trim();
|
||||
var forParams = variable.split(' ');
|
||||
if (forParams.length === 3) {
|
||||
var contextVar = forParams[2];
|
||||
var arrString = getDeepPropFromObj(context, contextVar);
|
||||
var eachArr;
|
||||
if (arrString.match(/\{(.*)\}/)) {
|
||||
eachArr = JSON.parse(arrString);
|
||||
} else if (arrString.match(/\[(.*)\]/)) {
|
||||
eachArr = arrString.slice(1, -1);
|
||||
eachArr = eachArr.split(',');
|
||||
eachArr = eachArr.map(function(arrEntry){
|
||||
return arrEntry.replace(/\s*(['"])(.*)\1\s*/, '$2');
|
||||
});
|
||||
} else {
|
||||
eachArr = arrString.split(',');
|
||||
}
|
||||
|
||||
var replaceToken = new RegExp(XRegExp.escape(forParams[0]), 'g');
|
||||
var recursedInclude = recurse(include);
|
||||
|
||||
return Object.keys(eachArr).reduce(function(stringBuilder, arrKey){
|
||||
var arrEntry = eachArr[arrKey];
|
||||
return stringBuilder + recursedInclude.replace(replaceToken, arrEntry);
|
||||
}, '');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.type.exclude) {
|
||||
rv = replaceRecursive(rv, opts.type.exclude, function(startMatches, endMatches, include, recurse){
|
||||
var test = (startMatches[1] || '').trim();
|
||||
return testPasses(test,context) ? '' : recurse(include);
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.type.if) {
|
||||
rv = replaceRecursive(rv, opts.type.if, function (startMatches, endMatches, include, recurse) {
|
||||
// I need to recurse first, so I don't catch "inner" else-directives
|
||||
var recursed = recurse(include);
|
||||
|
||||
// look for the first else-directive
|
||||
var matches = opts.type.else && recursed.match(new RegExp(opts.type.else));
|
||||
var match = (matches || [""])[0];
|
||||
var index = match ? recursed.indexOf(match) : recursed.length;
|
||||
|
||||
var ifBlock = recursed.substring(0, index);
|
||||
var elseBlock = recursed.substring(index + match.length); // empty string if no else-directive
|
||||
|
||||
var variant = startMatches[1];
|
||||
var test = (startMatches[2] || '').trim();
|
||||
|
||||
switch(variant) {
|
||||
case 'if':
|
||||
return testPasses(test,context) ? ifBlock : elseBlock;
|
||||
case 'ifdef':
|
||||
return typeof getDeepPropFromObj(context, test) !== 'undefined' ? ifBlock : elseBlock;
|
||||
case 'ifndef':
|
||||
return typeof getDeepPropFromObj(context, test) === 'undefined' ? ifBlock : elseBlock;
|
||||
default:
|
||||
throw new Error('Unknown if variant ' + variant + '.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
rv = replace(rv, opts.type.echo, function (match, variable) {
|
||||
variable = (variable || '').trim();
|
||||
// if we are surrounded by quotes, echo as a string
|
||||
var stringMatch = variable.match(/^(['"])(.*)\1$/);
|
||||
if (stringMatch) return stringMatch[2];
|
||||
|
||||
return getDeepPropFromObj(context, (variable || '').trim());
|
||||
});
|
||||
|
||||
rv = replace(rv, opts.type.exec, function (match, name, value) {
|
||||
name = (name || '').trim();
|
||||
value = value || '';
|
||||
|
||||
var params = value.split(',');
|
||||
var stringRegex = /^['"](.*)['"]$/;
|
||||
|
||||
params = params.map(function(param){
|
||||
param = param.trim();
|
||||
if (stringRegex.test(param)) { // handle string parameter
|
||||
return param.replace(stringRegex, '$1');
|
||||
} else { // handle variable parameter
|
||||
return getDeepPropFromObj(context, param);
|
||||
}
|
||||
});
|
||||
|
||||
var fn = getDeepPropFromObj(context, name);
|
||||
if (!fn || typeof fn !== 'function') return '';
|
||||
|
||||
return fn.apply(context, params);
|
||||
});
|
||||
|
||||
rv = replace(rv, opts.type['include-static'], processIncludeDirective.bind(null, true, context, opts));
|
||||
|
||||
if (!noRestoreEol) {
|
||||
rv = restoreEol(rv, opts.srcEol);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
function getEolType(source) {
|
||||
var eol;
|
||||
var foundEolTypeCnt = 0;
|
||||
|
||||
if (source.indexOf('\r\n') >= 0) {
|
||||
eol = '\r\n';
|
||||
foundEolTypeCnt++;
|
||||
}
|
||||
if (/\r[^\n]/.test(source)) {
|
||||
eol = '\r';
|
||||
foundEolTypeCnt++;
|
||||
}
|
||||
if (/[^\r]\n/.test(source)) {
|
||||
eol = '\n';
|
||||
foundEolTypeCnt++;
|
||||
}
|
||||
|
||||
if (eol == null || foundEolTypeCnt > 1) {
|
||||
eol = os.EOL;
|
||||
}
|
||||
|
||||
return eol;
|
||||
}
|
||||
|
||||
function normalizeEol(source, indent) {
|
||||
// only process any kind of EOL if indentation has to be added, otherwise replace only non \n EOLs
|
||||
if (indent) {
|
||||
source = source.replace(/(?:\r?\n)|\r/g, '\n' + indent);
|
||||
} else {
|
||||
source = source.replace(/(?:\r\n)|\r/g, '\n');
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
function restoreEol(normalizedSource, originalEol) {
|
||||
if (originalEol !== '\n') {
|
||||
normalizedSource = normalizedSource.replace(/\n/g, originalEol);
|
||||
}
|
||||
|
||||
return normalizedSource;
|
||||
}
|
||||
|
||||
function replace(rv, rule, processor) {
|
||||
var isRegex = typeof rule === 'string' || rule instanceof RegExp;
|
||||
var isArray = Array.isArray(rule);
|
||||
|
||||
if (isRegex) {
|
||||
rule = [new RegExp(rule,'gmi')];
|
||||
} else if (isArray) {
|
||||
rule = rule.map(function(subRule){
|
||||
return new RegExp(subRule,'gmi');
|
||||
});
|
||||
} else {
|
||||
throw new Error('Rule must be a String, a RegExp, or an Array.');
|
||||
}
|
||||
|
||||
return rule.reduce(function(rv, rule){
|
||||
return rv.replace(rule, processor);
|
||||
}, rv);
|
||||
}
|
||||
|
||||
function replaceRecursive(rv, rule, processor) {
|
||||
if(!rule.start || !rule.end) {
|
||||
throw new Error('Recursive rule must have start and end.');
|
||||
}
|
||||
|
||||
var startRegex = new RegExp(rule.start, 'mi');
|
||||
var endRegex = new RegExp(rule.end, 'mi');
|
||||
|
||||
function matchReplacePass(content) {
|
||||
var matches = XRegExp.matchRecursive(content, rule.start, rule.end, 'gmi', {
|
||||
valueNames: ['between', 'left', 'match', 'right']
|
||||
});
|
||||
|
||||
var matchGroup = {
|
||||
left: null,
|
||||
match: null,
|
||||
right: null
|
||||
};
|
||||
|
||||
return matches.reduce(function (builder, match) {
|
||||
switch(match.name) {
|
||||
case 'between':
|
||||
builder += match.value;
|
||||
break;
|
||||
case 'left':
|
||||
matchGroup.left = startRegex.exec(match.value);
|
||||
break;
|
||||
case 'match':
|
||||
matchGroup.match = match.value;
|
||||
break;
|
||||
case 'right':
|
||||
matchGroup.right = endRegex.exec(match.value);
|
||||
builder += processor(matchGroup.left, matchGroup.right, matchGroup.match, matchReplacePass);
|
||||
break;
|
||||
}
|
||||
return builder;
|
||||
}, '');
|
||||
}
|
||||
|
||||
return matchReplacePass(rv);
|
||||
}
|
||||
|
||||
|
||||
function processIncludeDirective(isStatic, context, opts, match, linePrefix, file) {
|
||||
file = (file || '').trim();
|
||||
var indent = linePrefix.replace(/\S/g, ' ');
|
||||
var includedContext = copy(context);
|
||||
var includedOpts = copy(opts);
|
||||
includedContext.src = path.join(opts.srcDir,file);
|
||||
includedOpts.srcDir = path.dirname(includedContext.src);
|
||||
|
||||
var fileContents = getFileContents(includedContext.src, opts.fileNotFoundSilentFail, context.src);
|
||||
if (fileContents.error) {
|
||||
return linePrefix + fileContents.contents;
|
||||
}
|
||||
|
||||
var includedSource = fileContents.contents;
|
||||
if (isStatic) {
|
||||
includedSource = fileContents.contents;
|
||||
} else {
|
||||
includedSource = preprocessor(fileContents.contents, includedContext, includedOpts, true);
|
||||
}
|
||||
|
||||
includedSource = normalizeEol(includedSource, indent);
|
||||
|
||||
if (includedSource) {
|
||||
return linePrefix + includedSource;
|
||||
} else {
|
||||
return linePrefix;
|
||||
}
|
||||
}
|
||||
|
||||
function getTestTemplate(test) {
|
||||
/*jshint evil:true*/
|
||||
test = test || 'true';
|
||||
test = test.trim();
|
||||
|
||||
// force single equals replacement
|
||||
test = test.replace(/([^=!])=([^=])/g, '$1==$2');
|
||||
|
||||
return new Function("context", "with (context||{}){ return ( " + test + " ); }");
|
||||
}
|
||||
|
||||
function testPasses(test,context) {
|
||||
var testFn = getTestTemplate(test);
|
||||
return testFn(context, getDeepPropFromObj);
|
||||
}
|
||||
|
||||
function getFileContents(path, failSilent, requesterPath) {
|
||||
try {
|
||||
fs.statSync(path);
|
||||
} catch (e) {
|
||||
if (failSilent) {
|
||||
return {error: true, contents: path + ' not found!'};
|
||||
} else {
|
||||
var errMsg = path;
|
||||
errMsg = requesterPath ? errMsg + ' requested from ' + requesterPath : errMsg;
|
||||
errMsg += ' not found!';
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
}
|
||||
return {error: false, contents: fs.readFileSync(path).toString()};
|
||||
}
|
||||
|
||||
function copy(obj) {
|
||||
return Object.keys(obj).reduce(function (copyObj, objKey) {
|
||||
copyObj[objKey] = obj[objKey];
|
||||
return copyObj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function getDeepPropFromObj(obj, propPath) {
|
||||
propPath.replace(/\[([^\]+?])\]/g, '.$1');
|
||||
propPath = propPath.split('.');
|
||||
|
||||
// fast path, no need to loop if structurePath contains only a single segment
|
||||
if (propPath.length === 1) {
|
||||
return obj[propPath[0]];
|
||||
}
|
||||
|
||||
// loop only as long as possible (no exceptions for null/undefined property access)
|
||||
propPath.some(function (pathSegment) {
|
||||
obj = obj[pathSegment];
|
||||
return (obj == null);
|
||||
});
|
||||
|
||||
return obj;
|
||||
}
|
||||
Reference in New Issue
Block a user