-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 084e558
Showing
7 changed files
with
404 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
Copyright (c) 2011, Christopher Jeffrey (http://epsilon-not.net/) | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
# Liquor - a templating engine minus the code | ||
|
||
A word of warning: Liquor's idea of a template is that it is separate from | ||
the code. Liquor follows the philosophy that if you're executing | ||
raw code within your templates, you might be doing something wrong. This | ||
is specifically for people who think massive amounts of logic do not belong | ||
in templates. Liquor doesn't allow for any raw evaluation of code. It tries | ||
to stay as declarative as possible, while remaining convenient. This is my | ||
personal templating engine, things may change depending on how I use it. | ||
|
||
Liquor is also for nerds who care about their markup to a slightly absurd degree: | ||
it will maintain whitespace, clean up empty lines, etc. This is all to make | ||
sure your "style" of markup is preserved. | ||
|
||
* * * | ||
|
||
This engine has 3 capabilities: __variable interpolation__, __conditionals__, | ||
and __collection traversal__. Liquor tries to have a very concise notation for | ||
expressing these statements. | ||
|
||
## Variable Interpolation | ||
|
||
A variable is represented with an ampersand (`&`) followed by a | ||
colon (`:`), followed by the variable name and a semicolon (`;`). Similar to an | ||
HTML entity or character refernce, only with a colon. | ||
|
||
&:data; | ||
|
||
Variable names can make use of any character except whitespace characters, | ||
semicolon, and hash/pound (`#`). | ||
|
||
Collection variables can access their members with a hash (`#`). | ||
|
||
&:obj#key; | ||
|
||
However, you can also access an object's members the regular JS way: | ||
|
||
&:obj.key; | ||
|
||
## Conditionals | ||
|
||
A conditional statement is denoted by curly braces, `{` and `}`. | ||
|
||
The contents of a conditional will __only__ be included in the output if every | ||
variable contained within the top level of the containing conditional has a | ||
truthy value. However, truthy and falsey values differ from those of JS: | ||
- Falsey values include `false`, `null`, (and `undefined`, `NaN`). | ||
- This means the empty string (`''`) and zero (`0`) are both truthy. | ||
|
||
Variables that are booleans (and nulls or any other non-displayable value) will | ||
not be displayed in any way in the output, however they are taken into account | ||
when determining the conditional's outcome. | ||
|
||
var hello = '<div>{<p>&:hello;</p>}</div>'; | ||
|
||
liquor.compile(hello)({ hello: 'hello world' }); | ||
|
||
Outputs: | ||
|
||
<div><p>hello world!</p></div> | ||
|
||
A variable can be forced into a boolean context with a bang `!`. If this is | ||
done, the position of the variable within the conditional does not make any | ||
difference. | ||
|
||
<div>{!!&:num;<p>hello world!</p>}</div> | ||
|
||
liquor.compile(hello)({ num: 250 }); | ||
|
||
Outputs: | ||
|
||
<div><p>hello world!</p></div> | ||
|
||
Whereas using a single exclamation point to check whether the variable is | ||
false would yield (in this case): | ||
|
||
<div></div> | ||
|
||
## Collection Traversal | ||
|
||
To traverse through a collection, the contents of the desired output must be | ||
contained in a wrapper, as demonstrated below. Within the statement, | ||
the context (`this`) refers to the value/subject of the current iteration. | ||
|
||
Note: Liquor will try to duplicate the surrounding whitespace to make things | ||
look pretty when producing the output of a collection traversal. | ||
|
||
<table> | ||
<tr><td>&col[0];</td><td>&col[1];</td></tr> | ||
:data[<tr> | ||
<td>&:this#color;</td> | ||
<td>&:this#animal;</td> | ||
</tr>]; | ||
</table> | ||
|
||
liquor.compile(table)({ | ||
col: ['color', 'animal'], | ||
data: [ | ||
{ color: 'brown', animal: 'bear' }, | ||
{ color: 'black', animal: 'cat' }, | ||
{ color: 'white', animal: 'horse' } | ||
] | ||
}); | ||
|
||
The above will output: | ||
|
||
<table> | ||
<tr><td>color</td><td>animal</td></tr> | ||
<tr> | ||
<td>brown</td> | ||
<td>bear</td> | ||
</tr> | ||
<tr> | ||
<td>black</td> | ||
<td>cat</td> | ||
</tr> | ||
<tr> | ||
<td>white</td> | ||
<td>horse</td> | ||
</tr> | ||
</table> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('./liquor'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
(function() { | ||
// liquor - javascript templates | ||
// Copyright (c) 2011, Christopher Jeffrey (MIT Licensed) | ||
|
||
// a very simple lexer | ||
var tokenize = (function() { | ||
var IN_LOOP = 0, | ||
IN_CONDITIONAL = 1; | ||
|
||
// i wish i could bring myself | ||
// to use an object instead | ||
var rules = [ | ||
['ESCAPED', /^\\([\s\S])/], | ||
['LOOP_START', /^(\s*):([^\s[;]+)\[/], | ||
['LOOP_END', /^\];/], | ||
['TMP_VAR', /^(!*)&:([^;]+);/], | ||
['COND_START', /^{/], | ||
['COND_END', /^}/] | ||
]; | ||
|
||
// basically anything that | ||
// isnt the above patterns | ||
rules.push(['TEXT', | ||
RegExp([ | ||
'^([\\s\\S]+?)(?=', | ||
rules.map(function(r) { | ||
return r[1].source.slice(1); | ||
}).join('|'), | ||
'|$)' | ||
].join('')) | ||
]); | ||
|
||
return function(src) { | ||
var stack = [], tokens = []; | ||
var cap, type, pos = 0; | ||
|
||
var state = function() { | ||
return stack[stack.length-1]; | ||
}; | ||
|
||
var scan = function() { | ||
for (var i = 0, l = rules.length; i < l; i++) { | ||
if (cap = rules[i][1].exec(src)) { | ||
type = rules[i][0]; | ||
src = src.slice(cap[0].length); | ||
return true; | ||
} | ||
} | ||
}; | ||
|
||
src = src.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); | ||
|
||
// scan for tokens | ||
// its easy to do some of the | ||
// exception throwing here | ||
while (scan()) { | ||
tokens.push({ | ||
type: type, | ||
cap: cap | ||
}); | ||
switch (type) { | ||
case 'LOOP_START': | ||
stack.push(IN_LOOP); | ||
break; | ||
case 'LOOP_END': | ||
if (state() === IN_LOOP) { | ||
stack.pop(); | ||
} else { | ||
throw new | ||
SyntaxError('Unexpected "];" at: ' + pos); | ||
} | ||
break; | ||
case 'COND_START': | ||
stack.push(IN_CONDITIONAL); | ||
break; | ||
case 'COND_END': | ||
if (state() !== IN_CONDITIONAL) { | ||
throw new | ||
SyntaxError('Unexpected "}" at: ' + pos); | ||
} | ||
stack.pop(); | ||
break; | ||
} | ||
pos += cap[0].length; | ||
} | ||
if (stack.length > 0) { | ||
throw new | ||
SyntaxError('Unexpected EOF.'); | ||
} | ||
return tokens; | ||
}; | ||
})(); | ||
|
||
// recursive descent is my only god. | ||
// this will parse the tokens and output a one-line string expression. | ||
var parse = (function() { | ||
var escape = function(txt) { // escape regular text | ||
return txt.replace(/"/g, '\\"').replace(/\n/g, '\\n'); | ||
}; | ||
|
||
// if a `with` statement were used, | ||
// this wouldn't be necessary | ||
var name = function(n) { | ||
if (n.indexOf('this') !== 0) n = '__locals.' + n; | ||
return n.replace(/#/g, '.').replace(/\.(\d+)/g, '[$1]'); | ||
}; | ||
|
||
var cur, tokens; | ||
var next = function() { | ||
cur = tokens.shift(); | ||
return cur; | ||
}; | ||
|
||
var conditional = function() { | ||
var body = [], checks = []; | ||
while (next().type !== 'COND_END') { | ||
// need to grab all the variables in the top-level for | ||
// conditional checks (this includes collection vars) | ||
if (cur.type === 'TMP_VAR' || cur.type === 'LOOP_START') { | ||
checks.push( | ||
(cur.type === 'TMP_VAR' ? (cur.cap[1] || '') : '') | ||
+ '__help.truthy(' + name(cur.cap[2]) + ')' | ||
); | ||
} | ||
body.push(tok()); | ||
} | ||
return '"+((' + checks.join(' && ') + ') ? "' + body.join('') + '" : "")+"'; | ||
}; | ||
|
||
// pretty straightforward but need to | ||
// remeber to manage the whitespace properly | ||
var loop = function() { | ||
var body = [], token = cur; | ||
while (next().type !== 'LOOP_END') { | ||
body.push(tok()); | ||
} | ||
return [ | ||
'"+(__help.iterate(', | ||
name(token.cap[2]), | ||
', function() { return "', | ||
escape(token.cap[1]), | ||
body.join(''), | ||
'"; }))+"' | ||
].join(''); | ||
}; | ||
|
||
var tok = function() { | ||
var token = cur; | ||
switch (token.type) { | ||
case 'TMP_VAR': | ||
return !token.cap[1] | ||
? '"+__help.show(' + name(token.cap[2]) + ')+"' | ||
: ''; | ||
case 'LOOP_START': | ||
return loop(); | ||
case 'COND_START': | ||
return conditional(); | ||
case 'ESCAPED': | ||
case 'TEXT': | ||
return escape(token.cap[1]); | ||
default: | ||
throw new | ||
SyntaxError('Unexpected token: ' + token.cap[0]); | ||
} | ||
}; | ||
|
||
return function(src) { | ||
var out = []; | ||
tokens = tokenize(src); | ||
while (next()) { | ||
out.push(tok()); | ||
} | ||
return '"' + out.join('') + '"'; | ||
}; | ||
})(); | ||
|
||
var compile = (function() { | ||
// helper functions to use inside the compiled template | ||
var helpers = { | ||
// we use this for collection traversal statements | ||
iterate: function(obj, func) { | ||
var str = []; | ||
if (typeof obj.length === 'number') { | ||
for (var i = 0, l = obj.length; i < l; i++) { | ||
str.push(func.call(obj[i])); | ||
} | ||
} else { | ||
var k = Object.keys(obj); | ||
for (var i = 0, l = k.length; i < l; i++) { | ||
str.push(func.call(obj[k[i]])); | ||
} | ||
} | ||
return str.join(''); | ||
}, | ||
// custom truthy/falsey checks | ||
// basically the same as JS, except a | ||
// variable is truthy if it is '' or 0 | ||
truthy: function(v) { | ||
return !(v === undefined || v === false || v === null || v !== v); | ||
}, | ||
// the function to determine whether to actually display a value | ||
// display any truthy value except for "true" | ||
show: function(v) { | ||
return (!helpers.truthy(v) || v === true) ? '' : v; | ||
} | ||
}; | ||
return function(src, debug) { | ||
if (debug === 'debug') return parse(src); | ||
var func = Function('__locals, __help', 'return ' + parse(src) + ';'); | ||
return function(locals) { // curry on the helpers | ||
// do some post-processing here to make whitespace nice and neat | ||
var out = func.call(locals, locals, helpers); | ||
out = out.replace(/(\n)\n+/g, '$1').replace(/(\n)[\x20\t]+\n/g, '$1'); | ||
return out; | ||
}; | ||
}; | ||
})(); | ||
|
||
if (typeof module !== 'undefined' && module.exports) { | ||
module.exports = exports = compile; | ||
exports.compile = compile; | ||
exports.parse = parse; | ||
exports.tokenize = tokenize; | ||
} else { | ||
this.liquor = compile; | ||
} | ||
|
||
}).call(this); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"<ol>"+(__help.iterate(__locals.list, function() { return "\n <li>\n <a href=\""+__help.show(this.href)+"\">"+__help.show(this.text)+"</a>\n "+((__help.truthy(this.datetime)) ? "<time datetime=\""+__help.show(this.datetime)+"\">\n "+((__help.truthy(this.time)) ? ""+__help.show(this.time)+"" : "")+"\n </time>" : "")+"\n </li>"; }))+""+(__help.iterate(__locals.list2, function() { return "\n <li>\n <a href=\""+__help.show(this.href)+"\">"+__help.show(this.text)+"</a>\n "+((__help.truthy(this.datetime)) ? "<time datetime=\""+__help.show(this.datetime)+"\">\n "+((__help.truthy(this.time)) ? ""+__help.show(this.time)+"" : "")+"\n </time>" : "")+"\n </li>"; }))+"\n</ol>" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<ol> | ||
:list[<li> | ||
<a href="&:this#href;">&:this#text;</a> | ||
{<time datetime="&:this#datetime;"> | ||
{&:this#time;} | ||
</time>} | ||
</li>]; | ||
:list2[<li> | ||
<a href="&:this#href;">&:this#text;</a> | ||
{<time datetime="&:this#datetime;"> | ||
{&:this#time;} | ||
</time>} | ||
</li>]; | ||
</ol> |
Oops, something went wrong.