Files
liquidjs/index.js
T

136 lines
4.2 KiB
JavaScript

const Scope = require('./src/scope');
const tokenizer = require('./src/tokenizer.js');
const fs = require('fs');
const Render = require('./src/render.js');
const lexical = require('./src/lexical.js');
const Tag = require('./src/tag.js');
const Filter = require('./src/filter.js');
const Template = require('./src/parser');
const Expression = require('./src/expression.js');
const tags = require('./tags');
const filters = require('./filters');
const Promise = require('any-promise');
var _engine = {
init: function(tag, filter, options) {
if (options.cache) {
this.cache = {};
}
this.options = options;
this.tag = tag;
this.filter = filter;
this.parser = Template(tag, filter);
this.renderer = Render();
tags(this);
filters(this);
return this;
},
parse: function(html) {
var tokens = tokenizer.parse(html);
return this.parser.parse(tokens);
},
render: function(tpl, ctx, opts) {
opts = opts || {};
opts.strict_variables = opts.strict_variables || false;
opts.strict_filters = opts.strict_filters || false;
this.renderer.resetRegisters();
var scope = Scope.factory(ctx, {
strict: opts.strict_variables,
});
return this.renderer.renderTemplates(tpl, scope, opts);
},
parseAndRender: function(html, ctx, opts) {
try {
var tpl = this.parse(html);
return this.render(tpl, ctx, opts);
} catch (error) {
// A throw inside of a then or catch of a Promise automatically rejects, but since we mix a sync call
// with an async call, we need to do this in case the sync call throws.
return Promise.reject(error);
}
},
renderFile: function(filepath, ctx, opts) {
return this.handleCache(filepath)
.then((templates) => {
return this.render(templates, ctx, opts);
})
.catch((e) => {
e.file = filepath;
throw e;
});
},
evalOutput: function(str, scope) {
var tpl = this.parser.parseOutput(str.trim());
return this.renderer.evalOutput(tpl, scope);
},
registerFilter: function(name, filter) {
return this.filter.register(name, filter);
},
registerTag: function(name, tag) {
return this.tag.register(name, tag);
},
handleCache: function(filepath) {
if (!filepath) throw new Error('filepath cannot be null');
return this.getTemplate(filepath)
.then((html) => {
var tpl = this.options.cache && this.cache[filepath] || this.parse(html);
return this.options.cache ? (this.cache[filepath] = tpl) : tpl;
});
},
getTemplate: function(filepath) {
filepath = resolvePath(this.options.root, filepath);
if (!filepath.match(/\.\w+$/)) {
filepath += this.options.extname;
}
return new Promise(function(resolve, reject) {
fs.readFile(filepath, 'utf8', function(err, html) {
err ? reject(err) : resolve(html);
});
});
},
express: function(renderingOptions) {
return (filePath, options, callback) => {
this.renderFile(filePath, options, renderingOptions)
.then(html => callback(null, html))
.catch(e => callback(e));
};
}
};
function factory(options) {
options = options || {};
options.root = options.root || '';
options.extname = options.extname || '.liquid';
var engine = Object.create(_engine);
engine.init(Tag(), Filter(), options);
return engine;
}
function resolvePath(root, path) {
if (path[0] == '/') return path;
var arr = root.split('/').concat(path.split('/'));
var result = [];
arr.forEach(function(slug) {
if (slug == '..') result.pop();
else if (!slug || slug == '.');
else result.push(slug);
});
return '/' + result.join('/');
}
factory.lexical = lexical;
factory.isTruthy = Expression.isTruthy;
factory.isFalsy = Expression.isFalsy;
factory.evalExp = Expression.evalExp;
factory.evalValue = Expression.evalValue;
module.exports = factory;