diff --git a/Makefile b/Makefile index 80dee91..9c6000a 100644 --- a/Makefile +++ b/Makefile @@ -5,14 +5,18 @@ lib_objects:= $(subst src/, lib/, $(lib_sources:%.coffee=%.js)) default: build -build: $(lib_objects) +build: $(lib_objects) lib/tumble.js + +lib/tumble.js: src/tumble.peg + ./node_modules/.bin/pegjs src/tumble.peg lib/tumble.js $(lib_objects): lib/%.js: src/%.coffee @mkdir -p $(@D) coffee -o $(@D) -c $< -test: test/[0-9]*_mocha.coffee +test: test/[0-9]*_mocha.coffee lib/tumble.js lib/parser.js ./node_modules/.bin/mocha -C --compilers coffee:coffee-script -u tdd $< + clean: rm -fr lib diff --git a/README.md b/README.md new file mode 100644 index 0000000..e9ef878 --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +Tumble is an implementation of the Tumblr parser/compiler/renderer, +with keyword substitutions suitable to my needs on my story website. +The idea is that the database side will produce an object consisting +of the title of a series + + +block:Series + 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. + +block:Subseries + + Must be found within a block:toc + It must contain the special tag {titles} as a child tag. + + This will be rendered for any subseries of the main series. This + 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 + Has two meanings, based on context. + + If it is found in the *document* (i.e. *not* within a , it is treated at a story block, + and will only be rendered if this is a story page. + + If it is found in a *series* or *subseries* block, it's contents + are rendered during the rendering of a series or subseries for + each story found. + +block:Next + Valid only in a block:story + Will render if there is a "next" story in the parent series. + +block:Prev + Valid only in a block:story + Will render if there is a "previous" story in the parent series. + +block:Title + Will render if there is a title in the current context + +block:Blurb + Will render if there is a blurb. + +block:Excerpt + Will render if there is an excerpt + +Variables: + +Title + The title in the current context. + +Body + The body of the story. Only available in top-level block:story. + +SeriesTitle + The title of the series in the current context. + +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 + +{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. + +{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} + + diff --git a/package.json b/package.json index 3e2519b..bce0bcf 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,37 @@ { "name": "Tumble", - "description": "An implementation of a parser for Tumbler.", - "author": { - "name": "Elf M. Sternberg" - }, + "description": "Trivial reimplementation of Tumbler template parser", "version": "0.0.1", - "keywords": ["parser", "coffeescript"], - "licenses": [{ - "type": "ARR", - "url": "http://elfsternberg.com/home/elfsternberg/repos/Tumble/LICENSE" - }], - "dependencies": { - "coffee-script": "1.x.x", - "reparse-coffeescript": "git://github.com/elfsternberg/reparse-coffeescript#master" + "author": { + "name": "Kenneth \"Elf\" M. Sternberg", + "email": "elf.sternberg@gmail.com", + "url": "http://elfsternberg.com" }, - "devDependencies": { - "docco": "0.3.x", - "mocha": "1.8.x", - "chai": "1.5.x" + "repository": { + "type": "git", + "url": "ssh://elfstenberg@elfsternberg.com/home/elfsternberg/repos/tumble.git" }, - "directories": { - "lib": "./lib" - }, - "main": "./lib/tumble", + "licenses": [ + { + "type": "PRIVATE" + } + ], + "main": "lib/tumble", "engines": { "node": ">= 0.6.0" - } + }, + "scripts": { + "test": "make test" + }, + "dependencies": { + "underscore": "1.4.x" + }, + "devDependencies": { + "pegjs": "0.7.x", + "mocha": "1.8.x", + "chai": "1.5.x", + "coffee-script": "1.6.x", + "docco": "0.3.x" + }, + "keywords": [] } diff --git a/src/parser.coffee b/src/parser.coffee new file mode 100644 index 0000000..60fd90d --- /dev/null +++ b/src/parser.coffee @@ -0,0 +1,35 @@ +tumble = require('./tumble') +util = require('util') + +module.exports = (template) -> + + ast = tumble.parse(template) + console.log(util.inspect(ast, null, null)) + # 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) -> + console.log(content) + subtypes = (name) -> + return 'cond' + + handler = (obj) -> + isLegal = (name) -> true + { + 'text': () -> + obj.content + 'variable': () -> + return '' if not (isLegal(obj.content) and content.hasOwnProperty(obj.content)) + content[obj.content] + + 'block': () -> + return '' if not (isLegal(obj.content) and content.hasOwnProperty(obj.content)) + { + 'cond': () -> if obj.content then handler(obj.content) else '' + 'loop': () -> (handler(o) for o in obj.content) + }[subtypes(obj.name)]() + }[obj.type]() + + (handler(i) for i in ast).join("") diff --git a/src/tumble.coffee b/src/tumble.coffee deleted file mode 100644 index 91674c5..0000000 --- a/src/tumble.coffee +++ /dev/null @@ -1,14 +0,0 @@ -# _ _ _ _ _____ _ _ -# /_\ | |__ __| |_ _ _ __ _ __| ||_ _| _ _ __ | |__| |_ _ -# / _ \| '_ (_-< _| '_/ _` / _| _|| || || | ' \| '_ \ | '_| -# /_/ \_\_.__/__/\__|_| \__,_\__|\__||_| \_,_|_|_|_|_.__/_|_| -# - -# Built on top of the basic parser-combinator for Coffeescript, this -# defines a parser for the Tumblr engine, assuming the following: - -ReParse = require('reparse-coffeescript/lib/reparse').ReParse - -class AbstractTumbler extends ReParse - -module.exports = class extends AbstractTumbler diff --git a/src/tumble.peg b/src/tumble.peg new file mode 100644 index 0000000..6fae776 --- /dev/null +++ b/src/tumble.peg @@ -0,0 +1,57 @@ +{ + + + +} + +document + = p:part* { return p } + +part + = text / variable / block + +text + = b:(!tag c:. {return c})+ + { return { type: "text", content: b.join('') }; } + +variable "variable" + = t:tag_start rd + { return { type: "variable", content: 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, content: 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" + diff --git a/test/01_basics_mocha.coffee b/test/01_basics_mocha.coffee index d984573..ed08e56 100644 --- a/test/01_basics_mocha.coffee +++ b/test/01_basics_mocha.coffee @@ -3,39 +3,45 @@ assert = chai.assert expect = chai.expect should = chai.should() -Tumbler = require('../lib/tumble') - -tumbl = new Tumbler() +tumble = require('../lib/parser') test_data = [ { 'input': '', - 'output': '' + 'output': '', + 'description': "no input" } { 'input': '', 'output': '', + 'description': "just text" } { 'input': '

{name}

' 'output': '

Elf Sternberg

' - 'data': {'name': 'Elf Sternberg'} + 'data': {'name': 'Elf Sternberg'}, + 'description': "a simple substitution" } { 'input': '

{title} {name}

' 'output': '

Mr. Elf Sternberg

' - 'data': {'name': 'Elf Sternberg', 'title': 'Mr.'} + 'data': {'name': 'Elf Sternberg', 'title': 'Mr.'}, + 'description': "two simple substitutions" } { 'input': '