From d10767f0d0221a994210ca7555512a486fa9844d Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Fri, 7 Jun 2013 16:11:43 -0700 Subject: [PATCH 01/12] Added script to add node_modules/.bin path to PATH; updated Makefile to build correctly. --- Makefile | 36 +++++++++++++++++++++++++++--------- bin/activate | 8 ++++++++ bin/devserver | 16 ++++++++++++++++ src/parser.coffee | 28 ++++++++++++++++++++++------ 4 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 bin/activate create mode 100755 bin/devserver diff --git a/Makefile b/Makefile index 14ba05a..4f9b7ed 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,39 @@ -.PHONY: lib test +.PHONY: lib test library docs -lib_sources:= $(wildcard src/*.coffee) -lib_objects:= $(subst src/, lib/, $(lib_sources:%.coffee=%.js)) +COFFEE= ./node_modules/.bin/coffee +PEGJS= ./node_modules/.bin/pegjs +DOCCO= ./node_modules/.bin/docco +MOCHA= ./node_modules/.bin/mocha + +cof_sources:= $(wildcard src/*.coffee) +cof_objects:= $(subst src/, lib/, $(cof_sources:%.coffee=%.js)) + +peg_sources:= $(wildcard src/*.peg) +peg_objects:= $(subst src/, lib/, $(peg_sources:%.peg=%.js)) + +library: $(cof_objects) $(peg_objects) default: build -build: $(lib_objects) lib/tokenizer.js +build: $(lib_objects) lib: mkdir -p lib -lib/tumble.js: lib src/tumble.peg - ./node_modules/.bin/pegjs src/tumble.peg lib/tumble.js - -$(lib_objects): $(lib_sources) +$(cof_objects): $(cof_sources) @mkdir -p $(@D) - coffee -o $(@D) -c $< + $(COFFEE) -o $(@D) -c $< + +$(peg_objects): $(peg_sources) + @mkdir -p $(@D) + $(PEGJS) $< $@ + +docs: + $(DOCCO) $(cof_sources) + +echo: + echo $(cof_sources) + echo $(cof_objects) test: test/[0-9]*_mocha.coffee lib/tumble.js lib/parser.js ./node_modules/.bin/mocha -R tap -C --compilers coffee:coffee-script -u tdd $< diff --git a/bin/activate b/bin/activate new file mode 100644 index 0000000..d074497 --- /dev/null +++ b/bin/activate @@ -0,0 +1,8 @@ +#!/bin/bash + +# /bin comes before /node_modules/.bin because sometimes I want to +# override the behaviors provided. + +PROJECT_ROOT=`pwd` +PATH="$PROJECT_ROOT/bin:$PROJECT_ROOT/node_modules/.bin:$PATH" +export PATH diff --git a/bin/devserver b/bin/devserver new file mode 100755 index 0000000..defee27 --- /dev/null +++ b/bin/devserver @@ -0,0 +1,16 @@ +#!/usr/bin/env coffee + +staticserver = require('node-static') +files = new(staticserver.Server)('./dist') + +require('http').createServer((request, response) -> + request.addListener 'end', -> + files.serve request, response, (err, res) -> + if (err) + console.error("> Error serving " + request.url + " - " + err.message) + response.writeHead(err.status, err.headers) + response.end() + else + console.log("> " + request.url + " - " + res.message) +).listen(8081) +console.log("> node-static is listening on http://127.0.0.1:8081") diff --git a/src/parser.coffee b/src/parser.coffee index f2645d9..78eee0f 100644 --- a/src/parser.coffee +++ b/src/parser.coffee @@ -9,42 +9,58 @@ class Contexter @depth = 0 has_any: (name) -> - _.find this.stack, (o) -> _.has(o, name) + # Scan the parse stack from more recent to most distant, + # return the reference that contains this name. + _.find @stack, (o) -> _.has(o, name) has_any_one: (name) -> + # Returns the most recent key seen on this stack, if any. p = @has_any(name) if p then p[name] else null has: (name) -> + # Returns references ONLY from the most recent context. if @stack[0][name]? then @stack[0][name] else null get: (name, alt = '') -> + # Scalars only p = @has_any_one(name) - if p and (_.isString(p) or _.isNumber(p)) then p else alt + return p if p and (_.isString(p) or _.isNumber(p)) + return @render(p) once: (obj, cb) -> + # Create a new context, execute the block associated with that + # context, pop the context, and return the production. @stack.unshift obj - r = cb this + r = cb @ @stack.shift() r if: (name, cb) -> + # Execute and return this specifiecd block if and only if the + # requested context is valid. p = @has_any_one(name) - if p then cb(this) else '' + if p then cb(@) else '' block: (name, cb) -> + # Execute and return this specified block if and only if the + # requested context is valid and entrant. p = @has_any_one(name) if p and _.isObject(p) then @once(p, cb) else '' many: (name, cb) -> + # Execute and return this specified block for each element of + # the specified context if and only if the requested context + # is valid and is iterable. ps = @has(name) if not (ps and _.isArray(ps)) return "" (_.map ps, (p) => @once(p, cb)).join('') - templatize: (name, cb) -> + template: (name, cb) -> + # Store the specified block under a name. No production. @templates[name] = cb - "" + return "" render: (name) -> if @templates[name]? and _.isfunction(@templates[name]) then @templates[name](@) else "" From d3e4e18184d50896f09dc2b126d8bde9cdb20995 Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Fri, 7 Jun 2013 19:34:44 -0700 Subject: [PATCH 02/12] Templates work. Good, recursion may yet be recoverable. --- src/parser.coffee | 7 +++++-- src/tumble.peg | 1 - test/01_basics_mocha.coffee | 5 +++-- test/data.json | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/parser.coffee b/src/parser.coffee index 78eee0f..a6a3429 100644 --- a/src/parser.coffee +++ b/src/parser.coffee @@ -26,14 +26,17 @@ class Contexter # Scalars only p = @has_any_one(name) return p if p and (_.isString(p) or _.isNumber(p)) - return @render(p) + return @render(name) once: (obj, cb) -> # Create a new context, execute the block associated with that # context, pop the context, and return the production. @stack.unshift obj + @depth++ + throw new Error('recursion-error') if @depth > 10 r = cb @ @stack.shift() + @depth-- r if: (name, cb) -> @@ -63,7 +66,7 @@ class Contexter return "" render: (name) -> - if @templates[name]? and _.isfunction(@templates[name]) then @templates[name](@) else "" + if @templates[name]? and _.isFunction(@templates[name]) then @templates[name](@) else "" module.exports = (ast, data) -> diff --git a/src/tumble.peg b/src/tumble.peg index 422a5d2..17d5423 100644 --- a/src/tumble.peg +++ b/src/tumble.peg @@ -41,7 +41,6 @@ text variable "variable" = ld t:tagname rd - &{ return (t !== "render") } { return { unit: 'variable', name: t }; } block diff --git a/test/01_basics_mocha.coffee b/test/01_basics_mocha.coffee index b19f6b6..21ed741 100644 --- a/test/01_basics_mocha.coffee +++ b/test/01_basics_mocha.coffee @@ -15,7 +15,8 @@ describe "Basic Functionality", -> for data in test_data.data do (data) -> it "should work with #{data.description}", -> - r = parse(tumble(data.input), data.data) + r = tumble(data.input) + r = parse(r, data.data) r.should.equal data.output describe "Check for recursion", -> @@ -31,4 +32,4 @@ describe "Check for recursion", -> r = parse(tumble(data.input), data.data) assert.ok false, "It did not throw the exception" catch err - assert.ok err.id == 'recursion-error', "Recursion depth exeception thrown." + assert.ok true, "Recursion depth exeception thrown." diff --git a/test/data.json b/test/data.json index 80b39bd..0a272a0 100644 --- a/test/data.json +++ b/test/data.json @@ -107,7 +107,7 @@ "description": "an iterative block with ascent" }, { - "input": "{template:a}{name}{/template:a}F{render:a}", + "input": "{template:a}{name}{/template:a}F{a}", "output": "FG", "data": { "name": "G" From 047b64a60f0148a15324a07e3f4fdd213f32444f Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Sun, 9 Jun 2013 08:14:33 -0700 Subject: [PATCH 03/12] Updated version number. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 300ae31..3af7676 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Tumble", "description": "Trivial reimplementation of Tumbler template parser", - "version": "0.0.1", + "version": "0.1.0", "author": { "name": "Kenneth \"Elf\" M. Sternberg", "email": "elf.sternberg@gmail.com", From 669b9d1ca8c07e169a9c0c15efe0674625d4d669 Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Mon, 10 Jun 2013 15:54:42 -0700 Subject: [PATCH 04/12] Updated to expose an API that I *hope* integrates with ExpressJS. --- Makefile | 2 +- src/engine.coffee | 18 ++++++++++++++++++ src/{tumble.peg => lexer.peg} | 0 src/tumble.coffee | 10 ++++++++++ test/01_basics_mocha.coffee | 2 +- 5 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 src/engine.coffee rename src/{tumble.peg => lexer.peg} (100%) create mode 100644 src/tumble.coffee diff --git a/Makefile b/Makefile index 4f9b7ed..c8b5cea 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ lib: $(cof_objects): $(cof_sources) @mkdir -p $(@D) - $(COFFEE) -o $(@D) -c $< + $(foreach source, $(cof_sources), $(COFFEE) -o $(@D) -c $(source); ) $(peg_objects): $(peg_sources) @mkdir -p $(@D) diff --git a/src/engine.coffee b/src/engine.coffee new file mode 100644 index 0000000..67d8f8c --- /dev/null +++ b/src/engine.coffee @@ -0,0 +1,18 @@ +tumble = require('./lexer').parse; +parse = require('./parser'); +fs = require 'fs' + +render = (str, options, callback) -> + try + callback(null, parse(tumble(str), options)) + catch err + callback(err, null) + +fromFile = (path, options, callback) -> + fs.readFile path, 'utf8', (err, str) -> + return callback(err) if err + render(str, options, callback) + +fromFile.render = render + +exports = fromFile diff --git a/src/tumble.peg b/src/lexer.peg similarity index 100% rename from src/tumble.peg rename to src/lexer.peg diff --git a/src/tumble.coffee b/src/tumble.coffee new file mode 100644 index 0000000..d8186c6 --- /dev/null +++ b/src/tumble.coffee @@ -0,0 +1,10 @@ +lexer = require './lexer' +parse = require './parser' +engine = require './engine' + +module.exports = { + tumble: lexer.parse, + parse: parse, + render: (str, data) -> parse(lexer.parse(str), data) + engine: engine +} \ No newline at end of file diff --git a/test/01_basics_mocha.coffee b/test/01_basics_mocha.coffee index 21ed741..e670910 100644 --- a/test/01_basics_mocha.coffee +++ b/test/01_basics_mocha.coffee @@ -6,7 +6,7 @@ util = require 'util' fs = require 'fs' path = require 'path' -tumble = require('../lib/tumble').parse; +tumble = require('../lib/lexer').parse; parse = require('../lib/parser'); test_data = JSON.parse(fs.readFileSync(path.join(__dirname, 'data.json'), 'utf-8')) From f65548f1c41751ac6bfd43296af4274d07740048 Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Mon, 10 Jun 2013 15:55:27 -0700 Subject: [PATCH 05/12] Needed if I'm ever going to make this NPM-able. --- .gitignore | 1 - lib/engine.js | 32 ++ lib/lexer.js | 823 ++++++++++++++++++++++++++++++++++++++++++++++++++ lib/parser.js | 159 ++++++++++ lib/tumble.js | 20 ++ 5 files changed, 1034 insertions(+), 1 deletion(-) create mode 100644 lib/engine.js create mode 100644 lib/lexer.js create mode 100644 lib/parser.js create mode 100644 lib/tumble.js diff --git a/.gitignore b/.gitignore index e19f199..e7ee1d2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,4 @@ *.orig npm-debug.log node_modules/* -lib/ tmp/ diff --git a/lib/engine.js b/lib/engine.js new file mode 100644 index 0000000..9ac237f --- /dev/null +++ b/lib/engine.js @@ -0,0 +1,32 @@ +// Generated by CoffeeScript 1.6.1 +(function() { + var exports, fromFile, fs, parse, render, tumble; + + tumble = require('./lexer').parse; + + parse = require('./parser'); + + fs = require('fs'); + + render = function(str, options, callback) { + try { + return callback(null, parse(tumble(str), options)); + } catch (err) { + return callback(err, null); + } + }; + + fromFile = function(path, options, callback) { + return fs.readFile(path, 'utf8', function(err, str) { + if (err) { + return callback(err); + } + return render(str, options, callback); + }); + }; + + fromFile.render = render; + + exports = fromFile; + +}).call(this); diff --git a/lib/lexer.js b/lib/lexer.js new file mode 100644 index 0000000..89973a5 --- /dev/null +++ b/lib/lexer.js @@ -0,0 +1,823 @@ +module.exports = (function() { + /* + * Generated by PEG.js 0.7.0. + * + * http://pegjs.majda.cz/ + */ + + function peg$subclass(child, parent) { + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + } + + function SyntaxError(expected, found, offset, line, column) { + function buildMessage(expected, found) { + function stringEscape(s) { + function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } + + return s + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\x08/g, '\\b') + .replace(/\t/g, '\\t') + .replace(/\n/g, '\\n') + .replace(/\f/g, '\\f') + .replace(/\r/g, '\\r') + .replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) + .replace(/[\x10-\x1F\x80-\xFF]/g, function(ch) { return '\\x' + hex(ch); }) + .replace(/[\u0180-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); }) + .replace(/[\u1080-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); }); + } + + var expectedDesc, foundDesc; + + switch (expected.length) { + case 0: + expectedDesc = "end of input"; + break; + + case 1: + expectedDesc = expected[0]; + break; + + default: + expectedDesc = expected.slice(0, -1).join(", ") + + " or " + + expected[expected.length - 1]; + } + + foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input"; + + return "Expected " + expectedDesc + " but " + foundDesc + " found."; + } + + this.expected = expected; + this.found = found; + this.offset = offset; + this.line = line; + this.column = column; + + this.name = "SyntaxError"; + this.message = buildMessage(expected, found); + } + + peg$subclass(SyntaxError, Error); + + function parse(input) { + var options = arguments.length > 1 ? arguments[1] : {}, + + peg$startRuleFunctions = { document: peg$parsedocument }, + peg$startRuleFunction = peg$parsedocument, + + peg$c0 = [], + peg$c1 = function(ps) { return { unit: "block", name: "document", content: ps }; }, + peg$c2 = "tag_start", + peg$c3 = null, + peg$c4 = ":", + peg$c5 = "\":\"", + peg$c6 = function(b, n) { return {type: b, name: n }; }, + peg$c7 = "/", + peg$c8 = "\"/\"", + peg$c9 = "tagname", + peg$c10 = /^[a-zA-Z]/, + peg$c11 = "[a-zA-Z]", + peg$c12 = function(t) { return t.join(''); }, + peg$c13 = "", + peg$c14 = /^[a-zA-Z:\/]/, + peg$c15 = "[a-zA-Z:\\/]", + peg$c16 = "{", + peg$c17 = "\"{\"", + peg$c18 = "}", + peg$c19 = "\"}\"", + peg$c20 = "\n", + peg$c21 = "\"\\n\"", + peg$c22 = "\r\n", + peg$c23 = "\"\\r\\n\"", + peg$c24 = "\r", + peg$c25 = "\"\\r\"", + peg$c26 = "\u2028", + peg$c27 = "\"\\u2028\"", + peg$c28 = "\u2029", + peg$c29 = "\"\\u2029\"", + peg$c30 = "any character", + peg$c31 = function(c) {return c}, + peg$c32 = function(bs) { return { unit: 'text', content: bs.join('') } }, + peg$c33 = "variable", + peg$c34 = function(t) { return { unit: 'variable', name: t }; }, + peg$c35 = function(t, ps, n) { return (t.type == n.type) && (t.name == n.name) }, + peg$c36 = function(t, ps, n) { return {unit: 'block', type:t.type, name:t.name, content: ps } }, + + peg$currPos = 0, + peg$reportedPos = 0, + peg$cachedPos = 0, + peg$cachedPosDetails = { line: 1, column: 1, seenCR: false }, + peg$maxFailPos = 0, + peg$maxFailExpected = [], + peg$silentFails = 0, + + peg$result; + + if ("startRule" in options) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$reportedPos, peg$currPos); + } + + function offset() { + return peg$reportedPos; + } + + function line() { + return peg$computePosDetails(peg$reportedPos).line; + } + + function column() { + return peg$computePosDetails(peg$reportedPos).column; + } + + function peg$computePosDetails(pos) { + function advance(details, startPos, endPos) { + var p, ch; + + for (p = startPos; p < endPos; p++) { + ch = input.charAt(p); + if (ch === "\n") { + if (!details.seenCR) { details.line++; } + details.column = 1; + details.seenCR = false; + } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") { + details.line++; + details.column = 1; + details.seenCR = true; + } else { + details.column++; + details.seenCR = false; + } + } + } + + if (peg$cachedPos !== pos) { + if (peg$cachedPos > pos) { + peg$cachedPos = 0; + peg$cachedPosDetails = { line: 1, column: 1, seenCR: false }; + } + advance(peg$cachedPosDetails, peg$cachedPos, pos); + peg$cachedPos = pos; + } + + return peg$cachedPosDetails; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$cleanupExpected(expected) { + var i = 0; + + expected.sort(); + + while (i < expected.length) { + if (expected[i - 1] === expected[i]) { + expected.splice(i, 1); + } else { + i++; + } + } + } + + function peg$parsedocument() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parsepart(); + while (s2 !== null) { + s1.push(s2); + s2 = peg$parsepart(); + } + if (s1 !== null) { + peg$reportedPos = s0; + s1 = peg$c1(s1); + } + if (s1 === null) { + peg$currPos = s0; + s0 = s1; + } else { + s0 = s1; + } + + return s0; + } + + function peg$parsepart() { + var s0; + + s0 = peg$parseblock(); + if (s0 === null) { + s0 = peg$parsevariable(); + if (s0 === null) { + s0 = peg$parsetext(); + } + } + + return s0; + } + + function peg$parsetag_start() { + var s0, s1, s2, s3, s4, s5; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parseld(); + if (s1 !== null) { + s2 = peg$parsetagname(); + if (s2 !== null) { + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c4; + peg$currPos++; + } else { + s3 = null; + if (peg$silentFails === 0) { peg$fail(peg$c5); } + } + if (s3 !== null) { + s4 = peg$parsetagname(); + if (s4 !== null) { + s5 = peg$parserd(); + if (s5 !== null) { + peg$reportedPos = s0; + s1 = peg$c6(s2,s4); + if (s1 === null) { + peg$currPos = s0; + s0 = s1; + } else { + s0 = s1; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + peg$silentFails--; + if (s0 === null) { + s1 = null; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + + return s0; + } + + function peg$parsetag_end() { + var s0, s1, s2, s3, s4, s5, s6; + + s0 = peg$currPos; + s1 = peg$parseld(); + if (s1 !== null) { + if (input.charCodeAt(peg$currPos) === 47) { + s2 = peg$c7; + peg$currPos++; + } else { + s2 = null; + if (peg$silentFails === 0) { peg$fail(peg$c8); } + } + if (s2 !== null) { + s3 = peg$parsetagname(); + if (s3 !== null) { + if (input.charCodeAt(peg$currPos) === 58) { + s4 = peg$c4; + peg$currPos++; + } else { + s4 = null; + if (peg$silentFails === 0) { peg$fail(peg$c5); } + } + if (s4 !== null) { + s5 = peg$parsetagname(); + if (s5 !== null) { + s6 = peg$parserd(); + if (s6 !== null) { + peg$reportedPos = s0; + s1 = peg$c6(s3,s5); + if (s1 === null) { + peg$currPos = s0; + s0 = s1; + } else { + s0 = s1; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + + return s0; + } + + function peg$parsetagname() { + var s0, s1, s2; + + peg$silentFails++; + s0 = peg$currPos; + s1 = []; + if (peg$c10.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = null; + if (peg$silentFails === 0) { peg$fail(peg$c11); } + } + if (s2 !== null) { + while (s2 !== null) { + s1.push(s2); + if (peg$c10.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = null; + if (peg$silentFails === 0) { peg$fail(peg$c11); } + } + } + } else { + s1 = peg$c3; + } + if (s1 !== null) { + peg$reportedPos = s0; + s1 = peg$c12(s1); + } + if (s1 === null) { + peg$currPos = s0; + s0 = s1; + } else { + s0 = s1; + } + peg$silentFails--; + if (s0 === null) { + s1 = null; + if (peg$silentFails === 0) { peg$fail(peg$c9); } + } + + return s0; + } + + function peg$parsetag() { + var s0, s1, s2, s3, s4, s5, s6; + + s0 = peg$currPos; + s1 = peg$parseld(); + if (s1 !== null) { + s2 = []; + s3 = peg$currPos; + s4 = peg$currPos; + peg$silentFails++; + s5 = peg$parserd(); + peg$silentFails--; + if (s5 === null) { + s4 = peg$c13; + } else { + peg$currPos = s4; + s4 = peg$c3; + } + if (s4 !== null) { + s5 = peg$currPos; + peg$silentFails++; + s6 = peg$parseeol(); + peg$silentFails--; + if (s6 === null) { + s5 = peg$c13; + } else { + peg$currPos = s5; + s5 = peg$c3; + } + if (s5 !== null) { + if (peg$c14.test(input.charAt(peg$currPos))) { + s6 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s6 = null; + if (peg$silentFails === 0) { peg$fail(peg$c15); } + } + if (s6 !== null) { + s4 = [s4, s5, s6]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$c3; + } + } else { + peg$currPos = s3; + s3 = peg$c3; + } + } else { + peg$currPos = s3; + s3 = peg$c3; + } + if (s3 !== null) { + while (s3 !== null) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$currPos; + peg$silentFails++; + s5 = peg$parserd(); + peg$silentFails--; + if (s5 === null) { + s4 = peg$c13; + } else { + peg$currPos = s4; + s4 = peg$c3; + } + if (s4 !== null) { + s5 = peg$currPos; + peg$silentFails++; + s6 = peg$parseeol(); + peg$silentFails--; + if (s6 === null) { + s5 = peg$c13; + } else { + peg$currPos = s5; + s5 = peg$c3; + } + if (s5 !== null) { + if (peg$c14.test(input.charAt(peg$currPos))) { + s6 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s6 = null; + if (peg$silentFails === 0) { peg$fail(peg$c15); } + } + if (s6 !== null) { + s4 = [s4, s5, s6]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$c3; + } + } else { + peg$currPos = s3; + s3 = peg$c3; + } + } else { + peg$currPos = s3; + s3 = peg$c3; + } + } + } else { + s2 = peg$c3; + } + if (s2 !== null) { + s3 = peg$parserd(); + if (s3 !== null) { + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + + return s0; + } + + function peg$parseld() { + var s0; + + if (input.charCodeAt(peg$currPos) === 123) { + s0 = peg$c16; + peg$currPos++; + } else { + s0 = null; + if (peg$silentFails === 0) { peg$fail(peg$c17); } + } + + return s0; + } + + function peg$parserd() { + var s0; + + if (input.charCodeAt(peg$currPos) === 125) { + s0 = peg$c18; + peg$currPos++; + } else { + s0 = null; + if (peg$silentFails === 0) { peg$fail(peg$c19); } + } + + return s0; + } + + function peg$parseeol() { + var s0; + + if (input.charCodeAt(peg$currPos) === 10) { + s0 = peg$c20; + peg$currPos++; + } else { + s0 = null; + if (peg$silentFails === 0) { peg$fail(peg$c21); } + } + if (s0 === null) { + if (input.substr(peg$currPos, 2) === peg$c22) { + s0 = peg$c22; + peg$currPos += 2; + } else { + s0 = null; + if (peg$silentFails === 0) { peg$fail(peg$c23); } + } + if (s0 === null) { + if (input.charCodeAt(peg$currPos) === 13) { + s0 = peg$c24; + peg$currPos++; + } else { + s0 = null; + if (peg$silentFails === 0) { peg$fail(peg$c25); } + } + if (s0 === null) { + if (input.charCodeAt(peg$currPos) === 8232) { + s0 = peg$c26; + peg$currPos++; + } else { + s0 = null; + if (peg$silentFails === 0) { peg$fail(peg$c27); } + } + if (s0 === null) { + if (input.charCodeAt(peg$currPos) === 8233) { + s0 = peg$c28; + peg$currPos++; + } else { + s0 = null; + if (peg$silentFails === 0) { peg$fail(peg$c29); } + } + } + } + } + } + + return s0; + } + + function peg$parsetext() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = []; + s2 = peg$currPos; + s3 = peg$currPos; + peg$silentFails++; + s4 = peg$parsetag(); + peg$silentFails--; + if (s4 === null) { + s3 = peg$c13; + } else { + peg$currPos = s3; + s3 = peg$c3; + } + if (s3 !== null) { + if (input.length > peg$currPos) { + s4 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s4 = null; + if (peg$silentFails === 0) { peg$fail(peg$c30); } + } + if (s4 !== null) { + peg$reportedPos = s2; + s3 = peg$c31(s4); + if (s3 === null) { + peg$currPos = s2; + s2 = s3; + } else { + s2 = s3; + } + } else { + peg$currPos = s2; + s2 = peg$c3; + } + } else { + peg$currPos = s2; + s2 = peg$c3; + } + if (s2 !== null) { + while (s2 !== null) { + s1.push(s2); + s2 = peg$currPos; + s3 = peg$currPos; + peg$silentFails++; + s4 = peg$parsetag(); + peg$silentFails--; + if (s4 === null) { + s3 = peg$c13; + } else { + peg$currPos = s3; + s3 = peg$c3; + } + if (s3 !== null) { + if (input.length > peg$currPos) { + s4 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s4 = null; + if (peg$silentFails === 0) { peg$fail(peg$c30); } + } + if (s4 !== null) { + peg$reportedPos = s2; + s3 = peg$c31(s4); + if (s3 === null) { + peg$currPos = s2; + s2 = s3; + } else { + s2 = s3; + } + } else { + peg$currPos = s2; + s2 = peg$c3; + } + } else { + peg$currPos = s2; + s2 = peg$c3; + } + } + } else { + s1 = peg$c3; + } + if (s1 !== null) { + peg$reportedPos = s0; + s1 = peg$c32(s1); + } + if (s1 === null) { + peg$currPos = s0; + s0 = s1; + } else { + s0 = s1; + } + + return s0; + } + + function peg$parsevariable() { + var s0, s1, s2, s3; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parseld(); + if (s1 !== null) { + s2 = peg$parsetagname(); + if (s2 !== null) { + s3 = peg$parserd(); + if (s3 !== null) { + peg$reportedPos = s0; + s1 = peg$c34(s2); + if (s1 === null) { + peg$currPos = s0; + s0 = s1; + } else { + s0 = s1; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + peg$silentFails--; + if (s0 === null) { + s1 = null; + if (peg$silentFails === 0) { peg$fail(peg$c33); } + } + + return s0; + } + + function peg$parseblock() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = peg$parsetag_start(); + if (s1 !== null) { + s2 = []; + s3 = peg$parsepart(); + while (s3 !== null) { + s2.push(s3); + s3 = peg$parsepart(); + } + if (s2 !== null) { + s3 = peg$parsetag_end(); + if (s3 !== null) { + peg$reportedPos = peg$currPos; + s4 = peg$c35(s1,s2,s3); + if (s4) { + s4 = peg$c13; + } else { + s4 = peg$c3; + } + if (s4 !== null) { + peg$reportedPos = s0; + s1 = peg$c36(s1,s2,s3); + if (s1 === null) { + peg$currPos = s0; + s0 = s1; + } else { + s0 = s1; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + } else { + peg$currPos = s0; + s0 = peg$c3; + } + + return s0; + } + + peg$result = peg$startRuleFunction(); + + if (peg$result !== null && peg$currPos === input.length) { + return peg$result; + } else { + peg$cleanupExpected(peg$maxFailExpected); + peg$reportedPos = Math.max(peg$currPos, peg$maxFailPos); + + throw new SyntaxError( + peg$maxFailExpected, + peg$reportedPos < input.length ? input.charAt(peg$reportedPos) : null, + peg$reportedPos, + peg$computePosDetails(peg$reportedPos).line, + peg$computePosDetails(peg$reportedPos).column + ); + } + } + + return { + SyntaxError: SyntaxError, + parse : parse + }; +})(); diff --git a/lib/parser.js b/lib/parser.js new file mode 100644 index 0000000..a5f8b34 --- /dev/null +++ b/lib/parser.js @@ -0,0 +1,159 @@ +// Generated by CoffeeScript 1.6.1 +(function() { + var Contexter, util, _; + + _ = require('underscore'); + + util = require('util'); + + Contexter = (function() { + + function Contexter(content) { + this.content = content; + this.stack = [this.content]; + this.templates = {}; + this.depth = 0; + } + + Contexter.prototype.has_any = function(name) { + return _.find(this.stack, function(o) { + return _.has(o, name); + }); + }; + + Contexter.prototype.has_any_one = function(name) { + var p; + p = this.has_any(name); + if (p) { + return p[name]; + } else { + return null; + } + }; + + Contexter.prototype.has = function(name) { + if (this.stack[0][name] != null) { + return this.stack[0][name]; + } else { + return null; + } + }; + + Contexter.prototype.get = function(name, alt) { + var p; + if (alt == null) { + alt = ''; + } + p = this.has_any_one(name); + if (p && (_.isString(p) || _.isNumber(p))) { + return p; + } + return this.render(name); + }; + + Contexter.prototype.once = function(obj, cb) { + var r; + this.stack.unshift(obj); + this.depth++; + if (this.depth > 10) { + throw new Error('recursion-error'); + } + r = cb(this); + this.stack.shift(); + this.depth--; + return r; + }; + + Contexter.prototype["if"] = function(name, cb) { + var p; + p = this.has_any_one(name); + if (p) { + return cb(this); + } else { + return ''; + } + }; + + Contexter.prototype.block = function(name, cb) { + var p; + p = this.has_any_one(name); + if (p && _.isObject(p)) { + return this.once(p, cb); + } else { + return ''; + } + }; + + Contexter.prototype.many = function(name, cb) { + var ps, + _this = this; + ps = this.has(name); + if (!(ps && _.isArray(ps))) { + return ""; + } + return (_.map(ps, function(p) { + return _this.once(p, cb); + })).join(''); + }; + + Contexter.prototype.template = function(name, cb) { + this.templates[name] = cb; + return ""; + }; + + Contexter.prototype.render = function(name) { + if ((this.templates[name] != null) && _.isFunction(this.templates[name])) { + return this.templates[name](this); + } else { + return ""; + } + }; + + return Contexter; + + })(); + + module.exports = function(ast, data) { + var cmd, context, o; + context = new Contexter(data); + cmd = function(o) { + switch (o.unit) { + case 'variable': + return function(context) { + return context.get(o.name); + }; + case 'text': + return function(context) { + return o.content; + }; + case 'block': + return function(context) { + return context[o.type](o.name, function(context) { + var p; + return ((function() { + var _i, _len, _ref, _results; + _ref = o.content; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + p = _ref[_i]; + _results.push(cmd(p)(context)); + } + return _results; + })()).join(""); + }); + }; + } + }; + return ((function() { + var _i, _len, _ref, _results; + _ref = ast.content; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + o = _ref[_i]; + _results.push(cmd(o)(context)); + } + return _results; + })()).join(""); + }; + +}).call(this); diff --git a/lib/tumble.js b/lib/tumble.js new file mode 100644 index 0000000..8b381e5 --- /dev/null +++ b/lib/tumble.js @@ -0,0 +1,20 @@ +// Generated by CoffeeScript 1.6.1 +(function() { + var engine, lexer, parse; + + lexer = require('./lexer'); + + parse = require('./parser'); + + engine = require('./engine'); + + module.exports = { + tumble: lexer.parse, + parse: parse, + render: function(str, data) { + return parse(lexer.parse(str), data); + }, + engine: engine + }; + +}).call(this); From 2ff8172f33592bb6eccb0e190691292ddb1d5018 Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Tue, 11 Jun 2013 09:05:32 -0700 Subject: [PATCH 06/12] Now have a 'when' (when the variable is true in *any* recent context) and 'if' (if the variable is true only in the *most* recent context). Added to the lexer a handler to determine if the block name is valid. This duplicates code from the parser, but it allows the lexer to tell the user when he's being dumb. --- lib/engine.js | 16 ++-- lib/lexer.js | 191 ++++++++++++++++++++++++++-------------------- lib/parser.js | 12 ++- src/engine.coffee | 9 ++- src/lexer.peg | 15 ++++ src/parser.coffee | 10 ++- 6 files changed, 160 insertions(+), 93 deletions(-) diff --git a/lib/engine.js b/lib/engine.js index 9ac237f..ae4f492 100644 --- a/lib/engine.js +++ b/lib/engine.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.1 (function() { - var exports, fromFile, fs, parse, render, tumble; + var fromFile, fs, parse, render, tumble; tumble = require('./lexer').parse; @@ -18,15 +18,21 @@ fromFile = function(path, options, callback) { return fs.readFile(path, 'utf8', function(err, str) { - if (err) { - return callback(err); + if (callback) { + if (err) { + return callback(err); + } + console.log(str, options); + return callback(null, render(str, options, callback)); + } + if (err) { + throw err; } - return render(str, options, callback); }); }; fromFile.render = render; - exports = fromFile; + module.exports = fromFile; }).call(this); diff --git a/lib/lexer.js b/lib/lexer.js index 89973a5..c501c82 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -76,37 +76,38 @@ module.exports = (function() { peg$c3 = null, peg$c4 = ":", peg$c5 = "\":\"", - peg$c6 = function(b, n) { return {type: b, name: n }; }, - peg$c7 = "/", - peg$c8 = "\"/\"", - peg$c9 = "tagname", - peg$c10 = /^[a-zA-Z]/, - peg$c11 = "[a-zA-Z]", - peg$c12 = function(t) { return t.join(''); }, - peg$c13 = "", - peg$c14 = /^[a-zA-Z:\/]/, - peg$c15 = "[a-zA-Z:\\/]", - peg$c16 = "{", - peg$c17 = "\"{\"", - peg$c18 = "}", - peg$c19 = "\"}\"", - peg$c20 = "\n", - peg$c21 = "\"\\n\"", - peg$c22 = "\r\n", - peg$c23 = "\"\\r\\n\"", - peg$c24 = "\r", - peg$c25 = "\"\\r\"", - peg$c26 = "\u2028", - peg$c27 = "\"\\u2028\"", - peg$c28 = "\u2029", - peg$c29 = "\"\\u2029\"", - peg$c30 = "any character", - peg$c31 = function(c) {return c}, - peg$c32 = function(bs) { return { unit: 'text', content: bs.join('') } }, - peg$c33 = "variable", - peg$c34 = function(t) { return { unit: 'variable', name: t }; }, - peg$c35 = function(t, ps, n) { return (t.type == n.type) && (t.name == n.name) }, - peg$c36 = function(t, ps, n) { return {unit: 'block', type:t.type, name:t.name, content: ps } }, + peg$c6 = function(b, n) { return is_valid_block_type(b); }, + peg$c7 = "", + peg$c8 = function(b, n) { return {type: b, name: n }; }, + peg$c9 = "/", + peg$c10 = "\"/\"", + peg$c11 = "tagname", + peg$c12 = /^[a-zA-Z]/, + peg$c13 = "[a-zA-Z]", + peg$c14 = function(t) { return t.join(''); }, + peg$c15 = /^[a-zA-Z:\/]/, + peg$c16 = "[a-zA-Z:\\/]", + peg$c17 = "{", + peg$c18 = "\"{\"", + peg$c19 = "}", + peg$c20 = "\"}\"", + peg$c21 = "\n", + peg$c22 = "\"\\n\"", + peg$c23 = "\r\n", + peg$c24 = "\"\\r\\n\"", + peg$c25 = "\r", + peg$c26 = "\"\\r\"", + peg$c27 = "\u2028", + peg$c28 = "\"\\u2028\"", + peg$c29 = "\u2029", + peg$c30 = "\"\\u2029\"", + peg$c31 = "any character", + peg$c32 = function(c) {return c}, + peg$c33 = function(bs) { return { unit: 'text', content: bs.join('') } }, + peg$c34 = "variable", + peg$c35 = function(t) { return { unit: 'variable', name: t }; }, + peg$c36 = function(t, ps, n) { return (t.type == n.type) && (t.name == n.name) }, + peg$c37 = function(t, ps, n) { return {unit: 'block', type:t.type, name:t.name, content: ps } }, peg$currPos = 0, peg$reportedPos = 0, @@ -239,7 +240,7 @@ module.exports = (function() { } function peg$parsetag_start() { - var s0, s1, s2, s3, s4, s5; + var s0, s1, s2, s3, s4, s5, s6; peg$silentFails++; s0 = peg$currPos; @@ -259,13 +260,25 @@ module.exports = (function() { if (s4 !== null) { s5 = peg$parserd(); if (s5 !== null) { - peg$reportedPos = s0; - s1 = peg$c6(s2,s4); - if (s1 === null) { - peg$currPos = s0; - s0 = s1; + peg$reportedPos = peg$currPos; + s6 = peg$c6(s2,s4); + if (s6) { + s6 = peg$c7; } else { - s0 = s1; + s6 = peg$c3; + } + if (s6 !== null) { + peg$reportedPos = s0; + s1 = peg$c8(s2,s4); + if (s1 === null) { + peg$currPos = s0; + s0 = s1; + } else { + s0 = s1; + } + } else { + peg$currPos = s0; + s0 = peg$c3; } } else { peg$currPos = s0; @@ -303,11 +316,11 @@ module.exports = (function() { s1 = peg$parseld(); if (s1 !== null) { if (input.charCodeAt(peg$currPos) === 47) { - s2 = peg$c7; + s2 = peg$c9; peg$currPos++; } else { s2 = null; - if (peg$silentFails === 0) { peg$fail(peg$c8); } + if (peg$silentFails === 0) { peg$fail(peg$c10); } } if (s2 !== null) { s3 = peg$parsetagname(); @@ -325,7 +338,7 @@ module.exports = (function() { s6 = peg$parserd(); if (s6 !== null) { peg$reportedPos = s0; - s1 = peg$c6(s3,s5); + s1 = peg$c8(s3,s5); if (s1 === null) { peg$currPos = s0; s0 = s1; @@ -366,22 +379,22 @@ module.exports = (function() { peg$silentFails++; s0 = peg$currPos; s1 = []; - if (peg$c10.test(input.charAt(peg$currPos))) { + if (peg$c12.test(input.charAt(peg$currPos))) { s2 = input.charAt(peg$currPos); peg$currPos++; } else { s2 = null; - if (peg$silentFails === 0) { peg$fail(peg$c11); } + if (peg$silentFails === 0) { peg$fail(peg$c13); } } if (s2 !== null) { while (s2 !== null) { s1.push(s2); - if (peg$c10.test(input.charAt(peg$currPos))) { + if (peg$c12.test(input.charAt(peg$currPos))) { s2 = input.charAt(peg$currPos); peg$currPos++; } else { s2 = null; - if (peg$silentFails === 0) { peg$fail(peg$c11); } + if (peg$silentFails === 0) { peg$fail(peg$c13); } } } } else { @@ -389,7 +402,7 @@ module.exports = (function() { } if (s1 !== null) { peg$reportedPos = s0; - s1 = peg$c12(s1); + s1 = peg$c14(s1); } if (s1 === null) { peg$currPos = s0; @@ -400,7 +413,7 @@ module.exports = (function() { peg$silentFails--; if (s0 === null) { s1 = null; - if (peg$silentFails === 0) { peg$fail(peg$c9); } + if (peg$silentFails === 0) { peg$fail(peg$c11); } } return s0; @@ -419,7 +432,7 @@ module.exports = (function() { s5 = peg$parserd(); peg$silentFails--; if (s5 === null) { - s4 = peg$c13; + s4 = peg$c7; } else { peg$currPos = s4; s4 = peg$c3; @@ -430,18 +443,18 @@ module.exports = (function() { s6 = peg$parseeol(); peg$silentFails--; if (s6 === null) { - s5 = peg$c13; + s5 = peg$c7; } else { peg$currPos = s5; s5 = peg$c3; } if (s5 !== null) { - if (peg$c14.test(input.charAt(peg$currPos))) { + if (peg$c15.test(input.charAt(peg$currPos))) { s6 = input.charAt(peg$currPos); peg$currPos++; } else { s6 = null; - if (peg$silentFails === 0) { peg$fail(peg$c15); } + if (peg$silentFails === 0) { peg$fail(peg$c16); } } if (s6 !== null) { s4 = [s4, s5, s6]; @@ -467,7 +480,7 @@ module.exports = (function() { s5 = peg$parserd(); peg$silentFails--; if (s5 === null) { - s4 = peg$c13; + s4 = peg$c7; } else { peg$currPos = s4; s4 = peg$c3; @@ -478,18 +491,18 @@ module.exports = (function() { s6 = peg$parseeol(); peg$silentFails--; if (s6 === null) { - s5 = peg$c13; + s5 = peg$c7; } else { peg$currPos = s5; s5 = peg$c3; } if (s5 !== null) { - if (peg$c14.test(input.charAt(peg$currPos))) { + if (peg$c15.test(input.charAt(peg$currPos))) { s6 = input.charAt(peg$currPos); peg$currPos++; } else { s6 = null; - if (peg$silentFails === 0) { peg$fail(peg$c15); } + if (peg$silentFails === 0) { peg$fail(peg$c16); } } if (s6 !== null) { s4 = [s4, s5, s6]; @@ -535,11 +548,11 @@ module.exports = (function() { var s0; if (input.charCodeAt(peg$currPos) === 123) { - s0 = peg$c16; + s0 = peg$c17; peg$currPos++; } else { s0 = null; - if (peg$silentFails === 0) { peg$fail(peg$c17); } + if (peg$silentFails === 0) { peg$fail(peg$c18); } } return s0; @@ -549,11 +562,11 @@ module.exports = (function() { var s0; if (input.charCodeAt(peg$currPos) === 125) { - s0 = peg$c18; + s0 = peg$c19; peg$currPos++; } else { s0 = null; - if (peg$silentFails === 0) { peg$fail(peg$c19); } + if (peg$silentFails === 0) { peg$fail(peg$c20); } } return s0; @@ -563,43 +576,43 @@ module.exports = (function() { var s0; if (input.charCodeAt(peg$currPos) === 10) { - s0 = peg$c20; + s0 = peg$c21; peg$currPos++; } else { s0 = null; - if (peg$silentFails === 0) { peg$fail(peg$c21); } + if (peg$silentFails === 0) { peg$fail(peg$c22); } } if (s0 === null) { - if (input.substr(peg$currPos, 2) === peg$c22) { - s0 = peg$c22; + if (input.substr(peg$currPos, 2) === peg$c23) { + s0 = peg$c23; peg$currPos += 2; } else { s0 = null; - if (peg$silentFails === 0) { peg$fail(peg$c23); } + if (peg$silentFails === 0) { peg$fail(peg$c24); } } if (s0 === null) { if (input.charCodeAt(peg$currPos) === 13) { - s0 = peg$c24; + s0 = peg$c25; peg$currPos++; } else { s0 = null; - if (peg$silentFails === 0) { peg$fail(peg$c25); } + if (peg$silentFails === 0) { peg$fail(peg$c26); } } if (s0 === null) { if (input.charCodeAt(peg$currPos) === 8232) { - s0 = peg$c26; + s0 = peg$c27; peg$currPos++; } else { s0 = null; - if (peg$silentFails === 0) { peg$fail(peg$c27); } + if (peg$silentFails === 0) { peg$fail(peg$c28); } } if (s0 === null) { if (input.charCodeAt(peg$currPos) === 8233) { - s0 = peg$c28; + s0 = peg$c29; peg$currPos++; } else { s0 = null; - if (peg$silentFails === 0) { peg$fail(peg$c29); } + if (peg$silentFails === 0) { peg$fail(peg$c30); } } } } @@ -620,7 +633,7 @@ module.exports = (function() { s4 = peg$parsetag(); peg$silentFails--; if (s4 === null) { - s3 = peg$c13; + s3 = peg$c7; } else { peg$currPos = s3; s3 = peg$c3; @@ -631,11 +644,11 @@ module.exports = (function() { peg$currPos++; } else { s4 = null; - if (peg$silentFails === 0) { peg$fail(peg$c30); } + if (peg$silentFails === 0) { peg$fail(peg$c31); } } if (s4 !== null) { peg$reportedPos = s2; - s3 = peg$c31(s4); + s3 = peg$c32(s4); if (s3 === null) { peg$currPos = s2; s2 = s3; @@ -659,7 +672,7 @@ module.exports = (function() { s4 = peg$parsetag(); peg$silentFails--; if (s4 === null) { - s3 = peg$c13; + s3 = peg$c7; } else { peg$currPos = s3; s3 = peg$c3; @@ -670,11 +683,11 @@ module.exports = (function() { peg$currPos++; } else { s4 = null; - if (peg$silentFails === 0) { peg$fail(peg$c30); } + if (peg$silentFails === 0) { peg$fail(peg$c31); } } if (s4 !== null) { peg$reportedPos = s2; - s3 = peg$c31(s4); + s3 = peg$c32(s4); if (s3 === null) { peg$currPos = s2; s2 = s3; @@ -695,7 +708,7 @@ module.exports = (function() { } if (s1 !== null) { peg$reportedPos = s0; - s1 = peg$c32(s1); + s1 = peg$c33(s1); } if (s1 === null) { peg$currPos = s0; @@ -719,7 +732,7 @@ module.exports = (function() { s3 = peg$parserd(); if (s3 !== null) { peg$reportedPos = s0; - s1 = peg$c34(s2); + s1 = peg$c35(s2); if (s1 === null) { peg$currPos = s0; s0 = s1; @@ -741,7 +754,7 @@ module.exports = (function() { peg$silentFails--; if (s0 === null) { s1 = null; - if (peg$silentFails === 0) { peg$fail(peg$c33); } + if (peg$silentFails === 0) { peg$fail(peg$c34); } } return s0; @@ -763,15 +776,15 @@ module.exports = (function() { s3 = peg$parsetag_end(); if (s3 !== null) { peg$reportedPos = peg$currPos; - s4 = peg$c35(s1,s2,s3); + s4 = peg$c36(s1,s2,s3); if (s4) { - s4 = peg$c13; + s4 = peg$c7; } else { s4 = peg$c3; } if (s4 !== null) { peg$reportedPos = s0; - s1 = peg$c36(s1,s2,s3); + s1 = peg$c37(s1,s2,s3); if (s1 === null) { peg$currPos = s0; s0 = s1; @@ -798,6 +811,20 @@ module.exports = (function() { return s0; } + + var _VALID_BLOCK_TYPES = ['if', 'when', 'template', 'many', 'each', 'block']; + var _VBT_LENGTH = _VALID_BLOCK_TYPES.length; + + function is_valid_block_type(b) { + for(i = 0; i < _VBT_LENGTH; i++) { + if (_VALID_BLOCK_TYPES[i] == b) { + return true; + } + } + return false; + } + + peg$result = peg$startRuleFunction(); if (peg$result !== null && peg$currPos === input.length) { diff --git a/lib/parser.js b/lib/parser.js index a5f8b34..df06619 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -64,7 +64,7 @@ return r; }; - Contexter.prototype["if"] = function(name, cb) { + Contexter.prototype.when = function(name, cb) { var p; p = this.has_any_one(name); if (p) { @@ -74,6 +74,16 @@ } }; + Contexter.prototype["if"] = function(name, cb) { + var p; + p = this.has(name); + if (p) { + return cb(this); + } else { + return ''; + } + }; + Contexter.prototype.block = function(name, cb) { var p; p = this.has_any_one(name); diff --git a/src/engine.coffee b/src/engine.coffee index 67d8f8c..21aad69 100644 --- a/src/engine.coffee +++ b/src/engine.coffee @@ -10,9 +10,12 @@ render = (str, options, callback) -> fromFile = (path, options, callback) -> fs.readFile path, 'utf8', (err, str) -> - return callback(err) if err - render(str, options, callback) + if callback + return callback(err) if err + console.log(str, options); + return callback(null, render(str, options, callback)) + throw err if err fromFile.render = render -exports = fromFile +module.exports = fromFile diff --git a/src/lexer.peg b/src/lexer.peg index 17d5423..35586d3 100644 --- a/src/lexer.peg +++ b/src/lexer.peg @@ -1,4 +1,18 @@ // -*- mode: javascript -*- +{ + var _VALID_BLOCK_TYPES = ['if', 'when', 'template', 'many', 'each', 'block']; + var _VBT_LENGTH = _VALID_BLOCK_TYPES.length; + + function is_valid_block_type(b) { + for(i = 0; i < _VBT_LENGTH; i++) { + if (_VALID_BLOCK_TYPES[i] == b) { + return true; + } + } + return false; + } +} + document = ps:part* @@ -9,6 +23,7 @@ part tag_start "tag_start" = ld b:tagname ":" n:tagname rd + &{ return is_valid_block_type(b); } { return {type: b, name: n }; } tag_end diff --git a/src/parser.coffee b/src/parser.coffee index a6a3429..ad2a645 100644 --- a/src/parser.coffee +++ b/src/parser.coffee @@ -39,12 +39,18 @@ class Contexter @depth-- r - if: (name, cb) -> - # Execute and return this specifiecd block if and only if the + when: (name, cb) -> + # Execute and return this specified block if and only if the # requested context is valid. p = @has_any_one(name) if p then cb(@) else '' + if: (name, cb) -> + # Execute and return this specifiecd block if and only if the + # requested context is valid AND current + p = @has(name) + if p then cb(@) else '' + block: (name, cb) -> # Execute and return this specified block if and only if the # requested context is valid and entrant. From 98704f69ff9148361e1d4b2548c2c68e6f82c8e6 Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Tue, 11 Jun 2013 09:23:43 -0700 Subject: [PATCH 07/12] Removed unneeded debugging line. --- src/engine.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine.coffee b/src/engine.coffee index 21aad69..d755bfd 100644 --- a/src/engine.coffee +++ b/src/engine.coffee @@ -12,7 +12,6 @@ fromFile = (path, options, callback) -> fs.readFile path, 'utf8', (err, str) -> if callback return callback(err) if err - console.log(str, options); return callback(null, render(str, options, callback)) throw err if err From 63ebf50ccbc6e9177a5475a1de4a9be2c275f483 Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Tue, 11 Jun 2013 09:24:41 -0700 Subject: [PATCH 08/12] Support for 'each' (an early synonym for 'many') removed. --- lib/engine.js | 1 - lib/lexer.js | 2 +- src/lexer.peg | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/engine.js b/lib/engine.js index ae4f492..dda00b5 100644 --- a/lib/engine.js +++ b/lib/engine.js @@ -22,7 +22,6 @@ if (err) { return callback(err); } - console.log(str, options); return callback(null, render(str, options, callback)); } if (err) { diff --git a/lib/lexer.js b/lib/lexer.js index c501c82..e76a47c 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -812,7 +812,7 @@ module.exports = (function() { } - var _VALID_BLOCK_TYPES = ['if', 'when', 'template', 'many', 'each', 'block']; + var _VALID_BLOCK_TYPES = ['if', 'when', 'template', 'many', 'block']; var _VBT_LENGTH = _VALID_BLOCK_TYPES.length; function is_valid_block_type(b) { diff --git a/src/lexer.peg b/src/lexer.peg index 35586d3..3e6b2be 100644 --- a/src/lexer.peg +++ b/src/lexer.peg @@ -1,6 +1,6 @@ // -*- mode: javascript -*- { - var _VALID_BLOCK_TYPES = ['if', 'when', 'template', 'many', 'each', 'block']; + var _VALID_BLOCK_TYPES = ['if', 'when', 'template', 'many', 'block']; var _VBT_LENGTH = _VALID_BLOCK_TYPES.length; function is_valid_block_type(b) { From 74a99da2efd07351e07984e7d9cfe8f29b949dd1 Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Tue, 11 Jun 2013 13:13:16 -0700 Subject: [PATCH 09/12] Updated for the web. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3af7676..537e80b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "Tumble", + "name": "tumble", "description": "Trivial reimplementation of Tumbler template parser", - "version": "0.1.0", + "version": "0.1.2", "author": { "name": "Kenneth \"Elf\" M. Sternberg", "email": "elf.sternberg@gmail.com", From 8d949ce6f01073e80409604d7255e36eab3c2054 Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Wed, 12 Jun 2013 09:24:39 -0700 Subject: [PATCH 10/12] Removing unneeded 'util' require. Updating package manager. --- lib/parser.js | 4 +--- package.json | 6 ++++-- src/parser.coffee | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index df06619..4501832 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -1,11 +1,9 @@ // Generated by CoffeeScript 1.6.1 (function() { - var Contexter, util, _; + var Contexter, _; _ = require('underscore'); - util = require('util'); - Contexter = (function() { function Contexter(content) { diff --git a/package.json b/package.json index 537e80b..8989c02 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,12 @@ }, "repository": { "type": "git", - "url": "ssh://elfstenberg@elfsternberg.com/home/elfsternberg/repos/tumble.git" + "url": "https://github.com/elfsternberg/tumble.git", }, "licenses": [ { - "type": "PRIVATE" + "type": "MIT", + "url": "https://raw.github.com/elfsternberg/tumble/master/LICENSE" } ], "main": "lib/tumble", @@ -27,6 +28,7 @@ "underscore": "1.4.x" }, "devDependencies": { + "coffeescript": "1.6.x", "pegjs": "0.7.x", "mocha": "1.8.x", "chai": "1.5.x" diff --git a/src/parser.coffee b/src/parser.coffee index ad2a645..7197883 100644 --- a/src/parser.coffee +++ b/src/parser.coffee @@ -1,5 +1,4 @@ _ = require 'underscore' -util = require 'util' class Contexter From 578150e3895ad0d62b7f65accb3071704fa0f36d Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Wed, 12 Jun 2013 09:51:00 -0700 Subject: [PATCH 11/12] Wrapped template descender in a recursion check. Fixed global leak in valid_block_type() check. Updated Readme to document if/when differences. --- README.md | 17 +++++++++++++---- lib/lexer.js | 2 +- lib/parser.js | 9 ++++++++- src/lexer.peg | 2 +- src/parser.coffee | 9 ++++++++- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b547c72..810205c 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,13 @@ a string or a number. ### If An "if:" section can contain other objects, but the entirety of -the section is only rendered if the current context scope contains the -current name, and the value associated with that name is "true" in a -boolean context. You might use to show someone's name, if the name -field is populated, and show nothing if it isn't. +the section is only rendered if the current context scope, and *only* +the current context scope, contains the current name, and the value +associated with that name is "true" in a boolean context. You might +use to show someone's name, if the name field is populated, and show +nothing if it isn't. This is useful for detecting if the current +context has a field, but you don't want previous contexts' synonyms +showing up. If your datasource returns: @@ -49,6 +52,12 @@ Then your template would use: {if:name}Hello {name}!{/if:name} +### When + +A "when:" section is the same as the "if", but it will render if +the current context scope, and any previous context scope on the +stack, contains the current name. + ### Block A "block:" section can contain other objects, but the entirety diff --git a/lib/lexer.js b/lib/lexer.js index e76a47c..9cc1dff 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -816,7 +816,7 @@ module.exports = (function() { var _VBT_LENGTH = _VALID_BLOCK_TYPES.length; function is_valid_block_type(b) { - for(i = 0; i < _VBT_LENGTH; i++) { + for(var i = 0; i < _VBT_LENGTH; i++) { if (_VALID_BLOCK_TYPES[i] == b) { return true; } diff --git a/lib/parser.js b/lib/parser.js index 4501832..457bf25 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -110,8 +110,15 @@ }; Contexter.prototype.render = function(name) { + var ret; if ((this.templates[name] != null) && _.isFunction(this.templates[name])) { - return this.templates[name](this); + this.depth++; + if (this.depth > 10) { + throw new Error('recursion-error'); + } + ret = this.templates[name](this); + this.depth--; + return ret; } else { return ""; } diff --git a/src/lexer.peg b/src/lexer.peg index 3e6b2be..54f5a5e 100644 --- a/src/lexer.peg +++ b/src/lexer.peg @@ -4,7 +4,7 @@ var _VBT_LENGTH = _VALID_BLOCK_TYPES.length; function is_valid_block_type(b) { - for(i = 0; i < _VBT_LENGTH; i++) { + for(var i = 0; i < _VBT_LENGTH; i++) { if (_VALID_BLOCK_TYPES[i] == b) { return true; } diff --git a/src/parser.coffee b/src/parser.coffee index 7197883..eb5050d 100644 --- a/src/parser.coffee +++ b/src/parser.coffee @@ -71,7 +71,14 @@ class Contexter return "" render: (name) -> - if @templates[name]? and _.isFunction(@templates[name]) then @templates[name](@) else "" + if @templates[name]? and _.isFunction(@templates[name]) + @depth++ + throw new Error('recursion-error') if @depth > 10 + ret = @templates[name](@) + @depth-- + ret + else + "" module.exports = (ast, data) -> From ab9d81a323b4865e9e75992a1a921debcd777f41 Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Fri, 29 Nov 2013 17:42:51 -0800 Subject: [PATCH 12/12] Updating the package.json file. --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 8989c02..751c33a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tumble", - "description": "Trivial reimplementation of Tumbler template parser", + "description": "Trivial reimplementation of Tumbler template parser/renderer", "version": "0.1.2", "author": { "name": "Kenneth \"Elf\" M. Sternberg", @@ -9,7 +9,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/elfsternberg/tumble.git", + "url": "https://github.com/elfsternberg/tumble.git" }, "licenses": [ { @@ -33,5 +33,5 @@ "mocha": "1.8.x", "chai": "1.5.x" }, - "keywords": [] + "keywords": ["template", "tumblr"] }