Passes the first tests.
This commit is contained in:
		
							parent
							
								
									6e00e56e84
								
							
						
					
					
						commit
						a28a1f5d36
					
				|  | @ -0,0 +1,19 @@ | ||||||
|  | Copyright (c) 2012 Elf M. Sternberg | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
							
								
								
									
										13
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										13
									
								
								Makefile
								
								
								
								
							|  | @ -5,16 +5,19 @@ lib_objects:= $(subst src/, lib/, $(lib_sources:%.coffee=%.js)) | ||||||
| 
 | 
 | ||||||
| default: build | default: build | ||||||
| 
 | 
 | ||||||
| build: $(lib_objects) lib/parser.js | build: $(lib_objects) lib/tokenizer.js | ||||||
| 
 | 
 | ||||||
| lib/parser.js: src/parser.peg | lib: | ||||||
| 	./node_modules/.bin/pegjs src/parser.peg lib/parser.js | 	mkdir -p lib | ||||||
| 
 | 
 | ||||||
| $(lib_objects): lib/%.js: src/%.coffee | lib/tumble.js: lib src/tumble.peg | ||||||
|  | 	./node_modules/.bin/pegjs src/tumble.peg lib/tumble.js | ||||||
|  | 
 | ||||||
|  | $(lib_objects): lib lib/%.js: src/%.coffee | ||||||
| 	@mkdir -p $(@D) | 	@mkdir -p $(@D) | ||||||
| 	coffee -o $(@D) -c $< | 	coffee -o $(@D) -c $< | ||||||
| 
 | 
 | ||||||
| test: test/[0-9]*_mocha.coffee lib/tumble.js lib/parser.js | test: test/[0-9]*_mocha.coffee lib/tumble.js | ||||||
| 	./node_modules/.bin/mocha -R tap -C --compilers coffee:coffee-script -u tdd $< | 	./node_modules/.bin/mocha -R tap -C --compilers coffee:coffee-script -u tdd $< | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										241
									
								
								README.md
								
								
								
								
							
							
						
						
									
										241
									
								
								README.md
								
								
								
								
							|  | @ -1,120 +1,173 @@ | ||||||
|  | # Tumble - Generalized, hyper-simple templating engine | ||||||
|  | 
 | ||||||
|  | ## Purpose | ||||||
|  | 
 | ||||||
|  | I like Tumblr's templating system, with its simplicity and brevity. | ||||||
|  | The idea that, for small projects, one should embed everything in a | ||||||
|  | single page and switch sections on and off as needed appeals to me, so | ||||||
|  | I decided to write my own implementation of the Tumblr parser. | ||||||
|  | 
 | ||||||
| Tumble is an implementation of the Tumblr parser/compiler/renderer, | Tumble is an implementation of the Tumblr parser/compiler/renderer, | ||||||
| with keyword substitutions suitable to my needs on my story website. | with keyword substitutions suitable to my needs on my story website. | ||||||
| The idea is that the database side will produce an object consisting | The idea is that the database side will produce an object consisting | ||||||
| of the title of a series | of variable, object, and array substitutions, and that the | ||||||
|  | corresponding template will describe how to unpack and illustrate that | ||||||
|  | object. | ||||||
|  | 
 | ||||||
|  | ## Usage | ||||||
|  | 
 | ||||||
|  | There are five kinds of objects in my templating language: plain text, | ||||||
|  | variables, "if", "block", and "many" sections. | ||||||
|  | 
 | ||||||
|  | ### Plain Text | ||||||
|  | 
 | ||||||
|  | Plain text is just that, the text of your template without any | ||||||
|  | substitutions. | ||||||
|  | 
 | ||||||
|  | ### Variable | ||||||
|  | 
 | ||||||
|  | Tumble tags are contained in { } entries.  Sadly, there's no current | ||||||
|  | way to escape this. | ||||||
|  | 
 | ||||||
|  | A variable is a Tumble tag without a colon in it. It refers to a valid | ||||||
|  | name in the current context scope (more on that below) that is either | ||||||
|  | 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. | ||||||
|  | 
 | ||||||
|  | If your datasource returns: | ||||||
|  | 
 | ||||||
|  | obj = { "name": "Mr. Smith"} | ||||||
|  | 
 | ||||||
|  | Then your template would use: | ||||||
|  | 
 | ||||||
|  | {if:name}Hello {name}!{/if:name} | ||||||
|  | 
 | ||||||
|  | ### Block | ||||||
|  | 
 | ||||||
|  | A "block:<name>" section can contain other objects, but the entirety | ||||||
|  | of the section is only rendered if the current context scope contains | ||||||
|  | the current name, a value exists for that name, and the value is an | ||||||
|  | object itself.  When the block is entered, the object referred to | ||||||
|  | becomes the top of the current context scope.  You might use it to | ||||||
|  | render "next/prev" blocks in a webcomic. | ||||||
|  | 
 | ||||||
|  | If your datasource returns: | ||||||
|  | 
 | ||||||
|  | obj = { "next": {"title": "The Next Story"},  | ||||||
|  |         "prev": {"title": "The Previous Story"}} | ||||||
|  | 
 | ||||||
|  | Then your template would use: | ||||||
|  | 
 | ||||||
|  | {block:next}The next story is entitled {title}{/block:next} | ||||||
|  | {block:prev}The next story is entitled {title}{/block:prev} | ||||||
|  | 
 | ||||||
|  | ### Many | ||||||
|  | 
 | ||||||
|  | A "many:<name>" section can contain other objects, but the entirety of | ||||||
|  | the section is only rendered if the current context scope contains the | ||||||
|  | current name, a value exists for that name, and the value is an array | ||||||
|  | that itself contains objects.  When the block is entered, each object | ||||||
|  | in the named array is serially made the top object of the current | ||||||
|  | context scope, the section is rendered, and the object is popped off | ||||||
|  | the context scope stack once more.  You might use it to render a | ||||||
|  | series of titles in a series: | ||||||
|  | 
 | ||||||
|  | If your datasource returns: | ||||||
|  | 
 | ||||||
|  | obj = {  | ||||||
|  |     "series":  | ||||||
|  |         [ {"title": "A Story"},  | ||||||
|  |           {"title": "A Second Story"},  | ||||||
|  |           {"title": "A Third Story"} | ||||||
|  |         ] | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | Then you could render a list of your stories this way: | ||||||
|  | 
 | ||||||
|  |     {if:series}     | ||||||
|  |     <h1>Table of Contents:</h1> | ||||||
|  |     <ul> | ||||||
|  |         {many:series}<li>{title}</li>{/many:series} | ||||||
|  |     </ul> | ||||||
|  |     {/if:series} | ||||||
|          |          | ||||||
| 
 | 
 | ||||||
| block:Series | ### The current context scope | ||||||
|     Must be found within a block:toc |  | ||||||
|     It must contain the special tag {titles} as a child tag. |  | ||||||
| 
 | 
 | ||||||
|     This will be rendered if this is a series page.  | The Tumble parser is intended to render a website for series and | ||||||
|  | stories.  Both of which have titles, so you might have an object that | ||||||
|  | says: | ||||||
| 
 | 
 | ||||||
| block:Subseries | obj = { | ||||||
|  |     title: "An awesome series", | ||||||
|  |     author: "Twilight Sparkle", | ||||||
|  |     stories: [{title: "The first awesome story"}, | ||||||
|  |               {title: "The second awesome story"}] | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     Must be found within a block:toc | In both "block" and "many", the current context descends into these | ||||||
|     It must contain the special tag {titles} as a child tag. | objects in a deterministic way.  While inside a block or many, when | ||||||
|  | searching for a variable substitution (and *only* variable | ||||||
|  | substitutions), the context handler will scan upwards from the current | ||||||
|  | context scope to the root to find a possible substitution. | ||||||
| 
 | 
 | ||||||
|     This will be rendered for any subseries of the main series.  This | For example | ||||||
|     will recurse to a maximum depth of four.  Users can use clever CSS |  | ||||||
|     to make this look awesome.  There may be settings in the database |  | ||||||
|     that prevent subseries recursion deliberately.   |  | ||||||
| 
 | 
 | ||||||
| block:Story |     <h1>{title}</h1> | ||||||
|     Has two meanings, based on context.   |     {if:stories}     | ||||||
|  |     <p>Table of Contents:</p> | ||||||
|  |     <ul> | ||||||
|  |         {many:stories}<li>{title} by {author}</li>{/many:stories} | ||||||
|  |     </ul> | ||||||
|  |     {/if:stories} | ||||||
| 
 | 
 | ||||||
|     If it is found in the *document* (i.e. *not* within a , it is treated at a story block, | The first "title" will be the series title, but the titles in the | ||||||
|     and will only be rendered if this is a story page. | "many" block will be story titles, in order.  Because each story does | ||||||
|  | not have an "author" block, the context will scan up to the parent | ||||||
|  | scope and find the author's name. | ||||||
| 
 | 
 | ||||||
|     If it is found in a *series* or *subseries* block, it's contents | ## Requirements | ||||||
|     are rendered during the rendering of a series or subseries for |  | ||||||
|     each story found. |  | ||||||
| 
 | 
 | ||||||
| block:Next | nodejs & npm.   | ||||||
|     Valid only in a block:story |  | ||||||
|     Will render if there is a "next" story in the parent series. |  | ||||||
| 
 | 
 | ||||||
| block:Prev | Underscore is a dependency. | ||||||
|     Valid only in a block:story |  | ||||||
|     Will render if there is a "previous" story in the parent series. |  | ||||||
| 
 | 
 | ||||||
| block:Title | PegJS is a required build tool.  Mocha & Chai are used for testing. | ||||||
|     Will render if there is a title in the current context |  | ||||||
| 
 | 
 | ||||||
| block:Blurb | All of these are specified in the package.json file. | ||||||
|     Will render if there is a blurb. |  | ||||||
| 
 | 
 | ||||||
| block:Excerpt | ## LICENSE AND COPYRIGHT NOTICE: NO WARRANTY GRANTED OR IMPLIED | ||||||
|     Will render if there is an excerpt |  | ||||||
| 
 | 
 | ||||||
| Variables: | Copyright (c) 2012 Elf M. Sternberg | ||||||
| 
 | 
 | ||||||
| Title | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|     The title in the current context.   | 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: | ||||||
| 
 | 
 | ||||||
| Body | The above copyright notice and this permission notice shall be included in | ||||||
|     The body of the story.  Only available in top-level block:story. | all copies or substantial portions of the Software. | ||||||
| 
 | 
 | ||||||
| SeriesTitle | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|     The title of the series in the current context. | 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. | ||||||
|  | 
 | ||||||
|  | 	- Elf M. Sternberg <elf@pendorwright.com> | ||||||
| 
 | 
 | ||||||
| MainSeriesTitle |  | ||||||
|     The title of the top-level series for the current page. |  | ||||||
| 
 |  | ||||||
| AuthorsName |  | ||||||
| 
 |  | ||||||
| Blurb |  | ||||||
|     Valid in a series or series/story |  | ||||||
| 
 |  | ||||||
| Excerpt |  | ||||||
|     Valid in a series or series/story |  | ||||||
| 
 |  | ||||||
| URL |  | ||||||
|     Context-sensitive.  In a Story, refers to the URL used to access |  | ||||||
|     that story.  In a Next or Prev, refers to the URL of the next or |  | ||||||
|     previous story, respectively.   |  | ||||||
| 
 |  | ||||||
| SeriesURL |  | ||||||
| 
 |  | ||||||
| MainSeriesURL |  | ||||||
| 
 |  | ||||||
| AuthorsURL |  | ||||||
| 
 |  | ||||||
| # {block:Series}{URL}{Title}{Contents}{/block:Series} |  | ||||||
| 
 |  | ||||||
| # > Handle these first |  | ||||||
| 
 |  | ||||||
| Minus the actual content of a template, the HTML that we use to build |  | ||||||
| every page, a document tends to look like this: |  | ||||||
| 
 |  | ||||||
| {URL} |  | ||||||
| {SeriesTitle} |  | ||||||
| {AuthorName} |  | ||||||
| {AuthorDescription} |  | ||||||
| 
 |  | ||||||
| {block:IfSeries}  |  | ||||||
|     {Title} |  | ||||||
|     {Description} |  | ||||||
|     {block:TableOfContents} |  | ||||||
|         {block:Story}{URL}{Title}{/block:Story} |  | ||||||
|     {/block:TableOfContents} |  | ||||||
| {/block:IfSeries} |  | ||||||
| 
 |  | ||||||
| The important trick here is that the TableOfContents will be recursed |  | ||||||
| wherever the {Contents} block is seen, up to a maximum depth of four. |  | ||||||
| IfContents will be true if this is a subseries and there is content of |  | ||||||
| a subseries; the reason for this is to prevent the rendering of an |  | ||||||
| empty subseries. |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| {block:IfStory} |  | ||||||
|     {Title} |  | ||||||
|     {Body} |  | ||||||
|     {block:Next}{URL}{Title}{/block:Next} |  | ||||||
|     {block:Prev}{URL}{Title}{/block:Prev} |  | ||||||
|     {Pubdate} |  | ||||||
|     {block:IfLicense}{License}{/block:IfLicense} |  | ||||||
|     {Copyright} |  | ||||||
| {block:/IfStory} |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -29,9 +29,7 @@ | ||||||
|     "devDependencies": { |     "devDependencies": { | ||||||
|         "pegjs": "0.7.x", |         "pegjs": "0.7.x", | ||||||
|         "mocha": "1.8.x", |         "mocha": "1.8.x", | ||||||
|         "chai": "1.5.x", |         "chai": "1.5.x" | ||||||
|         "coffee-script": "1.6.x", |  | ||||||
|         "docco": "0.3.x" |  | ||||||
|     }, |     }, | ||||||
|     "keywords": [] |     "keywords": [] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,51 +0,0 @@ | ||||||
| document |  | ||||||
|     = p:part* { return p } |  | ||||||
| 
 |  | ||||||
| part |  | ||||||
|     = text / variable / block |  | ||||||
| 
 |  | ||||||
| text |  | ||||||
|     = b:(!tag c:. {return c})+ |  | ||||||
|     { return { type: "text", text: b.join('') }; } |  | ||||||
| 
 |  | ||||||
| variable "variable" |  | ||||||
|     = t:tag_start rd          |  | ||||||
|     { return { type: "variable", name: t }; } |  | ||||||
| 
 |  | ||||||
| tag_start "tag_start" |  | ||||||
|     =   ld n:tagname |  | ||||||
|     { return n; } |  | ||||||
| 
 |  | ||||||
| tagname "tagname" |  | ||||||
|     = t:[a-zA-Z]+ |  | ||||||
|     { return t.join(''); } |  | ||||||
| 
 |  | ||||||
| block "block" |  | ||||||
|     = t:block_tag_start p:part* n:block_end_tag |  | ||||||
|       &{ return t == n } |  | ||||||
|       { return { type: "block", name: n, data: p }; } |  | ||||||
| 
 |  | ||||||
| block_tag_start "tag_start" |  | ||||||
|     =  ld "block:" n:tagname rd |  | ||||||
|     { return n; } |  | ||||||
| 
 |  | ||||||
| block_end_tag |  | ||||||
|     =  ld "/block:" n:tagname rd |  | ||||||
|     { return n; } |  | ||||||
| 
 |  | ||||||
| tag |  | ||||||
|     = ld (!rd !eol [a-zA-Z\:\/])+ rd |  | ||||||
| 
 |  | ||||||
| ld |  | ||||||
|     = "{" |  | ||||||
| 
 |  | ||||||
| rd |  | ||||||
|     = "}" |  | ||||||
| 
 |  | ||||||
| eol |  | ||||||
|     = "\n" |  | ||||||
|     / "\r\n" |  | ||||||
|     / "\r" |  | ||||||
|     / "\u2028" |  | ||||||
|     / "\u2029" |  | ||||||
|    |  | ||||||
|  | @ -1,39 +0,0 @@ | ||||||
| tumble = require('./parser') |  | ||||||
| util = require('util') |  | ||||||
| 
 |  | ||||||
| module.exports = (template) -> |  | ||||||
| 
 |  | ||||||
|     ast = tumble.parse(template) |  | ||||||
| 
 |  | ||||||
|     # Using the AST, return a function that will render each component |  | ||||||
|     # of the AST out, as long as the data provided to the AST makes |  | ||||||
|     # sens. |  | ||||||
|     # |  | ||||||
| 
 |  | ||||||
|     (content) -> |  | ||||||
|         subtypes = (name) -> |  | ||||||
|             return 'cond' |  | ||||||
| 
 |  | ||||||
|         handler = (content, ast) -> |  | ||||||
| 
 |  | ||||||
|             isLegal = (name) -> true |  | ||||||
| 
 |  | ||||||
|             { |  | ||||||
|                 'text': () -> |  | ||||||
|                     ast.text |  | ||||||
| 
 |  | ||||||
|                 'variable': () -> |  | ||||||
|                     return '' if not (isLegal(ast.name) and content.hasOwnProperty(ast.name)) |  | ||||||
|                     content[ast.name] |  | ||||||
| 
 |  | ||||||
|                 'block': () -> |  | ||||||
|                     return '' if not (isLegal(ast.name) and content.hasOwnProperty(ast.name)) |  | ||||||
|                     f = { |  | ||||||
|                         'cond': () -> handler(content[ast.name], ast.data) |  | ||||||
|                         'loop': () -> (handler(c, ast.data) for c in content[ast.name]) |  | ||||||
|                     }[subtypes(ast.name)] |  | ||||||
|                     console.log("F:", f) |  | ||||||
|                     f() |  | ||||||
|             }[obj.type]() |  | ||||||
| 
 |  | ||||||
|         (handler(i) for i in ast).join("") |  | ||||||
|  | @ -0,0 +1,220 @@ | ||||||
|  | { | ||||||
|  |     var _ = require('underscore'); | ||||||
|  |     var depth = 0; | ||||||
|  | 
 | ||||||
|  |     var Contexter = function(c) { | ||||||
|  |         this.content = c | ||||||
|  |         this.stack = [c]; | ||||||
|  |         this.templates = {}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     _.extend(Contexter.prototype, { | ||||||
|  |         has_any: function(name) { | ||||||
|  |             return _.find(this.stack, function(o) { return _.has(o, name); }); | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         has: function(name) { | ||||||
|  |             if (typeof this.stack[0][name] != 'undefined') { | ||||||
|  |                 return this.stack[0][name]; | ||||||
|  |             } | ||||||
|  |             return null; | ||||||
|  |         }, | ||||||
|  |         | ||||||
|  |         get: function(name) { | ||||||
|  |             var p = this.has_any(name); | ||||||
|  |             if (p && (_.isString(p[name]) || _.isNumber(p[name]))) { | ||||||
|  |                 return p[name]; | ||||||
|  |             } | ||||||
|  |             if (arguments.length > 1) { | ||||||
|  |                 return arguments[1]; | ||||||
|  |             } | ||||||
|  |             return ''; | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         once: function(obj, cb) { | ||||||
|  |             this.stack.unshift(obj); | ||||||
|  |             var r = cb(this); | ||||||
|  |             this.stack.shift(); | ||||||
|  |             return r; | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         if: function(name, cb) { | ||||||
|  |             var p = this.has_any(name); | ||||||
|  |             if (p && p[name]) { | ||||||
|  |                 return cb(this); | ||||||
|  |             } | ||||||
|  |             return ""; | ||||||
|  |         }, | ||||||
|  |          | ||||||
|  |         descend: function(name, cb) { | ||||||
|  |             var p = this.has(name); | ||||||
|  |             if (p && _.isObject(p)) { | ||||||
|  |                 return this.once(p, cb); | ||||||
|  |             } | ||||||
|  |             return ""; | ||||||
|  |         }, | ||||||
|  |          | ||||||
|  |         many: function(name, cb) { | ||||||
|  |             var ps = this.has(name), | ||||||
|  |             _this = this; | ||||||
|  |             if (ps && _.isArray(ps)) { | ||||||
|  |                 return _.map(ps, function(p) { | ||||||
|  |                     return _this.once(p, cb); | ||||||
|  |                 }).join(""); | ||||||
|  |             } | ||||||
|  |             return ""; | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         templatize: function(name, cb) { | ||||||
|  |             this.templates[name] = cb; | ||||||
|  |             return ""; | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         template_render: function(name) { | ||||||
|  |             if (this.templates[name] && _.isFunction(this.templates[name])) { | ||||||
|  |                 return this.templates[name](this); | ||||||
|  |             } | ||||||
|  |             return ""; | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | document | ||||||
|  |     = ps:document_part* {  | ||||||
|  |         return function(content) { | ||||||
|  |             var context = new Contexter(content); | ||||||
|  |             return _.map(ps, function(p) { | ||||||
|  |                 return p(context); | ||||||
|  |             }).join(""); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | document_part | ||||||
|  |     = iterative / descendant / conditional / variable / text | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | text | ||||||
|  |     = b:(!tag c:. {return c})+ {  | ||||||
|  |         return (function() { | ||||||
|  |                 var t = b.join(""); | ||||||
|  |                 return function(content) {  | ||||||
|  |                     return t; | ||||||
|  |                 } | ||||||
|  |             }()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | variable "variable" | ||||||
|  |     = t:tag_start rd {  | ||||||
|  |         return function(content) { return content.get(t, ""); }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | simple_part | ||||||
|  |     = descendant / variable / conditional / text | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | conditional | ||||||
|  |     = t:ifblock_tag_start ps:simple_part* n:ifblock_tag_end | ||||||
|  |       &{ return (t == n) } | ||||||
|  |       {  | ||||||
|  |           return function(content) { | ||||||
|  |               return content.if(t, function(c) { | ||||||
|  |                   return _.map(ps, function(p) { | ||||||
|  |                       return p(c); | ||||||
|  |                   }).join(''); | ||||||
|  |               }); | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ifblock_tag_start "tag_start" | ||||||
|  |     =  ld "if:" n:tagname rd | ||||||
|  |     { return n; } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ifblock_tag_end | ||||||
|  |     =  ld "/if:" n:tagname rd | ||||||
|  |     { return n; } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | descendant | ||||||
|  |     = n:blockblock_tag_start ps:simple_part* t:blockblock_tag_end | ||||||
|  |       &{ return (t == n) } | ||||||
|  |       {  | ||||||
|  |           return function(content) { | ||||||
|  |               return content.descend(t, function(c) { | ||||||
|  |                   return _.map(ps, function(p) { | ||||||
|  |                       return p(c); | ||||||
|  |                   }).join(''); | ||||||
|  |               }); | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | blockblock_tag_start | ||||||
|  |     =  ld "block:" n:tagname rd | ||||||
|  |     { return n; } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | blockblock_tag_end | ||||||
|  |     =  ld "/block:" n:tagname rd | ||||||
|  |     { return n; } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | iterative | ||||||
|  |     = t:loopblock_tag_start ps:simple_part* n:loopblock_tag_end | ||||||
|  |       &{ return t == n } | ||||||
|  |       {  | ||||||
|  |           return function(content) { | ||||||
|  |               return content.many(t, function(c) { | ||||||
|  |                   return _.map(ps, function(p) { | ||||||
|  |                       return p(c); | ||||||
|  |                   }).join('');  | ||||||
|  |               }); | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  | loopblock_tag_start "tag_start" | ||||||
|  |     =  ld "many:" n:tagname rd | ||||||
|  |     { return n; } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | loopblock_tag_end | ||||||
|  |     =  ld "/many:" n:tagname rd | ||||||
|  |     { return n; } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | tag_start "tag_start" | ||||||
|  |     =   ld n:tagname | ||||||
|  |     { return n; } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | tagname "tagname" | ||||||
|  |     = t:[a-zA-Z]+ | ||||||
|  |     { return t.join(''); } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | tag | ||||||
|  |     = ld (!rd !eol [a-zA-Z\:\/])+ rd | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ld | ||||||
|  |     = "{" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | rd | ||||||
|  |     = "}" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | eol | ||||||
|  |     = "\n" | ||||||
|  |     / "\r\n" | ||||||
|  |     / "\r" | ||||||
|  |     / "\u2028" | ||||||
|  |     / "\u2029" | ||||||
|  |    | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @ -3,7 +3,7 @@ assert = chai.assert | ||||||
| expect = chai.expect | expect = chai.expect | ||||||
| should = chai.should() | should = chai.should() | ||||||
| 
 | 
 | ||||||
| tumble = require('../lib/tumble') | tumble = require('../lib/tumble').parse; | ||||||
| 
 | 
 | ||||||
| test_data = [ | test_data = [ | ||||||
|     { |     { | ||||||
|  | @ -30,12 +30,60 @@ test_data = [ | ||||||
|         'description': "two simple substitutions" |         'description': "two simple substitutions" | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|     { |     { | ||||||
|         'input': '<ul>{block:Stories}{Title}{/block:Stories}</ul>' |         'input': '<ul>{if:title}{title}BBB{/if:title}</ul>' | ||||||
|         'output': '<ul>AAABBB</ul>' |         'output': '<ul>AAABBB</ul>' | ||||||
|         'data': {'stories': {'title': 'AAA'}}, |         'data': {'title': 'AAA'} | ||||||
|         'description': "a conditional block" |         'description': "a conditional block" | ||||||
|     }] |     } | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |         'input': '<ul>{if:title}{title}BBB{/if:title}</ul>' | ||||||
|  |         'output': '<ul></ul>' | ||||||
|  |         'data': {'title': ''} | ||||||
|  |         'description': "a conditional block with no input" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |         'input': '<ul>{block:stories}{title}{/block:stories}</ul>' | ||||||
|  |         'output': '<ul></ul>' | ||||||
|  |         'data': {'stories': {'title': ''}} | ||||||
|  |         'description': "a descendent block" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |         'input': '<ul>{block:stories}{title}BBB{/block:stories}</ul>' | ||||||
|  |         'output': '<ul>AAABBB</ul>' | ||||||
|  |         'data': {'stories': {'title': 'AAA'}} | ||||||
|  |         'description': "a descendent block 2" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |         'input': '<ul>{many:stories}{title}{/many:stories}</ul>' | ||||||
|  |         'output': '<ul></ul>' | ||||||
|  |         'data': {'stories': [{'title': ''}]} | ||||||
|  |         'description': "an iterative block" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |         'input': '<ul>{many:stories}{title}BBB{/many:stories}</ul>' | ||||||
|  |         'output': '<ul>AAABBBCCCBBB</ul>' | ||||||
|  |         'data': {'stories': [{'title': 'AAA'}, {'title': 'CCC'}]}, | ||||||
|  |         'description': "an iterative block 2" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |         'input': '<ul>{author}{many:stories}{title}BBB{author}{/many:stories}</ul>' | ||||||
|  |         'output': '<ul>DDDAAABBBDDDCCCBBBDDD</ul>' | ||||||
|  |         'data': {'author': 'DDD', 'stories': [{'title': 'AAA'}, {'title': 'CCC'}]}, | ||||||
|  |         'description': "an iterative block with ascent" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | ] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| describe "Basic Functionality", -> | describe "Basic Functionality", -> | ||||||
|  |  | ||||||
|  | @ -1,94 +0,0 @@ | ||||||
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> |  | ||||||
| <html> |  | ||||||
| 
 |  | ||||||
| <head> |  | ||||||
|   <link rel="stylesheet" type="text/css" href="css/aimee.css" title="Style"></link> |  | ||||||
|   <title>Elf Sternberg | Aimee' | {block:title}{title}{/block:title}</title> |  | ||||||
| </head> |  | ||||||
| 
 |  | ||||||
| <body> |  | ||||||
|   <div id="container"> |  | ||||||
|     <div id="header"> |  | ||||||
|       <h1>Aimee</h1> |  | ||||||
|     </div> |  | ||||||
| 
 |  | ||||||
|     {block:index} |  | ||||||
|     <div id="content"> |  | ||||||
| 
 |  | ||||||
|     <p>The Aimee' series grew out of a longstanding desire on my part to |  | ||||||
|       write stories outside of the comforting future depicted in the |  | ||||||
|       Journal Entries.  The fantasy setting allowed me to get away with |  | ||||||
|       a number of tropes and scenes that I would otherwise never inflict |  | ||||||
|       on my favorite characters.  It has, like the Journal Entries, |  | ||||||
|       grown from one kind of fantasy into another, one where meaning |  | ||||||
|       matters.</p> |  | ||||||
| 
 |  | ||||||
|       <div id="toc"> |  | ||||||
|       {block:contents} |  | ||||||
|         {block:series} |  | ||||||
|           <h2>{title}</h2> |  | ||||||
|         {/block:series} |  | ||||||
|         {block:story} |  | ||||||
|           <div class="title"><a href="{url}" class="tanch"> </a></div> |  | ||||||
|         {/block:story} |  | ||||||
|       {/block:contents} |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|     {/block:index} |  | ||||||
| 
 |  | ||||||
|     {block:story} |  | ||||||
|     <h1>{title}</h1> |  | ||||||
|     {body} |  | ||||||
| 
 |  | ||||||
|     <div id="nextprevblock"> |  | ||||||
|     {block:next}<h4>Next: <a href="{url}">{title}</a></h4>{/block:next} |  | ||||||
|     {block:prev}<h4>Previous: <a href="{url}">{title}</a></h4>{/block:prev} |  | ||||||
|     </div> |  | ||||||
| 
 |  | ||||||
|     <div id="copytitle"> |  | ||||||
|       <h5>{title}</h5> |  | ||||||
| 
 |  | ||||||
|       <div id="canchor"> |  | ||||||
|         <a rel="license" href="http://creativecommons.org/licenses/by-nd-nc/1.0/"><img alt= |  | ||||||
|         "Creative Commons License" src="images/somerights20.png"></a> |  | ||||||
|       </div>is copyright © {pubdate|"F j, Y"} Elf Mathieu Sternberg and is available |  | ||||||
|             under a <a rel="license" href= |  | ||||||
|                        "http://creativecommons.org/licenses/by-nd-nc/1.0/">Creative Commons License</a>. |  | ||||||
|       </div> |  | ||||||
| 
 |  | ||||||
| <!-- /Creative Commons License --><!-- |  | ||||||
| <rdf:RDF xmlns="http://web.resource.org/cc/" |  | ||||||
|     xmlns:dc="http://purl.org/dc/elements/1.1/" |  | ||||||
|     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">  |  | ||||||
|     <Work rdf:about="">  |  | ||||||
|       <dc:type rdf:resource="http://purl.org/dc/dcmitype/Text" />  |  | ||||||
|       <license rdf:resource="http://creativecommons.org/licenses/by-nd-nc/1.0/" /> |  | ||||||
|     </Work> |  | ||||||
| 
 |  | ||||||
|     <License rdf:about="http://creativecommons.org/licenses/by-nd-nc/1.0/"> |  | ||||||
|       <permits rdf:resource="http://web.resource.org/cc/Reproduction" /> |  | ||||||
|       <permits rdf:resource="http://web.resource.org/cc/Distribution" /> |  | ||||||
|       <requires rdf:resource="http://web.resource.org/cc/Notice" /> |  | ||||||
|       <requires rdf:resource="http://web.resource.org/cc/Attribution" /> |  | ||||||
|       <prohibits rdf:resource="http://web.resource.org/cc/CommercialUse" /> |  | ||||||
|    </License> |  | ||||||
| </rdf:RDF> |  | ||||||
| --> |  | ||||||
| 
 |  | ||||||
| <!-- Piwik --> |  | ||||||
| <script type="text/javascript"> |  | ||||||
| var pkBaseURL = (("https:" == document.location.protocol) ? "https://elfsternberg.com/piwik/" : "http://elfsternberg.com/piwik/"); |  | ||||||
| document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/javascript'%3E%3C/script%3E")); |  | ||||||
| </script><script type="text/javascript"> |  | ||||||
| try { |  | ||||||
| var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 2); |  | ||||||
| piwikTracker.trackPageView(); |  | ||||||
| piwikTracker.enableLinkTracking(); |  | ||||||
| } catch( err ) {} |  | ||||||
| </script><noscript><p><img src="http://elfsternberg.com/piwik/piwik.php?idsite=2" style="border:0" alt="" /></p></noscript> |  | ||||||
| <!-- End Piwik Tag --> |  | ||||||
|     </div> |  | ||||||
|     {/block:story} |  | ||||||
| </body> |  | ||||||
| </html> |  | ||||||
| 
 |  | ||||||
		Loading…
	
		Reference in New Issue