Updated with some modernity.
This commit is contained in:
		
							parent
							
								
									fac48b727a
								
							
						
					
					
						commit
						1c3103815f
					
				|  | @ -4,5 +4,6 @@ | |||
| *.orig | ||||
| npm-debug.log | ||||
| node_modules/* | ||||
| lib/ | ||||
| tmp/ | ||||
| src/*.js | ||||
| lib/*.js | ||||
|  |  | |||
							
								
								
									
										36
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										36
									
								
								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 $< | ||||
| 	$(foreach source, $(cof_sources), $(COFFEE) -o $(@D) -c $(source); ) | ||||
| 
 | ||||
| $(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 $< | ||||
|  |  | |||
							
								
								
									
										17
									
								
								README.md
								
								
								
								
							
							
						
						
									
										17
									
								
								README.md
								
								
								
								
							|  | @ -36,10 +36,13 @@ a string or a number. | |||
| ### If | ||||
| 
 | ||||
| An "if:<name>" 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:<name>" 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:<name>" section can contain other objects, but the entirety | ||||
|  |  | |||
|  | @ -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 | ||||
|  | @ -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") | ||||
							
								
								
									
										14
									
								
								package.json
								
								
								
								
							
							
						
						
									
										14
									
								
								package.json
								
								
								
								
							|  | @ -1,7 +1,7 @@ | |||
| { | ||||
|     "name": "Tumble", | ||||
|     "description": "Trivial reimplementation of Tumbler template parser", | ||||
|     "version": "0.0.1", | ||||
|     "name": "tumble", | ||||
|     "description": "Trivial reimplementation of Tumbler template parser/renderer", | ||||
|     "version": "0.1.2", | ||||
|     "author": { | ||||
|         "name": "Kenneth \"Elf\" M. Sternberg", | ||||
|         "email": "elf.sternberg@gmail.com", | ||||
|  | @ -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,9 +28,10 @@ | |||
|         "underscore": "1.4.x" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "coffeescript": "1.6.x", | ||||
|         "pegjs": "0.7.x", | ||||
|         "mocha": "1.8.x", | ||||
|         "chai": "1.5.x" | ||||
|     }, | ||||
|     "keywords": [] | ||||
|     "keywords": ["template", "tumblr"] | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,20 @@ | |||
| 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) -> | ||||
|         if callback | ||||
|             return callback(err) if err | ||||
|             return callback(null, render(str, options, callback)) | ||||
|         throw err if err | ||||
| 
 | ||||
| fromFile.render = render | ||||
| 
 | ||||
| module.exports = fromFile | ||||
|  | @ -1,4 +1,18 @@ | |||
| // -*- mode: javascript -*- | ||||
| { | ||||
|     var _VALID_BLOCK_TYPES = ['if', 'when', 'template', 'many', 'block']; | ||||
|     var _VBT_LENGTH = _VALID_BLOCK_TYPES.length; | ||||
| 
 | ||||
|     function is_valid_block_type(b) { | ||||
|         for(var i = 0; i < _VBT_LENGTH; i++) { | ||||
|             if (_VALID_BLOCK_TYPES[i] == b) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| document | ||||
|     = ps:part*  | ||||
|  | @ -9,8 +23,14 @@ part | |||
| 
 | ||||
| tag_start "tag_start" | ||||
|     =  ld b:tagname ":" n:tagname rd | ||||
|     &{ return is_valid_block_type(b); } | ||||
|     { return {type: b, name: n }; } | ||||
| 
 | ||||
| block | ||||
|     = t:tag_start ps:part* n:tag_end | ||||
|       &{ return (t.type == n.type) && (t.name == n.name) }  | ||||
|       { return {unit: 'block', type:t.type, name:t.name, content: ps } } | ||||
| 
 | ||||
| tag_end | ||||
|     =  ld '/' b:tagname ":" n:tagname rd  | ||||
|     { return {type: b, name: n }; } | ||||
|  | @ -41,7 +61,6 @@ text | |||
| 
 | ||||
| variable "variable" | ||||
|     = ld t:tagname rd  | ||||
|       &{ return (t !== "render") } | ||||
|       { return { unit: 'variable', name: t }; } | ||||
| 
 | ||||
| block | ||||
|  | @ -1,5 +1,4 @@ | |||
| _ = require 'underscore' | ||||
| util = require 'util' | ||||
| 
 | ||||
| class Contexter | ||||
| 
 | ||||
|  | @ -9,45 +8,76 @@ 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(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 | ||||
|         r = cb this | ||||
|         @depth++ | ||||
|         throw new Error('recursion-error') if @depth > 10 | ||||
|         r = cb @ | ||||
|         @stack.shift() | ||||
|         @depth-- | ||||
|         r | ||||
| 
 | ||||
|     if: (name, cb) -> | ||||
|     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(this) else '' | ||||
|         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. | ||||
|         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 "" | ||||
|         return "" unless (ps and _.isArray(ps)) | ||||
|         (_.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 "" | ||||
|         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) -> | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  | @ -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')) | ||||
|  | @ -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." | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue