Socket
Socket
Sign inDemoInstall

beard

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

beard - npm Package Compare versions

Comparing version 0.4.0 to 0.5.1

164

beard.js

@@ -1,14 +0,55 @@

module.exports = function(cache = {}, lookup = path => path) {
let compiledCache = {};
const fs = require('fs');
const exts = '(.brd$|.brd.html$)';
const traversy = require('traversy');
const normalize = require('path').normalize;
function hash(str) {
let hash = 5381;
let i = str.length;
while (i) hash = (hash * 33) ^ str.charCodeAt(--i);
return hash >>> 0;
}
module.exports = function(opts = {}) {
opts.cache = opts.cache != undefined ? opts.cache : true;
opts.templates = opts.templates || {};
let fnCache = {};
let pathMap = {};
let iterator = 0;
const Beard = function() {}
const Beard = function() {
if (opts.root) {
const regex = new RegExp(`(^${opts.root}|.brd$|.brd.html$)`, 'g');
traversy(opts.root, exts, (path) => {
const key = path.replace(regex, '');
opts.templates[key] = fs.readFileSync(path, 'utf8');
pathMap[key] = path;
});
}
}
Beard.prototype = {
render: (template, data = {}) => {
render: (path, data = {}) => {
iterator = 0;
return compiled(template, data)(data);
let context = {
globals: {},
locals: [data],
path: null
};
return compiled(path, '/')(context);
}
};
function resolvePath(path, parentPath) {
if (path.startsWith('/')) {
return path;
} else {
const currentDir = parentPath.replace(/\/[^\/]+$/, '');
return normalize(`${currentDir}/${path}`);
}
}
const exps = {

@@ -18,4 +59,6 @@ extends: (/\{{extends\s\'([^}}]+?)\'\}}/g),

includeFn: (/^include\((\s?\'([^\(]*?)\'\,\s?\{([^\)]*)\})\)$/g),
block: (/{{block\s+(.[^}]*)}}([^]*?){{endblock}}/g),
statement: (/{{\s*((?!}}).+?)\s*}}/g),
block: (/^block\s+(.[^}]*)/g),
blockEnd: (/^endblock$/g),
encode: (/^\:(.*)/),
statement: (/{{\s*([\S\s(?!}})]+?)\s*}}/g),
if: (/^if\s+([^]*)$/),

@@ -30,5 +73,7 @@ elseIf: (/^else\s+if\s+([^]*)$/),

const parse = {
include: (_, path) => `_buffer += compiled("${cache[lookup(path)]}", _data)(_data)`,
includeFn: (_, __, path, data) => `_buffer += compiled("${cache[lookup(path)]}", _data)({${data}})`,
block: (_, varname, content) => `{{:var ${varname} = compiled("${content}", _data)(_data)}}{{:_data["${varname}"] = ${varname}}}`,
include: (_, includePath) => `_capture(compiled("${includePath}", path)(_context));`,
includeFn: (_, __, includePath, data) => `_context.locals.push({${data}}); _capture(compiled("${includePath}", path)(_context)); _context.locals.pop();`,
block: (_, blockname) => `_blockName = "${blockname}"; _blockCapture = "";`,
blockEnd: () => 'eval(`var ${_blockName} = _blockCapture`); _context.globals[_blockName] = _blockCapture; _blockName = null;',
encode: (_, statement) => `_encode(${statement});`,
if: (_, statement) => `if (${statement}) {`,

@@ -38,3 +83,2 @@ elseIf: (_, statement) => `} else if (${statement}) {`,

end: () => '}',
for: (_, key, value, object) => {

@@ -44,3 +88,2 @@ if (!value) key = (value = key, 'iterator' + iterator++);

},
each: (_, iter, value, array) => {

@@ -58,2 +101,5 @@ if (!value) iter = (value = iter, 'iterator' + iterator++);

.replace(exps.includeFn, parse.includeFn)
.replace(exps.block, parse.block)
.replace(exps.blockEnd, parse.blockEnd)
.replace(exps.encode, parse.encode)
.replace(exps.end, parse.end)

@@ -66,37 +112,36 @@ .replace(exps.else, parse.else)

return `"; ${(inner === prev && !/^:/.test(inner) ? ' _buffer += ' : '')} ${inner.replace(/\t|\n|\r|^:/, '')}; _buffer += "`;
const middle = inner === prev && !/^:/.test(inner)
? `_capture(${inner});`
: inner.replace(/\t|\n|\r|^:/, '');
return `"); ${middle} _capture("`;
}
function compiled(str, data) {
function compiled(path, parentPath) {
const fullPath = resolvePath(path, parentPath);
const str = opts.cache
? opts.templates[fullPath]
: fs.readFileSync(pathMap[fullPath], 'utf8');
let key = hash(str);
if (!compiledCache[key]) {
compiledCache[key] = compile(str);
if (!fnCache[key]) {
fnCache[key] = compile(str, fullPath);
}
return compiledCache[key];
return fnCache[key];
}
function hash(str) {
let hash = 5381;
let i = str.length;
function compile(str, path) {
let layout = '';
while(i) {
hash = (hash * 33) ^ str.charCodeAt(--i);
}
return hash >>> 0;
}
function compile(str) {
let layout;
str = str
.replace(exps.extends, (_, path) => {
layout = cache[lookup(path)];
layout = `
_context.globals.view = _buffer;
_buffer = compiled("${path}", path)(_context);
`;
return '';
})
.replace(exps.block, parse.block)
.replace(new RegExp('\\\\', 'g'), '\\\\').replace(/"/g, '\\"')
.replace(exps.statement, parser)
.replace(/_buffer_\s\+=\s"";/g, '')
.replace(/\n/g, '\\n')

@@ -106,34 +151,49 @@ .replace(/\t/g, '\\t')

let fn = `
function _compiledTemplate(_data){
const fn = `
function _compiledFn(_context){
var path = "${path}";
var _buffer = "";
var _blockName;
var _blockCapture;
function _valForEval(val) {
if (typeof val == 'function') return val.toString();
return JSON.stringify(val);
function _capture(str) {
if (_blockName) {
_blockCapture += str;
} else {
_buffer += str;
}
}
for (var prop in _data) {
if (_data.hasOwnProperty(prop)) {
eval("var " + prop + " = " + _valForEval(_data[prop]));
function _encode(str) {
_capture(str
.replace(/&(?!\\w+;)/g, '&')
.replace(/\</g, '&#60;')
.replace(/\>/g, '&#62;')
.replace(/\"/g, '&#34;')
.replace(/\'/g, '&#39;')
.replace(/\\//g, '&#47;'));
}
for (var prop in _context.globals) {
if (_context.globals.hasOwnProperty(prop)) {
eval("var " + prop + " = _context.globals[prop]");
}
}
_buffer += "${str}";
`;
if (layout) {
fn += `
_data['view'] = _buffer;
_buffer = compiled("${layout}", _data)(_data);
`;
}
var _locals = _context.locals[_context.locals.length - 1];
for (var prop in _locals) {
if (_locals.hasOwnProperty(prop)) {
eval("var " + prop + " = _locals[prop]");
}
}
fn += `
_capture("${str}");
${layout}
return _buffer;
}
`;
`.replace(/_capture\(""\);(\s+)?/g, '');
try {
eval(fn);
return _compiledTemplate.bind(_compiledTemplate);
return _compiledFn.bind(_compiledFn);
} catch (e) {

@@ -140,0 +200,0 @@ throw new Error(`Compilation error: ${fn}`);

const { time } = require('brisky-performance');
const beard = require('./beard');
const benchmarkTemplate = function(name, template, beardInstance, times = 10000) {
const benchmarkTemplate = function(name, path, engine, times = 10000) {
let start;

@@ -11,6 +11,6 @@ let elapsed;

beardInstance.render(template)
engine.render(path);
start = time();
for(i = 0; i < times; i++) beardInstance.render(template);
for(i = 0; i < times; i++) engine.render(path);
elapsed = time(start);

@@ -22,10 +22,16 @@ console.log(`Rendering ${times} times with caching took ${elapsed}ms to complete.`);

benchmarkTemplate('Simple Content', 'some content', beard({}));
benchmarkTemplate('Simple Content', 'content', beard({
templates: {
'/content': 'some content'
}
}));
benchmarkTemplate(
'Page with Layout',
"{{include 'page'}}",
'page',
beard({
'page': "{{extends 'layout'}}page content{{block header}}the header{{endblock}}",
'layout': 'top of page {{header}} -- {{view}} -- the footer'
templates: {
'/page': "{{extends 'layout'}}page content{{block header}}the header{{endblock}}",
'/layout': 'top of page {{header}} -- {{view}} -- the footer'
}
})

@@ -35,10 +41,9 @@ );

benchmarkTemplate(
'Page with Layout with Path Lookup',
"{{include 'page'}}",
'Escaped Content',
'escape',
beard({
'/views/page': "{{extends 'layout'}}page content{{block header}}the header{{endblock}}",
'/views/layout': 'top of page {{header}} -- {{view}} -- the footer'
},
(path) => `/views/${path}`
)
templates: {
'/escape': "{{:'<script>alert('this\'')</script>'}"
}
})
);
{
"name": "beard",
"version": "0.4.0",
"version": "0.5.1",
"description": "More than a mustache.",
"license": "MIT",
"keywords": [

@@ -19,2 +20,5 @@ "templating engine",

},
"dependencies": {
"traversy": "0.0.2"
},
"devDependencies": {

@@ -21,0 +25,0 @@ "mocha": "4.1.0",

@@ -1,71 +0,162 @@

Beard
===================
# Beard
More than a mustache.
Released under a [MIT license](http://en.wikipedia.org/wiki/MIT_License).
## Features
Features
--------
* Clean syntax
* Ability to dynamically cache templates
* Cached rendered templates for faster renders
Usage
-----
## Install
### Install ###
`npm install beard`
### API ###
## Usage
``` js
const data = {
noun: 'beards',
capitalize: str => str.charAt(0).toUpperCase() + str.slice(1)
};
const Beard = require('beard');
const engine = new Beard(cache, lookup);
engine.render(template, locals);
const engine = new Beard({
templates: {
'/example': '{{capitalize(noun)}} are itchy.'
}
});
const result = engine.render('/example', data);
console.log(result); // returns 'Beards are itchy.'
```
### Beard Constructor Arguments ###
### Constructor Arguments
**cache** - (object) An object literal containing your templates.
**opts** (object) - An object literal with the following optional engine options:
**lookup** - (function) A function that accepts the path value and can modify the path value before Beard looks up your template from the cache. E.g., `(path) => '/absolute/cached/path/${path}'`.
- **templates** (object) - An object literal containing your templates index.
- **root** (string) - The absolute path to the root directory where all templates are stored. If you provide a root directory, beard will create your templates cache for you.
- **home** (string) - Relative path to home directory (used via `'~'` in paths, E.g. `'~/layout'`).
- **cache** (boolean) - Set to `false` to disable caching of template files. Defaults to `true`.
### Render Arguments ###
### Render Arguments
**template** - (string) A string to be parsed and populated by the view object.
**path** (string) - A string to be parsed and populated by the view object.
**locals** - (object) An object of data and/or methods which will populate the template string.
**locals** (object) - An object of data and/or methods which will populate the template string.
### Example ###
## Examples
``` js
const templates = {
'example': "{{noun}} get {{makeUpperCase('stinky')}}."
};
const Beard = require('beard');
const engine = new Beard({
templates: {
'/layout': 'header | {{view}} | footer',
'/app/page/content': "{{extends '/layout'}}content {{include '~/component'}}",
'/app/component': 'and component'
},
root: '/',
home: 'app/',
cache: true
});
const result = engine.render('/app/page/content');
console.log(result); // returns 'header | content and component | footer'
const locals = {
noun: "Beards",
makeUpperCase: function(str){
return str.toUpperCase();
}
};
```
const Beard = require('beard');
const engine = new Beard(templates);
const result = engine.render("{{include 'example'}}", locals);
console.log(result); // returns 'Beards get STINKY.'
### include
Includes a template, can optionally pass locals.
```
{{include 'template'}}
{{include('template', {arg: 'val', arg2: 'val2'})}}
```
### More docs to come... ###
### extends
Extends template with a layout. Template will be accessible as "view" variable.
* cache
* optional cache lookup function
**layout.brd.html**
```
<html>
<body>
{{view}}
</body>
</html>
```
### Thanks to ###
and rendering:
* keeto (Mark Obcena) for the parser/compiler
* shinetech (Danny Brain) for syntax ideas
```
{{extends 'layout'}}view content
```
Returns:
```
<html>
<body>
view content
</body>
</html>
```
### block
Makes content available as variable name.
```
{{block middle}}
Middle
{{endblock}}
Top
{{middle}}
Bottom
```
Returns:
```
Top
Middle
Bottom
```
### conditionals
```
{{if x === 1}}
x is 1
{{else if x > 1}}
x is greater than 1
{{else}}
x is less than 1
{{end}}
```
### for loop
Iterate over properties in object.
```
{{for key, value in object}}
{{key}} = {{value}}
{{end}}
```
#### each loop
Iterate over array.
```
{{each item in array}}
{{item.property}}
{{end}}
```
## Thanks to
* keeto (Mark Obcena) for the first iteration of the parser/compiler
* joeosburn (Joe Osburn) for the updated compiler, cached compiled functions, tests, and benchmarks
Released under [MIT license](http://en.wikipedia.org/wiki/MIT_License).
SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc