Socket
Socket
Sign inDemoInstall

manila

Package Overview
Dependencies
0
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.2.2 to 2.0.0

parse.js

332

manila.es6.js
'use strict';
const read = require('fs').readFile,
vm = require('vm'),
path = require('path'),
rx = {
include: /{{\s*?include\s(\S*?)\s*?}}/i,
variable: /{{([\s\S]*?)}}/,
forIn: /{{\s*?for\s*?\S*?\s*?in\s*?\S*?\s*?}}/i,
endFor: /{{\s*?endfor\s*?}}/i,
ifBlock: /{{\s*?if\s*?[\s\S]*?\s*?}}/i,
endIf: /{{\s*?endif\s*?}}/i,
elseBlock: /{{\s*?else\s*?}}/i
},
ifrx = {
start: '{{\\s*if\\s*([^}]*)\\s*}}([\\s\\S]*?',
open: rx.ifBlock,
close: rx.endIf,
childOpen: '{{\\s*if\\s*[^}]*?\\s*}}[\\s\\S]*?',
childClose: '{{\\s*endif\\s*}}[\\s\\S]*?',
end: '){{\\s*endif\\s*}}',
endLength: 23
},
looprx = {
start: '{{\\s*for\\s*([^}]*)\\s*in\\s*([^}]*)\\s*}}([\\s\\S]*?',
open: rx.forIn,
close: rx.endFor,
childOpen: '{{\\s*for\\s*[^}]*\\s*}}[\\s\\S]*?',
childClose: '{{\\s*endfor\\s*}}[\\s\\S]*?',
end: '){{\\s*endfor\\s*}}',
endLength: 24
},
parse = require('./parse'),
escapeMap = {

@@ -38,18 +11,6 @@ '<': '&lt;',

'\'': '&apos;'
},
unescapeMap = {
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&apos;': '\''
};
/*---------------- Utility functions ----------------*/
let compiled = {};
function run(expression, context) {
return vm.runInNewContext(expression, context, {
timeout: 1000
});
}
function htmlEscape(str) {

@@ -61,125 +22,26 @@ return str.replace(/[&<>'"]/g, c => {

function htmlUnescape(str) {
return str.replace(/(&lt;)(&gt;)(&quot;)(&apos;)/g, c => {
return unescapeMap[c];
});
}
global._e_ = function(val) {
return typeof val === 'string' ? htmlEscape(val) : val;
};
/*---------------- Parsing functions ----------------*/
function parseVars(template, context, match) {
let value,
raw = match[0],
filters = match[1].split('|'),
ref = filters.shift(0);
filters.forEach(function(filter, i) {
filters[i] = filter.replace(/\s/g, '');
});
if (filters.indexOf('skip') !== -1) {
value = '&lcub;&lcub;' + ref + '&rcub;&rcub;';
} else {
try {
value = run(ref, context);
if (typeof value === 'string') {
if (filters.indexOf('safe') !== -1) {
//value = htmlUnescape(value);
} else {
value = htmlEscape(value);
}
} else if (value === undefined) {
value = '';
}
} catch(err) {
value = '';
}
}
return render(template.replace(raw, value), context);
}
function parseLoops(template, context, match) {
let raw = match[0],
index = match[1].trim(),
arrName = match[2],
html = match[3],
list = [],
output = '',
key,
subContext = Object.create(context);
try {
list = run(arrName, context);
}
catch(err) {}
if (index.indexOf('.') !== -1) {
let keys = index.split('.');
index = keys[0];
key = keys[1];
}
function promisify(callback, resolve, reject) {
if (list) {
Object.keys(list).forEach(function(value) {
if (key) {
subContext[index] = value;
subContext[key] = list[value];
} else {
subContext[index] = list[value];
}
resolve = callback ? data => {
callback(undefined, data);
} : resolve;
output += render(html, subContext);
});
}
reject = callback ? error => {
callback(error);
} : reject;
return render(template.replace(raw, output), context);
return {
resolve: resolve,
reject: reject
};
}
function parseIfs(template, context, match) {
let raw = match[0],
expression = match[1].trim(),
html = match[2],
doShow,
negate;
if (expression.indexOf('not ') === 0) {
negate = true;
expression = expression.substring(3).trim();
}
html = html.split(rx.elseBlock);
try {
doShow = run(expression, context);
}
catch(err) { }
if (negate) {
doShow = !doShow;
}
if (doShow) {
html = html[0];
} else if (html[1]) {
html = html[1];
} else {
html = '';
}
return render(template.replace(raw, html), context);
}
// Recursively asyncronously parses partial includes
// then calls the callback with the result
function parseIncludes(template, callback, opts) {
let match = rx.include.exec(template),
let match = /<<\s*?include\s(\S*?)\s*?>>/i.exec(template),
ext = new RegExp(opts.extension + '$'),
raw, include;

@@ -192,3 +54,3 @@

if (!include.match(new RegExp(opts.extension + '$'))) {
if (!include.match(ext)) {
include += opts.extension;

@@ -205,100 +67,2 @@ }

/*---------------- Regex generating functions ----------------*/
function splitTemplate(template, regx) {
let openIndex = template.search(regx),
openMatch = regx.exec(template);
return openMatch ? template.substring(openIndex + openMatch[0].length) : null;
}
function getRegx(template, config) {
let counts = {
open: 1,
close: 0
},
tmpl = splitTemplate(template, config.open),
result = config.start;
while (tmpl && counts.open !== counts.close) {
let open = tmpl.search(config.open),
close = tmpl.search(config.close);
if (open !== -1 && open < close) {
result += config.childOpen;
counts.open++;
tmpl = splitTemplate(tmpl, config.open);
} else if (close !== -1) {
result += config.childClose;
counts.close++;
tmpl = splitTemplate(tmpl, config.close);
} else {
break;
}
}
if (counts.close !== 0) {
result = result.substring(0, result.length - config.endLength) + config.end;
}
return new RegExp(result, 'i');
}
function process(loop, template, context, index) {
let regx;
if (index === undefined) {
index = template.search(loop ? rx.forIn : rx.ifBlock);
}
if (index !== -1) {
regx = getRegx(template, loop ? looprx : ifrx);
}
if (regx) {
template = loop ? parseLoops(template, context, regx.exec(template))
: parseIfs(template, context, regx.exec(template));
}
return template;
}
/*---------------- Main rendering function (syncronous) ----------------*/
function render(template, context) {
let match, regx,
indexOfLoop = template.search(rx.forIn),
indexOfIf = template.search(rx.ifBlock),
loopFirst = indexOfLoop < indexOfIf;
if (loopFirst) {
template = process(true, template, context, indexOfLoop);
template = process(false, template, context);
} else {
template = process(false, template, context, indexOfIf);
template = process(true, template, context);
}
match = rx.variable.exec(template);
if (match) {
template = parseVars(template, context, match);
}
return template;
}
/*---------------- Manila ----------------*/
function manila(opts) {

@@ -318,6 +82,3 @@

// By creating and returning a closure we can support multiple
// "instances" of manila with different configurations running
// in one app. All configurable variable references go here:
function compile(filepath) {
function getFile(filepath) {

@@ -350,15 +111,48 @@ return new Promise((resolve, reject) => {

}
function parse(filepath, context, callback) {
compile(filepath)
.then(template => {
callback(undefined, render(template, context));
})
.catch(error => {
callback(error);
});
function compile(filepath, callback) {
return new Promise((resolve, reject) => {
let p = promisify(callback, resolve, reject);
getFile(filepath)
.then(template => {
compiled[filepath] = parse(template);
p.resolve(compiled[filepath]);
})
.catch(error => {
p.reject(error);
});
});
}
return parse;
function init(filepath, context, callback) {
return new Promise((resolve, reject) => {
let p = promisify(callback, resolve, reject);
if (compiled[filepath]) {
let result = compiled[filepath](context);
p.resolve(result);
} else {
compile(filepath, (error, render) => {
if (error) {
p.reject(error);
} else {
p.resolve(render(context));
}
});
}
});
};
init.compile = compile;
return init;
}

@@ -365,0 +159,0 @@

'use strict';
const read = require('fs').readFile,
vm = require('vm'),
path = require('path'),
rx = {
include: /{{\s*?include\s(\S*?)\s*?}}/i,
variable: /{{([\s\S]*?)}}/,
forIn: /{{\s*?for\s*?\S*?\s*?in\s*?\S*?\s*?}}/i,
endFor: /{{\s*?endfor\s*?}}/i,
ifBlock: /{{\s*?if\s*?[\s\S]*?\s*?}}/i,
endIf: /{{\s*?endif\s*?}}/i,
elseBlock: /{{\s*?else\s*?}}/i
},
ifrx = {
start: '{{\\s*if\\s*([^}]*)\\s*}}([\\s\\S]*?',
open: rx.ifBlock,
close: rx.endIf,
childOpen: '{{\\s*if\\s*[^}]*?\\s*}}[\\s\\S]*?',
childClose: '{{\\s*endif\\s*}}[\\s\\S]*?',
end: '){{\\s*endif\\s*}}',
endLength: 23
},
looprx = {
start: '{{\\s*for\\s*([^}]*)\\s*in\\s*([^}]*)\\s*}}([\\s\\S]*?',
open: rx.forIn,
close: rx.endFor,
childOpen: '{{\\s*for\\s*[^}]*\\s*}}[\\s\\S]*?',
childClose: '{{\\s*endfor\\s*}}[\\s\\S]*?',
end: '){{\\s*endfor\\s*}}',
endLength: 24
},
parse = require('./parse'),
escapeMap = {

@@ -38,18 +11,6 @@ '<': '&lt;',

'\'': '&apos;'
},
unescapeMap = {
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&apos;': '\''
};
/*---------------- Utility functions ----------------*/
let compiled = {};
function run(expression, context) {
return vm.runInNewContext(expression, context, {
timeout: 1000
});
}
function htmlEscape(str) {

@@ -61,125 +22,26 @@ return str.replace(/[&<>'"]/g, c => {

function htmlUnescape(str) {
return str.replace(/(&lt;)(&gt;)(&quot;)(&apos;)/g, c => {
return unescapeMap[c];
});
}
global._e_ = function(val) {
return typeof val === 'string' ? htmlEscape(val) : val;
};
/*---------------- Parsing functions ----------------*/
function parseVars(template, context, match) {
let value,
raw = match[0],
filters = match[1].split('|'),
ref = filters.shift(0);
filters.forEach(function(filter, i) {
filters[i] = filter.replace(/\s/g, '');
});
if (filters.indexOf('skip') !== -1) {
value = '&lcub;&lcub;' + ref + '&rcub;&rcub;';
} else {
try {
value = run(ref, context);
if (typeof value === 'string') {
if (filters.indexOf('safe') !== -1) {
//value = htmlUnescape(value);
} else {
value = htmlEscape(value);
}
} else if (value === undefined) {
value = '';
}
} catch(err) {
value = '';
}
}
return render(template.replace(raw, value), context);
}
function parseLoops(template, context, match) {
let raw = match[0],
index = match[1].trim(),
arrName = match[2],
html = match[3],
list = [],
output = '',
key,
subContext = Object.create(context);
try {
list = run(arrName, context);
}
catch(err) {}
if (index.indexOf('.') !== -1) {
let keys = index.split('.');
index = keys[0];
key = keys[1];
}
function promisify(callback, resolve, reject) {
if (list) {
Object.keys(list).forEach(function(value) {
if (key) {
subContext[index] = value;
subContext[key] = list[value];
} else {
subContext[index] = list[value];
}
resolve = callback ? data => {
callback(undefined, data);
} : resolve;
output += render(html, subContext);
});
}
reject = callback ? error => {
callback(error);
} : reject;
return render(template.replace(raw, output), context);
return {
resolve: resolve,
reject: reject
};
}
function parseIfs(template, context, match) {
let raw = match[0],
expression = match[1].trim(),
html = match[2],
doShow,
negate;
if (expression.indexOf('not ') === 0) {
negate = true;
expression = expression.substring(3).trim();
}
html = html.split(rx.elseBlock);
try {
doShow = run(expression, context);
}
catch(err) { }
if (negate) {
doShow = !doShow;
}
if (doShow) {
html = html[0];
} else if (html[1]) {
html = html[1];
} else {
html = '';
}
return render(template.replace(raw, html), context);
}
// Recursively asyncronously parses partial includes
// then calls the callback with the result
function parseIncludes(template, callback, opts) {
let match = rx.include.exec(template),
let match = /<<\s*?include\s(\S*?)\s*?>>/i.exec(template),
ext = new RegExp(opts.extension + '$'),
raw, include;

@@ -192,3 +54,3 @@

if (!include.match(new RegExp(opts.extension + '$'))) {
if (!include.match(ext)) {
include += opts.extension;

@@ -205,100 +67,2 @@ }

/*---------------- Regex generating functions ----------------*/
function splitTemplate(template, regx) {
let openIndex = template.search(regx),
openMatch = regx.exec(template);
return openMatch ? template.substring(openIndex + openMatch[0].length) : null;
}
function getRegx(template, config) {
let counts = {
open: 1,
close: 0
},
tmpl = splitTemplate(template, config.open),
result = config.start;
while (tmpl && counts.open !== counts.close) {
let open = tmpl.search(config.open),
close = tmpl.search(config.close);
if (open !== -1 && open < close) {
result += config.childOpen;
counts.open++;
tmpl = splitTemplate(tmpl, config.open);
} else if (close !== -1) {
result += config.childClose;
counts.close++;
tmpl = splitTemplate(tmpl, config.close);
} else {
break;
}
}
if (counts.close !== 0) {
result = result.substring(0, result.length - config.endLength) + config.end;
}
return new RegExp(result, 'i');
}
function process(loop, template, context, index) {
let regx;
if (index === undefined) {
index = template.search(loop ? rx.forIn : rx.ifBlock);
}
if (index !== -1) {
regx = getRegx(template, loop ? looprx : ifrx);
}
if (regx) {
template = loop ? parseLoops(template, context, regx.exec(template))
: parseIfs(template, context, regx.exec(template));
}
return template;
}
/*---------------- Main rendering function (syncronous) ----------------*/
function render(template, context) {
let match, regx,
indexOfLoop = template.search(rx.forIn),
indexOfIf = template.search(rx.ifBlock),
loopFirst = indexOfLoop < indexOfIf;
if (loopFirst) {
template = process(true, template, context, indexOfLoop);
template = process(false, template, context);
} else {
template = process(false, template, context, indexOfIf);
template = process(true, template, context);
}
match = rx.variable.exec(template);
if (match) {
template = parseVars(template, context, match);
}
return template;
}
/*---------------- Manila ----------------*/
function manila(opts) {

@@ -318,6 +82,3 @@

// By creating and returning a closure we can support multiple
// "instances" of manila with different configurations running
// in one app. All configurable variable references go here:
function compile(filepath) {
function getFile(filepath) {

@@ -350,15 +111,47 @@ return new Promise((resolve, reject) => {

}
function parse(filepath, context, callback) {
compile(filepath)
.then(template => {
callback(undefined, render(template, context));
})
.catch(error => {
callback(error);
});
function compile(filepath, callback) {
return new Promise((resolve, reject) => {
let p = promisify(callback, resolve, reject);
getFile(filepath)
.then(template => {
compiled[filepath] = parse(template);
p.resolve(compiled[filepath]);
})
.catch(error => {
p.reject(error);
});
});
}
return parse;
function init(filepath, context, callback) {
return new Promise((resolve, reject) => {
let p = promisify(callback, resolve, reject);
if (compiled[filepath]) {
p.resolve(compiled[filepath](context));
} else {
compile(filepath).then(render => {
p.resolve(render(context));
}).catch(error => {
p.reject(error);
});
}
});
};
init.compile = compile;
return init;
}

@@ -365,0 +158,0 @@

{
"name": "manila",
"version": "1.2.2",
"description": "The Node template engine you're looking for",
"version": "2.0.0",
"description": "A simple template engine for Node",
"main": "manila.js",

@@ -6,0 +6,0 @@ "jsnext:main": "manila.es6.js",

@@ -5,2 +5,4 @@ # Manila

Version 2.0 brings massive performance improvements and an even simpler syntax. Thanks to [John Resig](http://ejohn.org/blog/javascript-micro-templating/) for inspiring this approach.
# Installation

@@ -36,6 +38,6 @@ ```

<!-- views/index.mnla -->
<h1>{{message}}</h1>
<h1><<message>></h1>
```
Run `node .` and open `http://localhost:3000` in your browser to see the rendered result.
Run `node index` and open `http://localhost:3000` in your browser to see the rendered result.

@@ -51,6 +53,6 @@ # Use with vanilla Node

manila(__dirname + 'views/index.mnla', { message: 'Hello, world!' }, (err, html) => {
res.writeHead(200, 'text/html; charset=UTF-8');
res.end(html);
});
manila('index.mnla', { message: 'Hello, world!' }, function(err, html) {
res.writeHead(200, 'text/html; charset=UTF-8');
res.end(html);
});

@@ -62,5 +64,27 @@ }).listen(3000);

<!-- views/index.mnla -->
<h1>{{message}}</h1>
<h1><<message>></h1>
```
# Promise Support
When calling `manila`, you can either provide a callback or use a promise. The following is effectively the same as the example above:
```javascript
// index.js
const http = require('http'),
manila = require('manila')();
http.createServer((req, res) => {
manila('index.mnla', { message: 'Hello, world!' })
.then(html => {
res.writeHead(200, 'text/html; charset=UTF-8');
res.end(html);
}).catch(err => {
console.trace(err);
});
}).listen(3000);
```
## Configuration

@@ -74,3 +98,3 @@

`partials`: the directory in which to look for partial mnla files to use with `{{include ... }}` tags, realtive to the root. Defaults to the same directory as the `views` setting.
`partials`: the directory in which to look for partial mnla files to use with `<<include ... >>` tags, realtive to the root. Defaults to the same directory as the `views` setting.

@@ -91,45 +115,41 @@ `extension`: the file extension of your views/partials. Defaults to `'.mnla'`.

`{{ <expression> }}`: This tag will be replaced with the HTML-escaped result of evaluating the expression or variable with the current context.
`<< expression >>`: This tag will be replaced with the HTML-escaped result of evaluating the expression or variable with the current context.
## Filters
`<<< expression >>>`: Use three carets instead of two to prevent HTML-escaping of the expression.
`{{ <expression> | <filter> }}`: Filters can be used to modify the treatment of `<expression>`. At present, there are only two built-in filters:
`safe`: prevents HTML escaping the value. Use only when you can trust the content as code.
`skip`: prevents parsing of the tag. For example, `{{var|skip}}` would render `{{var}}`.
## Includes
`{{ include <path/to/file> }}`: Includes the content of the named file as part of the current template. `<path/to/file>` is relative to `views/` by default, but the partials folder is configurable by passing `{ partials: 'path' }` to the `manila` function.
`<< include path/to/file >>`: Includes the content of the named file as part of the current template. `path/to/file` is relative to `views/` unless overwritten during configuration.
## Conditional Blocks
## Blocks
`{{ if <expression> }} ... {{ endif }}`: Renders the contained markup only if `<expression>` evaluates as truthy.
Manila tags are executed as plain 'ol JavaScript, so there's no template language to learn. While you can use any JavaScript you want, here are some examples:
`{{ if <expression> }} ... {{ else }} ... {{ endif }}`: Renders the first block if `<expression>` is truthy, and the second block if `<expression>` is falsy.
Manila templates evaluate expressions as plain JavaScript, rather than implementing a custom expression parser. This has performance benefits, but it means that you can get unexpected results by writing expressions that throw JavaScript errors. Notably, be careful when using variables that may be undefined. For example, if you only want to render a block of content if value is falsy, you might try to use the following syntax:
#### Conditionals
```
{{ if !value }} ... {{ endif }}
<< if (expression) { >>
<p>This markup renders if expression is truthy</p>
<< } else { >>
<p>This markup renders if expression is falsy</p>
<< } >>
```
Unfortunately, if `value` is undefined, then the expression `!value` will throw an Uncaught Reference error, and the expression will be treated as falsy. Like other template engines you may be familiar with, Manila provides a `not` operator to properly handle this scenario:
#### Array Loops
```
{{ if not value }} ... {{ endif }}
<< list.forEach(item => { >>
<li><<item>></li>
<< }) >>
```
## Loops
#### Array Loops
`{{ for <item> in <array> }} ... {{ endfor }}`: Renders the contained markup once for each item in `<array>`. Within each loop, `{{<item>}}` will be replaced with the corresponding item in `<array>`.
#### Object Loops
`{{ for <key>.<value> in <object> }} ... {{ endfor }}`: Renders the contained markup once for each property on `<object>`. Within each loop, `{{<key>}}` will be replaced with the corresponding key in `<object>`, and `{{<value>}}` will be replaced with that key's value.
```
<< for (key in obj) { >>
<li> <<key>> is <<obj[key]>> </li>
<< } >>
```
## Use with Angular
### Syntax Highlighting
Manila's double curly brace syntax is the same as Angular's tag syntax. If you try to write an Angular template as a Manila view, it will be interpolated by Manila before Angular gets it. You can get around this by using the `skip` filter on the tags you want to leave unparsed by Manila.
If your text editor thinks your mnla file is an html file, it might indicate that `<< manila >>` tags are errors. To prevent this, change your syntax setting for the .mnla filetype to XML. In Sublime: View > Syntax > Open all with current extension as > XML.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc