twig.js | |
---|---|
| var Twig = (function (Twig) {
Twig.VERSION = "0.5.8";
return Twig;
})(Twig || {}); |
| var Twig = (function (Twig) {
"use strict"; |
twig.core.jsThis file handles template level tokenizing, compiling and parsing. | Twig.trace = false;
Twig.debug = false; |
Default caching to true for the improved performance it offers | Twig.cache = true;
Twig.placeholders = {
parent: "{{|PARENT|}}"
};
/**
* Exception thrown by twig.js.
*/
Twig.Error = function(message) {
this.message = message;
this.name = "TwigException";
this.type = "TwigException";
};
/**
* Get the string representation of a Twig error.
*/
Twig.Error.prototype.toString = function() {
return this.name + ": " + this.message;
};
/**
* Wrapper for logging to the console.
*/
Twig.log = {
trace: function() {if (Twig.trace && console) {console.log(Array.prototype.slice.call(arguments));}},
debug: function() {if (Twig.debug && console) {console.log(Array.prototype.slice.call(arguments));}}
};
/**
* Container for methods related to handling high level template tokens
* (for example: {{ expression }}, {% logic %}, {# comment #}, raw data)
*/
Twig.token = {};
/**
* Token types.
*/
Twig.token.type = {
output: 'output',
logic: 'logic',
comment: 'comment',
raw: 'raw'
};
/**
* Token syntax definitions.
*/
Twig.token.definitions = [
{
type: Twig.token.type.raw,
open: '{% raw %}',
close: '{% endraw %}',
}, |
Output type tokens These typically take the form | {
type: Twig.token.type.output,
open: '{{',
close: '}}'
}, |
Logic type tokens These typically take a form like | {
type: Twig.token.type.logic,
open: '{%',
close: '%}'
}, |
Comment type tokens These take the form | {
type: Twig.token.type.comment,
open: '{#',
close: '#}'
}
];
/**
* What characters start "strings" in token definitions. We need this to ignore token close
* strings inside an expression.
*/
Twig.token.strings = ['"', "'"];
Twig.token.findStart = function (template) {
var output = {
position: null,
def: null
},
i,
token_template,
first_key_position;
for (i=0;i<Twig.token.definitions.length;i++) {
token_template = Twig.token.definitions[i];
first_key_position = template.indexOf(token_template.open);
Twig.log.trace("Twig.token.findStart: ", "Searching for ", token_template.open, " found at ", first_key_position); |
Does this token occur before any other types? | if (first_key_position >= 0 && (output.position === null || first_key_position < output.position)) {
output.position = first_key_position;
output.def = token_template;
}
}
return output;
};
Twig.token.findEnd = function (template, token_def, start) {
var end = null,
found = false,
offset = 0, |
String position variables | str_pos = null,
str_found = null,
pos = null,
end_offset = null,
this_str_pos = null,
end_str_pos = null, |
For loop variables | i,
l;
while (!found) {
str_pos = null;
str_found = null;
pos = template.indexOf(token_def.close, offset);
if (pos >= 0) {
end = pos;
found = true;
} else { |
throw an exception | throw new Twig.Error("Unable to find closing bracket '" + token_def.close +
"'" + " opened near template position " + start);
}
l = Twig.token.strings.length;
for (i = 0; i < l; i += 1) {
this_str_pos = template.indexOf(Twig.token.strings[i], offset);
if (this_str_pos > 0 && this_str_pos < pos &&
(str_pos === null || this_str_pos < str_pos)) {
str_pos = this_str_pos;
str_found = Twig.token.strings[i];
}
} |
We found a string before the end of the token, now find the string's end and set the search offset to it | if (str_pos !== null) {
end_offset = str_pos + 1;
end = null;
found = false;
while (true) {
end_str_pos = template.indexOf(str_found, end_offset);
if (end_str_pos < 0) {
throw "Unclosed string in template";
} |
Ignore escaped quotes | if (template.substr(end_str_pos - 1, 1) !== "\\") {
offset = end_str_pos + 1;
break;
} else {
end_offset = end_str_pos + 1;
}
}
}
}
return end;
};
/**
* Convert a template into high-level tokens.
*/
Twig.tokenize = function (template) {
var tokens = [], |
An offset for reporting errors locations in the template. | error_offset = 0, |
The start and type of the first token found in the template. | found_token = null, |
The end position of the matched token. | end = null;
while (template.length > 0) { |
Find the first occurance of any token type in the template | found_token = Twig.token.findStart(template);
Twig.log.trace("Twig.tokenize: ", "Found token: ", found_token);
if (found_token.position !== null) { |
Add a raw type token for anything before the start of the token | if (found_token.position > 0) {
tokens.push({
type: Twig.token.type.raw,
value: template.substring(0, found_token.position)
});
}
template = template.substr(found_token.position + found_token.def.open.length);
error_offset += found_token.position + found_token.def.open.length; |
Find the end of the token | end = Twig.token.findEnd(template, found_token.def, error_offset);
Twig.log.trace("Twig.tokenize: ", "Token ends at ", end);
tokens.push({
type: found_token.def.type,
value: template.substring(0, end).trim()
});
if ( found_token.def.type === "logic" && template.substr( end + found_token.def.close.length, 1 ) === "\n" ) { |
Newlines directly after logic tokens are ignored | end += 1;
}
template = template.substr(end + found_token.def.close.length); |
Increment the position in the template | error_offset += end + found_token.def.close.length;
} else { |
No more tokens -> add the rest of the template as a raw-type token | tokens.push({
type: Twig.token.type.raw,
value: template
});
template = '';
}
}
return tokens;
};
Twig.compile = function (tokens) { |
Output and intermediate stacks | var output = [],
stack = [], |
The tokens between open and close tags | intermediate_output = [],
token = null,
logic_token = null,
unclosed_token = null, |
Temporary previous token. | prev_token = null, |
The previous token's template | prev_template = null, |
The output token | tok_output = null, |
Logic Token values | type = null,
open = null,
next = null;
while (tokens.length > 0) {
token = tokens.shift();
Twig.log.trace("Compiling token ", token);
switch (token.type) {
case Twig.token.type.raw:
if (stack.length > 0) {
intermediate_output.push(token);
} else {
output.push(token);
}
break;
case Twig.token.type.logic: |
Compile the logic token | logic_token = Twig.logic.compile.apply(this, [token]);
type = logic_token.type;
open = Twig.logic.handler[type].open;
next = Twig.logic.handler[type].next;
Twig.log.trace("Twig.compile: ", "Compiled logic token to ", logic_token,
" next is: ", next, " open is : ", open); |
Not a standalone token, check logic stack to see if this is expected | if (open !== undefined && !open) {
prev_token = stack.pop();
prev_template = Twig.logic.handler[prev_token.type];
if (prev_template.next.indexOf(type) < 0) {
throw new Error(type + " not expected after a " + prev_token.type);
}
prev_token.output = prev_token.output || [];
prev_token.output = prev_token.output.concat(intermediate_output);
intermediate_output = [];
tok_output = {
type: Twig.token.type.logic,
token: prev_token
};
if (stack.length > 0) {
intermediate_output.push(tok_output);
} else {
output.push(tok_output);
}
} |
This token requires additional tokens to complete the logic structure. | if (next !== undefined && next.length > 0) {
Twig.log.trace("Twig.compile: ", "Pushing ", logic_token, " to logic stack.");
if (stack.length > 0) { |
Put any currently held output into the output list of the logic operator currently at the head of the stack before we push a new one on. | prev_token = stack.pop();
prev_token.output = prev_token.output || [];
prev_token.output = prev_token.output.concat(intermediate_output);
stack.push(prev_token);
intermediate_output = [];
} |
Push the new logic token onto the logic stack | stack.push(logic_token);
} else if (open !== undefined && open) {
tok_output = {
type: Twig.token.type.logic,
token: logic_token
}; |
Standalone token (like {% set ... %} | if (stack.length > 0) {
intermediate_output.push(tok_output);
} else {
output.push(tok_output);
}
}
break; |
Do nothing, comments should be ignored | case Twig.token.type.comment:
break;
case Twig.token.type.output:
Twig.expression.compile.apply(this, [token]);
if (stack.length > 0) {
intermediate_output.push(token);
} else {
output.push(token);
}
break;
}
Twig.log.trace("Twig.compile: ", " Output: ", output,
" Logic Stack: ", stack,
" Pending Output: ", intermediate_output );
} |
Verify that there are no logic tokens left in the stack. | if (stack.length > 0) {
unclosed_token = stack.pop();
throw new Error("Unable to find an end tag for " + unclosed_token.type +
", expecting one of " + unclosed_token.next);
}
return output;
};
/**
* Parse a compiled template.
*
* @param {Array} tokens The compiled tokens.
* @param {Object} context The render context.
*
* @return {string} The parsed template.
*/
Twig.parse = function (tokens, context) {
var output = [], |
Track logic chains | chain = true,
that = this; |
Default to an empty object if none provided | context = context || { };
tokens.forEach(function parseToken(token) {
Twig.log.debug("Twig.parse: ", "Parsing token: ", token);
switch (token.type) {
case Twig.token.type.raw:
output.push(token.value);
break;
case Twig.token.type.logic:
var logic_token = token.token,
logic = Twig.logic.parse.apply(that, [logic_token, context, chain]);
if (logic.chain !== undefined) {
chain = logic.chain;
}
if (logic.context !== undefined) {
context = logic.context;
}
if (logic.output !== undefined) {
output.push(logic.output);
}
break;
case Twig.token.type.comment: |
Do nothing, comments should be ignored | break;
case Twig.token.type.output:
Twig.log.debug("Twig.parse: ", "Output token: ", token.stack); |
Parse the given expression in the given context | output.push(Twig.expression.parse.apply(that, [token.stack, context]));
break;
}
});
return output.join("");
};
/**
* Tokenize and compile a string template.
*
* @param {string} data The template.
*
* @return {Array} The compiled tokens.
*/
Twig.prepare = function(data) {
var tokens, raw_tokens; |
Tokenize | Twig.log.debug("Twig.prepare: ", "Tokenizing ", data);
raw_tokens = Twig.tokenize.apply(this, [data]); |
Compile | Twig.log.debug("Twig.prepare: ", "Compiling ", raw_tokens);
tokens = Twig.compile.apply(this, [raw_tokens]);
Twig.log.debug("Twig.prepare: ", "Compiled ", tokens);
return tokens;
}; |
Namespace for template storage and retrieval | Twig.Templates = {
registry: {}
};
/**
* Is this id valid for a twig template?
*
* @param {string} id The ID to check.
*
* @throws {Twig.Error} If the ID is invalid or used.
* @return {boolean} True if the ID is valid.
*/
Twig.validateId = function(id) {
if (id === "prototype") {
throw new Twig.Error(id + " is not a valid twig identifier");
} else if (Twig.Templates.registry.hasOwnProperty(id)) {
throw new Twig.Error("There is already a template with the ID " + id);
}
return true;
}
/**
* Save a template object to the store.
*
* @param {Twig.Template} template The twig.js template to store.
*/
Twig.Templates.save = function(template) {
if (template.id === undefined) {
throw new Twig.Error("Unable to save template with no id");
}
Twig.Templates.registry[template.id] = template;
};
/**
* Load a previously saved template from the store.
*
* @param {string} id The ID of the template to load.
*
* @return {Twig.Template} A twig.js template stored with the provided ID.
*/
Twig.Templates.load = function(id) {
if (!Twig.Templates.registry.hasOwnProperty(id)) {
return null;
}
return Twig.Templates.registry[id];
};
/**
* Load a template from a remote location using AJAX and saves in with the given ID.
*
* Available parameters:
*
* async: Should the HTTP request be performed asynchronously.
* Defaults to true.
* method: What method should be used to load the template
* (fs or ajax)
* precompiled: Has the template already been compiled.
*
* @param {string} location The remote URL to load as a template.
* @param {Object} params The template parameters.
* @param {function} callback A callback triggered when the template finishes loading.
* @param {function} error_callback A callback triggered if an error occurs loading the template.
*
*
*/
Twig.Templates.loadRemote = function(location, params, callback, error_callback) {
var id = params.id,
method = params.method,
async = params.async,
precompiled = params.precompiled,
template = null; |
Default to async | if (async === undefined) async = true; |
Default to the URL so the template is cached. | if (id === undefined) {
id = location;
}
params.id = id; |
Check for existing template | if (Twig.cache && Twig.Templates.registry.hasOwnProperty(id)) { |
A template is already saved with the given id. | if (callback) {
callback(Twig.Templates.registry[id]);
}
return Twig.Templates.registry[id];
}
if (method == 'ajax') {
if (typeof XMLHttpRequest == "undefined") {
throw new Twig.Error("Unsupported platform: Unable to do remote requests " +
"because there is no XMLHTTPRequest implementation");
}
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
var data = null;
if(xmlhttp.readyState == 4) {
Twig.log.debug("Got template ", xmlhttp.responseText);
if (precompiled === true) {
data = JSON.parse(xmlhttp.responseText);
} else {
data = xmlhttp.responseText;
}
params.url = location;
params.data = data;
template = new Twig.Template(params);
if (callback) {
callback(template);
}
}
};
xmlhttp.open("GET", location, async);
xmlhttp.send();
} else { // if method = 'fs' |
Create local scope | (function() {
var fs = require('fs'),
path = require('path'),
data = null,
loadTemplateFn = function(err, data) {
if (err) {
if (error_callback) {
error_callback(err);
}
return;
}
if (precompiled === true) {
data = JSON.parse(data);
}
params.data = data;
params.path = location; |
template is in data | template = new Twig.Template(params);
if (callback) {
callback(template);
}
};
if (async === true) {
fs.stat(location, function (err, stats) {
if (err || !stats.isFile())
throw new Twig.Error("Unable to find template file " + location);
fs.readFile(location, 'utf8', loadTemplateFn);
});
} else {
if (!fs.statSync(location).isFile())
throw new Twig.Error("Unable to find template file " + location);
data = fs.readFileSync(location, 'utf8');
loadTemplateFn(undefined, data);
}
})();
}
if (async === false) {
return template;
} else { |
placeholder for now, should eventually return a deferred object. | return true;
}
}; |
Determine object type | function is(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && clas === type;
}
/**
* Create a new twig.js template.
*
* Parameters: {
* data: The template, either pre-compiled tokens or a string template
* id: The name of this template
* blocks: Any pre-existing block from a child template
* }
*
* @param {Object} params The template parameters.
*/
Twig.Template = function ( params ) {
var data = params.data,
id = params.id,
blocks = params.blocks,
base = params.base,
path = params.path,
url = params.url, |
parser options | options = params.options; |
What is stored in a Twig.TemplateThe Twig Template hold several chucks of data. | this.id = id;
this.base = base;
this.path = path;
this.url = url;
this.options = options;
this.reset(blocks);
if (is('String', data)) {
this.tokens = Twig.prepare.apply(this, [data]);
} else {
this.tokens = data;
}
if (id !== undefined) {
Twig.Templates.save(this);
}
};
Twig.Template.prototype.reset = function(blocks) {
Twig.log.debug("Twig.Template.reset", "Reseting template " + this.id);
this.blocks = {};
this.child = {
blocks: blocks || {}
};
this.extend = null;
};
Twig.Template.prototype.render = function (context, params) {
params = params || {};
var output,
url;
this.context = context || {}; |
Clear any previous state | this.reset();
if (params.blocks) {
this.blocks = params.blocks;
}
output = Twig.parse.apply(this, [this.tokens, this.context]); |
Does this template extend another | if (this.extend) {
url = relativePath(this, this.extend); |
This template extends another, load it with this template's blocks | this.parent = Twig.Templates.loadRemote(url, {
method: this.url?'ajax':'fs',
base: this.base,
async: false,
id: url,
options: this.options
});
return this.parent.render(this.context, {
blocks: this.blocks
});
}
if (params.output == 'blocks') {
return this.blocks;
} else {
return output;
}
};
Twig.Template.prototype.importFile = function(file) {
var url, sub_template;
if ( !this.url && !this.path && this.options.allowInlineIncludes ) {
sub_template = Twig.Templates.load(file);
sub_template.options = this.options;
if ( sub_template ) {
return sub_template;
}
throw new Twig.Error("Didn't find the inline template by id");
}
url = relativePath(this, file); |
Load blocks from an external file | sub_template = Twig.Templates.loadRemote(url, {
method: this.url?'ajax':'fs',
base: this.base,
async: false,
options: this.options,
id: url
});
return sub_template;
};
Twig.Template.prototype.importBlocks = function(file, override) {
var sub_template = this.importFile(file),
context = this.context,
that = this,
key;
override = override || false;
sub_template.render(context); |
Mixin blocks | Object.keys(sub_template.blocks).forEach(function(key) {
if (override || that.blocks[key] === undefined) {
that.blocks[key] = sub_template.blocks[key];
}
});
};
Twig.Template.prototype.compile = function(options) { |
compile the template into raw JS | return Twig.compiler.compile(this, options);
};
/**
* Generate the relative canonical version of a url based on the given base path and file path.
*
* @param {string} template The Twig.Template.
* @param {string} file The file path, relative to the base path.
*
* @return {string} The canonical version of the path.
*/
function relativePath(template, file) {
var base,
base_path,
sep_chr = "/",
new_path = [],
val;
if (template.url) {
if (typeof template.base !== 'undefined') {
base = template.base + ((template.base.charAt(template.base.length-1) === '/') ? '' : '/');
} else {
base = template.url;
}
} else if (template.path) { |
Get the system-specific path separator | var path = require("path"),
sep = path.sep || sep_chr,
relative = new RegExp("^\\.{1,2}" + sep.replace("\\", "\\\\"));
if (template.base !== undefined && file.match(relative) == null) {
file = file.replace(template.base, '');
base = template.base + sep;
} else {
base = template.path;
}
base = base.replace(sep+sep, sep);
sep_chr = sep;
} else {
throw new Twig.Error("Cannot extend an inline template.");
}
base_path = base.split(sep_chr); |
Remove file from url | base_path.pop();
base_path = base_path.concat(file.split(sep_chr));
while (base_path.length > 0) {
val = base_path.shift();
if (val == ".") { |
Ignore | } else if (val == ".." && new_path.length > 0 && new_path[new_path.length-1] != "..") {
new_path.pop();
} else {
new_path.push(val);
}
}
return new_path.join(sep_chr);
}
return Twig;
}) (Twig || { }); |
The following methods are from MDN and are available under a MIT License or are Public Domain. See: | |
twig.fills.jsThis file contains fills for backwards compatability. | (function() {
"use strict"; |
Handle methods that don't yet exist in every browser | if (!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g,'');
}
};
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
if (this === void 0 || this === null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (len === 0) {
return -1;
}
var n = 0;
if (arguments.length > 0) {
n = Number(arguments[1]);
if (n !== n) { // shortcut for verifying if it's NaN
n = 0;
} else if (n !== 0 && n !== Infinity && n !== -Infinity) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
if (n >= len) {
return -1;
}
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
for (; k < len; k++) {
if (k in t && t[k] === searchElement) {
return k;
}
}
return -1;
}
}; |
Production steps of ECMA-262, Edition 5, 15.4.4.18 Reference: http://es5.github.com/#x15.4.4.18 | if ( !Array.prototype.forEach ) {
Array.prototype.forEach = function( callback, thisArg ) {
var T, k;
if ( this == null ) {
throw new TypeError( " this is null or not defined" );
} |
| var O = Object(this); |
| var len = O.length >>> 0; // Hack to convert O.length to a UInt32 |
| if ( {}.toString.call(callback) != "[object Function]" ) {
throw new TypeError( callback + " is not a function" );
} |
| if ( thisArg ) {
T = thisArg;
} |
| k = 0; |
| while( k < len ) {
var kValue; |
a. Let Pk be ToString(k). This is implicit for LHS operands of the in operator b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. This step can be combined with c c. If kPresent is true, then | if ( k in O ) { |
i. Let kValue be the result of calling the Get internal method of O with argument Pk. | kValue = O[ k ]; |
ii. Call the Call internal method of callback with T as the this value and argument list containing kValue, k, and O. | callback.call( T, kValue, k, O );
} |
d. Increase k by 1. | k++;
} |
| };
};
if(!Object.keys) Object.keys = function(o){
if (o !== Object(o)) {
throw new TypeError('Object.keys called on non-object');
}
var ret = [], p;
for (p in o) if (Object.prototype.hasOwnProperty.call(o, p)) ret.push(p);
return ret;
}
})(); |
twig.lib.jsThis file contains 3rd party libraries used within twig. Copies of the licenses for the code included here can be found in the LICENSES.md file. | var Twig = (function(Twig) { |
Namespace for libraries | Twig.lib = { };
/**
sprintf() for JavaScript 0.7-beta1
http://www.diveintojavascript.com/projects/javascript-sprintf
**/
var sprintf = (function() {
function get_type(variable) {
return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
}
function str_repeat(input, multiplier) {
for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
return output.join('');
}
var str_format = function() {
if (!str_format.cache.hasOwnProperty(arguments[0])) {
str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
}
return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
};
str_format.format = function(parse_tree, argv) {
var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
for (i = 0; i < tree_length; i++) {
node_type = get_type(parse_tree[i]);
if (node_type === 'string') {
output.push(parse_tree[i]);
}
else if (node_type === 'array') {
match = parse_tree[i]; // convenience purposes only
if (match[2]) { // keyword argument
arg = argv[cursor];
for (k = 0; k < match[2].length; k++) {
if (!arg.hasOwnProperty(match[2][k])) {
throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
}
arg = arg[match[2][k]];
}
}
else if (match[1]) { // positional argument (explicit)
arg = argv[match[1]];
}
else { // positional argument (implicit)
arg = argv[cursor++];
}
if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
}
switch (match[8]) {
case 'b': arg = arg.toString(2); break;
case 'c': arg = String.fromCharCode(arg); break;
case 'd': arg = parseInt(arg, 10); break;
case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
case 'o': arg = arg.toString(8); break;
case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
case 'u': arg = Math.abs(arg); break;
case 'x': arg = arg.toString(16); break;
case 'X': arg = arg.toString(16).toUpperCase(); break;
}
arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
pad_length = match[6] - String(arg).length;
pad = match[6] ? str_repeat(pad_character, pad_length) : '';
output.push(match[5] ? arg + pad : pad + arg);
}
}
return output.join('');
};
str_format.cache = {};
str_format.parse = function(fmt) {
var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
while (_fmt) {
if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
parse_tree.push(match[0]);
}
else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
parse_tree.push('%');
}
else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
if (match[2]) {
arg_names |= 1;
var field_list = [], replacement_field = match[2], field_match = [];
if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
}
else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
}
else {
throw('[sprintf] huh?');
}
}
}
else {
throw('[sprintf] huh?');
}
match[2] = field_list;
}
else {
arg_names |= 2;
}
if (arg_names === 3) {
throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
}
parse_tree.push(match);
}
else {
throw('[sprintf] huh?');
}
_fmt = _fmt.substring(match[0].length);
}
return parse_tree;
};
return str_format;
})();
var vsprintf = function(fmt, argv) {
argv.unshift(fmt);
return sprintf.apply(null, argv);
}; |
Expose to Twig | Twig.lib.sprintf = sprintf;
Twig.lib.vsprintf = vsprintf;
/**
* jPaq - A fully customizable JavaScript/JScript library
* http://jpaq.org/
*
* Copyright (c) 2011 Christopher West
* Licensed under the MIT license.
* http://jpaq.org/license/
*
* Version: 1.0.6.0000W
* Revised: April 6, 2011
*/
; (function() {
var shortDays = "Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(",");
var fullDays = "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(",");
var shortMonths = "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(",");
var fullMonths = "January,February,March,April,May,June,July,August,September,October,November,December".split(",");
function getOrdinalFor(intNum) {
return (((intNum = Math.abs(intNum) % 100) % 10 == 1 && intNum != 11) ? "st"
: (intNum % 10 == 2 && intNum != 12) ? "nd" : (intNum % 10 == 3
&& intNum != 13) ? "rd" : "th");
}
function getISO8601Year(aDate) {
var d = new Date(aDate.getFullYear() + 1, 0, 4);
if((d - aDate) / 86400000 < 7 && (aDate.getDay() + 6) % 7 < (d.getDay() + 6) % 7)
return d.getFullYear();
if(aDate.getMonth() > 0 || aDate.getDate() >= 4)
return aDate.getFullYear();
return aDate.getFullYear() - (((aDate.getDay() + 6) % 7 - aDate.getDate() > 2) ? 1 : 0);
}
function getISO8601Week(aDate) { |
Get a day during the first week of the year. | var d = new Date(getISO8601Year(aDate), 0, 4); |
Get the first monday of the year. | d.setDate(d.getDate() - (d.getDay() + 6) % 7);
return parseInt((aDate - d) / 604800000) + 1;
}
Twig.lib.formatDate = function(date, format) { |
/ | if(typeof format !== "string" || /^\s*$/.test(format))
return date + "";
var jan1st = new Date(date.getFullYear(), 0, 1);
var me = date;
return format.replace(/[dDjlNSwzWFmMntLoYyaABgGhHisu]/g, function(option) {
switch(option) { |
Day of the month, 2 digits with leading zeros | case "d": return ("0" + me.getDate()).replace(/^.+(..)$/, "$1"); |
A textual representation of a day, three letters | case "D": return shortDays[me.getDay()]; |
Day of the month without leading zeros | case "j": return me.getDate(); |
A full textual representation of the day of the week | case "l": return fullDays[me.getDay()]; |
ISO-8601 numeric representation of the day of the week | case "N": return (me.getDay() + 6) % 7 + 1; |
English ordinal suffix for the day of the month, 2 characters | case "S": return getOrdinalFor(me.getDate()); |
Numeric representation of the day of the week | case "w": return me.getDay(); |
The day of the year (starting from 0) | case "z": return Math.ceil((jan1st - me) / 86400000); |
ISO-8601 week number of year, weeks starting on Monday | case "W": return ("0" + getISO8601Week(me)).replace(/^.(..)$/, "$1"); |
A full textual representation of a month, such as January or March | case "F": return fullMonths[me.getMonth()]; |
Numeric representation of a month, with leading zeros | case "m": return ("0" + (me.getMonth() + 1)).replace(/^.+(..)$/, "$1"); |
A short textual representation of a month, three letters | case "M": return shortMonths[me.getMonth()]; |
Numeric representation of a month, without leading zeros | case "n": return me.getMonth() + 1; |
Number of days in the given month | case "t": return new Date(me.getFullYear(), me.getMonth() + 1, -1).getDate(); |
Whether it's a leap year | case "L": return new Date(me.getFullYear(), 1, 29).getDate() == 29 ? 1 : 0; |
ISO-8601 year number. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. | case "o": return getISO8601Year(me); |
A full numeric representation of a year, 4 digits | case "Y": return me.getFullYear(); |
A two digit representation of a year | case "y": return (me.getFullYear() + "").replace(/^.+(..)$/, "$1"); |
Lowercase Ante meridiem and Post meridiem | case "a": return me.getHours() < 12 ? "am" : "pm"; |
Uppercase Ante meridiem and Post meridiem | case "A": return me.getHours() < 12 ? "AM" : "PM"; |
Swatch Internet time | case "B": return Math.floor((((me.getUTCHours() + 1) % 24) + me.getUTCMinutes() / 60 + me.getUTCSeconds() / 3600) * 1000 / 24); |
12-hour format of an hour without leading zeros | case "g": return me.getHours() % 12 != 0 ? me.getHours() % 12 : 12; |
24-hour format of an hour without leading zeros | case "G": return me.getHours(); |
12-hour format of an hour with leading zeros | case "h": return ("0" + (me.getHours() % 12 != 0 ? me.getHours() % 12 : 12)).replace(/^.+(..)$/, "$1"); |
24-hour format of an hour with leading zeros | case "H": return ("0" + me.getHours()).replace(/^.+(..)$/, "$1"); |
Minutes with leading zeros | case "i": return ("0" + me.getMinutes()).replace(/^.+(..)$/, "$1"); |
Seconds, with leading zeros | case "s": return ("0" + me.getSeconds()).replace(/^.+(..)$/, "$1"); |
Milliseconds | case "u": return me.getMilliseconds();
}
});
};
})();
Twig.lib.strip_tags = function(input, allowed) { |
Strips HTML and PHP tags from a string version: 1109.2015 discuss at: http://phpjs.org/functions/striptags + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + improved by: Luke Godfrey + input by: Pul + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + bugfixed by: Onno Marsman + input by: Alex + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + input by: Marc Palau + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + input by: Brett Zamir (http://brett-zamir.me) + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + bugfixed by: Eric Nagel + input by: Bobby Drake + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + bugfixed by: Tomasz Wesolowski + input by: Evertjan Garretsen + revised by: Rafał Kukawski (http://blog.kukawski.pl/) * example 1: striptags(' Kevin van Zonneveld', ''); * returns 1: 'Kevin van Zonneveld' * example 2: striptags('Kevin '); * returns 2: ' Kevin van Zonneveld ' * example 3: striptags("Kevin van Zonneveld", ""); * returns 3: 'Kevin van Zonneveld' * example 4: striptags('1 < 5 5 > 1'); * returns 4: '1 < 5 5 > 1' * example 5: striptags('11'); * returns 5: '1 1' * example 6: striptags('1 1', ' '); * returns 6: '1 1' * example 7: striptags('1 1', ' '); * returns 7: '1 1' | allowed = (((allowed || "") + "").toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join(''); // making sure the allowed arg is a string containing only tags in lowercase (<a><b><c>)
var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) {
return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
});
}
Twig.lib.strtotime = function (str, now) { |
http://kevin.vanzonneveld.net + original by: Caio Ariede (http://caioariede.com) + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + input by: David + improved by: Caio Ariede (http://caioariede.com) + improved by: Brett Zamir (http://brett-zamir.me) + bugfixed by: Wagner B. Soares + bugfixed by: Artur Tchernychev % note 1: Examples all have a fixed timestamp to prevent tests to fail because of variable time(zones) * example 1: strtotime('+1 day', 1129633200); * returns 1: 1129719600 * example 2: strtotime('+1 week 2 days 4 hours 2 seconds', 1129633200); * returns 2: 1130425202 * example 3: strtotime('last month', 1129633200); * returns 3: 1127041200 * example 4: strtotime('2009-05-04 08:30:00'); * returns 4: 1241418600 | var i, l, match, s, parse = '';
str = str.replace(/\s{2,}|^\s|\s$/g, ' '); // unecessary spaces
str = str.replace(/[\t\r\n]/g, ''); // unecessary chars
if (str === 'now') {
return now === null || isNaN(now) ? new Date().getTime() / 1000 | 0 : now | 0;
} else if (!isNaN(parse = Date.parse(str))) {
return parse / 1000 | 0;
} else if (now) {
now = new Date(now * 1000); // Accept PHP-style seconds
} else {
now = new Date();
}
str = str.toLowerCase();
var __is = {
day: {
'sun': 0,
'mon': 1,
'tue': 2,
'wed': 3,
'thu': 4,
'fri': 5,
'sat': 6
},
mon: [
'jan',
'feb',
'mar',
'apr',
'may',
'jun',
'jul',
'aug',
'sep',
'oct',
'nov',
'dec'
]
};
var process = function (m) {
var ago = (m[2] && m[2] === 'ago');
var num = (num = m[0] === 'last' ? -1 : 1) * (ago ? -1 : 1);
switch (m[0]) {
case 'last':
case 'next':
switch (m[1].substring(0, 3)) {
case 'yea':
now.setFullYear(now.getFullYear() + num);
break;
case 'wee':
now.setDate(now.getDate() + (num * 7));
break;
case 'day':
now.setDate(now.getDate() + num);
break;
case 'hou':
now.setHours(now.getHours() + num);
break;
case 'min':
now.setMinutes(now.getMinutes() + num);
break;
case 'sec':
now.setSeconds(now.getSeconds() + num);
break;
case 'mon':
if (m[1] === "month") {
now.setMonth(now.getMonth() + num);
break;
} |
fall through | default:
var day = __is.day[m[1].substring(0, 3)];
if (typeof day !== 'undefined') {
var diff = day - now.getDay();
if (diff === 0) {
diff = 7 * num;
} else if (diff > 0) {
if (m[0] === 'last') {
diff -= 7;
}
} else {
if (m[0] === 'next') {
diff += 7;
}
}
now.setDate(now.getDate() + diff);
now.setHours(0, 0, 0, 0); // when jumping to a specific last/previous day of week, PHP sets the time to 00:00:00
}
}
break;
default:
if (/\d+/.test(m[0])) {
num *= parseInt(m[0], 10);
switch (m[1].substring(0, 3)) {
case 'yea':
now.setFullYear(now.getFullYear() + num);
break;
case 'mon':
now.setMonth(now.getMonth() + num);
break;
case 'wee':
now.setDate(now.getDate() + (num * 7));
break;
case 'day':
now.setDate(now.getDate() + num);
break;
case 'hou':
now.setHours(now.getHours() + num);
break;
case 'min':
now.setMinutes(now.getMinutes() + num);
break;
case 'sec':
now.setSeconds(now.getSeconds() + num);
break;
}
} else {
return false;
}
break;
}
return true;
};
match = str.match(/^(\d{2,4}-\d{2}-\d{2})(?:\s(\d{1,2}:\d{2}(:\d{2})?)?(?:\.(\d+))?)?$/);
if (match !== null) {
if (!match[2]) {
match[2] = '00:00:00';
} else if (!match[3]) {
match[2] += ':00';
}
s = match[1].split(/-/g);
s[1] = __is.mon[s[1] - 1] || s[1];
s[0] = +s[0];
s[0] = (s[0] >= 0 && s[0] <= 69) ? '20' + (s[0] < 10 ? '0' + s[0] : s[0] + '') : (s[0] >= 70 && s[0] <= 99) ? '19' + s[0] : s[0] + '';
return parseInt(this.strtotime(s[2] + ' ' + s[1] + ' ' + s[0] + ' ' + match[2]) + (match[4] ? match[4] / 1000 : ''), 10);
}
var regex = '([+-]?\\d+\\s' + '(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?' + '|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday' + '|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday)' + '|(last|next)\\s' + '(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?' + '|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday' + '|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday))' + '(\\sago)?';
match = str.match(new RegExp(regex, 'gi')); // Brett: seems should be case insensitive per docs, so added 'i'
if (match === null) {
return false;
}
for (i = 0, l = match.length; i < l; i++) {
if (!process(match[i].split(' '))) {
return false;
}
}
return now.getTime() / 1000 | 0;
};
Twig.lib.is = function(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && clas === type;
}; |
shallow-copy an object | Twig.lib.copy = function(src) {
var target = {},
key;
for (key in src)
target[key] = src[key];
return target;
};
Twig.lib.replaceAll = function(string, search, replace) {
return string.split(search).join(replace);
};
return Twig;
})(Twig || { }); |
| |
twig.logic.jsThis file handles tokenizing, compiling and parsing logic tokens. {% ... %} | var Twig = (function (Twig) {
"use strict";
/**
* Namespace for logic handling.
*/
Twig.logic = {};
/**
* Logic token types.
*/
Twig.logic.type = {
if_: 'Twig.logic.type.if',
endif: 'Twig.logic.type.endif',
for_: 'Twig.logic.type.for',
endfor: 'Twig.logic.type.endfor',
else_: 'Twig.logic.type.else',
elseif: 'Twig.logic.type.elseif',
set: 'Twig.logic.type.set',
filter: 'Twig.logic.type.filter',
endfilter: 'Twig.logic.type.endfilter',
block: 'Twig.logic.type.block',
endblock: 'Twig.logic.type.endblock',
extends_: 'Twig.logic.type.extends',
use: 'Twig.logic.type.use',
include: 'Twig.logic.type.include',
spaceless: 'Twig.logic.type.spaceless',
endspaceless: 'Twig.logic.type.endspaceless'
}; |
Regular expressions for handling logic tokens. Properties:
Functions: | Twig.logic.definitions = [
{
/**
* If type logic tokens.
*
* Format: {% if expression %}
*/
type: Twig.logic.type.if_,
regex: /^if\s+([^\s].+)$/,
next: [
Twig.logic.type.else_,
Twig.logic.type.elseif,
Twig.logic.type.endif
],
open: true,
compile: function (token) {
var expression = token.match[1]; |
Compile the expression. | token.stack = Twig.expression.compile.apply(this, [{
type: Twig.expression.type.expression,
value: expression
}]).stack;
delete token.match;
return token;
},
parse: function (token, context, chain) {
var output = '', |
Parse the expression | result = Twig.expression.parse.apply(this, [token.stack, context]); |
Start a new logic chain | chain = true;
if (result) {
chain = false; |
parse if output | output = Twig.parse.apply(this, [token.output, context]);
}
return {
chain: chain,
output: output
};
}
},
{
/**
* Else if type logic tokens.
*
* Format: {% elseif expression %}
*/
type: Twig.logic.type.elseif,
regex: /^elseif\s+([^\s].*)$/,
next: [
Twig.logic.type.else_,
Twig.logic.type.elseif,
Twig.logic.type.endif
],
open: false,
compile: function (token) {
var expression = token.match[1]; |
Compile the expression. | token.stack = Twig.expression.compile.apply(this, [{
type: Twig.expression.type.expression,
value: expression
}]).stack;
delete token.match;
return token;
},
parse: function (token, context, chain) {
var output = '';
if (chain && Twig.expression.parse.apply(this, [token.stack, context]) === true) {
chain = false; |
parse if output | output = Twig.parse.apply(this, [token.output, context]);
}
return {
chain: chain,
output: output
};
}
},
{
/**
* Else if type logic tokens.
*
* Format: {% elseif expression %}
*/
type: Twig.logic.type.else_,
regex: /^else$/,
next: [
Twig.logic.type.endif,
Twig.logic.type.endfor
],
open: false,
parse: function (token, context, chain) {
var output = '';
if (chain) {
output = Twig.parse.apply(this, [token.output, context]);
}
return {
chain: chain,
output: output
};
}
},
{
/**
* End if type logic tokens.
*
* Format: {% endif %}
*/
type: Twig.logic.type.endif,
regex: /^endif$/,
next: [ ],
open: false
},
{
/**
* For type logic tokens.
*
* Format: {% for expression %}
*/
type: Twig.logic.type.for_,
regex: /^for\s+([a-zA-Z0-9_,\s]+)\s+in\s+([^\s].*?)(?:\s+if\s+([^\s].*))?$/,
next: [
Twig.logic.type.else_,
Twig.logic.type.endfor
],
open: true,
compile: function (token) {
var key_value = token.match[1],
expression = token.match[2],
conditional = token.match[3],
kv_split = null;
token.key_var = null;
token.value_var = null;
if (key_value.indexOf(",") >= 0) {
kv_split = key_value.split(',');
if (kv_split.length === 2) {
token.key_var = kv_split[0].trim();
token.value_var = kv_split[1].trim();
} else {
throw new Twig.Error("Invalid expression in for loop: " + key_value);
}
} else {
token.value_var = key_value;
} |
Valid expressions for a for loop for item in expression for key,item in expression | |
Compile the expression. | token.expression = Twig.expression.compile.apply(this, [{
type: Twig.expression.type.expression,
value: expression
}]).stack; |
Compile the conditional (if available) | if (conditional) {
token.conditional = Twig.expression.compile.apply(this, [{
type: Twig.expression.type.expression,
value: conditional
}]).stack;
}
delete token.match;
return token;
},
parse: function (token, context, continue_chain) { |
Parse expression | var result = Twig.expression.parse.apply(this, [token.expression, context]),
output = [],
len,
index = 0,
keyset,
that = this,
conditional = token.conditional,
buildLoop = function(index, len) {
var isConditional = conditional !== undefined;
return {
index: index+1,
index0: index,
revindex: isConditional?undefined:len-index,
revindex0: isConditional?undefined:len-index-1,
first: (index === 0),
last: isConditional?undefined:(index === len-1),
length: isConditional?undefined:len,
parent: context
};
},
loop = function(key, value) {
var inner_context = Twig.lib.copy(context);
inner_context[token.value_var] = value;
if (token.key_var) {
inner_context[token.key_var] = key;
} |
Loop object | inner_context.loop = buildLoop(index, len);
if (conditional === undefined ||
Twig.expression.parse.apply(that, [conditional, inner_context]))
{
output.push(Twig.parse.apply(that, [token.output, inner_context]));
index += 1;
}
};
if (result instanceof Array) {
len = result.length;
result.forEach(function (value) {
var key = index;
loop(key, value);
});
} else if (result instanceof Object) {
if (result._keys !== undefined) {
keyset = result._keys;
} else {
keyset = Object.keys(result);
}
len = keyset.length;
keyset.forEach(function(key) { |
Ignore the _keys property, it's internal to twig.js | if (key === "_keys") return;
loop(key, result[key]);
});
} |
Only allow else statements if no output was generated | continue_chain = (output.length === 0);
return {
chain: continue_chain,
output: output.join("")
};
}
},
{
/**
* End if type logic tokens.
*
* Format: {% endif %}
*/
type: Twig.logic.type.endfor,
regex: /^endfor$/,
next: [ ],
open: false
},
{
/**
* Set type logic tokens.
*
* Format: {% set key = expression %}
*/
type: Twig.logic.type.set,
regex: /^set\s+([a-zA-Z0-9_,\s]+)\s*=\s*(.+)$/,
next: [ ],
open: true,
compile: function (token) {
var key = token.match[1].trim(),
expression = token.match[2], |
Compile the expression. | expression_stack = Twig.expression.compile.apply(this, [{
type: Twig.expression.type.expression,
value: expression
}]).stack;
token.key = key;
token.expression = expression_stack;
delete token.match;
return token;
},
parse: function (token, context, continue_chain) {
var value = Twig.expression.parse.apply(this, [token.expression, context]),
key = token.key; |
set on both the global and local context | this.context[key] = value;
context[key] = value;
return {
chain: continue_chain,
context: context
};
}
},
{
/**
* Filter logic tokens.
*
* Format: {% filter upper %} or {% filter lower|escape %}
*/
type: Twig.logic.type.filter,
regex: /^filter\s+(.+)$/,
next: [
Twig.logic.type.endfilter
],
open: true,
compile: function (token) {
var expression = "|" + token.match[1].trim(); |
Compile the expression. | token.stack = Twig.expression.compile.apply(this, [{
type: Twig.expression.type.expression,
value: expression
}]).stack;
delete token.match;
return token;
},
parse: function (token, context, chain) {
var unfiltered = Twig.parse.apply(this, [token.output, context]),
stack = [{
type: Twig.expression.type.string,
value: unfiltered
}].concat(token.stack);
var output = Twig.expression.parse.apply(this, [stack, context]);
return {
chain: chain,
output: output
};
}
},
{
/**
* End filter logic tokens.
*
* Format: {% endfilter %}
*/
type: Twig.logic.type.endfilter,
regex: /^endfilter$/,
next: [ ],
open: false
},
{
/**
* Block logic tokens.
*
* Format: {% block title %}
*/
type: Twig.logic.type.block,
regex: /^block\s+([a-zA-Z0-9_]+)$/,
next: [
Twig.logic.type.endblock
],
open: true,
compile: function (token) {
token.block = token.match[1].trim();
delete token.match;
return token;
},
parse: function (token, context, chain) {
var block_output = "",
output = "",
hasParent = this.blocks[token.block] && this.blocks[token.block].indexOf(Twig.placeholders.parent) > -1; |
Don't override previous blocks | if (this.blocks[token.block] === undefined || hasParent) {
block_output = Twig.expression.parse.apply(this, [{
type: Twig.expression.type.string,
value: Twig.parse.apply(this, [token.output, context])
}, context]);
if (hasParent) {
this.blocks[token.block] = this.blocks[token.block].replace(Twig.placeholders.parent, block_output);
} else {
this.blocks[token.block] = block_output;
}
} |
Check if a child block has been set from a template extending this one. | if (this.child.blocks[token.block]) {
output = this.child.blocks[token.block];
} else {
output = this.blocks[token.block];
}
return {
chain: chain,
output: output
};
}
},
{
/**
* End filter logic tokens.
*
* Format: {% endfilter %}
*/
type: Twig.logic.type.endblock,
regex: /^endblock$/,
next: [ ],
open: false
},
{
/**
* Block logic tokens.
*
* Format: {% extends "template.twig" %}
*/
type: Twig.logic.type.extends_,
regex: /^extends\s+(.+)$/,
next: [ ],
open: true,
compile: function (token) {
var expression = token.match[1].trim();
delete token.match;
token.stack = Twig.expression.compile.apply(this, [{
type: Twig.expression.type.expression,
value: expression
}]).stack;
return token;
},
parse: function (token, context, chain) { |
Resolve filename | var file = Twig.expression.parse.apply(this, [token.stack, context]); |
Set parent template | this.extend = file;
return {
chain: chain,
output: ''
};
}
},
{
/**
* Block logic tokens.
*
* Format: {% extends "template.twig" %}
*/
type: Twig.logic.type.use,
regex: /^use\s+(.+)$/,
next: [ ],
open: true,
compile: function (token) {
var expression = token.match[1].trim();
delete token.match;
token.stack = Twig.expression.compile.apply(this, [{
type: Twig.expression.type.expression,
value: expression
}]).stack;
return token;
},
parse: function (token, context, chain) { |
Resolve filename | var file = Twig.expression.parse.apply(this, [token.stack, context]); |
Import blocks | this.importBlocks(file);
return {
chain: chain,
output: ''
};
}
},
{
/**
* Block logic tokens.
*
* Format: {% includes "template.twig" [with {some: 'values'} only] %}
*/
type: Twig.logic.type.include,
regex: /^include\s+(ignore missing\s+)?(.+?)\s*(?:with\s+(.+?))?\s*(only)?$/,
next: [ ],
open: true,
compile: function (token) {
var match = token.match,
includeMissing = match[1] !== undefined,
expression = match[2].trim(),
withContext = match[3],
only = ((match[4] !== undefined) && match[4].length);
delete token.match;
token.only = only;
token.includeMissing = includeMissing;
token.stack = Twig.expression.compile.apply(this, [{
type: Twig.expression.type.expression,
value: expression
}]).stack;
if (withContext !== undefined) {
token.withStack = Twig.expression.compile.apply(this, [{
type: Twig.expression.type.expression,
value: withContext.trim()
}]).stack;
}
return token;
},
parse: function (token, context, chain) { |
Resolve filename | var innerContext = {},
withContext,
i,
template;
if (!token.only) {
for (i in context) {
if (context.hasOwnProperty(i))
innerContext[i] = context[i];
}
}
if (token.withStack !== undefined) {
withContext = Twig.expression.parse.apply(this, [token.withStack, context]);
for (i in withContext) {
if (withContext.hasOwnProperty(i))
innerContext[i] = withContext[i];
}
}
var file = Twig.expression.parse.apply(this, [token.stack, innerContext]); |
Import file | template = this.importFile(file);
return {
chain: chain,
output: template.render(innerContext)
};
}
},
{
type: Twig.logic.type.spaceless,
regex: /^spaceless$/,
next: [
Twig.logic.type.endspaceless
],
open: true, |
Parse the html and return it without any spaces between tags | parse: function (token, context, chain) {
var // Parse the output without any filter
unfiltered = Twig.parse.apply(this, [token.output, context]), |
A regular expression to find closing and opening tags with spaces between them | rBetweenTagSpaces = />\s+</g, |
Replace all space between closing and opening html tags | output = unfiltered.replace(rBetweenTagSpaces,'><').trim();
return {
chain: chain,
output: output
};
}
}, |
Add the {% endspaceless %} token | {
type: Twig.logic.type.endspaceless,
regex: /^endspaceless$/,
next: [ ],
open: false
}
];
/**
* Registry for logic handlers.
*/
Twig.logic.handler = {};
/**
* Define a new token type, available at Twig.logic.type.{type}
*/
Twig.logic.extendType = function (type, value) {
value = value || ("Twig.logic.type" + type);
Twig.logic.type[type] = value;
};
/**
* Extend the logic parsing functionality with a new token definition.
*
* // Define a new tag
* Twig.logic.extend({
* type: Twig.logic.type.{type},
* // The pattern to match for this token
* regex: ...,
* // What token types can follow this token, leave blank if any.
* next: [ ... ]
* // Create and return compiled version of the token
* compile: function(token) { ... }
* // Parse the compiled token with the context provided by the render call
* // and whether this token chain is complete.
* parse: function(token, context, chain) { ... }
* });
*
* @param {Object} definition The new logic expression.
*/
Twig.logic.extend = function (definition) {
if (!definition.type) {
throw new Twig.Error("Unable to extend logic definition. No type provided for " + definition);
}
if (Twig.logic.type[definition.type]) {
throw new Twig.Error("Unable to extend logic definitions. Type " +
definition.type + " is already defined.");
} else {
Twig.logic.extendType(definition.type);
}
Twig.logic.handler[definition.type] = definition;
}; |
Extend with built-in expressions | while (Twig.logic.definitions.length > 0) {
Twig.logic.extend(Twig.logic.definitions.shift());
}
/**
* Compile a logic token into an object ready for parsing.
*
* @param {Object} raw_token An uncompiled logic token.
*
* @return {Object} A compiled logic token, ready for parsing.
*/
Twig.logic.compile = function (raw_token) {
var expression = raw_token.value.trim(),
token = Twig.logic.tokenize.apply(this, [expression]),
token_template = Twig.logic.handler[token.type]; |
Check if the token needs compiling | if (token_template.compile) {
token = token_template.compile.apply(this, [token]);
Twig.log.trace("Twig.logic.compile: ", "Compiled logic token to ", token);
}
return token;
};
/**
* Tokenize logic expressions. This function matches token expressions against regular
* expressions provided in token definitions provided with Twig.logic.extend.
*
* @param {string} expression the logic token expression to tokenize
* (i.e. what's between {% and %})
*
* @return {Object} The matched token with type set to the token type and match to the regex match.
*/
Twig.logic.tokenize = function (expression) {
var token = {},
token_template_type = null,
token_type = null,
token_regex = null,
regex_array = null,
regex = null,
match = null; |
Ignore whitespace around expressions. | expression = expression.trim();
for (token_template_type in Twig.logic.handler) {
if (Twig.logic.handler.hasOwnProperty(token_template_type)) { |
Get the type and regex for this template type | token_type = Twig.logic.handler[token_template_type].type;
token_regex = Twig.logic.handler[token_template_type].regex; |
Handle multiple regular expressions per type. | regex_array = [];
if (token_regex instanceof Array) {
regex_array = token_regex;
} else {
regex_array.push(token_regex);
} |
Check regular expressions in the order they were specified in the definition. | while (regex_array.length > 0) {
regex = regex_array.shift();
match = regex.exec(expression.trim());
if (match !== null) {
token.type = token_type;
token.match = match;
Twig.log.trace("Twig.logic.tokenize: ", "Matched a ", token_type, " regular expression of ", match);
return token;
}
}
}
} |
No regex matches | throw new Twig.Error("Unable to parse '" + expression.trim() + "'");
};
/**
* Parse a logic token within a given context.
*
* What are logic chains?
* Logic chains represent a series of tokens that are connected,
* for example:
* {% if ... %} {% else %} {% endif %}
*
* The chain parameter is used to signify if a chain is open of closed.
* open:
* More tokens in this chain should be parsed.
* closed:
* This token chain has completed parsing and any additional
* tokens (else, elseif, etc...) should be ignored.
*
* @param {Object} token The compiled token.
* @param {Object} context The render context.
* @param {boolean} chain Is this an open logic chain. If false, that means a
* chain is closed and no further cases should be parsed.
*/
Twig.logic.parse = function (token, context, chain) {
var output = '',
token_template;
context = context || { };
Twig.log.debug("Twig.logic.parse: ", "Parsing logic token ", token);
token_template = Twig.logic.handler[token.type];
if (token_template.parse) {
output = token_template.parse.apply(this, [token, context, chain]);
}
return output;
};
return Twig;
})(Twig || { }); |
| |
twig.expression.jsThis file handles tokenizing, compiling and parsing expressions. | var Twig = (function (Twig) {
"use strict";
/**
* Namespace for expression handling.
*/
Twig.expression = { };
/**
* Reserved word that can't be used as variable names.
*/
Twig.expression.reservedWords = [
"true", "false", "null"
];
/**
* The type of tokens used in expressions.
*/
Twig.expression.type = {
comma: 'Twig.expression.type.comma',
operator: {
unary: 'Twig.expression.type.operator.unary',
binary: 'Twig.expression.type.operator.binary'
},
string: 'Twig.expression.type.string',
bool: 'Twig.expression.type.bool',
array: {
start: 'Twig.expression.type.array.start',
end: 'Twig.expression.type.array.end'
},
object: {
start: 'Twig.expression.type.object.start',
end: 'Twig.expression.type.object.end'
},
parameter: {
start: 'Twig.expression.type.parameter.start',
end: 'Twig.expression.type.parameter.end'
},
key: {
period: 'Twig.expression.type.key.period',
brackets: 'Twig.expression.type.key.brackets'
},
filter: 'Twig.expression.type.filter',
_function: 'Twig.expression.type._function',
variable: 'Twig.expression.type.variable',
number: 'Twig.expression.type.number',
_null: 'Twig.expression.type.null',
test: 'Twig.expression.type.test'
};
Twig.expression.set = { |
What can follow an expression (in general) | operations: [
Twig.expression.type.filter,
Twig.expression.type.operator.unary,
Twig.expression.type.operator.binary,
Twig.expression.type.array.end,
Twig.expression.type.object.end,
Twig.expression.type.parameter.end,
Twig.expression.type.comma,
Twig.expression.type.test
],
expressions: [
Twig.expression.type._function,
Twig.expression.type.bool,
Twig.expression.type.string,
Twig.expression.type.variable,
Twig.expression.type.number,
Twig.expression.type._null,
Twig.expression.type.parameter.start,
Twig.expression.type.array.start,
Twig.expression.type.object.start
]
}; |
Most expressions allow a '.' or '[' after them, so we provide a convenience set | Twig.expression.set.operations_extended = Twig.expression.set.operations.concat([
Twig.expression.type.key.period,
Twig.expression.type.key.brackets]); |
Some commonly used compile and parse functions. | Twig.expression.fn = {
compile: {
push: function(token, stack, output) {
output.push(token);
},
push_both: function(token, stack, output) {
output.push(token);
stack.push(token);
}
},
parse: {
push: function(token, stack, context) {
stack.push(token);
},
push_value: function(token, stack, context) {
stack.push(token.value);
}
}
}; |
The regular expressions and compile/parse logic used to match tokens in expressions. Properties:
Functions: | Twig.expression.definitions = [
{
type: Twig.expression.type.test,
regex: /^is\s+(not)?\s*([a-zA-Z_][a-zA-Z0-9_]*)/,
next: Twig.expression.set.operations.concat([Twig.expression.type.parameter.start]),
compile: function(token, stack, output) {
token.filter = token.match[2];
token.modifier = token.match[1];
delete token.match;
delete token.value;
output.push(token);
},
parse: function(token, stack, context) {
var value = stack.pop(),
params = token.params && Twig.expression.parse.apply(this, [token.params, context]),
result = Twig.test(token.filter, value, params);
if (token.modifier == 'not') {
stack.push(!result);
} else {
stack.push(result);
}
}
},
{
type: Twig.expression.type.comma, |
Match a comma | regex: /^,/,
next: Twig.expression.set.expressions,
compile: function(token, stack, output) {
var i = stack.length - 1,
stack_token;
delete token.match;
delete token.value; |
pop tokens off the stack until the start of the object | for(;i >= 0; i--) {
stack_token = stack.pop();
if (stack_token.type === Twig.expression.type.object.start
|| stack_token.type === Twig.expression.type.parameter.start
|| stack_token.type === Twig.expression.type.array.start) {
stack.push(stack_token);
break;
}
output.push(stack_token);
}
output.push(token);
}
},
{
type: Twig.expression.type.operator.binary, |
Match any of +, , /, -, %, ~, <, <=, >, >=, !=, ==, *, ?, :, and, or, not | regex: /(^[\+\-~%\?\:]|^[!=]==?|^[!<>]=?|^\*\*?|^\/\/?|^and\s+|^or\s+|^in\s+|^not in\s+|^\.\.)/,
next: Twig.expression.set.expressions.concat([Twig.expression.type.operator.unary]),
compile: function(token, stack, output) {
delete token.match;
token.value = token.value.trim();
var value = token.value,
operator = Twig.expression.operator.lookup(value, token);
Twig.log.trace("Twig.expression.compile: ", "Operator: ", operator, " from ", value);
while (stack.length > 0 &&
(stack[stack.length-1].type == Twig.expression.type.operator.unary || stack[stack.length-1].type == Twig.expression.type.operator.binary) &&
(
(operator.associativity === Twig.expression.operator.leftToRight &&
operator.precidence >= stack[stack.length-1].precidence) ||
(operator.associativity === Twig.expression.operator.rightToLeft &&
operator.precidence > stack[stack.length-1].precidence)
)
) {
var temp = stack.pop();
output.push(temp);
}
if (value === ":") { |
Check if this is a ternary or object key being set | if (stack[stack.length - 1] && stack[stack.length-1].value === "?") { |
Continue as normal for a ternary | } else { |
This is not a ternary so we push the token to the output where it can be handled when the assocated object is closed. | var key_token = output.pop();
if (key_token.type === Twig.expression.type.string ||
key_token.type === Twig.expression.type.variable ||
key_token.type === Twig.expression.type.number) {
token.key = key_token.value;
} else {
throw new Twig.Error("Unexpected value before ':' of " + key_token.type + " = " + key_token.value);
}
output.push(token);
return;
}
} else {
stack.push(operator);
}
},
parse: function(token, stack, context) {
if (token.key) { |
handle ternary ':' operator | stack.push(token);
} else {
Twig.expression.operator.parse(token.value, stack);
}
}
},
{
type: Twig.expression.type.operator.unary, |
Match any of not | regex: /(^not\s+)/,
next: Twig.expression.set.expressions,
compile: function(token, stack, output) {
delete token.match;
token.value = token.value.trim();
var value = token.value,
operator = Twig.expression.operator.lookup(value, token);
Twig.log.trace("Twig.expression.compile: ", "Operator: ", operator, " from ", value);
while (stack.length > 0 &&
(stack[stack.length-1].type == Twig.expression.type.operator.unary || stack[stack.length-1].type == Twig.expression.type.operator.binary) &&
(
(operator.associativity === Twig.expression.operator.leftToRight &&
operator.precidence >= stack[stack.length-1].precidence) ||
(operator.associativity === Twig.expression.operator.rightToLeft &&
operator.precidence > stack[stack.length-1].precidence)
)
) {
var temp = stack.pop();
output.push(temp);
}
stack.push(operator);
},
parse: function(token, stack, context) {
Twig.expression.operator.parse(token.value, stack);
}
},
{
/**
* Match a string. This is anything between a pair of single or double quotes.
*/
type: Twig.expression.type.string, |
See: http://blog.stevenlevithan.com/archives/match-quoted-string | regex: /^(["'])(?:(?=(\\?))\2.)*?\1/,
next: Twig.expression.set.operations,
compile: function(token, stack, output) {
var value = token.value;
delete token.match |
Remove the quotes from the string | if (value.substring(0, 1) === '"') {
value = value.replace('\\"', '"');
} else {
value = value.replace("\\'", "'");
}
token.value = value.substring(1, value.length-1).replace( /\\n/g, "\n" ).replace( /\\r/g, "\r" );
Twig.log.trace("Twig.expression.compile: ", "String value: ", token.value);
output.push(token);
},
parse: Twig.expression.fn.parse.push_value
},
{
/**
* Match a parameter set start.
*/
type: Twig.expression.type.parameter.start,
regex: /^\(/,
next: Twig.expression.set.expressions.concat([Twig.expression.type.parameter.end]),
compile: Twig.expression.fn.compile.push_both,
parse: Twig.expression.fn.parse.push
},
{
/**
* Match a parameter set end.
*/
type: Twig.expression.type.parameter.end,
regex: /^\)/,
next: Twig.expression.set.operations_extended,
compile: function(token, stack, output) {
var stack_token,
end_token = token;
stack_token = stack.pop();
while(stack.length > 0 && stack_token.type != Twig.expression.type.parameter.start) {
output.push(stack_token);
stack_token = stack.pop();
} |
Move contents of parens into preceding filter | var param_stack = [];
while(token.type !== Twig.expression.type.parameter.start) { |
Add token to arguments stack | param_stack.unshift(token);
token = output.pop();
}
param_stack.unshift(token);
var is_expression = false; |
Get the token preceding the parameters | token = output[output.length-1];
if (token === undefined ||
(token.type !== Twig.expression.type._function &&
token.type !== Twig.expression.type.filter &&
token.type !== Twig.expression.type.test &&
token.type !== Twig.expression.type.key.brackets &&
token.type !== Twig.expression.type.key.period)) {
end_token.expression = true; |
remove start and end token from stack | param_stack.pop();
param_stack.shift();
end_token.params = param_stack;
output.push(end_token);
} else {
end_token.expression = false;
token.params = param_stack;
}
},
parse: function(token, stack, context) {
var new_array = [],
array_ended = false,
value = null;
if (token.expression) {
value = Twig.expression.parse.apply(this, [token.params, context])
stack.push(value);
} else {
while (stack.length > 0) {
value = stack.pop(); |
Push values into the array until the start of the array | if (value && value.type && value.type == Twig.expression.type.parameter.start) {
array_ended = true;
break;
}
new_array.unshift(value);
}
if (!array_ended) {
throw new Twig.Error("Expected end of parameter set.");
}
stack.push(new_array);
}
}
},
{
/**
* Match an array start.
*/
type: Twig.expression.type.array.start,
regex: /^\[/,
next: Twig.expression.set.expressions.concat([Twig.expression.type.array.end]),
compile: Twig.expression.fn.compile.push_both,
parse: Twig.expression.fn.parse.push
},
{
/**
* Match an array end.
*/
type: Twig.expression.type.array.end,
regex: /^\]/,
next: Twig.expression.set.operations_extended,
compile: function(token, stack, output) {
var i = stack.length - 1,
stack_token; |
pop tokens off the stack until the start of the object | for(;i >= 0; i--) {
stack_token = stack.pop();
if (stack_token.type === Twig.expression.type.array.start) {
break;
}
output.push(stack_token);
}
output.push(token);
},
parse: function(token, stack, context) {
var new_array = [],
array_ended = false,
value = null;
while (stack.length > 0) {
value = stack.pop(); |
Push values into the array until the start of the array | if (value.type && value.type == Twig.expression.type.array.start) {
array_ended = true;
break;
}
new_array.unshift(value);
}
if (!array_ended) {
throw new Twig.Error("Expected end of array.");
}
stack.push(new_array);
}
}, |
Token that represents the start of a hash map '}' Hash maps take the form: { "key": 'value', "another_key": item } Keys must be quoted (either single or double) and values can be any expression. | {
type: Twig.expression.type.object.start,
regex: /^\{/,
next: Twig.expression.set.expressions.concat([Twig.expression.type.object.end]),
compile: Twig.expression.fn.compile.push_both,
parse: Twig.expression.fn.parse.push
}, |
Token that represents the end of a Hash Map '}' This is where the logic for building the internal representation of a hash map is defined. | {
type: Twig.expression.type.object.end,
regex: /^\}/,
next: Twig.expression.set.operations_extended,
compile: function(token, stack, output) {
var i = stack.length-1,
stack_token; |
pop tokens off the stack until the start of the object | for(;i >= 0; i--) {
stack_token = stack.pop();
if (stack_token && stack_token.type === Twig.expression.type.object.start) {
break;
}
output.push(stack_token);
}
output.push(token);
},
parse: function(end_token, stack, context) {
var new_object = {},
object_ended = false,
token = null,
token_key = null,
has_value = false,
value = null;
while (stack.length > 0) {
token = stack.pop(); |
Push values into the array until the start of the object | if (token && token.type && token.type === Twig.expression.type.object.start) {
object_ended = true;
break;
}
if (token && token.type && (token.type === Twig.expression.type.operator.binary || token.type === Twig.expression.type.operator.unary) && token.key) {
if (!has_value) {
throw new Twig.Error("Missing value for key '" + token.key + "' in object definition.");
}
new_object[token.key] = value; |
Preserve the order that elements are added to the map This is necessary since JavaScript objects don't guarantee the order of keys | if (new_object._keys === undefined) new_object._keys = [];
new_object._keys.unshift(token.key); |
reset value check | value = null;
has_value = false;
} else {
has_value = true;
value = token;
}
}
if (!object_ended) {
throw new Twig.Error("Unexpected end of object.");
}
stack.push(new_object);
}
}, |
Token representing a filter Filters can follow any expression and take the form: expression|filter(optional, args) Filter parsing is done in the Twig.filters namespace. | {
type: Twig.expression.type.filter, |
match a | then a letter or _, then any number of letters, numbers, _ or - | regex: /^\|\s?([a-zA-Z_][a-zA-Z0-9_\-]*)/,
next: Twig.expression.set.operations_extended.concat([
Twig.expression.type.parameter.start]),
compile: function(token, stack, output) {
token.value = token.match[1];
output.push(token);
},
parse: function(token, stack, context) {
var input = stack.pop(),
params = token.params && Twig.expression.parse.apply(this, [token.params, context]);
stack.push(Twig.filter.apply(this, [token.value, input, params]));
}
},
{
type: Twig.expression.type._function, |
match any letter or _, then any number of letters, numbers, _ or - followed by ( | regex: /^([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/,
next: Twig.expression.type.parameter.start,
transform: function(match, tokens) {
return '(';
},
compile: function(token, stack, output) {
var fn = token.match[1];
token.fn = fn; |
cleanup token | delete token.match;
delete token.value;
output.push(token);
},
parse: function(token, stack, context) {
var params = token.params && Twig.expression.parse.apply(this, [token.params, context]),
fn = token.fn,
value;
if (Twig.functions[fn]) { |
Get the function from the built-in functions | value = Twig.functions[fn].apply(this, params);
} else if (typeof context[fn] == 'function') { |
Get the function from the user/context defined functions | value = context[fn].apply(context, params);
} else {
throw new Twig.Error(fn + ' function does not exist and is not defined in the context');
}
stack.push(value);
}
}, |
Token representing a variable. Variables can contain letters, numbers, underscores and dashes, but must start with a letter or underscore. Variables are retrieved from the render context and take the value of 'undefined' if the given variable doesn't exist in the context. | {
type: Twig.expression.type.variable, |
match any letter or _, then any number of letters, numbers, _ or - | regex: /^[a-zA-Z_][a-zA-Z0-9_]*/,
next: Twig.expression.set.operations_extended.concat([
Twig.expression.type.parameter.start]),
compile: Twig.expression.fn.compile.push,
validate: function(match, tokens) {
return Twig.expression.reservedWords.indexOf(match[0]) == -1;
},
parse: function(token, stack, context) { |
Get the variable from the context | var value = Twig.expression.resolve(context[token.value], context);
stack.push(value);
}
},
{
type: Twig.expression.type.key.period,
regex: /^\.([a-zA-Z0-9_]+)/,
next: Twig.expression.set.operations_extended.concat([
Twig.expression.type.parameter.start]),
compile: function(token, stack, output) {
token.key = token.match[1];
delete token.match;
delete token.value;
output.push(token);
},
parse: function(token, stack, context) {
var params = token.params && Twig.expression.parse.apply(this, [token.params, context]),
key = token.key,
object = stack.pop(),
value;
if (object === null || object === undefined) {
if (this.options.strict_variables) {
throw new Twig.Error("Can't access a key " + key + " on an null or undefined object.");
} else {
return null;
}
}
var capitalize = function(value) {return value.substr(0, 1).toUpperCase() + value.substr(1);}; |
Get the variable from the context | if (typeof object === 'object' && key in object) {
value = object[key];
} else if (object["get"+capitalize(key)] !== undefined) {
value = object["get"+capitalize(key)];
} else if (object["is"+capitalize(key)] !== undefined) {
value = object["is"+capitalize(key)];
} else {
value = null;
}
stack.push(Twig.expression.resolve(value, object, params));
}
},
{
type: Twig.expression.type.key.brackets,
regex: /^\[([^\]]*)\]/,
next: Twig.expression.set.operations_extended.concat([
Twig.expression.type.parameter.start]),
compile: function(token, stack, output) {
var match = token.match[1];
delete token.value;
delete token.match; |
The expression stack for the key | token.stack = Twig.expression.compile({
value: match
}).stack;
output.push(token);
},
parse: function(token, stack, context) { |
Evaluate key | var params = token.params && Twig.expression.parse.apply(this, [token.params, context]),
key = Twig.expression.parse.apply(this, [token.stack, context]),
object = stack.pop(),
value;
if (object === null || object === undefined) {
if (this.options.strict_variables) {
throw new Twig.Error("Can't access a key " + key + " on an null or undefined object.");
} else {
return null;
}
} |
Get the variable from the context | if (typeof object === 'object' && key in object) {
value = object[key];
} else {
value = null;
}
stack.push(Twig.expression.resolve(value, object, params));
}
},
{
/**
* Match a null value.
*/
type: Twig.expression.type._null, |
match a number | regex: /^null/,
next: Twig.expression.set.operations,
compile: function(token, stack, output) {
delete token.match;
token.value = null;
output.push(token);
},
parse: Twig.expression.fn.parse.push_value
},
{
/**
* Match a number (integer or decimal)
*/
type: Twig.expression.type.number, |
match a number | regex: /^\-?\d+(\.\d+)?/,
next: Twig.expression.set.operations,
compile: function(token, stack, output) {
token.value = Number(token.value);
output.push(token);
},
parse: Twig.expression.fn.parse.push_value
},
{
/**
* Match a boolean
*/
type: Twig.expression.type.bool,
regex: /^(true|false)/,
next: Twig.expression.set.operations,
compile: function(token, stack, output) {
token.value = (token.match[0] == "true");
delete token.match;
output.push(token);
},
parse: Twig.expression.fn.parse.push_value
}
];
/**
* Resolve a context value.
*
* If the value is a function, it is executed with a context parameter.
*
* @param {string} key The context object key.
* @param {Object} context The render context.
*/
Twig.expression.resolve = function(value, context, params) {
if (typeof value == 'function') {
return value.apply(context, params || []);
} else {
return value;
}
};
/**
* Registry for logic handlers.
*/
Twig.expression.handler = {};
/**
* Define a new expression type, available at Twig.logic.type.{type}
*
* @param {string} type The name of the new type.
*/
Twig.expression.extendType = function (type) {
Twig.expression.type[type] = "Twig.expression.type." + type;
};
/**
* Extend the expression parsing functionality with a new definition.
*
* Token definitions follow this format:
* {
* type: One of Twig.expression.type.[type], either pre-defined or added using
* Twig.expression.extendType
*
* next: Array of types from Twig.expression.type that can follow this token,
*
* regex: A regex or array of regex's that should match the token.
*
* compile: function(token, stack, output) called when this token is being compiled.
* Should return an object with stack and output set.
*
* parse: function(token, stack, context) called when this token is being parsed.
* Should return an object with stack and context set.
* }
*
* @param {Object} definition A token definition.
*/
Twig.expression.extend = function (definition) {
if (!definition.type) {
throw new Twig.Error("Unable to extend logic definition. No type provided for " + definition);
}
Twig.expression.handler[definition.type] = definition;
}; |
Extend with built-in expressions | while (Twig.expression.definitions.length > 0) {
Twig.expression.extend(Twig.expression.definitions.shift());
}
/**
* Break an expression into tokens defined in Twig.expression.definitions.
*
* @param {string} expression The string to tokenize.
*
* @return {Array} An array of tokens.
*/
Twig.expression.tokenize = function (expression) {
var tokens = [], |
Keep an offset of the location in the expression for error messages. | exp_offset = 0, |
The valid next tokens of the previous token | next = null, |
Match information | type, regex, regex_array, |
The possible next token for the match | token_next, |
Has a match been found from the definitions | match_found, invalid_matches = [], match_function;
match_function = function () {
var match = Array.prototype.slice.apply(arguments),
string = match.pop(),
offset = match.pop();
Twig.log.trace("Twig.expression.tokenize",
"Matched a ", type, " regular expression of ", match);
if (next && next.indexOf(type) < 0) {
invalid_matches.push(
type + " cannot follow a " + tokens[tokens.length - 1].type +
" at template:" + exp_offset + " near '" + match[0].substring(0, 20) +
"...'"
); |
Not a match, don't change the expression | return match[0];
} |
Validate the token if a validation function is provided | if (Twig.expression.handler[type].validate &&
!Twig.expression.handler[type].validate(match, tokens)) {
return match[0];
}
invalid_matches = [];
tokens.push({
type: type,
value: match[0],
match: match
});
match_found = true;
next = token_next;
exp_offset += match[0].length; |
Does the token need to return output back to the expression string e.g. a function match of cycle( might return the '(' back to the expression This allows look-ahead to differentiate between token types (e.g. functions and variable names) | if (Twig.expression.handler[type].transform) {
return Twig.expression.handler[type].transform(match, tokens);
}
return '';
};
Twig.log.debug("Twig.expression.tokenize", "Tokenizing expression ", expression);
while (expression.length > 0) {
expression = expression.trim();
for (type in Twig.expression.handler) {
if (Twig.expression.handler.hasOwnProperty(type)) {
token_next = Twig.expression.handler[type].next;
regex = Twig.expression.handler[type].regex; |
Twig.log.trace("Checking type ", type, " on ", expression); | if (regex instanceof Array) {
regex_array = regex;
} else {
regex_array = [regex];
}
match_found = false;
while (regex_array.length > 0) {
regex = regex_array.pop();
expression = expression.replace(regex, match_function);
} |
An expression token has been matched. Break the for loop and start trying to match the next template (if expression isn't empty.) | if (match_found) {
break;
}
}
}
if (!match_found) {
if (invalid_matches.length > 0) {
throw new Twig.Error(invalid_matches.join(" OR "));
} else {
throw new Twig.Error("Unable to parse '" + expression + "' at template position" + exp_offset);
}
}
}
Twig.log.trace("Twig.expression.tokenize", "Tokenized to ", tokens);
return tokens;
};
/**
* Compile an expression token.
*
* @param {Object} raw_token The uncompiled token.
*
* @return {Object} The compiled token.
*/
Twig.expression.compile = function (raw_token) {
var expression = raw_token.value, |
Tokenize expression | tokens = Twig.expression.tokenize(expression),
token = null,
output = [],
stack = [],
token_template = null;
Twig.log.trace("Twig.expression.compile: ", "Compiling ", expression); |
Push tokens into RPN stack using the Sunting-yard algorithm See http://en.wikipedia.org/wiki/Shuntingyardalgorithm | while (tokens.length > 0) {
token = tokens.shift();
token_template = Twig.expression.handler[token.type];
Twig.log.trace("Twig.expression.compile: ", "Compiling ", token); |
Compile the template | token_template.compile && token_template.compile(token, stack, output);
Twig.log.trace("Twig.expression.compile: ", "Stack is", stack);
Twig.log.trace("Twig.expression.compile: ", "Output is", output);
}
while(stack.length > 0) {
output.push(stack.pop());
}
Twig.log.trace("Twig.expression.compile: ", "Final output is", output);
raw_token.stack = output;
delete raw_token.value;
return raw_token;
};
/**
* Parse an RPN expression stack within a context.
*
* @param {Array} tokens An array of compiled expression tokens.
* @param {Object} context The render context to parse the tokens with.
*
* @return {Object} The result of parsing all the tokens. The result
* can be anything, String, Array, Object, etc... based on
* the given expression.
*/
Twig.expression.parse = function (tokens, context) {
var that = this; |
If the token isn't an array, make it one. | if (!(tokens instanceof Array)) {
tokens = [tokens];
} |
The output stack | var stack = [],
token_template = null;
tokens.forEach(function (token) {
token_template = Twig.expression.handler[token.type];
token_template.parse && token_template.parse.apply(that, [token, stack, context]);
}); |
Pop the final value off the stack | return stack.pop();
};
return Twig;
})( Twig || { } ); |
| |
twig.expression.operator.jsThis file handles operator lookups and parsing. | var Twig = (function (Twig) {
"use strict";
/**
* Operator associativity constants.
*/
Twig.expression.operator = {
leftToRight: 'leftToRight',
rightToLeft: 'rightToLeft'
};
var containment = function(a, b) {
if (b.indexOf !== undefined) { |
String | return a === b || a !== '' && b.indexOf(a) > -1;
} else {
var el;
for (el in b) {
if (b.hasOwnProperty(el) && b[el] === a) {
return true;
}
}
return false;
}
};
/**
* Get the precidence and associativity of an operator. These follow the order that C/C++ use.
* See http://en.wikipedia.org/wiki/Operators_in_C_and_C++ for the table of values.
*/
Twig.expression.operator.lookup = function (operator, token) {
switch (operator) {
case "..":
case 'not in':
case 'in':
token.precidence = 20;
token.associativity = Twig.expression.operator.leftToRight;
break;
case ',':
token.precidence = 18;
token.associativity = Twig.expression.operator.leftToRight;
break; |
Ternary | case '?':
case ':':
token.precidence = 16;
token.associativity = Twig.expression.operator.rightToLeft;
break;
case 'or':
token.precidence = 14;
token.associativity = Twig.expression.operator.leftToRight;
break;
case 'and':
token.precidence = 13;
token.associativity = Twig.expression.operator.leftToRight;
break;
case '==':
case '!=':
token.precidence = 9;
token.associativity = Twig.expression.operator.leftToRight;
break;
case '<':
case '<=':
case '>':
case '>=':
token.precidence = 8;
token.associativity = Twig.expression.operator.leftToRight;
break;
case '~': // String concatination
case '+':
case '-':
token.precidence = 6;
token.associativity = Twig.expression.operator.leftToRight;
break;
case '//':
case '**':
case '*':
case '/':
case '%':
token.precidence = 5;
token.associativity = Twig.expression.operator.leftToRight;
break;
case 'not':
token.precidence = 3;
token.associativity = Twig.expression.operator.rightToLeft;
break;
default:
throw new Twig.Error(operator + " is an unknown operator.");
}
token.operator = operator;
return token;
};
/**
* Handle operations on the RPN stack.
*
* Returns the updated stack.
*/
Twig.expression.operator.parse = function (operator, stack) {
Twig.log.trace("Twig.expression.operator.parse: ", "Handling ", operator);
var a, b, c;
switch (operator) {
case ':': |
Ignore | break;
case '?':
c = stack.pop(); // false expr
b = stack.pop(); // true expr
a = stack.pop(); // conditional
if (a) {
stack.push(b);
} else {
stack.push(c);
}
break;
case '+':
b = parseFloat(stack.pop());
a = parseFloat(stack.pop());
stack.push(a + b);
break;
case '-':
b = parseFloat(stack.pop());
a = parseFloat(stack.pop());
stack.push(a - b);
break;
case '*':
b = parseFloat(stack.pop());
a = parseFloat(stack.pop());
stack.push(a * b);
break;
case '/':
b = parseFloat(stack.pop());
a = parseFloat(stack.pop());
stack.push(a / b);
break;
case '//':
b = parseFloat(stack.pop());
a = parseFloat(stack.pop());
stack.push(parseInt(a / b));
break;
case '%':
b = parseFloat(stack.pop());
a = parseFloat(stack.pop());
stack.push(a % b);
break;
case '~':
b = stack.pop();
a = stack.pop();
stack.push( (a !== undefined ? a.toString() : "")
+ (b !== undefined ? b.toString() : "") );
break;
case 'not':
case '!':
stack.push(!stack.pop());
break;
case '<':
b = stack.pop();
a = stack.pop();
stack.push(a < b);
break;
case '<=':
b = stack.pop();
a = stack.pop();
stack.push(a <= b);
break;
case '>':
b = stack.pop();
a = stack.pop();
stack.push(a > b);
break;
case '>=':
b = stack.pop();
a = stack.pop();
stack.push(a >= b);
break;
case '===':
b = stack.pop();
a = stack.pop();
stack.push(a === b);
break;
case '==':
b = stack.pop();
a = stack.pop();
stack.push(a == b);
break;
case '!==':
b = stack.pop();
a = stack.pop();
stack.push(a !== b);
break;
case '!=':
b = stack.pop();
a = stack.pop();
stack.push(a != b);
break;
case 'or':
b = stack.pop();
a = stack.pop();
stack.push(a || b);
break;
case 'and':
b = stack.pop();
a = stack.pop();
stack.push(a && b);
break;
case '**':
b = stack.pop();
a = stack.pop();
stack.push(Math.pow(a, b));
break;
case 'not in':
b = stack.pop();
a = stack.pop();
stack.push( !containment(a, b) );
break;
case 'in':
b = stack.pop();
a = stack.pop();
stack.push( containment(a, b) );
break;
case '..':
b = stack.pop();
a = stack.pop();
stack.push( Twig.functions.range(a, b) );
break;
default:
throw new Twig.Error(operator + " is an unknown operator.");
}
};
return Twig;
})( Twig || { } ); |
| |
twig.filters.jsThis file handles parsing filters. | var Twig = (function (Twig) { |
Determine object type | function is(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && clas === type;
}
Twig.filters = { |
String Filters | upper: function(value) {
if ( typeof value !== "string" ) {
return value;
}
return value.toUpperCase();
},
lower: function(value) {
if ( typeof value !== "string" ) {
return value;
}
return value.toLowerCase();
},
capitalize: function(value) {
if ( typeof value !== "string" ) {
return value;
}
return value.substr(0, 1).toUpperCase() + value.substr(1);
},
title: function(value) {
if ( typeof value !== "string" ) {
return value;
}
return value.replace( /(^|\s)([a-z])/g , function(m, p1, p2){
return p1 + p2.toUpperCase();
});
},
length: function(value) {
if (value instanceof Array || typeof value === "string") {
return value.length;
} else if (value instanceof Object) {
if (value._keys === undefined) {
return Object.keys(value).length;
} else {
return value._keys.length;
}
} else {
return 0;
}
}, |
Array/Object Filters | reverse: function(value) {
if (is("Array", value)) {
return value.reverse();
} else if (is("String", value)) {
return value.split("").reverse().join("");
} else if (value instanceof Object) {
var keys = value._keys || Object.keys(value).reverse();
value._keys = keys;
return value;
}
},
sort: function(value) {
if (is("Array", value)) {
return value.sort();
} else if (value instanceof Object) { |
Sorting objects isn't obvious since the order of returned keys isn't guaranteedin JavaScript. Because of this we use a "hidden" key called _keys to store the keys in the order we want to return them. | delete value._keys;
var keys = Object.keys(value),
sorted_keys = keys.sort(function(a, b) {
return value[a] > value[b];
});
value._keys = sorted_keys;
return value;
}
},
keys: function(value) {
if (value === undefined){
return;
}
var keyset = value._keys || Object.keys(value),
output = [];
keyset.forEach(function(key) {
if (key === "_keys") return; // Ignore the _keys property
if (value.hasOwnProperty(key)) {
output.push(key);
}
});
return output;
},
url_encode: function(value) {
if (value === undefined){
return;
}
return encodeURIComponent(value);
},
join: function(value, params) {
if (value === undefined){
return;
}
var join_str = "",
output = [],
keyset = null;
if (params && params[0]) {
join_str = params[0];
}
if (value instanceof Array) {
output = value;
} else {
keyset = value._keys || Object.keys(value);
keyset.forEach(function(key) {
if (key === "_keys") return; // Ignore the _keys property
if (value.hasOwnProperty(key)) {
output.push(value[key]);
}
});
}
return output.join(join_str);
},
"default": function(value, params) {
if (params === undefined || params.length !== 1) {
throw new Twig.Error("default filter expects one argument");
}
if (value === undefined || value === null || value === '' ) {
return params[0];
} else {
return value;
}
},
json_encode: function(value) {
if (value && value.hasOwnProperty( "_keys" ) ) {
delete value._keys;
}
if(value === undefined || value === null) {
return "null";
}
return JSON.stringify(value);
},
merge: function(value, params) {
var obj = [],
arr_index = 0,
keyset = []; |
Check to see if all the objects being merged are arrays | if (!(value instanceof Array)) { |
Create obj as an Object | obj = { };
} else {
params.forEach(function(param) {
if (!(param instanceof Array)) {
obj = { };
}
});
}
if (!(obj instanceof Array)) {
obj._keys = [];
}
if (value instanceof Array) {
value.forEach(function(val) {
if (obj._keys) obj._keys.push(arr_index);
obj[arr_index] = val;
arr_index++;
});
} else {
keyset = value._keys || Object.keys(value);
keyset.forEach(function(key) {
obj[key] = value[key];
obj._keys.push(key); |
Handle edge case where a number index in an object is greater than the array counter. In such a case, the array counter is increased one past the index. Example {{ ["a", "b"]|merge({"4":"value"}, ["c", "d"]) Without this, d would have an index of "4" and overwrite the value of "value" | var int_key = parseInt(key, 10);
if (!isNaN(int_key) && int_key >= arr_index) {
arr_index = int_key + 1;
}
});
} |
mixin the merge arrays | params.forEach(function(param) {
if (param instanceof Array) {
param.forEach(function(val) {
if (obj._keys) obj._keys.push(arr_index);
obj[arr_index] = val;
arr_index++;
});
} else {
keyset = param._keys || Object.keys(param);
keyset.forEach(function(key) {
if (!obj[key]) obj._keys.push(key);
obj[key] = param[key];
var int_key = parseInt(key, 10);
if (!isNaN(int_key) && int_key >= arr_index) {
arr_index = int_key + 1;
}
});
}
});
if (params.length === 0) {
throw new Twig.Error("Filter merge expects at least one parameter");
}
return obj;
},
date: function(value, params) {
if (value === undefined){
return;
}
var date = Twig.functions.date(value);
return Twig.lib.formatDate(date, params[0]);
},
replace: function(value, params) {
if (value === undefined){
return;
}
var pairs = params[0],
tag;
for (tag in pairs) {
if (pairs.hasOwnProperty(tag) && tag !== "_keys") {
value = Twig.lib.replaceAll(value, tag, pairs[tag]);
}
}
return value;
},
format: function(value, params) {
if (value === undefined){
return;
}
return Twig.lib.vsprintf(value, params);
},
striptags: function(value) {
if (value === undefined){
return;
}
return Twig.lib.strip_tags(value);
},
escape: function(value) {
if (value === undefined){
return;
}
return value.toString().replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
},
/* Alias of escape */
"e": function(value) {
return Twig.filters.escape(value);
},
nl2br: function(value) {
if (value === undefined){
return;
}
var linebreak_tag = "BACKSLASH_n_replace",
br = "<br />" + linebreak_tag;
value = Twig.filters.escape(value)
.replace(/\r\n/g, br)
.replace(/\r/g, br)
.replace(/\n/g, br);
return Twig.lib.replaceAll(value, linebreak_tag, "\n");
},
/**
* Adapted from: http://phpjs.org/functions/number_format:481
*/
number_format: function(value, params) {
var number = value,
decimals = (params && params[0]) ? params[0] : undefined,
dec = (params && params[1] !== undefined) ? params[1] : ".",
sep = (params && params[2] !== undefined) ? params[2] : ",";
number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number,
prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
s = '',
toFixedFix = function (n, prec) {
var k = Math.pow(10, prec);
return '' + Math.round(n * k) / k;
}; |
Fix for IE parseFloat(0.55).toFixed(0) = 0; | s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
if (s[0].length > 3) {
s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
}
if ((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
},
trim: function(value, params) {
if (value === undefined){
return;
}
var str = Twig.filters.escape( '' + value ),
whitespace;
if ( params && params[0] ) {
whitespace = '' + params[0];
} else {
whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000';
}
for (var i = 0; i < str.length; i++) {
if (whitespace.indexOf(str.charAt(i)) === -1) {
str = str.substring(i);
break;
}
}
for (i = str.length - 1; i >= 0; i--) {
if (whitespace.indexOf(str.charAt(i)) === -1) {
str = str.substring(0, i + 1);
break;
}
}
return whitespace.indexOf(str.charAt(0)) === -1 ? str : '';
}
};
Twig.filter = function(filter, value, params) {
if (!Twig.filters[filter]) {
throw "Unable to find filter " + filter;
}
return Twig.filters[filter].apply(this, [value, params]);
}
Twig.filter.extend = function(filter, definition) {
Twig.filters[filter] = definition;
};
return Twig;
})(Twig || { }); |
| |
twig.functions.jsThis file handles parsing filters. | var Twig = (function (Twig) { |
Determine object type | function is(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && clas === type;
}
Twig.functions = { |
attribute, block, constant, date, dump, parent, random,. | |
Range function from http://phpjs.org/functions/range:499 Used under an MIT License | range: function (low, high, step) { |
http://kevin.vanzonneveld.net + original by: Waldo Malqui Silva * example 1: range ( 0, 12 ); * returns 1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] * example 2: range( 0, 100, 10 ); * returns 2: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] * example 3: range( 'a', 'i' ); * returns 3: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'] * example 4: range( 'c', 'a' ); * returns 4: ['c', 'b', 'a'] | var matrix = [];
var inival, endval, plus;
var walker = step || 1;
var chars = false;
if (!isNaN(low) && !isNaN(high)) {
inival = parseInt(low, 10);
endval = parseInt(high, 10);
} else if (isNaN(low) && isNaN(high)) {
chars = true;
inival = low.charCodeAt(0);
endval = high.charCodeAt(0);
} else {
inival = (isNaN(low) ? 0 : low);
endval = (isNaN(high) ? 0 : high);
}
plus = ((inival > endval) ? false : true);
if (plus) {
while (inival <= endval) {
matrix.push(((chars) ? String.fromCharCode(inival) : inival));
inival += walker;
}
} else {
while (inival >= endval) {
matrix.push(((chars) ? String.fromCharCode(inival) : inival));
inival -= walker;
}
}
return matrix;
},
cycle: function(arr, i) {
var pos = i % arr.length;
return arr[pos];
},
dump: function() {
var EOL = '\n',
indentChar = ' ',
indentTimes = 0,
out = '',
args = Array.prototype.slice.call(arguments),
indent = function(times) {
var ind = '';
while (times > 0) {
times--;
ind += indentChar;
}
return ind;
},
displayVar = function(variable) {
out += indent(indentTimes);
if (typeof(variable) === 'object') {
dumpVar(variable);
} else if (typeof(variable) === 'function') {
out += 'function()' + EOL;
} else if (typeof(variable) === 'string') {
out += 'string(' + variable.length + ') "' + variable + '"' + EOL;
} else if (typeof(variable) === 'number') {
out += 'number(' + variable + ')' + EOL;
} else if (typeof(variable) === 'boolean') {
out += 'bool(' + variable + ')' + EOL;
}
},
dumpVar = function(variable) {
var i;
if (variable === null) {
out += 'NULL' + EOL;
} else if (variable === undefined) {
out += 'undefined' + EOL;
} else if (typeof variable === 'object') {
out += indent(indentTimes) + typeof(variable);
indentTimes++;
out += '(' + (function(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
size++;
}
}
return size;
})(variable) + ') {' + EOL;
for (i in variable) {
out += indent(indentTimes) + '[' + i + ']=> ' + EOL;
displayVar(variable[i]);
}
indentTimes--;
out += indent(indentTimes) + '}' + EOL;
} else {
displayVar(variable);
}
}; |
handle no argument case by dumping the entire render context | if (args.length == 0) args.push(this.context);
args.forEach(function(variable) {
dumpVar(variable);
});
return out;
},
date: function(date, time) {
var dateObj;
if (date === undefined) {
dateObj = new Date();
} else if (Twig.lib.is("Date", date)) {
dateObj = date;
} else if (Twig.lib.is("String", date)) {
dateObj = new Date(Twig.lib.strtotime(date) * 1000);
} else if (Twig.lib.is("Number", date)) { |
timestamp | dateObj = new Date(date * 1000);
} else {
throw new Twig.Error("Unable to parse date " + date);
}
return dateObj;
},
parent: function() { |
Add a placeholder | return Twig.placeholders.parent;
}
};
Twig._function = function(_function, value, params) {
if (!Twig.functions[_function]) {
throw "Unable to find function " + _function;
}
return Twig.functions[_function](value, params);
};
Twig._function.extend = function(_function, definition) {
Twig.functions[_function] = definition;
};
return Twig;
})(Twig || { }); |
| |
twig.tests.jsThis file handles expression tests. (is empty, is not defined, etc...) | var Twig = (function (Twig) {
"use strict";
Twig.tests = {
empty: function(value) {
if (value === null || value === undefined) return true; |
Handler numbers | if (typeof value === "number") return false; // numbers are never "empty" |
Handle strings and arrays | if (value.length && value.length > 0) return false; |
Handle objects | for (var key in value) {
if (value.hasOwnProperty(key)) return false;
}
return true;
},
odd: function(value) {
return value % 2 === 1;
},
even: function(value) {
return value % 2 === 0;
},
divisibleby: function(value, params) {
return value % params[0] === 0;
},
defined: function(value) {
return value !== undefined;
},
none: function(value) {
return value === null;
},
'null': function(value) {
return this.none(value); // Alias of none
},
sameas: function(value, params) {
return value === params[0];
}
/*
constant ?
*/
};
Twig.test = function(test, value, params) {
if (!Twig.tests[test]) {
throw "Test " + test + " is not defined.";
}
return Twig.tests[test](value, params);
};
Twig.test.extend = function(test, definition) {
Twig.tests[test] = definition;
};
return Twig;
})( Twig || { } ); |
| |
twig.exports.jsThis file provides extension points and other hooks into the twig functionality. | var Twig = (function (Twig) {
"use strict";
Twig.exports = {
VERSION: Twig.VERSION
};
/**
* Create and compile a twig.js template.
*
* @param {Object} param Paramteres for creating a Twig template.
*
* @return {Twig.Template} A Twig template ready for rendering.
*/
Twig.exports.twig = function twig(params) {
'use strict';
var id = params.id,
options = {
strict_variables: params.strict_variables || false,
allowInlineIncludes: params.allowInlineIncludes || false
};
if (id) {
Twig.validateId(id);
}
if (params.debug !== undefined) {
Twig.debug = params.debug;
}
if (params.trace !== undefined) {
Twig.trace = params.trace;
}
if (params.data !== undefined) {
return new Twig.Template({
data: params.data,
module: params.module,
id: id,
options: options
});
} else if (params.ref !== undefined) {
if (params.id !== undefined) {
throw new Error("Both ref and id cannot be set on a twig.js template.");
}
return Twig.Templates.load(params.ref);
} else if (params.href !== undefined) {
return Twig.Templates.loadRemote(params.href, {
id: id,
method: 'ajax',
base: params.base,
module: params.module,
precompiled: params.precompiled,
async: params.async,
options: options
}, params.load, params.error);
} else if (params.path !== undefined) {
return Twig.Templates.loadRemote(params.path, {
id: id,
method: 'fs',
base: params.base,
module: params.module,
precompiled: params.precompiled,
async: params.async,
options: options
}, params.load, params.error);
}
}; |
Extend Twig with a new filter. | Twig.exports.extendFilter = function(filter, definition) {
Twig.filter.extend(filter, definition);
}; |
Extend Twig with a new function. | Twig.exports.extendFunction = function(fn, definition) {
Twig._function.extend(fn, definition);
}; |
Extend Twig with a new test. | Twig.exports.extendTest = function(test, definition) {
Twig.test.extend(test, definition);
}; |
Extend Twig with a new definition. | Twig.exports.extendTag = function(definition) {
Twig.logic.extend(definition);
}; |
Provide an environment for extending Twig core. Calls fn with the internal Twig object. | Twig.exports.extend = function(fn) {
fn(Twig);
};
/**
* Provide an extension for use with express 2.
*
* @param {string} markup The template markup.
* @param {array} options The express options.
*
* @return {string} The rendered template.
*/
Twig.exports.compile = function(markup, options) {
var id = options.filename,
path = options.filename,
template; |
Try to load the template from the cache | template = new Twig.Template({
data: markup,
path: path,
id: id,
options: options.settings['twig options']
}); // Twig.Templates.load(id) ||
return function(context) {
return template.render(context);
};
};
/**
* Provide an extension for use with express 3.
*
* @param {string} path The location of the template file on disk.
* @param {Object|Function} The options or callback.
* @param {Function} fn callback.
*/
Twig.exports.renderFile = function(path, options, fn) { |
handle callback in options | if ('function' == typeof options) {
fn = options;
options = {};
}
options = options || {};
var params = {
path: path,
base: options.settings['views'],
load: function(template) { |
render and return template | fn(null, template.render(options));
}
}; |
mixin any options provided to the express app. | var view_options = options.settings['twig options'];
if (view_options) {
for (var option in view_options) if (view_options.hasOwnProperty(option)) {
params[option] = view_options[option];
}
}
Twig.exports.twig(params);
}; |
Express 3 handler | Twig.exports.__express = Twig.exports.renderFile;
/**
* Shoud Twig.js cache templates.
* Disable during development to see changes to templates without
* reloading, and disable in production to improve performance.
*
* @param {boolean} cache
*/
Twig.exports.cache = function(cache) {
Twig.cache = cache;
}
return Twig;
}) (Twig || { }); |
| |
twig.compiler.jsThis file handles compiling templates into JS | var Twig = (function (Twig) {
/**
* Namespace for compilation.
*/
Twig.compiler = {
module: {}
}; |
Compile a Twig Template to output. | Twig.compiler.compile = function(template, options) { |
Get tokens | var tokens = JSON.stringify(template.tokens)
, id = template.id
, output;
if (options.module) {
if (Twig.compiler.module[options.module] === undefined) {
throw new Twig.Error("Unable to find module type " + options.module);
}
output = Twig.compiler.module[options.module](id, tokens, options.twig);
} else {
output = Twig.compiler.wrap(id, tokens);
}
return output;
};
Twig.compiler.module = {
amd: function(id, tokens, pathToTwig) {
return 'define(["' + pathToTwig + '"], function (Twig) {\n\tvar twig = Twig.twig;\n' + Twig.compiler.wrap(id, tokens) + '\n\treturn templates;\n});';
}
, node: function(id, tokens) {
return 'var twig = require("twig").twig;\n'
+ 'exports.template = ' + Twig.compiler.wrap(id, tokens)
}
, cjs2: function(id, tokens, pathToTwig) {
return 'module.declare([{ twig: "' + pathToTwig + '" }], function (require, exports, module) {\n'
+ '\tvar twig = require("twig").twig;\n'
+ '\texports.template = ' + Twig.compiler.wrap(id, tokens)
+ '\n});'
}
};
Twig.compiler.wrap = function(id, tokens) {
return 'twig({id:"'+id.replace('"', '\\"')+'", data:'+tokens+', precompiled: true});\n';
};
return Twig;
})(Twig || {});// Twig.js |
| |
twig.module.jsProvide a CommonJS/AMD/Node module export. | if (typeof module !== 'undefined' && module.declare) { |
Provide a CommonJS Modules/2.0 draft 8 module | module.declare([], function(require, exports, module) { |
Add exports from the Twig exports | for (key in Twig.exports) {
if (Twig.exports.hasOwnProperty(key)) {
exports[key] = Twig.exports[key];
}
}
});
} else if (typeof define == 'function' && define.amd) {
define(function() {
return Twig.exports;
});
} else if (typeof module !== 'undefined' && module.exports) { |
Provide a CommonJS Modules/1.1 module | module.exports = Twig.exports;
} else { |
Export for browser use | window.twig = Twig.exports.twig;
window.Twig = Twig.exports;
}
|