Passing template text and variable substitution methods, at least to one level down. Not working: (1) blocks, (2) descending GET
This commit is contained in:
parent
41239a0b4c
commit
1806eccf20
8
Makefile
8
Makefile
|
@ -5,14 +5,18 @@ lib_objects:= $(subst src/, lib/, $(lib_sources:%.coffee=%.js))
|
||||||
|
|
||||||
default: build
|
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
|
$(lib_objects): lib/%.js: src/%.coffee
|
||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
coffee -o $(@D) -c $<
|
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 $<
|
./node_modules/.bin/mocha -C --compilers coffee:coffee-script -u tdd $<
|
||||||
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -fr lib
|
rm -fr lib
|
||||||
|
|
|
@ -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}
|
||||||
|
|
||||||
|
|
50
package.json
50
package.json
|
@ -1,29 +1,37 @@
|
||||||
{
|
{
|
||||||
"name": "Tumble",
|
"name": "Tumble",
|
||||||
"description": "An implementation of a parser for Tumbler.",
|
"description": "Trivial reimplementation of Tumbler template parser",
|
||||||
"author": {
|
|
||||||
"name": "Elf M. Sternberg"
|
|
||||||
},
|
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"keywords": ["parser", "coffeescript"],
|
"author": {
|
||||||
"licenses": [{
|
"name": "Kenneth \"Elf\" M. Sternberg",
|
||||||
"type": "ARR",
|
"email": "elf.sternberg@gmail.com",
|
||||||
"url": "http://elfsternberg.com/home/elfsternberg/repos/Tumble/LICENSE"
|
"url": "http://elfsternberg.com"
|
||||||
}],
|
|
||||||
"dependencies": {
|
|
||||||
"coffee-script": "1.x.x",
|
|
||||||
"reparse-coffeescript": "git://github.com/elfsternberg/reparse-coffeescript#master"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"repository": {
|
||||||
"docco": "0.3.x",
|
"type": "git",
|
||||||
"mocha": "1.8.x",
|
"url": "ssh://elfstenberg@elfsternberg.com/home/elfsternberg/repos/tumble.git"
|
||||||
"chai": "1.5.x"
|
|
||||||
},
|
},
|
||||||
"directories": {
|
"licenses": [
|
||||||
"lib": "./lib"
|
{
|
||||||
},
|
"type": "PRIVATE"
|
||||||
"main": "./lib/tumble",
|
}
|
||||||
|
],
|
||||||
|
"main": "lib/tumble",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6.0"
|
"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": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -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("")
|
|
@ -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
|
|
|
@ -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"
|
||||||
|
|
|
@ -3,39 +3,45 @@ assert = chai.assert
|
||||||
expect = chai.expect
|
expect = chai.expect
|
||||||
should = chai.should()
|
should = chai.should()
|
||||||
|
|
||||||
Tumbler = require('../lib/tumble')
|
tumble = require('../lib/parser')
|
||||||
|
|
||||||
tumbl = new Tumbler()
|
|
||||||
|
|
||||||
test_data = [
|
test_data = [
|
||||||
{
|
{
|
||||||
'input': '',
|
'input': '',
|
||||||
'output': ''
|
'output': '',
|
||||||
|
'description': "no input"
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
'input': '<html>',
|
'input': '<html>',
|
||||||
'output': '<html>',
|
'output': '<html>',
|
||||||
|
'description': "just text"
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
'input': '<h1>{name}</h1>'
|
'input': '<h1>{name}</h1>'
|
||||||
'output': '<h1>Elf Sternberg</h1>'
|
'output': '<h1>Elf Sternberg</h1>'
|
||||||
'data': {'name': 'Elf Sternberg'}
|
'data': {'name': 'Elf Sternberg'},
|
||||||
|
'description': "a simple substitution"
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
'input': '<h1>{title} {name}</h1>'
|
'input': '<h1>{title} {name}</h1>'
|
||||||
'output': '<h1>Mr. Elf Sternberg</h1>'
|
'output': '<h1>Mr. Elf Sternberg</h1>'
|
||||||
'data': {'name': 'Elf Sternberg', 'title': 'Mr.'}
|
'data': {'name': 'Elf Sternberg', 'title': 'Mr.'},
|
||||||
|
'description': "two simple substitutions"
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
'input': '<ul>{block:Stories}{Title}{/block:Stories}'
|
'input': '<ul>{block:Stories}{Title}{/block:Stories}'
|
||||||
'output': '<ul>AAABBB</ul>'
|
'output': '<ul>AAABBB</ul>'
|
||||||
'data': {'stories': [{'title': 'AAA'}, {'title': 'BBB'}]}
|
'data': {'stories': {'title': 'AAA'}},
|
||||||
|
'description': "a conditional block"
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
|
||||||
describe "Basic Functionality", ->
|
describe "Basic Functionality", ->
|
||||||
for data in test_data
|
for data in test_data
|
||||||
|
do (data) ->
|
||||||
it "should work with #{data.description}", ->
|
it "should work with #{data.description}", ->
|
||||||
tumbl.parse(data.input)(data.data).should.equal data.output
|
r = tumble(data.input)(data.data)
|
||||||
|
console.log("R:", r)
|
||||||
|
r.should.equal data.output
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
<!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