New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

ltl

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ltl - npm Package Compare versions

Comparing version 0.1.15 to 0.2.0

common/object/lru-cache.js

1379

ltl.js
/**
* ltl is a template language designed to be simple, beautiful and fast.
* Ltl is a template language designed to be simple, beautiful and fast.
* There is single Ltl reference in the process or window.
*/
var ltl = this.ltl = this.ltl || {
(function () {
// The scope is a window or process.
scope: this,
var isBrowser = (typeof window == 'object');
// Allow users to see what version of Ltl they're using.
version: '0.2.0',
// Some HTML tags won't have end tags.
var selfClosePattern = /^(!DOCTYPE|area|base|br|hr|img|input|link|meta|-|\/\/)(\b|$)/;
selfClosePattern: /^(!DOCTYPE|area|base|br|hr|img|input|link|meta|-|\/\/|space|js|css)(\b|$)/,
// Supported control keywords (usage appears like tags).
var controlPattern = /^(for|if|else|else if)\b/;
controlPattern: /^(for|if|else)\b/,
// Pattern for a JavaScript assignment.
var assignmentPattern = /^([$A-Za-z_][$A-Za-z_0-9\.\[\]'"]*\s*=[^\{])/;
// Pattern for a Jasignment.
assignmentPattern: /^([$A-Za-z_][$A-Za-z_0-9\.\[\]'"]*\s*=[^\{])/,
// Supported command keywords.
var commandPattern = /^(call|get|set)\b/;
commandPattern: /^(call|get|set)\b/,
// JavaScript tokens that don't need contextVar prepended for interpolation.
// JavaScript tokens that don't need the state "s" prepended for interpolation.
// TODO: Flesh out this list?
var jsPattern = /^(true|false|null|NaN|Infinity|window|location|Math|console|this)$/;
jsPattern: /^(undefined|true|false|null|function|NaN|Infinity|window|location|document|console|this|Math|Object|Date|Error|RegExp|JSON)$/,
// Stores available single character variable names.
var varCharacters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$';
vars: 'abcdefghijklmnqrtuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
// Register several languages and their targets.
languages: {
js: 'js',
coffee: 'js',
es6: 'js',
ts: 'js',
css: 'css',
less: 'css',
scss: 'css',
styl: 'css'
},
// Remove starting/ending whitespace.
function trim(text) {
trim: function(text) {
return text.replace(/(^\s+|\s+$)/g, '');
}
},
// Remove starting/ending whitespace.
function repeat(text, times) {
return (new Array(times + 1)).join(text);
}
// Repeat a string.
repeat: function(text, times) {
return times > 0 ? (new Array(times + 1)).join(text) : '';
},
// Escape single quotes with a backslash.
function escapeSingleQuotes(text) {
return text.replace(/'/g, "\\'");
}
escapeSingleQuotes: function(text) {
return text.replace(/'/g, '\\\'');
},
// Escape text with possible line breaks for appending to a string.
function escapeBlock(text) {
return escapeSingleQuotes(text).replace(/\n/g, '\\n');
}
escapeBlock: function(text) {
return text.replace(/'/g, '\\\'').replace(/\n/g, '\\n');
},
// When compilation fails, we can re-run in debug mode.
function debug(output, settings) {
var fs = require('fs');
var dir = process.cwd() + '/.debug';
var name = (settings.name || 'template').replace(/[\/\\]/g, '__');
try {
fs.mkdirSync(dir);
// Get a module for filtering.
getFilter: function(name) {
var filters = ltl.filters;
var filter = filters[name];
if (!filter) {
filter = filters[name] = ltl.scope[name] || (typeof require != 'undefined' ? require(name) : null);
}
catch (e) {
// Probably already exists.
if (!filter) {
var todo;
var into = ' into function that accepts a string and returns a string.';
if (ltl.scope.cwd) {
var cmd = 'cd ' + ltl.scope.cwd() + '; npm install --save ' + name;
todo = 'Run "' + cmd + '", or make require("ltl").filters.' + name;
} else {
todo = 'Set window.ltl.filters.' + name;
}
throw new Error('[Ltl] Unknown filter: "' + name + '". ' + todo + into);
}
fs.writeFileSync(dir + '/' + name + '.js', 'module.exports=' + output);
try {
require(dir + '/' + name);
return filter;
},
// In browsers and istanbul, run JS with `eval`, otherwise run with `vm`.
run: (
(typeof require == 'function') ?
require('./common/vm/run') :
function (src, name) {
var f;
try {
eval('f=' + src); // jshint ignore:line
}
catch (e) {
e.message = '[Ltl] ' + e.message;
if (name) {
e.message += '\nTemplate: ' + name;
}
e.message += '\nFunction: ' + src;
throw e.stack && e;
}
return f;
}
catch (e) {
name = (settings.name ? '"' + settings.name + '"' : 'template');
e.message = '[Ltl] Failed to compile ' + name + '. ' + e.message;
throw e;
}
}
),
// Public API.
var ltl = {
// Store all of the templates that have been compiled.
cache: {
'-': function(v){return '<!--'+(JSON.stringify(v)||'').replace(/-->/g,'--\\>')+'-->';},
'$': function(v){return (!v&&v!==0?'':(typeof v=='object'?JSON.stringify(v)||'':''+v)).replace(/</g,'&lt;');},
'&': function(v){return encodeURIComponent(!v&&v!==0?'':''+v);}
},
// Allow users to see what version of ltl they're using.
version: '0.1.15',
// Last value of an auto-incremented ID.
lastId: 0,
// Store all of the templates that have been compiled.
cache: {},
// Store filter modules, such as "coffee-script" and "marked".
filters: {
js: 'js',
css: 'css'
},
// Default compile settings.
_options: {
tabWidth: 4,
outputVar: 'o',
contextVar: 'c',
partsVar: 'p',
enableDebug: false
},
// Store tags that evaluate elsewhere.
tags: {},
// Change compile options.
setOption: function (name, value) {
this._options[name] = value;
},
// Default compile settings.
options: {
tabWidth: 4,
enableDebug: false
},
// Create a function that accepts context and returns markup.
compile: function (code, options) {
// Change compile options.
setOption: function (name, value) {
this.options[name] = value;
},
// Copy the default options.
var settings = {
tabWidth: this._options.tabWidth,
outputVar: this._options.outputVar,
contextVar: this._options.contextVar,
partsVar: this._options.partsVar,
space: this._options.space,
enableDebug: this._options.enableDebug
};
for (var name in options) {
settings[name] = options[name];
}
if (settings.enableDebug && !settings.space) {
settings.space = ' ';
}
var getPattern = new RegExp(settings.contextVar + '\\.get\\.([$A-Za-z_][$A-Za-z_\d]*)', 'ig');
// Create a function that accepts state and returns markup.
compile: function (code, options) {
// Don't allow context/output/parts vars to become user vars.
var vars = varCharacters;
vars = vars.replace(settings.contextVar, '');
vars = vars.replace(settings.outputVar, '');
vars = vars.replace(settings.partsVar, '');
// Copy the default options.
var settings = {
tabWidth: this.options.tabWidth,
space: this.options.space,
enableDebug: this.options.enableDebug
};
for (var name in options) {
settings[name] = options[name];
}
if (settings.enableDebug && !settings.space) {
settings.space = ' ';
}
var getPattern = /s\.get\.([$A-Za-z_][$A-Za-z_\d]*)/g;
if (settings.space) {
settings.space = escapeBlock(settings.space);
}
if (settings.space) {
settings.space = ltl.escapeBlock(settings.space);
}
// Find out if we're in the browser.
var inBrowser = false;
try {
inBrowser = window.document.body.tagName == 'BODY';
}
catch (e) {
}
// Replace carriage returns for Windows compatibility.
code = code.replace(/\r/g, '');
// Replace carriage returns for Windows compatibility.
code = code.replace(/\r/g, '');
// Be lenient with mixed tabs and spaces, assuming tab width of 4.
var tabReplacement = Array(settings.tabWidth + 1).join(' ');
code = code.replace(/\t/g, tabReplacement);
// Be lenient with mixed tabs and spaces, assuming tab width of 4.
var tabReplacement = Array(settings.tabWidth + 1).join(' ');
code = code.replace(/\t/g, tabReplacement);
// We'll auto-detect tab width.
var currentTabWidth = 0;
// We'll auto-detect tab width.
var currentTabWidth = 0;
// Initialize the code, and start at level zero with no nesting.
var lines = code.split('\n');
var lineIndex;
var lineCount = lines.length;
// Initialize the code, and start at level zero with no nesting.
var lines = code.split('\n');
var globalSpaces = 0;
var indent = 0;
var stack = [];
var mode = 'html';
var previousTag;
var hasHtmlTag = false;
var hasHtmlOutput = false;
var hasAssignments = false;
var hasContent = false;
var tagDepth = 0;
var indent = 0;
var stack = [];
var mode = 'html';
var previousTag;
var hasHtmlOutput = false;
var tagDepth = 0;
var output = 'var ' + settings.outputVar + "='";
var output = "var o='";
var varIndex = 0;
var escapeHtmlVar = false;
var encodeUriVar = false;
var loopVars = [];
var varIndex = 0;
var escapeHtmlVar = false;
var encodeUriVar = false;
var escapeCommentVar = false;
var loopVars = [];
function appendText(textMode, text) {
if (textMode != mode) {
if (mode == 'html') {
output += "'" + (text == '}' ? '' : ';');
} else {
output += settings.outputVar + "+='";
}
mode = textMode;
}
// If we end up in a dot block, remember the starting indent and filter, and gather lines.
var blockIndent = 0;
var blockFilter = '';
var blockLines = null;
var blockTag = null;
var blockName = '';
var blockContent = '';
var blockSets = null;
var hasGets = false;
var inComment = false;
// Support adding properties like "js" to the template function.
var properties = {};
var blockProperty;
var blockTarget;
var eventLanguage;
// Allow event listeners to be added.
var bindings = {};
function appendText(textMode, text) {
if (textMode != mode) {
if (mode == 'html') {
text = interpolate(text);
output += "'" + (text == '}' ? '' : ';');
}
output += text;
else {
output += "o+='";
}
mode = textMode;
}
if (mode == 'html') {
text = interpolate(text);
}
output += text;
}
function startBlock(filter, line) {
blockIndent = indent + 1;
blockFilter = filter;
blockLines = [];
if (line) {
blockLines.push(line);
}
function startBlock(filter, content) {
blockIndent = indent + 1;
blockFilter = filter;
blockLines = [];
if (content) {
(blockLines = blockLines || []).push(content);
}
}
function appendBlock() {
var text = blockLines.join('\n');
function appendBlock() {
var text = blockLines.join('\n');
// Reset the blockage.
blockIndent = 0;
// Reset the blockage.
blockIndent = 0;
// Some options should be passed through.
var blockOptions = {
outputVar: settings.outputVar,
contextVar: settings.contextVar,
partsVar: settings.partsVar,
space: settings.space,
enableDebug: settings.enableDebug
};
// Some options should be passed through.
var blockOptions = {
space: settings.space,
enableDebug: settings.enableDebug
};
// If we're in a "call" block, compile the contents.
if (blockFilter == 'call') {
appendText('html',
"'+this['" + blockName + "'].call(this," + settings.contextVar +
(text ? ',' + ltl.compile(text, blockOptions) : '') + ")+'");
return;
// If we're in a "call" block, call another template with compiled parts.
if (blockFilter == 'call') {
// If there's a key, pass a sub-state.
// * With a sub-state: this['VIEW'].call((s['KEY']._='KEY')&&s['KEY'])
// * Without sub-state: this['VIEW'].call(s)
var key = ltl.trim(blockContent || '');
var state = key ? "s['" + key + "']" : 's';
appendText('html',
"'+this['" + blockName + "'].call(this," + state +
(text ? ',' + ltl.compile(text, blockOptions) : '') + ")+'");
return;
}
// For a "set" block, add to the array of "set" block values.
else if (blockFilter == 'set' || blockFilter == 'set:') {
var block;
if (blockFilter == 'set') {
block = ltl.compile(text, blockOptions).toString();
} else {
block = "function(){return '" + ltl.escapeBlock(text) + "'}";
}
// For a "set" block.
else if (blockFilter == 'set' || blockFilter == 'set:') {
var block;
if (blockFilter == 'set') {
block = ltl.compile(text, blockOptions).toString();
} else {
block = "function(){return '" + escapeBlock(text) + "'}";
}
blockSets.push("'" + escapeSingleQuotes(blockName) + "':" + block);
return;
(blockSets = blockSets || []).push("'" + ltl.escapeSingleQuotes(blockName) + "':" + block);
return;
}
// If there's a filter, get its module.
else if (blockFilter) {
if (blockFilter == 'coffee') {
blockFilter = 'coffee-script';
}
// If there's a filter, get its module.
else if (blockFilter) {
if (blockFilter == 'coffee') {
blockFilter = 'coffee-script';
else if (blockFilter == 'md') {
blockFilter = 'marked';
}
var compiler = ltl.getFilter(blockFilter);
if (compiler.renderSync) {
text = compiler.renderSync(text, {
data: text,
compressed: true
});
}
else if (compiler.render) {
if (blockFilter == 'less') {
compiler.render(text, function (err, result) {
if (err) {
throw err;
}
text = result.css;
});
}
else if (blockFilter == 'md') {
blockFilter = 'marked';
else {
text = compiler.render(text);
}
try {
if (inBrowser) {
blockFilter = window[blockFilter];
}
else {
blockFilter = require(blockFilter);
}
}
catch (e) {
throw new Error('Unknown filter "' + blockFilter + '". Try "npm install ' + blockFilter + '" first.');
}
} else {
blockFilter = 'text';
}
// Detect the module's API, and filter the text.
if (blockFilter.compile) {
else if (compiler.compile) {
var nowrap = /^[^A-Z]*NOWRAP/.test(text);
text = blockFilter.compile(text);
text = compiler.compile(text);
if (nowrap) {

@@ -245,56 +307,71 @@ text = text.replace(/(^\(function\(\) \{\s*|\s*\}\)\.call\(this\);\s*$)/g, '');

}
else if (blockFilter.parse) {
text = blockFilter.parse(text);
else if (compiler.parse) {
text = compiler.parse(text);
}
else if (typeof blockFilter == 'function') {
text = blockFilter(text);
else if (typeof compiler == 'function') {
text = compiler(text);
}
}
else {
blockFilter = 'text';
}
text = trim(text);
if (settings.space) {
if (hasHtmlOutput) {
text = '\n' + text;
}
text = text.replace(/\n/g, '\n' + repeat(settings.space, tagDepth));
if (blockTag) {
text += '\n' + repeat(settings.space, tagDepth - 1);
}
text = ltl.trim(text);
if (settings.space) {
if (hasHtmlOutput) {
text = '\n' + text;
}
text = text.replace(/\n/g, '\n' + ltl.repeat(settings.space, tagDepth));
if (blockTag) {
text += '\n' + ltl.repeat(settings.space, tagDepth - 1);
}
}
appendText('html', escapeBlock(text));
blockTag = null;
blockFilter = null;
if (blockProperty) {
var value = properties[blockProperty];
value = (value ? value + '\n' : '') + text;
properties[blockProperty] = value;
}
else if (blockTarget == 'js') {
appendText('script', text);
}
else {
appendText('html', ltl.escapeBlock(text));
}
function backtrackIndent() {
while (stack.length > indent) {
var tags = stack.pop();
if (tags) {
tags = tags.split(/,/g);
for (var i = tags.length - 1; i >= 0; i--) {
var tag = tags[i];
if (tag == '//') {
inComment = false;
blockTag = null;
blockFilter = null;
blockProperty = null;
blockTarget = null;
}
function backtrackIndent() {
while (stack.length > indent) {
var tags = stack.pop();
if (tags) {
tags = tags.split(/,/g);
for (var i = tags.length - 1; i >= 0; i--) {
var tag = tags[i];
if (tag == '//') {
inComment = false;
}
else if (tag == '-') {
appendText('html', '-->');
}
else if (ltl.controlPattern.test(tag)) {
appendText('script', '}');
if (tag == 'for') {
loopVars.pop();
}
else if (tag == '-') {
appendText('html', '-->');
}
else if (!ltl.selfClosePattern.test(tag)) {
var html = '</' + tag + '>';
tagDepth--;
if (tag == previousTag) {
previousTag = null;
}
else if (controlPattern.test(tag)) {
appendText('script', '}');
if (tag == 'for') {
loopVars.pop();
}
else if (settings.space) {
html = '\\n' + ltl.repeat(settings.space, tagDepth) + html;
}
else if (!selfClosePattern.test(tag)) {
var html = '</' + tag + '>';
tagDepth--;
if (tag == previousTag) {
previousTag = null;
}
else if (settings.space) {
html = '\\n' + repeat(settings.space, tagDepth) + html;
}
appendText('html', html);
}
appendText('html', html);
}

@@ -304,472 +381,568 @@ }

}
}
function transformScript(script) {
var c = settings.contextVar;
var found = false;
function transformScript(script) {
var found = false;
script = script.replace(/^(for)\s+([$A-Za-z_][$A-Za-z_\d]*)\s+in\s+([$A-Za-z_][$A-Za-z_\d\.]*)\s*$/,
function(match, keyword, item, array) {
found = true;
var i = vars[varIndex++];
var l = vars[varIndex++];
var e = vars[varIndex++];
loopVars.push([[item, e]]);
return 'for(var ' + e + ',' + i + '=0,' + l + '=' + c + '.' + array + '.length;' +
i + '<' + l + ';++' + i + ')' +
'{' + e + '=' + c + '.' + array + '[' + i + ']' + ';';
});
if (found) {
stack.push('for');
return script;
}
script = script.replace(/^(for)\s+([$A-Za-z_][$A-Za-z_\d]*)\s+in\s+([$A-Za-z_][$A-Za-z_\d\.]*)\s*$/,
function(match, keyword, item, array) {
found = true;
var i = ltl.vars[varIndex++];
var l = ltl.vars[varIndex++];
var e = ltl.vars[varIndex++];
loopVars.push([[item, e]]);
return 'for(var ' + e + ',' + i + '=0,' + l + '=s.' + array + '.length;' +
i + '<' + l + ';++' + i + ')' +
'{' + e + '=s.' + array + '[' + i + ']' + ';';
});
if (found) {
stack.push('for');
return script;
}
script = script.replace(/^(for)\s+([$A-Za-z_][$A-Za-z_\d]*)\s*,\s*([$A-Za-z_][$A-Za-z_\d]*)\s+of\s+([$A-Za-z_][$A-Za-z_\d\.]*)\s*$/,
function(match, keyword, key, value, object) {
found = true;
var k = vars[varIndex++];
var v = vars[varIndex++];
loopVars.push([[key, k], [value, v]]);
return 'for(var ' + k + ' in ' + c + '.' + object + ')' +
'{if(!' + c + '.' + object + '.hasOwnProperty(' + k + '))continue;' +
v + '=' + c + '.' + object + '[' + k + ']' + ';';
});
script = script.replace(/^(for)\s+([$A-Za-z_][$A-Za-z_\d]*)\s*,\s*([$A-Za-z_][$A-Za-z_\d]*)\s+of\s+([$A-Za-z_][$A-Za-z_\d\.]*)\s*$/,
function(match, keyword, key, value, object) {
found = true;
var k = ltl.vars[varIndex++];
var v = ltl.vars[varIndex++];
loopVars.push([[key, k], [value, v]]);
return 'for(var ' + k + ' in s.' + object + ')' +
'{if(!s.' + object + '.hasOwnProperty(' + k + '))continue;' +
v + '=s.' + object + '[' + k + ']' + ';';
});
if (found) {
stack.push('for');
return script;
}
if (found) {
stack.push('for');
return script;
}
script = script.replace(/^(else if|else|if)\s*(.*)\s*$/i,
function(match, keyword, condition) {
found = true;
return keyword + (condition ? '(' + contextify(condition) + ')' : '') + '{';
});
script = script.replace(/^(else if|else|if)\s*(.*)\s*$/i,
function(match, keyword, condition) {
found = true;
return keyword + (condition ? '(' + prependState(condition) + ')' : '') + '{';
});
stack.push('if');
return script;
stack.push('if');
return script;
}
/**
* Give scope to a JavaScript expression by prepending the state variable "s".
* TODO: Parse using acorn so that strings can't be interpreted as ltl.vars.
*/
function prependState(code, unescapeSingleQuotes) {
// Interpolations got escaped as HTML and must be unescaped to be JS.
if (unescapeSingleQuotes) {
code = code.replace(/\\'/g, "'");
}
/**
* Convert a JavaScripty expression to a scoped expression using contextVar.
* TODO: Actually parse JavaScript so that strings can't be interpreted as vars.
*/
function contextify(code) {
var tokens = code.split(/\b/);
var isProperty = false;
var isLoopVar = false;
var isInString = false;
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
var stringTokens = token.match(/['"]/g);
if (stringTokens) {
isInString = ((isInString ? 1 : 0 ) + stringTokens.length) % 2;
}
else if (!isInString) {
if (/^[a-z_]/i.test(token)) {
if (!jsPattern.test(token)) {
for (var j = 0; j < loopVars.length; j++) {
for (var k = 0; k < loopVars[j].length; k++) {
if (token == loopVars[j][k][0]) {
isLoopVar = true;
tokens[i] = loopVars[j][k][1];
}
var tokens = code.split(/\b/);
var isProperty = false;
var isLoopVar = false;
var isInString = false;
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
var stringTokens = token.match(/['"]/g);
if (stringTokens) {
isInString = ((isInString ? 1 : 0 ) + stringTokens.length) % 2;
}
else if (!isInString) {
if (/^[a-z_]/i.test(token)) {
if (!ltl.jsPattern.test(token)) {
for (var j = 0; j < loopVars.length; j++) {
for (var k = 0; k < loopVars[j].length; k++) {
if (token == loopVars[j][k][0]) {
isLoopVar = true;
tokens[i] = loopVars[j][k][1];
}
}
if (!isProperty && !isLoopVar) {
tokens[i] = settings.contextVar + '.' + token;
}
}
if (!isProperty && !isLoopVar) {
tokens[i] = 's.' + token;
}
}
isProperty = token[token.length - 1] == '.';
isLoopVar = false;
}
isProperty = token[token.length - 1] == '.';
isLoopVar = false;
}
code = tokens.join('');
getPattern = /c\.get\.([$A-Za-z_][$A-Za-z_\d]*)/g;
code = code.replace(getPattern, function (match, part) {
hasGets = true;
return settings.partsVar + "['" + part + "'].call(this," + settings.contextVar + ")";
});
return code;
}
code = tokens.join('');
getPattern = /s\.get\.([$A-Za-z_][$A-Za-z_\d]*)/g;
code = code.replace(getPattern, function (match, part) {
hasGets = true;
return "p['" + part + "'].call(this,s)";
});
return code;
}
/**
* Find ${...}, &{...} and ={...} and turn them into contextified insertions unless escaped.
*/
function interpolate(code) {
return code.replace(/(\\?)([$=&])\{([^\}]+)\}/g, function(match, backslash, symbol, expression) {
if (backslash) {
return symbol + '{' + expression + '}';
/**
* Find ={...}, ${...}, &{...}, and -{...} interpolations.
* Turn them into state-aware insertions unless escaped.
*/
function interpolate(code) {
return code.replace(/(\\?)([$=&-])\{([^\}]+)\}/g, function(match, backslash, symbol, expression) {
if (backslash) {
return symbol + '{' + expression + '}';
}
if (symbol == '-') {
if (!escapeCommentVar) {
escapeCommentVar = ltl.vars[varIndex++];
}
if (symbol == '$') {
if (!escapeHtmlVar) {
escapeHtmlVar = vars[varIndex++];
}
return "'+" + escapeHtmlVar + '(' + contextify(expression) + ")+'";
return "'+" + escapeCommentVar + '(' + prependState(expression, true) + ")+'";
}
else if (symbol == '$') {
if (!escapeHtmlVar) {
escapeHtmlVar = ltl.vars[varIndex++];
}
else if (symbol == '&') {
if (!encodeUriVar) {
encodeUriVar = vars[varIndex++];
}
return "'+" + encodeUriVar + '(' + contextify(expression) + ")+'";
return "'+" + escapeHtmlVar + '(' + prependState(expression, true) + ")+'";
}
else if (symbol == '&') {
if (!encodeUriVar) {
encodeUriVar = ltl.vars[varIndex++];
}
else {
return "'+" + contextify(expression) + "+'";
}
});
}
return "'+" + encodeUriVar + '(' + prependState(expression, true) + ")+'";
}
else {
return "'+" + prependState(expression, true) + "+'";
}
});
}
// If we end up in a dot block, remember the starting indent and filter, and gather lines.
var blockIndent = 0;
var blockFilter = '';
var blockLines = [];
var blockTag = null;
// Iterate over each line.
for (lineIndex = 0; lineIndex < lineCount; lineIndex++) {
var line = lines[lineIndex];
var blockName = '';
var blockSets = [];
var hasGets = false;
var hasAssignments = false;
var inComment = false;
// Mitigate recursion past 100 deep.
var maxT = 1e2;
// Iterate over each line.
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
// If the line is all whitespace, ignore it.
if (!/\S/.test(line)) {
if (blockIndent) {
(blockLines = blockLines || []).push('');
}
continue;
}
// Mitigate recursion past 100 deep.
var maxT = 1e2;
// Find the number of leading spaces.
var spaces = line.search(/[^ ]/);
// If the line is all whitespace, ignore it.
if (!/\S/.test(line)) {
continue;
}
// If the first line with content has leading spaces, assume they all do.
if (!hasContent) {
globalSpaces = spaces;
hasContent = true;
}
// Find the number of leading spaces.
var spaces = line.search(/[^ ]/);
var adjustedSpaces = Math.max(spaces - globalSpaces, 0);
// If this is our first time seeing leading spaces, that's our tab width.
if (spaces > 0 && !currentTabWidth) {
currentTabWidth = spaces;
}
// If this is our first time seeing leading spaces, that's our tab width.
if (adjustedSpaces > 0 && !currentTabWidth) {
currentTabWidth = adjustedSpaces;
}
// Calculate the number of levels of indentation.
indent = spaces ? Math.round(spaces / currentTabWidth) : 0;
// Calculate the number of levels of indentation.
indent = adjustedSpaces ? Math.round(adjustedSpaces / currentTabWidth) : 0;
// If we're in a block, we can append or close it.
if (blockIndent) {
// If we've gone back to where the block started, close the block.
if (indent < blockIndent) {
appendBlock();
}
// If we're still in the block, append to the block code.
else {
line = line.substring(Math.min(spaces, currentTabWidth * blockIndent));
blockLines.push(line);
continue;
}
// If we're in a block, we can append or close it.
if (blockIndent) {
// If we've gone back to where the block started, close the block.
if (indent < blockIndent) {
appendBlock();
}
// Strip the leading spaces.
line = line.substring(spaces);
// Backtrack, closing any nested tags that need to be closed.
backtrackIndent();
if (inComment) {
// If we're still in the block, append to the block code.
else {
line = line.substring(Math.min(spaces, currentTabWidth * blockIndent));
(blockLines = blockLines || []).push(line);
continue;
}
}
// Control patterns such as if/else/for must transform into true JavaScript.
if (controlPattern.test(line)) {
var script = transformScript(line);
appendText('script', script);
}
// Strip the leading spaces.
line = line.substring(spaces);
// Assignment patterns just need to be contextified.
else if (assignmentPattern.test(line)) {
hasAssignments = true;
line = contextify(line) + ';';
appendText('script', line);
// Backtrack, closing any nested tags that need to be closed.
backtrackIndent();
if (inComment) {
continue;
}
// Control patterns such as if/else/for must transform into true JavaScript.
if (ltl.controlPattern.test(line)) {
var script = transformScript(line);
appendText('script', script);
}
// Assignment patterns just need to be stateified.
else if (ltl.assignmentPattern.test(line)) {
hasAssignments = true;
line = prependState(line) + ';';
appendText('script', line);
}
// Expression patterns make things append.
else if (ltl.commandPattern.test(line)) {
var data = ltl.trim(line).split(/\s+/);
var command = data.shift();
blockName = data.shift();
blockContent = data.join(' ');
var pair = blockName.split(':');
blockName = pair[0];
if (command == 'get') {
appendText('html', "'+" + "p['" + blockName + "'].call(this,s)+'");
hasGets = true;
}
// Expression patterns make things append.
else if (commandPattern.test(line)) {
var pair = trim(line).split(/\s+/);
var command = pair[0];
blockName = pair[1];
var content = pair[2];
pair = blockName.split(':');
blockName = pair[0];
if (command == 'get') {
appendText('html', "'+" + settings.partsVar + "['" + blockName + "'].call(this," + settings.contextVar + ")+'");
hasGets = true;
else {
if (pair[1] === '') {
command += ':';
}
else {
if (pair[1] === '') {
command += ':';
}
startBlock(command, content);
}
startBlock(command, blockContent);
}
}
// Tags must be parsed for id/class/attributes/content.
else {
var rest = line;
var t = 0;
// Tags must be parsed for id/class/attributes/content.
else {
var rest = line;
var t = 0;
// Process the rest of the line recursively.
while (rest && (++t < maxT)) {
var tag = '';
var id = '';
var autoClass = '';
var classes = [];
var attributes = '';
var character = '';
var end = 0;
var content = '';
// Process the rest of the line recursively.
while (rest && (++t < maxT)) {
var tag = '';
var id = '';
var classes = [];
var attributes = '';
var content = '';
var character = '';
var end = 0;
character = rest[0];
// Process the rest of the line recursively.
while (rest && (++t < maxT)) {
character = rest[0];
// If it's an ID, read up to the next thing, and save the ID.
if (character == '#') {
end = rest.search(/([\.\(>:\s]|$)/);
id = id || rest.substring(1, end);
rest = rest.substring(end);
}
// If it's an ID, read up to the next thing, and save the ID.
if (character == '#') {
end = rest.search(/([\.\(>:\s]|$)/);
id = id || rest.substring(1, end);
rest = rest.substring(end);
}
// If it's a class, read up to the next thing, and save the class.
else if (character == '.') {
end = rest.search(/([@#\(>:\s]|$)/);
classes.push(rest.substring(1, end).replace(/\./g, ' '));
rest = rest.substring(end);
}
// If it's a class, read up to the next thing, and save the class.
else if (character == '.') {
end = rest.search(/([#\(>:\s]|$)/);
classes.push(rest.substring(1, end).replace(/\./g, ' '));
rest = rest.substring(end);
}
// If it's the beginning of a list of attributes, iterate through them.
else if (character == '(') {
// If it's the beginning of a list of attributes, iterate through them.
else if (character == '(') {
// Move on from the parentheses.
rest = rest.substring(1);
// Move on from the parentheses.
rest = rest.substring(1);
// Build attributes.
attributes = '';
while (rest && (++t < maxT)) {
// Build attributes.
attributes = '';
while (rest && (++t < maxT)) {
// Find quoted attributes or the end of the list.
end = rest.search(/[\)"']/);
// Find quoted attributes or the end of the list.
end = rest.search(/[\)"']/);
// If there's no end, read what's left as attributes.
if (end < 0) {
attributes += rest;
rest = '';
break;
}
character = rest[end];
// If there's no end, read what's left as attributes.
if (end < 0) {
attributes += rest;
rest = '';
break;
}
character = rest[end];
// If it's the end, get any remaining attribute and get out.
if (character == ')') {
attributes += rest.substring(0, end);
rest = rest.substring(end + 1);
break;
}
// If it's the end, get any remaining attribute and get out.
if (character == ')') {
attributes += rest.substring(0, end);
rest = rest.substring(end + 1);
break;
}
// If it's not the end, read a quoted param.
else {
// Allow for attributes to be comma separated or not.
// Also allow for valueless attributes.
attributes += rest.substring(0, end).replace(/[,\s]+/g, ' ');
rest = rest.substring(end);
// If it's not the end, read a quoted param.
else {
// Allow for attributes to be comma separated or not.
// Also allow for valueless attributes.
attributes += rest.substring(0, end).replace(/[,\s]+/g, ' ');
rest = rest.substring(end);
// Find the next end quote.
// TODO: Deal with backslash-delimited quotes.
end = rest.indexOf(character, 1);
// Find the next end quote.
// TODO: Deal with backslash-delimited quotes.
end = rest.indexOf(character, 1);
// Read to the end of the attribute.
attributes += rest.substring(0, end + 1);
rest = rest.substring(end + 1);
}
// Read to the end of the attribute.
attributes += rest.substring(0, end + 1);
rest = rest.substring(end + 1);
}
}
}
// If the next character is a greater than symbol, break for inline nesting.
else if (character == '>') {
rest = rest.replace(/^>\s*/, '');
break;
}
// If the next character is a greater than symbol, break for inline nesting.
else if (character == '>') {
rest = rest.replace(/^>\s*/, '');
break;
}
// If the next character is a colon, enter a block.
else if (character == ':') {
blockTag = tag;
rest = rest.substring(1).split(' ');
startBlock(rest.shift());
if (rest.length > 0) {
blockLines.push(rest.join(' '));
// If the next character is a colon, enter a filter block.
else if (character == ':') {
blockTag = tag;
rest = rest.substring(1).split(' ');
startBlock(rest.shift(), rest.join(' '));
rest = '';
break;
}
// If the next character is a plus, store it as a property.
else if (character == '+') {
rest.replace(/^\+([^:\s]+):?(\S*)\s?(.*)$/, function (match, name, filter, content) {
var target = ltl.languages[name] || name;
blockProperty = target;
blockTag = '';
filter = (name == target ? '' : name) || filter;
startBlock(filter, content);
});
rest = '';
break;
}
// If the next character is an "at" symbol, create event listener references.
else if (character == '@') {
rest = rest.replace(/^@([$a-z0-9_~@]*)/i, function (match, events) {
autoClass = autoClass || '_ltl' + (++ltl.lastId);
classes.push(autoClass);
var bind = bindings[autoClass] = bindings[autoClass] || {};
events = events.split('@');
for (var e = 0; e < events.length; e++) {
var listeners = events[e].split('~');
var eventName = listeners.shift();
var listen = bind[eventName] = bind[eventName] || [];
for (var l = 0; l < listeners.length; l++) {
listen.push(listeners[l]);
}
}
rest = '';
break;
}
});
}
// If the next character is a space, it's the start of content.
else if (character == ' ') {
content = trim(rest);
rest = '';
}
// If the next character is a tilde, it's an event listener or language.
else if (character == '~') {
rest.replace(/^(~[^:\s]+):?(\S*)\s?(.*)$/, function (match, name, filter, content) {
var target = ltl.languages[name];
if (target) {
eventLanguage = name;
}
else {
blockProperty = name;
blockTag = '';
startBlock(filter || eventLanguage, content);
}
});
rest = '';
break;
}
// If the next character isn't special, it's part of a tag.
else {
end = rest.search(/([#\.\(>:\s]|$)/);
// Prevent overwriting the tag.
tag = tag || rest.substring(0, end);
rest = rest.substring(end);
}
// If the next character is a space, it's the start of content.
else if (character == ' ') {
content = ltl.trim(rest);
rest = '';
}
// If it's a comment, set a boolean so we can ignore its contents.
if (tag.indexOf('//') === 0) {
tag = '//';
inComment = true;
// If the next character isn't special, it's part of a tag.
else {
end = rest.search(/([#\.\(>:\s@]|$)/);
// Prevent overwriting the tag.
tag = tag || rest.substring(0, end);
rest = rest.substring(end);
}
// If it's not a comment, we'll add some HTML.
else {
var className = classes.join(' ');
}
// Default to a <div> unless we're in a tagless block.
if (!tag) {
var useDefault = (blockTag === null) || id || className || attributes;
if (useDefault) {
tag = blockTag = 'div';
}
}
// If it's a JS/CSS language, start an inline block.
if (ltl.languages[tag]) {
blockTarget = ltl.languages[tag];
var filter = (tag == blockTarget ? '' : tag) || filter;
tag = blockTag = blockTarget == 'css' ? 'style' : '//';
startBlock(filter, content);
}
// Convert ! or doctype into !DOCTYPE and assume html.
if (tag == '!' || tag == 'doctype') {
tag = '!DOCTYPE';
attributes = attributes || 'html';
}
// If it's a comment, set a boolean so we can ignore its contents.
if (tag.indexOf('//') === 0) {
tag = '//';
inComment = true;
}
// Add attributes to the tag.
var html = tag;
if (id) {
html += ' id="' + id + '"';
}
if (className) {
html += ' class="' + className + '"';
}
if (attributes) {
html += ' ' + attributes;
}
// If it's not a comment, we'll add some HTML.
else {
var className = classes.join(' ');
// Convert minus to a comment.
if (tag == '-') {
html = '<!--' + content;
// Default to a <div> unless we're in a tagless block.
if (!tag) {
var useDefault = (blockTag === null) || id || className || attributes;
if (useDefault) {
tag = blockTag = 'div';
}
else {
html = '<' + html + '>' + content;
}
}
html = escapeSingleQuotes(html);
if (tag == 'html' && !/DOCTYPE/.test(output)) {
// Convert ! or doctype into !DOCTYPE and assume html.
if (tag == '!' || tag == 'doctype') {
tag = '!DOCTYPE';
attributes = attributes || 'html';
}
// Add attributes to the tag.
var html = tag;
if (id) {
html += ' id="' + id + '"';
}
if (className) {
html += ' class="' + className + '"';
}
if (attributes) {
html += ' ' + attributes;
}
// Convert space tag to a space character.
if (tag == 'space') {
html = ' ' + content;
}
// Convert minus to a comment.
else if (tag == '-') {
html = '<!--' + content;
}
else {
html = '<' + html + '>' + content;
}
html = ltl.escapeSingleQuotes(html);
if (tag == 'html') {
// If there's an HTML tag, don't wrap with a state.
hasHtmlTag = true;
if (!/DOCTYPE/.test(output)) {
html = '<!DOCTYPE html>' + (settings.space ? '\\n' : '') + html;
}
}
// Prepend whitespace if requested via settings.space.
if (settings.space) {
html = repeat(settings.space, tagDepth) + html;
// Prepend a line break if this isn't the first tag.
if (hasHtmlOutput) {
html = '\\n' + html;
}
// Prepend whitespace if requested via settings.space.
if (settings.space) {
html = ltl.repeat(settings.space, tagDepth) + html;
// Prepend a line break if this isn't the first tag.
if (hasHtmlOutput) {
html = '\\n' + html;
}
// Add the HTML to the template function output.
if (tag) {
appendText('html', html);
hasHtmlOutput = true;
}
}
// Add the HTML to the template function output.
if (tag) {
appendText('html', html);
hasHtmlOutput = true;
}
}
// Make sure we can close this tag.
if (stack[indent]) {
stack[indent] += ',' + tag;
}
else {
stack[indent] = tag;
}
if (tag) {
// Allow same-line tag open/close in settings.space mode.
previousTag = tag;
if (!selfClosePattern.test(tag)) {
tagDepth++;
}
// Make sure we can close this tag.
if (stack[indent]) {
stack[indent] += ',' + tag;
}
else {
stack[indent] = tag;
}
tag = '';
id = '';
classes = [];
attributes = '';
content = '';
// Allow same-line tag open/close in settings.space mode.
previousTag = tag;
if (!ltl.selfClosePattern.test(tag)) {
tagDepth++;
}
}
tag = '';
id = '';
classes = [];
attributes = '';
content = '';
}
}
}
// We've reached the end, so unindent all the way.
indent = 0;
if (blockIndent) {
appendBlock();
}
backtrackIndent();
// We've reached the end, so unindent all the way.
indent = 0;
if (blockIndent) {
appendBlock();
}
backtrackIndent();
// Add the return statement (ending concatenation, where applicable).
appendText('script', 'return ' + settings.outputVar);
// Add the return statement (ending concatenation, where applicable).
appendText('script', 'return o');
if (blockSets.length) {
return '{' + blockSets.join(',') + '}';
}
if (blockSets) {
return '{' + blockSets.join(',') + '}';
}
// Create the function.
if (escapeHtmlVar) {
output = "function " + escapeHtmlVar + "(t){return (t==null?'':''+t).replace(/</g,'&lt;')};" + output;
}
if (encodeUriVar) {
output = "function " + encodeUriVar + "(t){return (encodeURIComponent||escape)(t==null?'':''+t)};" + output;
}
if (hasAssignments) {
output = settings.contextVar + '=' + settings.contextVar + '||{};' + output;
}
output = 'function(' + settings.contextVar + (hasGets ? ',' + settings.partsVar : '') + '){' + output + '}';
// Create the function.
if (escapeCommentVar) {
output = (settings.name ?
'var ' + escapeCommentVar + "=this['-'];" :
ltl.cache['-'].toString().replace(/\(/, escapeCommentVar + '(') + ';') + output;
}
if (escapeHtmlVar) {
output = (settings.name ?
'var ' + escapeHtmlVar + "=this.$;" :
ltl.cache.$.toString().replace(/\(/, escapeHtmlVar + '(') + ';') + output;
}
if (encodeUriVar) {
output = (settings.name ?
'var ' + encodeUriVar + "=this['&'];" :
ltl.cache['&'].toString().replace(/\(/, encodeUriVar + '(') + ';') + output;
}
if (hasAssignments) {
output = 's=s||{};' + output;
}
output = 'function(s' + (hasGets ? ',p' : '') + '){' + output + '}';
// Evaluate the template as a function.
try {
eval('eval.f=' + output); // jshint ignore:line
}
catch (e) {
// If we failed in a dev environment in Node, we can try to debug it.
if (process && settings.enableDebug) {
debug(output, settings);
// Evaluate the template as a function.
name = settings.name;
var template;
try {
template = ltl.run(output, name);
}
catch (e) {
name = (name ? '"' + name + '"' : 'template');
e.message = '[Ltl] Failed to compile ' + name + '. ' + e.message;
throw e;
}
// If there's a name specified, cache it and refer to it.
if (name) {
ltl.cache[name] = template;
template.key = name;
template.cache = ltl.cache;
}
// Add event bindings to the JS property.
for (var key in bindings) {
var events = bindings[key];
for (var event in events) {
var listeners = events[event];
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
var js = properties['~' + listener];
properties.js = properties.js ? properties.js + '\n' : '';
properties.js += 'Jymin.on(".' + key + '","' + event + '",' +
'function(element,event,target){' + js + '});';
}
// Otherwise, just fail.
else {
var name = (settings.name ? '"' + settings.name + '"' : 'template');
e.message = '[Ltl] Failed to compile ' + name + '. ' + e.message;
throw e;
}
}
var template = eval.f;
}
// If there's a name specified, cache the template with that name.
if (settings.name) {
this.cache[settings.name] = template;
// Add any discovered properties to the template.
for (name in properties) {
if (name[0] != '~') {
template[name] = template[name] || properties[name];
}
return template;
}
};
if (isBrowser) {
window.ltl = ltl;
return template;
}
else {
module.exports = ltl;
}
};
})();
// Export Ltl as a CommonJS module.
if (typeof module !== 'undefined') {
module.exports = ltl;
}
{
"name": "ltl",
"version": "0.1.15",
"version": "0.2.0",
"description": "Lean Template Language for JavaScript and HTML",
"dependencies": {},
"devDependencies": {
"coffee-script": "1.8.0",
"coveralls": "2.11.1",
"dot": "1.0.2",
"exam": "^0.0.7",
"istanbul": "0.3.1",
"jade": "1.6.0",
"coffee-script": "1.9.0",
"dot": "^1.0.3",
"exam": "^0.2.4",
"jade": "^1.9.2",
"jsx": "0.9.89",
"less": "^2.4.0",
"markdown": "0.5.0",
"marked": "0.3.2",
"zeriousify": "^0.1.10"
"marked": "0.3.3"
},

@@ -33,5 +32,5 @@ "keywords": [

"test": "./node_modules/exam/exam.js",
"cover": "istanbul cover ./node_modules/exam/exam.js",
"cover": "./node_modules/exam/exam-cover.js",
"report": "open coverage/lcov-report/index.html",
"coveralls": "istanbul cover ./node_modules/exam/exam.js --report lcovonly -- -R spec && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage",
"coveralls": "./node_modules/exam/exam-coveralls.js",
"perf": "node perf"

@@ -38,0 +37,0 @@ },

@@ -1,4 +0,4 @@

# Ltl
[![NPM Version](https://img.shields.io/npm/v/ltl.svg) ![Downloads](https://img.shields.io/npm/dm/ltl.svg)](https://npmjs.org/package/ltl)
# <a href="http://lighter.io/ltl" style="font-size:40px;text-decoration:none;color:#000"><img src="https://cdn.rawgit.com/lighterio/lighter.io/master/public/ltl.svg" style="width:90px;height:90px"> Ltl</a>
[![NPM Version](https://img.shields.io/npm/v/ltl.svg)](https://npmjs.org/package/ltl)
[![Downloads](https://img.shields.io/npm/dm/ltl.svg)](https://npmjs.org/package/ltl)
[![Build Status](https://img.shields.io/travis/lighterio/ltl.svg)](https://travis-ci.org/lighterio/ltl)

@@ -9,3 +9,4 @@ [![Code Coverage](https://img.shields.io/coveralls/lighterio/ltl/master.svg)](https://coveralls.io/r/lighterio/ltl)

The Ltl template language (pronounced "little") uses a clean
The [Ltl](http://lighter.io/ltl) template language (pronounced "little") uses a clean
[Jade](http://jade-lang.com/reference/)-like syntax to generate

@@ -16,18 +17,23 @@ HTML at [doT](https://github.com/olado/doT)-like speeds.

## Getting Started
Add Ltl to your project.
## Quick Start
Add `ltl` to your project:
```bash
$ npm install --save ltl
npm install --save ltl
```
Compile and render templates.
Compile and render templates:
```javascript
var ltl = require('ltl');
var template = ltl.compile('#hi Hello #{name}!');
var result = template({name: 'World'});
// result: '<div id="hi">Hello World!</div>'
var ltl = require("ltl");
var template = ltl.compile("#hi Hello ${who}!");
var result = template({who: "World"});
```
```html
<div id="hi">Hello World!</div>
```
## API
### ltl.compile(code, [options])

@@ -44,7 +50,7 @@ * `code` is a string of Ltl code.

Supported options:
* **outputVar** is the name of the variable that Ltl concatenates to. (Default: "o")
* **contextVar** is the name of the argument that passes context into a template. (Default: "c")
* **partsVar** is the name of the argument that a Ltl template receives from callers. (Default: "p")
* **tabWidth** is the number of spaces that tabs are converted to before compilation. (Default: 4)
* `tabWidth` is the number of spaces that tabs are converted to before compilation. (Default: 4)
### ltl.targets
Targets are key-value pairs of transpiler names and target language names.
## Language

@@ -153,4 +159,4 @@

:markdown
# Ltl
It's a recursive acronym for "Ltl Template Language".
# Ltl
It's a recursive acronym for "Ltl Template Language".
```

@@ -165,3 +171,3 @@ ```html

A filter must have a function named `compile` or `parse` which accepts a context
A filter must have a function named `compile` or `parse` which accepts a state
and returns a string, or it can be such a function itself.

@@ -199,3 +205,3 @@

You can output the value of a context property with `${..}`,
You can output the value of a state property with `${..}`,
and special HTML characters will be escaped for you to

@@ -214,3 +220,3 @@ prevent silly little XSS attacks.

Context: `{query: "good brewpubs"}`
State: `{query: "good brewpubs"}`
```jade

@@ -227,3 +233,3 @@ a(href="?q=&{query}")

Context: `{unsafe: "<script>alert('Gotcha!')</script>"}`
State: `{unsafe: "<script>alert('Gotcha!')</script>"}`
```jade

@@ -247,3 +253,3 @@ . ={unsafe}

You can assign a value to a variable in the template context using `=`.
You can assign a value to a variable in the template state using `=`.
```jade

@@ -258,5 +264,5 @@ who = 'World'

### Control
Use `for..in` to iterate over an array inside the context.
Use `for..in` to iterate over an array inside the state.
*Context:* `{list: ['IPA', 'Porter', 'Stout']}`
*State:* `{list: ['IPA', 'Porter', 'Stout']}`

@@ -266,3 +272,3 @@ ```jade

for item in list
li #{item}
li ${item}
```

@@ -275,6 +281,6 @@ ```html

*Context:* `{pairings: {Coffee: 'coding', Beer: 'bloviating'}}`
*State:* `{pairings: {Coffee: 'coding', Beer: 'bloviating'}}`
```jade
for drink, activity of pairings
. #{field} is for #{value}.
. ${field} is for ${value}.
```

@@ -306,3 +312,3 @@ ```html

### Using templates within templates
### Calling templates within templates

@@ -312,6 +318,6 @@ A template can call another template with `call`. To accomplish

they will be stored in `ltl.cache`. The template that's being
called can access the data context.
```jade
called can access the data state.
```js
var temp = ltl.compile('p\n call bold', {name: 'temp'});
var bold = ltl.compile('b #{text}', {name: 'bold'});
var bold = ltl.compile('b ${text}', {name: 'bold'});
ltl.cache.temp({text: 'Hi!'});

@@ -327,3 +333,3 @@ ```

reads data with `get` blocks.
```jade
```js
var layout = ltl.compile('#nav\n get nav\n#content\n get content', {name: 'layout'});

@@ -337,46 +343,199 @@ var page = ltl.compile('call layout\n set nav\n . Nav\n set content\n . Content', {name: 'page'});

#### Passing sub-states
## Contributing
A template can pass a portion of its state to another template by naming the
sub-state property after the template name in a call block:
Clone the repository.
```bash
$ git clone https://github.com/lighterio/ltl.git
**parent/view.ltl**:
```jade
p Expect a state like... {child: {name: "only child"}}
call child/view child
```
Install dependencies.
```bash
$ npm install
**child/view.ltl**
```
p This child is called ${name}.
```
### Testing
### Template properties
Run all tests.
```bash
$ npm test
A template can have properties applied to it by using a plus symbol.
**extra.ltl**:
```jade
html
head>title Template Properties
body:md
Properties can be used to provide hidden values to systems that compile
Ltl templates, such as [Chug](http://lighter.io/chug).
+extra
When compiled, the template will become a JavaScript function as usual.
In addition, it will have a property called "extra", whose value will be
a string containing the contents of this block.
+extra
If the plus symbol is used more than once for the same property, the value
of that property will be a concatenation of multiple blocks.
+also:md
# Also supports filters
Properties can have filters. This block will be evaluated as markdown,
and the resulting value will be set as the "also" property of the template.
// Note:
There are several reserved
```
Run tests an rerun them after changes are made.
```bash
$ npm run retest
### JS and CSS properties
The **js** and **css** properties of a template can be set using the plus
symbol, just like other properties. Unlike including JS or CSS in a script or
style tag block, these properties would need to be added to a page externally
in order to affect the HTML.
**js-and-css.ltl**:
```jade
p This will be included in the template's rendered HTML.
+js
console.log("This will not be included in the template's js property.");
+css
p {color: black}
```
Run individual test files.
```bash
$ mocha test/api
$ mocha test/blocks
$ mocha test/control
$ mocha test/interpolation
...
In addition, several languages that compile to JS/CSS are supported. Their
compilers can be invoked using their corresponding file extensions. For
JS, Ltl supports **coffee**, **litcoffee**, **iced**, **es6**,
and **ts**. For CSS, it supports **less**, **scss** and **styl**.
**coffee-and-less.ltl**:
```jade
p:md
This template will compile to a function which returns this paragraph, and
the function will have **js** and **css** properties.
+coffee
console.log "Hello from CoffeeScript!"
+styl
@textColor: #000;
p {
color: @textColor;
}
```
Test coverage (100% required).
```bash
$ npm run cover
### Inline JS and CSS
JavaScript and CSS can also be included inline in a template using directives
that appear as tags. Just as with JS and CSS properties, these support
compilers such as CoffeeScript and LESS.
**inline-js-and-css.ltl**
```jade
less
a {color: #000;}
coffee
s.linkText = 'hello'
a ${linkText}
```
View coverage report in a browser (uses Mac OS-friendly `open`).
```bash
$ npm run report
The state variable in a template is called `s`, so the above would set the
`linkText` value in the state object, and then it would render the following
HTML if called:
```html
<style>a {color: #000;}</style><a>hello</a>
```
### Write something awesome and submit a pull request!
## Acknowledgements
We would like to thank all of the amazing people who use, support,
promote, enhance, document, patch, and submit comments & issues.
Ltl couldn't exist without you.
Additionally, huge thanks go to [TUNE](http://www.tune.com) for employing
and supporting [Ltl](http://lighter.io/ltl) project maintainers,
and for being an epically awesome place to work (and play).
## MIT License
Copyright (c) 2014 Sam Eubank
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## How to Contribute
We welcome contributions from the community and are happy to have them.
Please follow this guide when logging issues or making code changes.
### Logging Issues
All issues should be created using the
[new issue form](https://github.com/lighterio/ltl/issues/new).
Please describe the issue including steps to reproduce. Also, make sure
to indicate the version that has the issue.
### Changing Code
Code changes are welcome and encouraged! Please follow our process:
1. Fork the repository on GitHub.
2. Fix the issue ensuring that your code follows the
[style guide](http://lighter.io/style-guide).
3. Add tests for your new code, ensuring that you have 100% code coverage.
(If necessary, we can help you reach 100% prior to merging.)
* Run `npm test` to run tests quickly, without testing coverage.
* Run `npm run cover` to test coverage and generate a report.
* Run `npm run report` to open the coverage report you generated.
4. [Pull requests](http://help.github.com/send-pull-requests/) should be made
to the [master branch](https://github.com/lighterio/ltl/tree/master).
### Contributor Code of Conduct
As contributors and maintainers of Ltl, we pledge to respect all
people who contribute through reporting issues, posting feature requests,
updating documentation, submitting pull requests or patches, and other
activities.
If any participant in this project has issues or takes exception with a
contribution, they are obligated to provide constructive feedback and never
resort to personal attacks, trolling, public or private harassment, insults, or
other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, edits, issues, and other contributions
that are not aligned with this Code of Conduct. Project maintainers who do
not follow the Code of Conduct may be removed from the project team.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by opening an issue or contacting one or more of the project
maintainers.
We promise to extend courtesy and respect to everyone involved in this project
regardless of gender, gender identity, sexual orientation, ability or
disability, ethnicity, religion, age, location, native language, or level of
experience.
SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc