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.8.1 to 0.9.0

lib/compile.js

232

lib/index.js

@@ -1,36 +0,14 @@

const { normalize, resolve } = require('path');
const { bundle, parseBlocks, writeBlockFiles } = require('./bundle');
const { exps, parse } = require('./statements');
const { vdom, cleanWhitespace, escape, hash } = require('./utils');
const defaultTags = require('./tags');
const { merge } = require('merge-anything');
const fs = require('fs');
const { defaultTag, parsers } = require('./statements');
const { resolvePath, compile } = require('./compile');
class BeardError {
constructor(realError, template, lineNumber, tag) {
this.name = 'Beard Syntax Error';
this.message = `"{{${tag}}}" in ${template} on line ${lineNumber}`;
this.lineNumber = lineNumber;
this.fileName = template;
this.functionName = tag;
Error.captureStackTrace(this, compile);
}
}
class Beard {
constructor(opts = {}) {
opts.templates = opts.templates || {};
opts.loadHandles = opts.hasOwnProperty('loadHandles') ? opts.loadHandles : true;
opts.tags = opts.tags || {};
this.opts = opts;
this.opts.tags = { ...defaultTag, ...opts.tags };
this.fns = {};
this.handles = {};
this.beardDir = `${this.opts.root}/../.beard`;
if (this.opts.root) {
bundle(this);
}
this.opts.tags = merge(defaultTags, this.opts.tags || {});
this.configureTags(this.opts.tags);

@@ -41,47 +19,11 @@

}
if (this.opts.ready) {
this.opts.ready(this);
}
}
bundleFile(path) {
const regex = new RegExp('(.beard$)', 'g');
const key = path.replace(regex, '');
const contents = fs.readFileSync(path, 'utf8');
const template = /<template>[\s\S]*?<\/template>/gm.test(contents)
? contents
: `<template>${contents}</template>`;
const original$ = vdom(template);
const blocks = parseBlocks(original$, path);
const $ = vdom(cleanWhitespace(original$('template').html()));
const whitespaceTagsSelectors = 'pre, code, textarea';
const originalWhitespaceTags = original$(whitespaceTagsSelectors);
const whitespaceTags = $(whitespaceTagsSelectors);
originalWhitespaceTags.each((i, el) => {
$(whitespaceTags[i]).replaceWith(el);
});
const body = $.html().replace(/=\"=(=?)\"/gm, '==$1');
writeBlockFiles(blocks);
if (this.opts.loadHandles && blocks.ssjs) {
const handlePath = `${this.beardDir}/${blocks.ssjs.file}`;
delete require.cache[require.resolve(handlePath)];
this.handles[key] = require(handlePath);
}
this.opts.templates[key] = body;
}
configureTags(tags) {
exps.tag = new RegExp(`^(${Object.keys(tags).join('|')})\\\s+([^,]+)(?:,\\\s*([\\\s\\\S]*))?$`);
parsers.tag = new RegExp(`^(${Object.keys(tags).join('|')})\\\s+([^,]+)(?:,\\\s*([\\\s\\\S]*))?$`);
const contentTags = Object.keys(tags).filter((key) => tags[key].content).join('|');
const contentTags = Object.keys(tags).filter((key) => tags[key].content).join('|');
if (contentTags.length) {
exps.contentTag = new RegExp(`^(${contentTags})\\\:content\\\s+([^,]+)(?:,\\\s*([\\\s\\\S]*))?$`);
exps.endTag = new RegExp(`^end(${contentTags})$`);
parsers.contentTag = new RegExp(`^(${contentTags})\\\:content\\\s+([^,]+)(?:,\\\s*([\\\s\\\S]*))?$`);
parsers.endTag = new RegExp(`^end(${contentTags})$`);
}

@@ -91,8 +33,8 @@ }

configureShortcuts(shortcuts, tags) {
exps.shortcut = new RegExp(`^@(${Object.keys(shortcuts).join('|')})(?:(?:\\\s+)([\\\s\\\S]+))?$`);
parsers.shortcut = new RegExp(`^@(${Object.keys(shortcuts).join('|')})(?:(?:\\\s+)([\\\s\\\S]+))?$`);
const contentTags = Object.keys(shortcuts).filter((key) => tags[shortcuts[key].tag].content).join('|');
const contentTags = Object.keys(shortcuts).filter((key) => tags[shortcuts[key].tag].content).join('|');
if (contentTags.length) {
exps.shortcutContent = new RegExp(`^@(${contentTags})\\\:content(?:(?:\\\s+)([\\\s\\\S]+))?$`);
exps.endShortcut = new RegExp(`^end(${contentTags})$`);
parsers.shortcutContent = new RegExp(`^@(${contentTags})\\\:content(?:(?:\\\s+)([\\\s\\\S]+))?$`);
parsers.endShortcut = new RegExp(`^end(${contentTags})$`);
}

@@ -104,5 +46,7 @@ }

const str = this.opts.templates[path];
if (process.env.NODE_ENV === 'development' || !this.fns[path]) {
this.fns[path] = compile(str, path, this.opts.root);
this.fns[path] = compile(str, path);
}
return this.fns[path];

@@ -112,7 +56,9 @@ }

tag(name, parentPath, firstArg, data) {
if (this.opts.tags[name].firstArgIsResolvedPath) firstArg = resolvePath(firstArg, parentPath, this.opts.root);
if (this.opts.tags[name].firstArgIsResolvedPath) {
firstArg = resolvePath(firstArg, parentPath, this.opts.root);
}
return this.opts.tags[name].render(
firstArg,
data,
this.handles,
this.render.bind(this),

@@ -144,3 +90,4 @@ this.partial.bind(this)

shortcut: this.shortcut.bind(this)
}
};
return compile(str, this.opts.root || '/')(context);

@@ -157,3 +104,3 @@ }

shortcut: this.shortcut.bind(this)
}
};

@@ -168,136 +115,3 @@ if (this.opts.root && !path.startsWith('/')) {

function validateSyntax(templateCode, tag, lineNumber, template) {
templateCode = templateCode.replace(/(\r\n|\n|\r)/gm, '');
if (templateCode.match(/^.*\{[^\}]*$/)) templateCode += '}'; // append a } to templateCode that needs it
if (templateCode.match(/^(\s*)\}/)) templateCode = 'if (false) {' + templateCode; // prepend a { to templateCode that needs it
try {
new Function(templateCode);
} catch(e) {
throw new BeardError(e, template, lineNumber, tag);
}
}
const getDir = path => path.replace(/\/[^\/]+$/, '');
const reducer = (inner, tag) => inner.replace(exps[tag], parse[tag]);
const tags = [
'include', 'includeContent', 'endInclude',
'block', 'blockEnd', 'put', 'encode',
'comment', 'if', 'exists', 'existsNot',
'elseIf', 'else', 'for', 'each', 'end',
'extends', 'tag', 'contentTag', 'endTag',
'shortcut', 'shortcutContent', 'endShortcut'
];
function resolvePath(path, parentPath, root) {
return path.startsWith('/')
? path
: path.startsWith('~')
? resolve(root, path.replace(/^~/, '.'))
: normalize(`${getDir(parentPath)}/${path}`);
}
function scanner(template, path) {
let statements = [];
const contentCompiler = (content) => statements.push(`_capture(\`${escape(content)}\`);`);
const tagCompiler = (tag, lineNumber) => {
const parsedStatement = parser(tag);
validateSyntax(parsedStatement, tag, lineNumber, path);
statements.push(parsedStatement);
};
exps.statement.lastIndex = 0;
let result = exps.statement.exec(template);
let lastIndex = 0;
let extendsResult;
while (result) {
const content = template.substring(lastIndex, result.index);
if (content.length > 0) contentCompiler(content);
const tag = result[1];
const extendsMatch = exps.extends.exec(tag);
if (extendsMatch) { // hold extends until the end
extendsResult = result;
} else {
const lineNumber = template.substring(0, result.index).split('\n').length;
tagCompiler(tag, lineNumber);
}
lastIndex = exps.statement.lastIndex;
result = exps.statement.exec(template);
}
if (lastIndex < template.length) {
const content = template.substring(lastIndex, template.length);
contentCompiler(content);
}
if (extendsResult) {
const lineNumber = template.substring(0, extendsResult.index).split('\n').length;
tagCompiler(extendsResult[1], lineNumber);
}
return statements;
}
function parser(statement) {
const parsedStatement = tags.reduce(reducer, statement);
return statement === parsedStatement
? `_capture(${statement});`
: cleanWhitespace(parsedStatement.replace(/\t|\n|\r/, ''));
}
function compile(str, path, root) {
const templateCode = scanner(str.replace(new RegExp('\\\\', 'g'), '\\\\').replace(/"/g, '\\"'), path).join(' ');
const fn = `
${cleanWhitespace(`
var scopedClass = 'b-${hash(path.replace(root, ''))}';
var _currentPath = '${path}';
var _buffer = '';
var _blockNames = [];
var _blockCaptures = [];
var _captureArgs = [];
function _capture(str) {
if (_blockNames.length > 0) {
_blockCaptures[_blockCaptures.length - 1] += str;
} else {
_buffer += str;
}
}
function _encode(str) {
_capture(str
.replace(/&(?!\\w+;)/g, '&#38;')
.replace(/\</g, '&#60;')
.replace(/\>/g, '&#62;')
.replace(/\"/g, '&#34;')
.replace(/\'/g, '&#39;')
.replace(/\\//g, '&#47;'));
}
for (var _prop in _context.globals) {
eval('var ' + _prop + ' = _context.globals[_prop]');
}
for (var i = 0; i < _context.locals.length; i++) {
var _locals = _context.locals[i];
for (var _prop in _locals) {
eval('var ' + _prop + ' = _locals[_prop]');
}
}
`)}
${templateCode}
return _buffer;
`.replace(/_capture\(``\);(\s+)?/g, '');
try {
return new Function('_context', fn);
} catch (e) {
throw new Error(`Compilation error: ${fn}`);
}
}
module.exports = opts => new Beard(opts);
module.exports = (opts) => new Beard(opts);

@@ -1,5 +0,15 @@

const uniqueIterator = value => Math.random().toString().substring(2);
const uid = () => Math.random().toString().substring(2);
exports.exps = {
const tags = [
'include', 'includeContent', 'endInclude',
'block', 'blockEnd', 'put', 'encode',
'comment', 'if', 'exists', 'existsNot',
'elseIf', 'else', 'for', 'each', 'end',
'extends', 'tag', 'contentTag', 'endTag',
'shortcut', 'shortcutContent', 'endShortcut'
];
const parsers = {
extends: (/^extends\s(.+)$/g),

@@ -32,3 +42,3 @@ include: (/^include\s+([^,]+)(?:,\s*([\s\S]*))?$/),

exports.parse = {
const converters = {

@@ -192,4 +202,4 @@ extends: (_, path) => `

for: (_, value, key, objValue) => {
if (!key) key = `_iterator_${uniqueIterator(value)}`;
const obj = `_iterator_${uniqueIterator(value)}`;
if (!key) key = `_iterator_${uid()}`;
const obj = `_iterator_${uid()}`;
return `

@@ -203,5 +213,5 @@ var ${obj} = ${objValue};

each: (_, value, iter, arrValue) => {
if (!iter) iter = `_iterator_${uniqueIterator(value)}`;
const length = `_iterator_${uniqueIterator(value)}`;
const arr = `_iterator_${uniqueIterator(value)}`;
if (!iter) iter = `_iterator_${uid()}`;
const length = `_iterator_${uid()}`;
const arr = `_iterator_${uid()}`;
return `

@@ -213,1 +223,48 @@ for (var ${iter} = 0, ${arr} = ${arrValue}, ${length} = ${arr}.length; ${iter} < ${length}; ${iter}++) {

};
const htmlSingletonTags = [
'area', 'base', 'br', 'col',
'command', 'embed', 'hr', 'img',
'input', 'keygen', 'link', 'meta',
'param', 'source', 'track', 'wbr'
];
const htmlTagsWithValueAttributes = [
'button', 'input', 'li', 'meter',
'option', 'param', 'progress'
];
const defaultTag = {
tag: {
render: (tagName, data) => {
const isValueTag = htmlTagsWithValueAttributes.includes(tagName);
const attributes = Object.entries(data).reduce((attrs, [key, value]) => {
if ((value || typeof value === 'string') && ((key === 'value' && isValueTag) || !['content', 'value'].includes(key))) {
attrs += value === true
? ` ${key}`
: ` ${key}="${value}"`;
}
return attrs;
}, '');
const tag = `<${tagName}${attributes}>`;
const tagContent = data.content || (!isValueTag && data.value ? data.value : '');
return htmlSingletonTags.includes(tagName)
? tag
: `${tag}${tagContent}</${tagName}>`;
},
firstArgIsResolvedPath: false,
content: true
}
};
exports.defaultTag = defaultTag;
exports.tags = tags;
exports.parsers = parsers;
exports.converters = converters;
{
"name": "beard",
"version": "0.8.1",
"version": "0.9.0",
"description": "More than a mustache.",

@@ -20,14 +20,2 @@ "license": "MIT",

},
"bin": {
"beard": "./cli.js"
},
"dependencies": {
"cheerio": "github:cheeriojs/cheerio#3368605edb3c5babecc8576602a9d54ccfdaef1e",
"fs-extra": "7.0.1",
"merge-anything": "~3.0.3",
"mismatch": "^1.2.0",
"normalize-selector": "0.2.0",
"traversy": "0.0.2",
"xregexp": "4.2.4"
},
"devDependencies": {

@@ -34,0 +22,0 @@ "mocha": "4.1.0",

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