Comparing version 0.1.0 to 0.1.1
325
index.js
@@ -7,4 +7,5 @@ | ||
var INCLUDE_VIRTUAL = new RegExp(/<!--#include virtual="(.+?)" -->/g); | ||
var INCLUDE_FILE = new RegExp(/<!--#include file="(.+?)" -->/g); | ||
var DIRECTIVE_MATCHER = /<!--#([a-z]+)([ ]+([a-z]+)="(.+?)")* -->/g; | ||
var ATTRIBUTE_MATCHER = /([a-z]+)="(.+?)"/g; | ||
var EXPRESSION_MATCHER = /\$\{(.+?)\}/g; | ||
@@ -14,2 +15,272 @@ (function() { | ||
var mergeSimpleObject = function() { | ||
var output = {}; | ||
for (var i = 0; i < arguments.length; i++) { | ||
var argument = arguments[i]; | ||
for (var key in argument) { | ||
if (argument.hasOwnProperty(key)) { | ||
output[key] = argument[key]; | ||
} | ||
} | ||
} | ||
return output; | ||
}; | ||
var IOUtils = function(documentRoot) { | ||
this.documentRoot = documentRoot; | ||
}; | ||
IOUtils.prototype = { | ||
/* Public Methods */ | ||
readFileSync: function(currentFile, includeFile) { | ||
var filename = path.resolve(path.dirname(currentFile), includeFile); | ||
return fs.readFileSync(filename, {encoding: "utf8"}); | ||
}, | ||
readVirtualSync: function(includeFile) { | ||
var filename = path.resolve(this.documentRoot, includeFile); | ||
return fs.readFileSync(filename, {encoding: "utf8"}); | ||
}, | ||
writeFileSync: function(filename, contents) { | ||
var directory = path.dirname(filename); | ||
if (!fs.existsSync(directory)) { | ||
// If the file's directory doesn't exists, recursively create it | ||
mkdirp.sync(directory); | ||
} | ||
fs.writeFileSync(filename, contents, {encoding: "utf8"}); | ||
} | ||
/* Private Methods */ | ||
}; | ||
var Conditional = function(expression) { | ||
this.expression = expression; | ||
this.directives = []; | ||
}; | ||
Conditional.prototype = { | ||
getExpression: function() { | ||
return this.expression; | ||
}, | ||
getDirectives: function() { | ||
return this.directives; | ||
}, | ||
addDirective: function(directive) { | ||
this.directives.push(directive); | ||
} | ||
}; | ||
var DirectiveHandler = function(ioUtils) { | ||
this.ioUtils = ioUtils; | ||
this.conditionals = []; | ||
this.currentConditional = undefined; | ||
}; | ||
DirectiveHandler.prototype = { | ||
/* Public Methods */ | ||
handleDirective: function(directive, directiveName, currentFile, variables) { | ||
if (this._inConditional()) { | ||
if (!this._isConditional(directiveName)) { | ||
this.currentConditional.addDirective(directive); | ||
return {output: ""}; | ||
} | ||
} | ||
var attributes = this._parseAttributes(directive); | ||
switch (directiveName) { | ||
case "if": | ||
return this._handleIf(attributes); | ||
case "elif": | ||
return this._handleElseIf(attributes); | ||
case "else": | ||
return this._handleElse(); | ||
case "endif": | ||
return this._handleEndIf(currentFile, variables); | ||
case "set": | ||
return this._handleSet(attributes); | ||
case "include": | ||
return this._handleInclude(attributes, currentFile); | ||
} | ||
return {error: "Could not find parse directive #" + directiveName}; | ||
}, | ||
/* Private Methods */ | ||
_parseAttributes: function(directive) { | ||
var attributes = []; | ||
directive.replace(ATTRIBUTE_MATCHER, function(attribute, name, value) { | ||
attributes.push({name: name, value: value}); | ||
}); | ||
return attributes; | ||
}, | ||
_parseExpression: function(expression, variables) { | ||
var instance = this; | ||
expression = expression.replace(EXPRESSION_MATCHER, function(variable, variableName) { | ||
// Either return the variable value or the original expression if it doesn't exist | ||
if (variables[variableName] !== undefined) { | ||
// Escape all double quotes and wrap the value in double quotes | ||
return instance._wrap(variables[variableName]); | ||
} | ||
return variable; | ||
}); | ||
if (expression.match(EXPRESSION_MATCHER)) { | ||
return {error: "Could not resolve all variables"} | ||
} | ||
// Return a boolean for the truthiness of the expression | ||
return {truthy: !!eval(expression)}; | ||
}, | ||
_wrap: function(value) { | ||
if (this._shouldWrap(value)) { | ||
return "\"" + value.toString().replace(/"/g, "\\\"") + "\""; | ||
} | ||
return value; | ||
}, | ||
_shouldWrap: function(value) { | ||
var type = typeof value; | ||
return (type !== "boolean" && type !== "number"); | ||
}, | ||
_handleSet: function(attributes) { | ||
if (attributes.length === 2 && attributes[0].name === "var" && | ||
attributes[1].name === "value") { | ||
return {variables: [{ | ||
name: attributes[0].value, | ||
value: attributes[1].value | ||
}]}; | ||
} | ||
return {error: "Directive #set did not contain a 'var' and 'value' attribute"}; | ||
}, | ||
_handleInclude: function(attributes, currentFile) { | ||
if (attributes.length === 1) { | ||
var attribute = attributes[0]; | ||
if (attribute.name === "file") { | ||
return {output: this.ioUtils.readFileSync(currentFile, attribute.value)}; | ||
} else if (attribute.name === "virtual") { | ||
return {output: this.ioUtils.readVirtualSync(attribute.value)}; | ||
} | ||
} | ||
return {error: "Directive #include did not contain a 'file' or 'virtual' attribute"}; | ||
}, | ||
_handleIf: function(attributes) { | ||
if (attributes.length === 1 && attributes[0].name === "expr") { | ||
// Create a new conditional, put it on the stack and assign as current conditional | ||
var conditional = new Conditional(attributes[0].value); | ||
this.conditionals.push(conditional); | ||
this.currentConditional = conditional; | ||
return {output: ""}; | ||
} | ||
return {error: "If does not have a single 'expr' attribute"}; | ||
}, | ||
_handleElseIf: function(attributes) { | ||
if (attributes.length === 1 && attributes[0].name === "expr") { | ||
if (!this._inConditional()) { | ||
return {error: "Elif while not inside of If block"}; | ||
} | ||
var conditional = new Conditional(attributes[0].value); | ||
this.conditionals.push(conditional); | ||
this.currentConditional = conditional; | ||
return {output: ""}; | ||
} | ||
return {error: "Elif does not have a single 'expr' attribute"}; | ||
}, | ||
_handleElse: function() { | ||
if (!this._inConditional()) { | ||
return {error: "Else while not inside of If block"}; | ||
} | ||
// As a hack, just provide an always true expression | ||
var conditional = new Conditional("true"); | ||
this.conditionals.push(conditional); | ||
this.currentConditional = conditional; | ||
return {output: ""}; | ||
}, | ||
_handleEndIf: function(currentFile, pageVariables) { | ||
if (!this._inConditional()) { | ||
return {error: "Endif while not inside of If block"}; | ||
} | ||
for (var i = 0; i < this.conditionals.length; i++) { | ||
var conditional = this.conditionals[i]; | ||
var variables = {}; | ||
// Find the first conditional that is true | ||
if (this._parseExpression(conditional.getExpression(), variables).truthy) { | ||
var directiveHandler = new DirectiveHandler(this.ioUtils); | ||
var output = {output: "", variables: {}}; | ||
// Iterate over the directives contained by the conditional, and parse them | ||
for (var j = 0; j < conditional.getDirectives().length; j++) { | ||
var directive = conditional.getDirectives()[j]; | ||
// We can assume this matches the directive format | ||
var directiveName = DIRECTIVE_MATCHER.exec(directive)[1]; | ||
var results = directiveHandler.handleDirective(directive, directiveName, currentFile, | ||
mergeSimpleObject(variables, pageVariables)); | ||
output.output += results.output || ""; | ||
output.variables = mergeSimpleObject(output.variables, results.variables || {}); | ||
} | ||
this.conditionals = []; | ||
this.currentConditional = undefined; | ||
return output; | ||
} | ||
} | ||
this.conditionals = []; | ||
this.currentConditional = undefined; | ||
return {output: ""}; | ||
}, | ||
_isConditional: function(directive) { | ||
return (directive === "if" || directive === "elif" || directive === "else" || directive === "endif"); | ||
}, | ||
_inConditional: function() { | ||
return this.conditionals.length > 0; | ||
} | ||
}; | ||
var ssi = function(inputDirectory, outputDirectory, matcher) { | ||
@@ -20,2 +291,5 @@ this.inputDirectory = inputDirectory; | ||
this.matcher = matcher; | ||
this.ioUtils = new IOUtils(this.documentRoot); | ||
this.directiveHandler = new DirectiveHandler(this.ioUtils); | ||
}; | ||
@@ -33,6 +307,6 @@ | ||
var contents = fs.readFileSync(input, {encoding: "utf8"}); | ||
contents = this.parse(input, contents); | ||
var data = this.parse(input, contents); | ||
var output = input.replace(this.inputDirectory, this.outputDirectory); | ||
this._writeFile(output, contents); | ||
this.ioUtils.writeFileSync(output, data.contents); | ||
} | ||
@@ -43,38 +317,23 @@ }, | ||
var instance = this; | ||
var variables = {}; | ||
contents = contents.replace(INCLUDE_VIRTUAL, function(match, virtual) { | ||
return instance._readVirtual(virtual); | ||
}); | ||
contents = contents.replace(DIRECTIVE_MATCHER, function(directive, directiveName) { | ||
var data = instance.directiveHandler.handleDirective(directive, directiveName, filename, variables); | ||
contents = contents.replace(INCLUDE_FILE, function(match, file) { | ||
return instance._readFile(filename, file); | ||
if (data.error) throw data.error; | ||
for (var key in data.variables) { | ||
if (data.variables.hasOwnProperty(key)) { | ||
variables[data.variables[key].name] = data.variables[key].value; | ||
} | ||
} | ||
return (data && data.output) || ""; | ||
}); | ||
return contents; | ||
}, | ||
return {contents: contents, variables: variables}; | ||
} | ||
/* Private Methods */ | ||
_readVirtual: function(virtual) { | ||
var filename = path.resolve(this.documentRoot, virtual); | ||
return fs.readFileSync(filename, {encoding: "utf8"}); | ||
}, | ||
_readFile: function(currentFile, file) { | ||
var filename = path.resolve(path.dirname(currentFile), file); | ||
return fs.readFileSync(filename, {encoding: "utf8"}); | ||
}, | ||
_writeFile: function(filename, contents) { | ||
var directory = path.dirname(filename); | ||
if (!fs.existsSync(directory)) { | ||
// If the file's directory doesn't exists, recusively create it | ||
mkdirp.sync(directory); | ||
} | ||
fs.writeFileSync(filename, contents, {encoding: "utf8"}); | ||
} | ||
}; | ||
@@ -81,0 +340,0 @@ |
{ | ||
"name": "ssi", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"description": "Server Side Includes for NodeJS", | ||
@@ -22,6 +22,8 @@ "author": "Glenn Nelson <glenn@hexcoder.us> (glenn@hexcoder.us)", | ||
}, | ||
"devDependencies": {}, | ||
"devDependencies": { | ||
"mocha": "~1.10.0" | ||
}, | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
"test": "mocha" | ||
} | ||
} |
node-ssi | ||
======== | ||
[![Build Status](https://travis-ci.org/67726e/node-ssi.png)](https://travis-ci.org/67726e/node-ssi) | ||
Server Side Includes for NodeJS | ||
__Note:__ The current version of ssi does all IO synchronously. Further development plans include writing methods asynchronously and migrating current methods to conform to Node conventions for synchronous methods. | ||
### Supported Instructions | ||
```html | ||
<!--#include virtual="" --> | ||
<!--#include file="" --> | ||
<!--#set var="" value="" --> | ||
<!--#if expr="" --> | ||
<!--#elif expr="" --> | ||
<!--#else --> | ||
<!--#endif --> | ||
``` | ||
### Installation | ||
```bash | ||
npm install ssi | ||
``` | ||
### Usage | ||
```javascript | ||
var ssi = require("ssi"); | ||
var inputDirectory = "/tmp/test"; | ||
var outputDirectory = "/tmp/output"; | ||
var matcher = "/**/*.shtml"; | ||
var includes = new ssi(inputDirectory, outputDirectory, matcher); | ||
includes.compile(); | ||
``` | ||
### Methods | ||
#### parse(filename, contents) | ||
_filename_ `String` path to the file | ||
_contents_ `String` Contents of the file to be parsed | ||
Method returns the parsed contents | ||
#### compile() | ||
Method parses all of the files found by the matcher in the input directory, and writes the files to the output directory with identical names and directory structure. | ||
### License | ||
MIT | ||
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
16828
17
389
1
59
1
2