Comparing version 0.2.0 to 0.3.1
@@ -5,7 +5,7 @@ /** | ||
* @origin https://github.com/lighterio/lighter-common/common/object/lru-cache.js | ||
* @version 0.0.1 | ||
* @version 0.0.2 | ||
* @import object/type | ||
*/ | ||
var Type = require('./type'); | ||
var Type = require('./type') | ||
module.exports = Type.extend({ | ||
@@ -17,5 +17,8 @@ | ||
init: function (options) { | ||
options = options || {}; | ||
this.maxSize = options.maxSize || 1e4; | ||
this.clear(); | ||
if (typeof options === 'number') { | ||
options = {maxSize: options} | ||
} | ||
options = options || {} | ||
this.maxSize = options.maxSize || 1e6 | ||
this.clear() | ||
}, | ||
@@ -27,6 +30,6 @@ | ||
clear: function () { | ||
this.size = 0; | ||
this.map = {}; | ||
this._next = this; | ||
this._prev = this; | ||
this.size = 0 | ||
this.map = {} | ||
this._next = this | ||
this._prev = this | ||
}, | ||
@@ -38,9 +41,9 @@ | ||
get: function (key) { | ||
var item = this.map[key]; | ||
var item = this.map[key] | ||
if (item) { | ||
if (item != this._next) { | ||
item.unlink(); | ||
item.link(this); | ||
if (item !== this._next) { | ||
item.unlink() | ||
item.link(this) | ||
} | ||
return item.value; | ||
return item.value | ||
} | ||
@@ -53,25 +56,23 @@ }, | ||
set: function (key, value) { | ||
var item = this.map[key]; | ||
var item = this.map[key] | ||
if (item) { | ||
item.value = value; | ||
if (item != this._next) { | ||
item.unlink(); | ||
item.link(this); | ||
item.value = value | ||
if (item !== this._next) { | ||
item.unlink() | ||
item.link(this) | ||
} | ||
} | ||
else { | ||
} else { | ||
// Create an item and add it to the head of the loop. | ||
item = this.map[key] = new Item(key, value); | ||
item.link(this); | ||
item = this.map[key] = new Item(key, value) | ||
item.link(this) | ||
// Remove the tail if necessary. | ||
if (this.size < this.maxSize) { | ||
this.size++; | ||
this.size++ | ||
} else { | ||
var tail = this._prev | ||
tail.unlink() | ||
delete this.map[tail.key] | ||
} | ||
else { | ||
var tail = this._prev; | ||
tail.unlink(); | ||
delete this.map[tail.key]; | ||
} | ||
} | ||
@@ -84,9 +85,9 @@ }, | ||
remove: function (key) { | ||
var item = this.map[key]; | ||
var item = this.map[key] | ||
if (item) { | ||
var prev = item._prev; | ||
var next = item._next; | ||
prev._next = next; | ||
next._prev = prev; | ||
delete this.map[key]; | ||
var prev = item._prev | ||
var next = item._next | ||
prev._next = next | ||
next._prev = prev | ||
delete this.map[key] | ||
} | ||
@@ -99,6 +100,6 @@ }, | ||
each: function (fn) { | ||
var item = this._next; | ||
while (item != this) { | ||
fn(item.key, item.value); | ||
item = item._next; | ||
var item = this._next | ||
while (item !== this) { | ||
fn(item.key, item.value) | ||
item = item._next | ||
} | ||
@@ -111,6 +112,6 @@ }, | ||
forEach: function (fn) { | ||
var item = this._next; | ||
while (item != this) { | ||
fn(item.value, item.key); | ||
item = item._next; | ||
var item = this._next | ||
while (item !== this) { | ||
fn(item.value, item.key) | ||
item = item._next | ||
} | ||
@@ -123,9 +124,9 @@ }, | ||
getMap: function () { | ||
var map = {}; | ||
var item = this._next; | ||
while (item != this) { | ||
map[item.key] = item.value; | ||
item = item._next; | ||
var map = {} | ||
var item = this._next | ||
while (item !== this) { | ||
map[item.key] = item.value | ||
item = item._next | ||
} | ||
return map; | ||
return map | ||
}, | ||
@@ -137,10 +138,10 @@ | ||
getKeys: function () { | ||
var keys = new Array(this.size); | ||
var index = 0; | ||
var item = this._next; | ||
while (item != this) { | ||
keys[index++] = item.key; | ||
item = item._next; | ||
var keys = new Array(this.size) | ||
var index = 0 | ||
var item = this._next | ||
while (item !== this) { | ||
keys[index++] = item.key | ||
item = item._next | ||
} | ||
return keys; | ||
return keys | ||
}, | ||
@@ -152,10 +153,10 @@ | ||
getValues: function () { | ||
var values = new Array(this.size); | ||
var index = 0; | ||
var item = this._next; | ||
while (item != this) { | ||
values[index++] = item.value; | ||
item = item._next; | ||
var values = new Array(this.size) | ||
var index = 0 | ||
var item = this._next | ||
while (item !== this) { | ||
values[index++] = item.value | ||
item = item._next | ||
} | ||
return values; | ||
return values | ||
}, | ||
@@ -167,13 +168,13 @@ | ||
getItems: function () { | ||
var items = new Array(this.size); | ||
var index = 0; | ||
var item = this._next; | ||
while (item != this) { | ||
items[index++] = item; | ||
item = item._next; | ||
var items = new Array(this.size) | ||
var index = 0 | ||
var item = this._next | ||
while (item !== this) { | ||
items[index++] = item | ||
item = item._next | ||
} | ||
return items; | ||
return items | ||
} | ||
}); | ||
}) | ||
@@ -183,7 +184,7 @@ /** | ||
*/ | ||
function Item(key, value) { | ||
this.key = key; | ||
this.value = value; | ||
this._prev = null; | ||
this._next = null; | ||
function Item (key, value) { | ||
this.key = key | ||
this.value = value | ||
this._prev = null | ||
this._next = null | ||
} | ||
@@ -197,6 +198,6 @@ | ||
unlink: function () { | ||
var prev = this._prev; | ||
var next = this._next; | ||
prev._next = next; | ||
next._prev = prev; | ||
var prev = this._prev | ||
var next = this._next | ||
prev._next = next | ||
next._prev = prev | ||
}, | ||
@@ -208,9 +209,9 @@ | ||
link: function (prev) { | ||
var next = prev._next; | ||
this._prev = prev; | ||
this._next = next; | ||
prev._next = this; | ||
next._prev = this; | ||
var next = prev._next | ||
this._prev = prev | ||
this._next = next | ||
prev._next = this | ||
next._prev = this | ||
} | ||
}; | ||
} |
@@ -10,3 +10,3 @@ /** | ||
// The default constructor does nothing. | ||
var Type = module.exports = function () {}; | ||
var Type = module.exports = function () {} | ||
@@ -17,20 +17,19 @@ /** | ||
Type.extend = function (properties) { | ||
// Create the constructor, using a new or inherited `init` method. | ||
var type = properties.init || function () { | ||
if (this.init) { | ||
this.init.apply(this, arguments); | ||
if (this.init) { | ||
this.init.apply(this, arguments) | ||
} | ||
} | ||
}; | ||
// Copy the parent and its prototype. | ||
var parent = type.parent = this; | ||
Type.decorate(type, parent); | ||
Type.decorate(type.prototype, parent.prototype); | ||
var parent = type.parent = this | ||
Type.decorate(type, parent) | ||
Type.decorate(type.prototype, parent.prototype) | ||
// Copy the properties that extend the parent. | ||
Type.decorate(type.prototype, properties); | ||
Type.decorate(type.prototype, properties) | ||
return type; | ||
}; | ||
return type | ||
} | ||
@@ -41,7 +40,7 @@ /** | ||
Type.decorate = function (object, properties) { | ||
properties = properties || this.prototype; | ||
properties = properties || this.prototype | ||
for (var key in properties) { | ||
object[key] = properties[key]; | ||
object[key] = properties[key] | ||
} | ||
return object; | ||
}; | ||
return object | ||
} |
@@ -13,7 +13,7 @@ /** | ||
var LruCache = require('../object/lru-cache'); | ||
var LruCache = require('../object/lru-cache') | ||
// Initialize the cache if it doesn't already exist. | ||
var cacheKey = '_lighterProcessCache_0'; | ||
var processCache = process[cacheKey]; | ||
var cacheKey = '_lighterProcessCache_0' | ||
var processCache = process[cacheKey] | ||
if (!processCache) { | ||
@@ -23,10 +23,10 @@ Object.defineProperty(process, cacheKey, { | ||
value: new LruCache() | ||
}); | ||
} | ||
}) | ||
// If it exists but doesn't look like a cache, replace it. | ||
else if (!processCache.get || !processCache.set) { | ||
process[cacheKey] = new LruCache(); | ||
} else if (!processCache.get || !processCache.set) { | ||
process[cacheKey] = new LruCache() | ||
} | ||
// Export the cache, whether newly-created or existing. | ||
module.exports = process[cacheKey]; | ||
module.exports = process[cacheKey] |
@@ -7,8 +7,8 @@ /** | ||
* @origin https://github.com/lighterio/lighter-common/common/vm/run.js | ||
* @version 0.0.1 | ||
* @version 0.0.2 | ||
* @import process/cache | ||
*/ | ||
var vm = require('vm'); | ||
var cache = require('../process/cache'); | ||
var vm = require('vm') | ||
var cache = require('../process/cache') | ||
@@ -18,22 +18,40 @@ /** | ||
* | ||
* @param {String} code A piece of JavaScript code to run. | ||
* @param {String} path An optional path for the file, used for debugging, etc. | ||
* @return {Any} The value extracted from the scripts context. | ||
* @param {String} code A piece of JavaScript code to run. | ||
* @param {String} path An optional path for the file, used for debugging, etc. | ||
* @param {Object} context An optional context to run in. | ||
* @return {Any} The value extracted from the scripts context. | ||
*/ | ||
var run = module.exports = function (code, path) { | ||
path = (path || 'run' + (++run._id)) + '.vm.js'; | ||
var key = (path[0] == '/') ? path : '/tmp/' + path; | ||
var src = 'var o=' + code; | ||
var context = {}; | ||
cache.set(key, src); | ||
var run = module.exports = function (code, path, context) { | ||
path = (path || 'run' + (++run._id)) + '.vm.js' | ||
context = context || run._context | ||
var key = (path[0] === '/') ? path : '/tmp/' + path | ||
var src = 'var o=' + code.replace(/\[\]/g, 'new Array()') | ||
cache.set(key, src) | ||
try { | ||
vm.runInNewContext(src, context, key); | ||
vm.runInNewContext(src, context, key) | ||
} catch (e) { | ||
console.log('WTF') | ||
e.message += '\n' + src + '\n' + (e instanceof SyntaxError) | ||
throw e | ||
} | ||
catch (e) { | ||
e.message += '\n' + src + '\n' + (e instanceof SyntaxError); | ||
throw e; | ||
return context.o | ||
} | ||
// Override for production. | ||
var env = process.env.NODE_ENV || '' | ||
if (env[0] !== 'd') { | ||
run = module.exports = function (code) { | ||
eval('var window={};eval.o=' + code.replace(/\[\]/g, 'new Array()')) | ||
return eval.o | ||
} | ||
return context.o; | ||
}; | ||
} | ||
run._id = 0; | ||
// Enable auto-incrementing of auto-generated code paths. | ||
run._id = 0 | ||
// Default context for running code. | ||
run._context = { | ||
window: {}, | ||
console: console, | ||
Array: Array | ||
} |
981
ltl.js
@@ -11,3 +11,3 @@ /** | ||
// Allow users to see what version of Ltl they're using. | ||
version: '0.2.0', | ||
version: '0.3.1', | ||
@@ -18,5 +18,5 @@ // Some HTML tags won't have end tags. | ||
// Supported control keywords (usage appears like tags). | ||
controlPattern: /^(for|if|else)\b/, | ||
controlPattern: /^(for|if|else|break|continue)\b/, | ||
// Pattern for a Jasignment. | ||
// Pattern for assignment. | ||
assignmentPattern: /^([$A-Za-z_][$A-Za-z_0-9\.\[\]'"]*\s*=[^\{])/, | ||
@@ -27,9 +27,6 @@ | ||
// JavaScript tokens that don't need the state "s" prepended for interpolation. | ||
// JavaScript tokens that don't need the scope "scope" prepended for interpolation. | ||
// TODO: Flesh out this list? | ||
jsPattern: /^(undefined|true|false|null|function|NaN|Infinity|window|location|document|console|this|Math|Object|Date|Error|RegExp|JSON)$/, | ||
jsPattern: /^(undefined|true|false|null|function|NaN|Infinity|window|location|document|console|new|this|typeof|instanceof|Math|Object|Array|Date|Error|RegExp|JSON|Jymin|scope|state|setTimeout|clearTimeout)$/, | ||
// Stores available single character variable names. | ||
vars: 'abcdefghijklmnqrtuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', | ||
// Register several languages and their targets. | ||
@@ -48,40 +45,40 @@ languages: { | ||
// Remove starting/ending whitespace. | ||
trim: function(text) { | ||
return text.replace(/(^\s+|\s+$)/g, ''); | ||
trim: function (text) { | ||
return text.replace(/(^\s+|\s+$)/g, '') | ||
}, | ||
// Repeat a string. | ||
repeat: function(text, times) { | ||
return times > 0 ? (new Array(times + 1)).join(text) : ''; | ||
repeat: function (text, times) { | ||
return times > 0 ? (new Array(times + 1)).join(text) : '' | ||
}, | ||
// Escape single quotes with a backslash. | ||
escapeSingleQuotes: function(text) { | ||
return text.replace(/'/g, '\\\''); | ||
escapeSingleQuotes: function (text) { | ||
return text.replace(/'/g, '\\\'') | ||
}, | ||
// Escape text with possible line breaks for appending to a string. | ||
escapeBlock: function(text) { | ||
return text.replace(/'/g, '\\\'').replace(/\n/g, '\\n'); | ||
escapeBlock: function (text) { | ||
return text.replace(/'/g, '\\\'').replace(/\n/g, '\\n') | ||
}, | ||
// Get a module for filtering. | ||
getFilter: function(name) { | ||
var filters = ltl.filters; | ||
var filter = filters[name]; | ||
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); | ||
filter = filters[name] = ltl.scope[name] || (typeof require !== 'undefined' ? require(name) : null) | ||
} | ||
if (!filter) { | ||
var todo; | ||
var into = ' into function that accepts a string and returns a string.'; | ||
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; | ||
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; | ||
todo = 'Set window.ltl.filters.' + name | ||
} | ||
throw new Error('[Ltl] Unknown filter: "' + name + '". ' + todo + into); | ||
throw new Error('[Ltl] Unknown filter: "' + name + '". ' + todo + into) | ||
} | ||
return filter; | ||
return filter | ||
}, | ||
@@ -91,31 +88,31 @@ | ||
run: ( | ||
(typeof require == 'function') ? | ||
(typeof require === 'function') ? | ||
require('./common/vm/run') : | ||
function (src, name) { | ||
var f; | ||
var f | ||
try { | ||
eval('f=' + src); // jshint ignore:line | ||
} | ||
catch (e) { | ||
e.message = '[Ltl] ' + e.message; | ||
} catch (e) { | ||
e.message = '[Ltl] ' + e.message | ||
if (name) { | ||
e.message += '\nTemplate: ' + name; | ||
e.message += '\nTemplate: ' + name | ||
} | ||
e.message += '\nFunction: ' + src; | ||
throw e.stack && e; | ||
e.message += '\nFunction: ' + src | ||
throw e.stack && e | ||
} | ||
return f; | ||
return f | ||
} | ||
), | ||
// Store all of the templates that have been compiled. | ||
// Store templates that have been compiled. | ||
// Include 2 built-ins for interpolation. | ||
cache: { | ||
'-': function(v){return '<!--'+(JSON.stringify(v)||'').replace(/-->/g,'--\\>')+'-->';}, | ||
'$': function(v){return (!v&&v!==0?'':(typeof v=='object'?JSON.stringify(v)||'':''+v)).replace(/</g,'<');}, | ||
'&': function(v){return encodeURIComponent(!v&&v!==0?'':''+v);} | ||
'$': function (v) { | ||
return (!v && v !== 0 ? '' : (typeof v === 'object' ? JSON.stringify(v) || '' : '' + v)).replace(/</g, '<') | ||
}, | ||
'&': function (v) { | ||
return encodeURIComponent(!v && v !== 0 ? '' : '' + v) | ||
} | ||
}, | ||
// Last value of an auto-incremented ID. | ||
lastId: 0, | ||
// Store filter modules, such as "coffee-script" and "marked". | ||
@@ -127,9 +124,6 @@ filters: { | ||
// Store tags that evaluate elsewhere. | ||
tags: {}, | ||
// Default compile settings. | ||
options: { | ||
tabWidth: 4, | ||
enableDebug: false | ||
defaultTag: 'div' | ||
}, | ||
@@ -139,6 +133,6 @@ | ||
setOption: function (name, value) { | ||
this.options[name] = value; | ||
this.options[name] = value | ||
}, | ||
// Create a function that accepts state and returns markup. | ||
// Create a function that accepts scope and returns markup. | ||
compile: function (code, options) { | ||
@@ -149,101 +143,99 @@ | ||
tabWidth: this.options.tabWidth, | ||
space: this.options.space, | ||
enableDebug: this.options.enableDebug | ||
}; | ||
defaultTag: this.options.defaultTag, | ||
space: this.options.space | ||
} | ||
for (var name in options) { | ||
settings[name] = options[name]; | ||
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 = ltl.escapeBlock(settings.space); | ||
settings.space = ltl.escapeBlock(settings.space) | ||
} | ||
// Replace carriage returns for Windows compatibility. | ||
code = code.replace(/\r/g, ''); | ||
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); | ||
var tabReplacement = Array(settings.tabWidth + 1).join(' ') | ||
code = code.replace(/\t/g, tabReplacement) | ||
// We'll auto-detect tab width. | ||
var currentTabWidth = 0; | ||
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; | ||
var lines = code.split('\n') | ||
var lineIndex | ||
var lineCount = lines.length | ||
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 newLine = settings.space ? '\n' : '' | ||
var output = "var o='"; | ||
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 useCache = false | ||
var tagDepth = 0 | ||
var varIndex = 0; | ||
var escapeHtmlVar = false; | ||
var encodeUriVar = false; | ||
var escapeCommentVar = false; | ||
var loopVars = []; | ||
var output = "var output='" | ||
var varIndex = 0 | ||
var escapeHtmlVar = false | ||
var encodeUriVar = false | ||
var escapeCommentVar = false | ||
var loopVars = [] | ||
// 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; | ||
var blockIndent = 0 | ||
var blockFilter = '' | ||
var blockLines = null | ||
var blockTag = null | ||
var blockName = '' | ||
var blockContent = '' | ||
var blockScope = null | ||
var inComment = false | ||
// Support adding properties like "js" to the template function. | ||
var properties = {}; | ||
var blockProperty; | ||
var blockTarget; | ||
var eventLanguage; | ||
var properties = {} | ||
var blockProperty | ||
var blockTarget | ||
var eventLanguage | ||
// Allow event listeners to be added. | ||
var bindings = {}; | ||
var bindings = {} | ||
function appendText(textMode, text) { | ||
if (textMode != mode) { | ||
if (mode == 'html') { | ||
output += "'" + (text == '}' ? '' : ';'); | ||
function appendText (textMode, text) { | ||
if (textMode !== mode) { | ||
if (mode === 'html') { | ||
output += "'" + (text === '}' ? '' : ';' + newLine) | ||
} else { | ||
output += "output+='" | ||
} | ||
else { | ||
output += "o+='"; | ||
} | ||
mode = textMode; | ||
mode = textMode | ||
} | ||
if (mode == 'html') { | ||
text = interpolate(text); | ||
if (mode === 'html') { | ||
text = interpolate(text) | ||
} | ||
output += text; | ||
output += text | ||
} | ||
function startBlock(filter, content) { | ||
blockIndent = indent + 1; | ||
blockFilter = filter; | ||
blockLines = []; | ||
function startBlock (filter, content) { | ||
blockIndent = indent + 1 | ||
blockFilter = filter | ||
blockLines = [] | ||
if (content) { | ||
(blockLines = blockLines || []).push(content); | ||
blockLines = blockLines || [] | ||
blockLines.push(content) | ||
} | ||
} | ||
function appendBlock() { | ||
var text = blockLines.join('\n'); | ||
function appendBlock () { | ||
var text = blockLines.join('\n') | ||
// Reset the blockage. | ||
blockIndent = 0; | ||
blockIndent = 0 | ||
@@ -254,36 +246,26 @@ // Some options should be passed through. | ||
enableDebug: settings.enableDebug | ||
}; | ||
} | ||
// 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) + "'}"; | ||
if (blockFilter === 'call') { | ||
if (/\S/.test(text)) { | ||
blockScope = blockScope || [] | ||
var blockJs = ltl.compile(text, blockOptions).toString() | ||
blockJs = blockJs.replace(/^.*?\)/, 'function()') | ||
blockScope.push('block:' + blockJs) | ||
} | ||
(blockSets = blockSets || []).push("'" + ltl.escapeSingleQuotes(blockName) + "':" + block); | ||
return; | ||
} | ||
blockScope = blockScope ? blockScope.join(',') : '' | ||
appendText('html', "'+cache['" + blockName + "']({" + blockScope + "},state)+'") | ||
useCache = true | ||
blockScope = null | ||
return | ||
// If there's a filter, get its module. | ||
else if (blockFilter) { | ||
if (blockFilter == 'coffee') { | ||
blockFilter = 'coffee-script'; | ||
} else if (blockFilter) { | ||
if (blockFilter === 'coffee') { | ||
blockFilter = 'coffee-script' | ||
} else if (blockFilter === 'md') { | ||
blockFilter = 'marked' | ||
} | ||
else if (blockFilter == 'md') { | ||
blockFilter = 'marked'; | ||
} | ||
var compiler = ltl.getFilter(blockFilter); | ||
var compiler = ltl.getFilter(blockFilter) | ||
@@ -294,43 +276,37 @@ if (compiler.renderSync) { | ||
compressed: true | ||
}); | ||
} | ||
else if (compiler.render) { | ||
if (blockFilter == 'less') { | ||
}) | ||
} else if (compiler.render) { | ||
if (blockFilter === 'less') { | ||
compiler.render(text, function (err, result) { | ||
if (err) { | ||
throw err; | ||
throw err | ||
} | ||
text = result.css; | ||
}); | ||
text = result.css | ||
}) | ||
} else { | ||
text = compiler.render(text) | ||
} | ||
else { | ||
text = compiler.render(text); | ||
} | ||
} | ||
else if (compiler.compile) { | ||
var nowrap = /^[^A-Z]*NOWRAP/.test(text); | ||
text = compiler.compile(text); | ||
} else if (compiler.compile) { | ||
var nowrap = /^[^A-Z]*NOWRAP/.test(text) | ||
text = compiler.compile(text) | ||
if (nowrap) { | ||
text = text.replace(/(^\(function\(\) \{\s*|\s*\}\)\.call\(this\);\s*$)/g, ''); | ||
text = text.replace(/(^\(function\(\) \{\s*|\s*\}\)\.call\(this\);\s*$)/g, '') | ||
} | ||
} else if (compiler.parse) { | ||
text = compiler.parse(text) | ||
} else if (typeof compiler === 'function') { | ||
text = compiler(text) | ||
} | ||
else if (compiler.parse) { | ||
text = compiler.parse(text); | ||
} | ||
else if (typeof compiler == 'function') { | ||
text = compiler(text); | ||
} | ||
} else { | ||
blockFilter = 'text' | ||
} | ||
else { | ||
blockFilter = 'text'; | ||
} | ||
text = ltl.trim(text); | ||
text = ltl.trim(text) | ||
if (settings.space) { | ||
if (hasHtmlOutput) { | ||
text = '\n' + text; | ||
text = '\n' + text | ||
} | ||
text = text.replace(/\n/g, '\n' + ltl.repeat(settings.space, tagDepth)); | ||
text = text.replace(/\n/g, '\n' + ltl.repeat(settings.space, tagDepth)) | ||
if (blockTag) { | ||
text += '\n' + ltl.repeat(settings.space, tagDepth - 1); | ||
text += '\n' + ltl.repeat(settings.space, tagDepth - 1) | ||
} | ||
@@ -340,48 +316,44 @@ } | ||
if (blockProperty) { | ||
var value = properties[blockProperty]; | ||
value = (value ? value + '\n' : '') + text; | ||
properties[blockProperty] = value; | ||
var value = properties[blockProperty] | ||
value = (value ? value + '\n' : '') + text | ||
properties[blockProperty] = value | ||
} else if (blockTarget === 'js') { | ||
text = text.replace(/;?$/, ';') | ||
appendText('script', text) | ||
} else { | ||
appendText('html', ltl.escapeBlock(text)) | ||
} | ||
else if (blockTarget == 'js') { | ||
appendText('script', text); | ||
} | ||
else { | ||
appendText('html', ltl.escapeBlock(text)); | ||
} | ||
blockTag = null; | ||
blockFilter = null; | ||
blockProperty = null; | ||
blockTarget = null; | ||
blockTag = null | ||
blockFilter = null | ||
blockProperty = null | ||
blockTarget = null | ||
blockScope = null | ||
} | ||
function backtrackIndent() { | ||
function backtrackIndent () { | ||
while (stack.length > indent) { | ||
var tags = stack.pop(); | ||
var tags = stack.pop() | ||
if (tags) { | ||
tags = tags.split(/,/g); | ||
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(); | ||
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 (!ltl.selfClosePattern.test(tag)) { | ||
var html = '</' + tag + '>'; | ||
tagDepth--; | ||
if (tag == previousTag) { | ||
previousTag = null; | ||
} else if (!ltl.selfClosePattern.test(tag)) { | ||
var html = '</' + tag + '>' | ||
tagDepth-- | ||
if (tag === previousTag) { | ||
previousTag = null | ||
} else if (settings.space) { | ||
html = '\\n' + ltl.repeat(settings.space, tagDepth) + html | ||
} | ||
else if (settings.space) { | ||
html = '\\n' + ltl.repeat(settings.space, tagDepth) + html; | ||
} | ||
appendText('html', html); | ||
appendText('html', html) | ||
} | ||
@@ -393,67 +365,77 @@ } | ||
function transformScript(script) { | ||
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 = 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 + ']' + ';'; | ||
}); | ||
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, item, array) { | ||
found = true | ||
var i = 'ltl' + varIndex++ | ||
var l = 'ltl' + varIndex++ | ||
var e = 'ltl' + varIndex++ | ||
loopVars.push([[item, e]]) | ||
return 'for(var ' + e + ',' + i + '=0,' + l + '=scope.' + array + '.length;' + | ||
i + '<' + l + ';++' + i + ')' + '{' + newLine + | ||
e + '=scope.' + array + '[' + i + ']' + ';' + newLine | ||
}) | ||
if (found) { | ||
stack.push('for'); | ||
return script; | ||
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 = 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 + ']' + ';'; | ||
}); | ||
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, key, value, object) { | ||
found = true | ||
var k = 'ltl' + varIndex++ | ||
var v = 'ltl' + varIndex++ | ||
loopVars.push([[key, k], [value, v]]) | ||
return 'for(var ' + k + ' in scope.' + object + '){' + newLine + | ||
'if(!scope.' + object + '.hasOwnProperty(' + k + '))continue;' + newLine + | ||
v + '=scope.' + object + '[' + k + ']' + ';' + newLine | ||
}) | ||
if (found) { | ||
stack.push('for'); | ||
return script; | ||
stack.push('for') | ||
return script | ||
} | ||
script = script.replace(/^(else if|else|if)\s*(.*)\s*$/i, | ||
function(match, keyword, condition) { | ||
found = true; | ||
return keyword + (condition ? '(' + prependState(condition) + ')' : '') + '{'; | ||
}); | ||
function (match, keyword, condition) { | ||
found = true | ||
return keyword + (condition ? '(' + scopify(condition) + ')' : '') + '{' + newLine | ||
}) | ||
stack.push('if'); | ||
return script; | ||
if (found) { | ||
stack.push('if') | ||
return script | ||
} | ||
/* | ||
script = script.replace(/^(break|continue)(\s+.*)$/i, | ||
function (match, keyword, condition) { | ||
found = true | ||
return keyword + (condition ? '(' + scopify(condition) + ')' : '') + '{' + newLine | ||
}) | ||
*/ | ||
} | ||
/** | ||
* 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. | ||
* Give scope to a JavaScript expression by prepending the scope variable "scope". | ||
* TODO: Parse using acorn so that strings can't be interpreted as vars. | ||
*/ | ||
function prependState(code, unescapeSingleQuotes) { | ||
function scopify (code, unescapeSingleQuotes) { | ||
// Interpolations got escaped as HTML and must be unescaped to be JS. | ||
if (unescapeSingleQuotes) { | ||
code = code.replace(/\\'/g, "'"); | ||
code = code.replace(/\\'/g, "'") | ||
} | ||
var tokens = code.split(/\b/); | ||
var isProperty = false; | ||
var isLoopVar = false; | ||
var isInString = false; | ||
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); | ||
var token = tokens[i] | ||
var stringTokens = token.match(/['"]/g) | ||
if (stringTokens) { | ||
isInString = ((isInString ? 1 : 0 ) + stringTokens.length) % 2; | ||
} | ||
else if (!isInString) { | ||
isInString = ((isInString ? 1 : 0 ) + stringTokens.length) % 2 | ||
} else if (!isInString) { | ||
if (/^[a-z_]/i.test(token)) { | ||
@@ -463,5 +445,5 @@ if (!ltl.jsPattern.test(token)) { | ||
for (var k = 0; k < loopVars[j].length; k++) { | ||
if (token == loopVars[j][k][0]) { | ||
isLoopVar = true; | ||
tokens[i] = loopVars[j][k][1]; | ||
if (token === loopVars[j][k][0]) { | ||
isLoopVar = true | ||
tokens[i] = loopVars[j][k][1] | ||
} | ||
@@ -471,50 +453,43 @@ } | ||
if (!isProperty && !isLoopVar) { | ||
tokens[i] = 's.' + token; | ||
tokens[i] = 'scope.' + token | ||
} | ||
} | ||
} | ||
isProperty = token[token.length - 1] == '.'; | ||
isLoopVar = false; | ||
isProperty = token[token.length - 1] === '.' | ||
isLoopVar = false | ||
} | ||
} | ||
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; | ||
code = tokens.join('') | ||
return code | ||
} | ||
/** | ||
* Find ={...}, ${...}, &{...}, and -{...} interpolations. | ||
* Turn them into state-aware insertions unless escaped. | ||
* Find ={...}, ${...} and &{...} interpolations. | ||
* Turn them into scope-aware insertions unless escaped. | ||
*/ | ||
function interpolate(code) { | ||
return code.replace(/(\\?)([$=&-])\{([^\}]+)\}/g, function(match, backslash, symbol, expression) { | ||
function interpolate (code) { | ||
return code.replace(/(\\?)([$=&])\{([^\}]+)\}/g, function (match, backslash, symbol, expression) { | ||
if (backslash) { | ||
return symbol + '{' + expression + '}'; | ||
return symbol + '{' + expression + '}' | ||
} | ||
if (symbol == '-') { | ||
if (!escapeCommentVar) { | ||
escapeCommentVar = ltl.vars[varIndex++]; | ||
} | ||
return "'+" + escapeCommentVar + '(' + prependState(expression, true) + ")+'"; | ||
if (expression === 'block') { | ||
expression = 'scope.block.call(cache,scope)' | ||
useCache = true | ||
} else { | ||
expression = scopify(expression, true) | ||
} | ||
else if (symbol == '$') { | ||
if (symbol === '$') { | ||
if (!escapeHtmlVar) { | ||
escapeHtmlVar = ltl.vars[varIndex++]; | ||
escapeHtmlVar = 'ltl' + varIndex++ | ||
} | ||
return "'+" + escapeHtmlVar + '(' + prependState(expression, true) + ")+'"; | ||
} | ||
else if (symbol == '&') { | ||
return "'+" + escapeHtmlVar + '(' + expression + ")+'" | ||
} else if (symbol === '&') { | ||
if (!encodeUriVar) { | ||
encodeUriVar = ltl.vars[varIndex++]; | ||
encodeUriVar = 'ltl' + varIndex++ | ||
} | ||
return "'+" + encodeUriVar + '(' + prependState(expression, true) + ")+'"; | ||
return "'+" + encodeUriVar + '(' + expression + ")+'" | ||
} else { | ||
return "'+(" + expression + ")+'" | ||
} | ||
else { | ||
return "'+" + prependState(expression, true) + "+'"; | ||
} | ||
}); | ||
}) | ||
} | ||
@@ -524,6 +499,6 @@ | ||
for (lineIndex = 0; lineIndex < lineCount; lineIndex++) { | ||
var line = lines[lineIndex]; | ||
var line = lines[lineIndex] | ||
// Mitigate recursion past 100 deep. | ||
var maxT = 1e2; | ||
var maxT = 1e2 | ||
@@ -533,25 +508,25 @@ // If the line is all whitespace, ignore it. | ||
if (blockIndent) { | ||
(blockLines = blockLines || []).push(''); | ||
(blockLines = blockLines || []).push('') | ||
} | ||
continue; | ||
continue | ||
} | ||
// Find the number of leading spaces. | ||
var spaces = line.search(/[^ ]/); | ||
var spaces = line.search(/[^ ]/) | ||
// If the first line with content has leading spaces, assume they all do. | ||
if (!hasContent) { | ||
globalSpaces = spaces; | ||
hasContent = true; | ||
globalSpaces = spaces | ||
hasContent = true | ||
} | ||
var adjustedSpaces = Math.max(spaces - globalSpaces, 0); | ||
var adjustedSpaces = Math.max(spaces - globalSpaces, 0) | ||
// If this is our first time seeing leading spaces, that's our tab width. | ||
if (adjustedSpaces > 0 && !currentTabWidth) { | ||
currentTabWidth = adjustedSpaces; | ||
currentTabWidth = adjustedSpaces | ||
} | ||
// Calculate the number of levels of indentation. | ||
indent = adjustedSpaces ? Math.round(adjustedSpaces / currentTabWidth) : 0; | ||
indent = adjustedSpaces ? Math.round(adjustedSpaces / currentTabWidth) : 0 | ||
@@ -562,9 +537,10 @@ // If we're in a block, we can append or close it. | ||
if (indent < blockIndent) { | ||
appendBlock(); | ||
} | ||
appendBlock() | ||
// 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; | ||
} else { | ||
line = line.substring(Math.min(spaces, currentTabWidth * blockIndent)) | ||
blockLines = blockLines || [] | ||
blockLines.push(line) | ||
continue | ||
} | ||
@@ -574,8 +550,8 @@ } | ||
// Strip the leading spaces. | ||
line = line.substring(spaces); | ||
line = line.substring(spaces) | ||
// Backtrack, closing any nested tags that need to be closed. | ||
backtrackIndent(); | ||
backtrackIndent() | ||
if (inComment) { | ||
continue; | ||
continue | ||
} | ||
@@ -585,188 +561,138 @@ | ||
if (ltl.controlPattern.test(line)) { | ||
var script = transformScript(line); | ||
appendText('script', script); | ||
} | ||
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); | ||
} | ||
} else if (ltl.assignmentPattern.test(line)) { | ||
hasAssignments = true | ||
line = scopify(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; | ||
} | ||
else { | ||
if (pair[1] === '') { | ||
command += ':'; | ||
} else if (line[0] === '@') { | ||
line.replace(/@([^\s\(\[]+)([\(\[][^\)\]]*[\)\]])?(.*)/, function (match, path, attributes, rest) { | ||
blockName = path | ||
blockContent = rest | ||
if (attributes) { | ||
blockScope = [] | ||
attributes = attributes.substr(1, attributes.length - 2) | ||
attributes.replace(/([^\s]+?)(=(['"])?[^'"]*?\3)?(\s|$)/g, function (match, name, value, space) { | ||
blockScope.push(name + ':' + (value ? scopify(value.substr(1)) : true)) | ||
}) | ||
} | ||
startBlock(command, blockContent); | ||
} | ||
} | ||
startBlock('call', blockContent) | ||
}) | ||
// Tags must be parsed for id/class/attributes/content. | ||
else { | ||
var rest = line; | ||
var t = 0; | ||
} 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 = ''; | ||
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)) { | ||
character = rest[0]; | ||
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 (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); | ||
} | ||
} 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 == '(') { | ||
} else if (character === '(' || character === '[') { | ||
// Move on from the parentheses. | ||
rest = rest.substring(1); | ||
rest = rest.substring(1) | ||
// Build attributes. | ||
attributes = ''; | ||
attributes = '' | ||
while (rest && (++t < maxT)) { | ||
// Find quoted attributes or the end of the list. | ||
end = rest.search(/[\)"']/); | ||
end = rest.search(/[\)\]"']/) | ||
// If there's no end, read what's left as attributes. | ||
if (end < 0) { | ||
attributes += rest; | ||
rest = ''; | ||
break; | ||
attributes += rest | ||
rest = '' | ||
break | ||
} | ||
character = rest[end]; | ||
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 (character === ')' || character === ']') { | ||
attributes += rest.substring(0, end) | ||
rest = rest.substring(end + 1) | ||
break | ||
// If it's not the end, read a quoted param. | ||
else { | ||
} 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); | ||
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); | ||
end = rest.indexOf(character, 1) | ||
// Read to the end of the attribute. | ||
attributes += rest.substring(0, end + 1); | ||
rest = rest.substring(end + 1); | ||
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; | ||
} | ||
} else if (character === '>') { | ||
rest = rest.replace(/^>\s*/, '') | ||
break | ||
// 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; | ||
} | ||
} 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 == '+') { | ||
} 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; | ||
} | ||
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]); | ||
} | ||
} | ||
}); | ||
} | ||
// 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 is a space, it's the start of content. | ||
else if (character == ' ') { | ||
content = ltl.trim(rest); | ||
rest = ''; | ||
} | ||
} else if (character === ' ') { | ||
content = ltl.trim(rest) | ||
rest = '' | ||
// If the next character isn't special, it's part of a tag. | ||
else { | ||
end = rest.search(/([#\.\(>:\s@]|$)/); | ||
} else { | ||
end = rest.search(/([#\.\(>:\s@]|$)/) | ||
// Prevent overwriting the tag. | ||
tag = tag || rest.substring(0, end); | ||
rest = rest.substring(end); | ||
tag = tag || rest.substring(0, end) | ||
rest = rest.substring(end) | ||
} | ||
@@ -777,6 +703,6 @@ } | ||
if (ltl.languages[tag]) { | ||
blockTarget = ltl.languages[tag]; | ||
var filter = (tag == blockTarget ? '' : tag) || filter; | ||
tag = blockTag = blockTarget == 'css' ? 'style' : '//'; | ||
startBlock(filter, content); | ||
blockTarget = ltl.languages[tag] | ||
var filter = (tag === blockTarget ? '' : tag) || filter | ||
tag = blockTag = blockTarget === 'css' ? 'style' : '//' | ||
startBlock(filter, content) | ||
} | ||
@@ -786,15 +712,14 @@ | ||
if (tag.indexOf('//') === 0) { | ||
tag = '//'; | ||
inComment = true; | ||
} | ||
tag = '//' | ||
inComment = true | ||
// If it's not a comment, we'll add some HTML. | ||
else { | ||
var className = classes.join(' '); | ||
} 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; | ||
var useDefault = (blockTag === null) || id || className || attributes | ||
if (useDefault) { | ||
tag = blockTag = 'div'; | ||
tag = blockTag = settings.defaultTag | ||
} | ||
@@ -804,37 +729,36 @@ } | ||
// Convert ! or doctype into !DOCTYPE and assume html. | ||
if (tag == '!' || tag == 'doctype') { | ||
tag = '!DOCTYPE'; | ||
attributes = attributes || 'html'; | ||
if (tag === '!' || tag === 'doctype') { | ||
tag = '!DOCTYPE' | ||
attributes = attributes || 'html' | ||
} | ||
// Add attributes to the tag. | ||
var html = tag; | ||
var html = tag | ||
if (id) { | ||
html += ' id="' + id + '"'; | ||
html += ' id="' + id + '"' | ||
} | ||
if (className) { | ||
html += ' class="' + className + '"'; | ||
html += ' class="' + className + '"' | ||
} | ||
if (attributes) { | ||
html += ' ' + attributes; | ||
html += ' ' + attributes | ||
} | ||
// Convert space tag to a space character. | ||
if (tag == 'space') { | ||
html = ' ' + content; | ||
} | ||
if (tag === 'space') { | ||
html = ' ' + content | ||
// Convert minus to a comment. | ||
else if (tag == '-') { | ||
html = '<!--' + content; | ||
} else if (tag === '-') { | ||
html = '<!--' + content | ||
} else { | ||
html = '<' + 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; | ||
html = ltl.escapeSingleQuotes(html) | ||
if (tag === 'html') { | ||
// If there's an HTML tag, don't wrap with a scope. | ||
hasHtmlTag = true | ||
if (!/DOCTYPE/.test(output)) { | ||
html = '<!DOCTYPE html>' + (settings.space ? '\\n' : '') + html; | ||
html = '<!DOCTYPE html>' + (settings.space ? '\\n' : '') + html | ||
} | ||
@@ -845,6 +769,6 @@ } | ||
if (settings.space) { | ||
html = ltl.repeat(settings.space, tagDepth) + html; | ||
html = ltl.repeat(settings.space, tagDepth) + html | ||
// Prepend a line break if this isn't the first tag. | ||
if (hasHtmlOutput) { | ||
html = '\\n' + html; | ||
html = '\\n' + html | ||
} | ||
@@ -855,4 +779,4 @@ } | ||
if (tag) { | ||
appendText('html', html); | ||
hasHtmlOutput = true; | ||
appendText('html', html) | ||
hasHtmlOutput = true | ||
} | ||
@@ -865,20 +789,19 @@ } | ||
if (stack[indent]) { | ||
stack[indent] += ',' + tag; | ||
stack[indent] += ',' + tag | ||
} else { | ||
stack[indent] = tag | ||
} | ||
else { | ||
stack[indent] = tag; | ||
} | ||
// Allow same-line tag open/close in settings.space mode. | ||
previousTag = tag; | ||
previousTag = tag | ||
if (!ltl.selfClosePattern.test(tag)) { | ||
tagDepth++; | ||
tagDepth++ | ||
} | ||
} | ||
tag = ''; | ||
id = ''; | ||
classes = []; | ||
attributes = ''; | ||
content = ''; | ||
tag = '' | ||
id = '' | ||
classes = [] | ||
attributes = '' | ||
content = '' | ||
} | ||
@@ -889,84 +812,76 @@ } | ||
// We've reached the end, so unindent all the way. | ||
indent = 0; | ||
indent = 0 | ||
if (blockIndent) { | ||
appendBlock(); | ||
appendBlock() | ||
} | ||
backtrackIndent(); | ||
backtrackIndent() | ||
// Add the return statement (ending concatenation, where applicable). | ||
appendText('script', 'return o'); | ||
appendText('script', 'return output') | ||
if (blockSets) { | ||
return '{' + blockSets.join(',') + '}'; | ||
} | ||
// Create the function. | ||
if (escapeCommentVar) { | ||
output = (settings.name ? | ||
'var ' + escapeCommentVar + "=this['-'];" : | ||
ltl.cache['-'].toString().replace(/\(/, escapeCommentVar + '(') + ';') + output; | ||
'var ' + escapeCommentVar + "=cache['-'];" + newLine : | ||
ltl.cache['-'].toString().replace(/\(/, escapeCommentVar + '(') + ';' + newLine) + output | ||
useCache = true | ||
} | ||
if (escapeHtmlVar) { | ||
output = (settings.name ? | ||
'var ' + escapeHtmlVar + "=this.$;" : | ||
ltl.cache.$.toString().replace(/\(/, escapeHtmlVar + '(') + ';') + output; | ||
'var ' + escapeHtmlVar + "=cache.$;" + newLine : | ||
ltl.cache.$.toString().replace(/\(/, escapeHtmlVar + '(') + ';' + newLine) + output | ||
useCache = true | ||
} | ||
if (encodeUriVar) { | ||
output = (settings.name ? | ||
'var ' + encodeUriVar + "=this['&'];" : | ||
ltl.cache['&'].toString().replace(/\(/, encodeUriVar + '(') + ';') + output; | ||
'var ' + encodeUriVar + "=cache['&'];" + newLine : | ||
ltl.cache['&'].toString().replace(/\(/, encodeUriVar + '(') + ';' + newLine) + output | ||
useCache = true | ||
} | ||
if (hasAssignments) { | ||
output = 's=s||{};' + output; | ||
if (useCache) { | ||
output = 'var cache=this;' + newLine + output | ||
} | ||
output = 'function(s' + (hasGets ? ',p' : '') + '){' + output + '}'; | ||
var index = 0 | ||
output = output.replace(/state=state\|\|scope;\s*var cache=this;\s*/g, function (match) { | ||
return index++ ? '' : match | ||
}) | ||
if (/\bstate\b/.test(output)) { | ||
output = 'function(scope,state){' + newLine + | ||
'state=state||scope;' + newLine + output + newLine + '}' | ||
} else { | ||
output = 'function(scope){' + newLine + output + newLine + '}' | ||
} | ||
// Evaluate the template as a function. | ||
name = settings.name; | ||
var template; | ||
name = settings.name | ||
var template | ||
try { | ||
template = ltl.run(output, name); | ||
template = ltl.run(output, name) | ||
} catch (e) { | ||
name = (name ? '"' + name + '"' : 'template') | ||
e.message = '[Ltl] Failed to compile ' + name + '. ' + e.message | ||
throw e | ||
} | ||
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; | ||
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 + '});'; | ||
} | ||
} | ||
} | ||
// Add any discovered properties to the template. | ||
for (name in properties) { | ||
if (name[0] != '~') { | ||
template[name] = template[name] || properties[name]; | ||
} | ||
template[name] = template[name] || properties[name] | ||
} | ||
return template; | ||
return template | ||
} | ||
}; | ||
} | ||
// Export Ltl as a CommonJS module. | ||
if (typeof module !== 'undefined') { | ||
module.exports = ltl; | ||
module.exports = ltl | ||
} |
{ | ||
"name": "ltl", | ||
"version": "0.2.0", | ||
"version": "0.3.1", | ||
"description": "Lean Template Language for JavaScript and HTML", | ||
@@ -9,5 +9,5 @@ "dependencies": {}, | ||
"dot": "^1.0.3", | ||
"exam": "^0.2.4", | ||
"exam": ">=0.3.2", | ||
"jade": "^1.9.2", | ||
"jsx": "0.9.89", | ||
"jsx": "^0.9.89", | ||
"less": "^2.4.0", | ||
@@ -14,0 +14,0 @@ "markdown": "0.5.0", |
@@ -268,11 +268,16 @@ # <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> | ||
Use `for..of` to iterate over an object's keys. | ||
Use `for..of` to iterate over object keys. | ||
*State:* `{pairings: {Coffee: 'coding', Beer: 'bloviating'}}` | ||
```jade | ||
``` | ||
for drink, activity of pairings | ||
. ${field} is for ${value}. | ||
. | ||
b \\${field} | ||
space | ||
: is for | ||
space | ||
i \\${value}. | ||
``` | ||
```html | ||
<div>Coffee is for coding.</div><div>Beer is for bloviating</div> | ||
<div><b>Coffee</b> is for <i>coding</i>.</div><div><b>Beer</b> is for <i>bloviating</i>.</div> | ||
``` | ||
@@ -429,3 +434,3 @@ | ||
coffee | ||
s.linkText = 'hello' | ||
state.linkText = 'hello' | ||
@@ -435,3 +440,3 @@ a ${linkText} | ||
The state variable in a template is called `s`, so the above would set the | ||
The state variable in a template is called `state`, so the above would set the | ||
`linkText` value in the state object, and then it would render the following | ||
@@ -449,3 +454,3 @@ HTML if called: | ||
Additionally, huge thanks go to [TUNE](http://www.tune.com) for employing | ||
Additionally, huge thanks go to [Goin’](https://goin.io) for employing | ||
and supporting [Ltl](http://lighter.io/ltl) project maintainers, | ||
@@ -452,0 +457,0 @@ and for being an epically awesome place to work (and play). |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
531
50361
1043
4
4