Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit. Test example (a fictitious blog) I'm coding against n…

…ow partially works.
  • Loading branch information...
commit fae6c2d110736fd0c6851fe9eef5ed8dbd5a4b7a 0 parents
@debrouwere authored
4 .gitignore
@@ -0,0 +1,4 @@
+.DS_Store
+node_modules
+lib
+public
8 Makefile
@@ -0,0 +1,8 @@
+all:
+ coffee -co lib src
+
+clean:
+ rm -rf lib
+
+test: all
+ ./node_modules/.bin/mocha --reporter list
161 NOTES.md
@@ -0,0 +1,161 @@
+Because Hector is based on Tilt and Railgun, the app itself can actually be really light. The only things it has to take care of are:
+
+* Using Tilt.js its context submodule to fetch data (blogposts etc.) and use it to generate a page for each context object.
+* A config file that specifies
+ - permalink structure
+ - what files to exclude (not render or otherwise serve)
+ - server port (if served)
+ - where to find data or templates, so you can use the same data to render e.g. a web version and an ebook version of a site... with a file structure somewhat like this
+
+ proj
+ - data
+ - platforms
+ - web
+ tpl.jade
+ - ipad
+ tpl.jade
+
+(Use a default config file location unless a config file is specified as a CLI flag)
+
+* (Optionally/pony) some sort of plugin system that can hook into the rendering and context gathering processes.
+* Pass Railgun a bundle instead of taking one from it, to account for 1-to-m rendering
+
+
+THE "NORMAL" ROAD (Draughtsman, Middleman)
+
+template --> find context
+
+THE "STATIC SITE" ROAD (Jekyll & co)
+
+context --> find template.html
+-or-
+route --> find template and context
+
+(Espy should acknowledge that sometimes you just want to fetch content without knowing what template it is for, by providing separate `find` and `findFor` methods.)
+
+Perhaps a route-like system is in order, similar to generic views in Django?
+
+<route> <template=data.template> <context=data>
+
+/about static.html data/about.yml (this is implied)
+/:title static.html pages
+/:year/:month/:day/:title blogpost.html
+/:year/:month/:day/:title :layout _posts
+/:year/:month/:day/:title :layout posts/:year
+ (this looks in all subdirs to 'posts'
+ and passes subdir name to the :year variable)
+
+ posts/:year/:month-:day-:title
+
+routes:
+ - name: blogposts (just for documentation's sake)
+ route: :year/:month/:day/:title (:title is default)
+ template: :layout (:layout is default)
+ context: posts/:year/:month-:day-:title
+ defaults:
+ language: en
+ template: blogpost
+ processors:
+ - processors/customblogcontroller
+
+`route` and `template` consume context, and `context` and `defaults` generate it, in the case of `context` both from the filename and from its contents
+
+Or in Python syntax, which is slightly more elegant perhaps:
+
+routes:
+ posts:
+ route: {year}/{month}/{day}/{title}
+ template: {layout|'blogpost'}
+ context: posts/{year}/{month}-{day}-{title}
+
+If the route contains placeholders, it'll loop through all context sets. If not, it'll just render the template once, and that template can access all the different context sets under `data` (also available there on dynamic renders a.k.a. loops, but usually not very useful in that case)
+
+Because there's no controller logic, we can also do little niceties like allow default arguments / fallbacks in route parameters, as in e.g. `{layout|'blogpost'}` (quotes mean literal instead of var) -- though the `defaults` parameter can work for that too.
+
+Default file structure could be like this:
+
+BLOG
+- posts
+- pages
+- layouts
+- assets
+
+That way, combined with routing, you never have to underscore anything and have full control over what ends up where.
+
+Serving assets could be magical (if in an `/assets` directory) but could just as well work manually with something like this:
+
+routes:
+ assets:
+ route: assets/{file} // we'd have to make sure our regex can match across slashes
+ context: assets/{file} // context usually doesn't care too much about extensions but
+ // in this case it should
+ processors:
+ - preprocess // preprocess CoffeeScript, Stylus etc. and modify the {file} variable
+ // accordingly (change extension where necessary)
+ - static // serve static files instead of gathering context, rendering templates etc.
+
+----------
+
+PLUGGABILITY
+
+- add new formats through tilt.js
+ (maybe add markup languages back into tilt.js anyway, because Tulsa
+ would be pluggable just like a template language is, and we'd need that kind of
+ pluggability to turn Hector into a documentation generator)
+- register plugins (either generic variables/functions or language-specific ones) in tilt.js
+- support optional views to preprocess context or do all sorts of other crazy things,
+ even down to deciding whether to render or not, or ignore / change the
+ template defined in the route (in a middleware-like fashion, so you can stack multiple
+ views/middlewares on top of each other; and you can enable middleware globally or
+ for a specific route)
+- ?
+
+Processors would be passed send and next functions; middlewares pass on state to next(), custom renderers call send() instead. Processors that can be used as both middleware and final renderer can check whether `next === undefined` and act accordingly.
+
+Built-in processors should use this same basic functionality. There should be a processor for fetching context (obviously) but also to add in helpers to the context, analog to Jekyll's prev/next variables, as well as date/time formatting helpers (convert year/month/day variables into a JS date object so people can use date.toLocaleFormat etc.; make file created and modified dates available etc.), and then finally the processor for rendering
+
+Processor paths are hector/processors, <myproject>/processors and then myproject/<path>
+
+Default processors:
+
+ - find
+ - help
+ - render
+
+----------
+
+OUTPUT
+
+- serve (to preview locally or because you don't have any other server running on your VPS)
+- local build w/ railgun
+- upload to S3 w/ railgun
+
+PARTIAL RERENDERING
+
+- Partial (re)rendering would be a true pony. Something like
+
+hector . ./public --only posts year:2012
+
+which would then fill in the route so instead of looking for context in
+/posts/{year}-{month}-{day}-{title} it would look for
+/posts/2012-{month}-{day}-{title}
+
+--only pages, home => only generate pages and homepage
+--only posts layout: quote => only generate quotes
+
+With a couple of magical selectors that go beyond filtering on metadata:
+
+--only posts recent => check last modified date on an existing /public
+ folder and look for anything newer than that
+--only posts published => don't publish content that's dated in the future
+
+Or maybe --filter and --exclude make more sense
+
+--filter posts recent --exclude pages => render every route except pages, and only rerender recent posts
+
+----------
+
+DRAUGHTSMAN INTEGRATION
+
+1. create a `data` subdir inside your `layouts` directory, where you can put fixtures / dummy data to test your templates with
+2. <link rel="stylesheet" src="../assets/stylesheet.css" /> should actually work just fine, because we'll pull the static site through Railgun and it'll know how to find that relative path and remove it from the final html.
340 PRIOR.md
@@ -0,0 +1,340 @@
+=========
+PRIOR ART
+=========
+
+Hector takes its inspiration from Jekyll and Middleman, two other excellent static generators. Hector is a little bit more flexible and is a node.js application rather than a Ruby one, but all three are very nice options.
+
+## Other options to explore (and their redeeming qualities)
+
+- Jekyll
+
+http://jekyllrb.com/
+
+The godfather. Easy configuration through config.yml. Plugin support.
+
+Extensible through
+* Liquid extensions (adding context, functions, filters, template tags to the template language)
+* Converters (new markup languages)
+* Generators (generate new content according to your own rules -- equivalent to route+controller)
+
+- Middleman
+
+http://middlemanapp.com/guides/dynamic-pages
+http://middlemanapp.com/guides/blog
+
+Interesting because it is primarily a prototyping tool, secondarily a static site generator and thirdly a blog generator -- and it shows the kinds of trade-offs needed to cater to all these needs.
+
+The thing to steal: its context-finding conventions make a ton of sense (though more for Draughtsman than for Hector)
+
+- Mynt
+
+http://mynt.mirroredwhite.com/
+
+Freedom of markup language and rendering engine. Explicitly geared towards more complex sites. Has a couple of niceties built-in like `tags` and `archives` context -- as you may know, generating archive pages with Jekyll can be a bit of a pain.
+
+- Octopress
+
+http://octopress.org/
+
+Octopress is built on top of Jekyll. Has plugins and other goodies (e.g. for code highlighting) that allow you to get started simply by forking a repo on GitHub and then you can get cracking. Sort of like wordpress.com for nerds.
+
+- Wintersmith
+
+http://jnordberg.github.com/wintersmith/
+
+Opinionated: Markdown and Jade, that's what you get.
+
+Solid CoffeeScript-based plugin system that has access to pretty much everything.
+
+- Django-bakery
+
+http://datadesk.latimes.com/posts/2012/03/introducing-django-bakery/
+https://github.com/datadesk/django-bakery
+
+Like Mynt, works for more complex sites too, because of its Django DNA. Interesting because sometimes we just build static sites because we're working with read-only data and would like the speed gains of going static... but aside from that, a solid back-end framework is really what we need. It's not because you're suddenly not connecting to a database that the entire way you develop a site should change.
+
+(It is actually even more advanced, because you can host a dynamic version of your site locally or wherever you want, and then "bake" it -- so admin users have an easy way to update a static site through a web interface, which is sort of nice, and also the rationale behind Dropbox-based static site generation.)
+
+OTOH, because it builds on Django, it has lots of complexity you don't need for static sites.
+
+Its built-in S3 integration is very useful.
+
+- Hyde
+
+http://ringce.com/hyde
+
+Hyde tries to offer a little bit more out of the box, like a Google sitemap, an RSS feed, breadcrumbs, recent posts block et cetera. Otherwise it's pretty much a Jekyll clone.
+
+- Phrozn
+
+Remarkably, Phrozn doesn't seem to distinguish between markup formats and theme languages, it calls all of them "text processors." Seems weird to me.
+
+Has a command-line app to quickly initialize a project.
+
+Because Phrozn is built in PHP, which doesn't really have a concept of "modules" or "packages" (PEAR notwithstanding), Phrozn supports "bundles" instead: pluggable code you can hook into your project without cluttering the project itself.
+
+YAML front matter is very similar to Jekyll. You can define the permalink inside of the front matter instead of depending solely on the filename.
+
+- Bonsai
+
+http://tinytree.info/
+
+Has a folder structure that doesn't depend on underscores:
+
+ /myblog
+ /content
+ /public
+ /templates
+
+For content, Bonsai supports content ordering: simply prefix a directory or file with `n.`, for example `1.introduction.md`. It's true that most static site generators have lousy support for content ordering (though for a blog this isn't an issue, and just manually ordering a 7-item menu is no big deal either -- but e.g. for documentation it *is* a problem).
+
+The trouble with Bonsai's ordering is that you can't easily update the order without renaming every file. If this is the route you take, at the very least, you need to provide reordering support through a CLI.
+
+In line with its support for ordering, it also supports hierarchical navigation, and has `children`, `ancestors`, `parent` and `siblings` context variables. Again, useful for documentation and book-like content.
+
+Also expects people will use YAML front matter a lot to create more complex sites.
+
+- blatter
+
+https://bitbucket.org/jek/blatter/
+
+Very basic. Has publication functionality built in: send a rendered site to a server over (s)ftp.
+
+- blogofile
+
+http://www.blogofile.com/
+
+Another one of those static site generators that should work very well for blogs (with goodies like Disqus integration, syntax highlighting, built-in feeds, tags, categories and so on) but isn't very suitable to anything outside of that use-case.
+
+Can publish to S3.
+
+- Chisel
+
+https://github.com/dz/chisel
+
+Very, very basic. One of the few generators that doesn't work with YAML frontmatter.
+
+- StaticMatic
+
+http://staticmatic.rubyforge.org/
+
+Looks like Middleman light.
+
+- Frank
+
+Very basic. Has no data format, similar to Phrozn, though with YAML front matter (in your templates) and template layouts you can fake it: extend from the layout you want and then simply write your content. Anyway, still not your best bet for anything that's not a brochure.
+
+- Hobix
+
+http://hobix.github.com/hobix/
+
+Hobix is one of a couple of static site generators that work entirely based on YAML, so not just YAML front matter, but your blog's body content would just be under a `content` key in a YAML file. I feel like this emphasizes purity over practicality, and also makes it less easy to specify the markup language you're using for your body copy.
+
+Anyway, this is a scratch-an-itch deal: it probably works for the guy who made it, but that's about it.
+
+- ikiwiki
+
+http://ikiwiki.info/
+
+Nothing particularly special here, except for the fact that this static site generator is explicitly used for wikis. They call it a "wiki compiler." Combined with Git, that gives you revision support etc., which is kind of neat (though perhaps not optimal in terms of performance)
+
+- Korma
+
+https://github.com/sandal/korma
+
+Git-based blog, much like Octopress and a couple of others. Very tiny and basic (or 'zen', if you will)
+
+- Pyll
+
+https://github.com/arthurk/pyll
+
+Looks for html, rst and markdown files and parses them, and that's all it does. Almost more like a generic template parser than a site generator.
+
+- Nanoc
+
+http://nanoc.stoneship.org/
+
+Nanoc claims to be a web publishing system for small to medium-sized websites, so it's more ambitious than most static site generators. CLI-based, like most generators.
+
+Very good (and pretty) documentation and has a good getting-started setup with a basic design built in. They even explain how to use Markdown and how to create HAML templates, so while they don't compromise on the CLI aspect of the whole thing, they do try to make it easy. They explain how to make common website elements like image galleries. (As a result, the docs are huuuuge, though.) This makes sense from a "see what this thing does" point of view, but I don't know if you can ever make a static site generator palatable to non-technical users unless you provide a WYSIWYG editor and wrap the CLI in a minimalistic desktop app that does the generating and uploading for you.
+
+Has the common YAML front matter + data files setup. Customization through filters and helpers, also pretty common. You can also add new data sources (context finders -- like Jekyll generators but without actually generating anything) and CLI commands.
+
+Like Jekyll and some others, has a watcher that'll recompile your site on the fly.
+
+- Pagegen
+
+http://pagegen.phnd.net/
+
+Not general-purpose, but speeds up side building by assuming certain things about your site (you'll want a sidebar, a header, etc.)
+
+- Stacey
+
+http://www.staceyapp.com/
+
+Stacey markets itself as "the lightweight content management system."
+
+Uses a YAML-like (but not YAML-compatible) data format. Has its own template language too. Not really static: uses PHP to process your flat files on the fly (though it does cache some things), so there's no site generation step.
+
+This is a flat-file-based publishing system, but not a generator.
+
+- Toto
+
+http://cloudhead.io/toto
+
+Very minimalistic. Jekyll-like. YAML front-matter, but it jams your body copy and YAML in a single file without YAML document separators (`---`), so you couldn't read it with a YAML parser without first chopping off everything after the first double newline.
+
+- Pelican
+
+Uses ReST syntax, eww. Has importing tools. Can output to PDF. Some build-in goodies like feeds and syntax highlighting. Pretty run of the mill, though the PDF feature is nice for ebooks, books or presentations I suppose.
+
+- Poole
+
+https://bitbucket.org/obensonne/poole
+
+Jekyll-like. Not a lot of redeeming features.
+
+- Webby
+
+http://webby.rubyforge.org/
+
+Super-basic, though it has some niceties for scientists: embed LaTeX formulates, GraphViz graphs and source code. Supports multiple template languages, though.
+
+- PieCrust
+
+http://bolt80.com/piecrust/
+
+Very Jekyll/Middleman-like. Has support for SmartyPants (proper typographic quotes etc.) out of the box, which is a nice touch. You can choose between static or dynamic serving.
+
+- Petrify
+
+https://github.com/caolan/petrify
+
+Node.js, by Caolan. Docs explain the benefits of static generation in detail: security, speed, deployment etc... though that's about all they explain.
+
+ /data
+ /media
+ /templates
+ /views
+
+Has views/controllers. Can't really tell if some of them are built-in or if you have to create all of them yourself. Would be annoying if the latter.
+
+- Ace
+
+http://blog.101ideas.cz/posts/ace-static-site-generator.html
+
+Ace isn't special in itself, but it's interesting because it claims to be "assumptionless": doesn't assume anything about the kind of content you're creating, where to output it etc.
+
+- Stasis
+
+http://stasis.me/
+
+Supports controllers, though they're not what you think: "Controllers contain Ruby code that executes once before all templates render." Doesn't seem to have a data format / context finder built in.
+
+- Graze
+
+http://mikaelkoskinen.net/post/Graze-Static-site-generator-using-Razor.aspx
+
+One from the C# crowd. Nothing special.
+
+- Rizzo
+
+http://incoherencies.net/archives/announcing-rizzo/
+
+Inspired on Jekyll, but in Groovy. Tiny.
+
+- Flask-Static
+
+http://exyr.org/2010/Flask-Static/
+
+Like Django-bakery and some of the Sinatra-based static site generators. The idea is that you build your site like a dynamic one... and then you "bake it." "Flask-Static builds a static snapshot of a Flask application. It takes a list of URLs, simulates requests to the application, and save the responses in files." You get more flexibility in some cases, though it can be a bit of a mess in other cases: for a blog, how do you know which urls to visit considering all of them share the same route?
+
+- BAM
+
+http://jackhq.tumblr.com/post/17864806180/bam-easiest-static-site-generator-on-the-planet
+
+Minimalistic.
+
+- Blacksmith
+
+http://blacksmith.jit.su/
+
+Minimalistic. Opinionated: no templating language (uses Weld instead), only Markdown.
+
+- Tempo
+
+http://www.catnapgames.com/blog/2011/10/13/tempo-php-static-site-generator.html
+
+A tiny Jekyll in PHP, originally built as a Tumblr replacement.
+
+- Masonic
+
+http://code.google.com/p/masonic/
+
+Study project, Jekyll-inspired.
+
+- Statix
+
+http://www.terafied.com/post/16776048600/statix-template-agnostic-static-site-generator
+
+Minimalistic. Jekyll-inspired. Supports many template languages, using consolidate.js.
+
+- Zodiac
+
+http://www.nu-ex.com/projects/zodiac.html
+
+Zodiac is shell-based: it uses general-purpose tools like awk and find to do its thing. Pretty basic / not very user-friendly. Combines files as such:
+
+ main.layout
+ index.md
+ index.meta
+
+- Molehill
+
+http://code.google.com/p/molehil/
+
+Clojure-based. Can import from WordPress. Otherwise tiny.
+
+- Machined
+
+http://rubygems.org/gems/machined
+
+Repurposes Sprockets, Rails 3.1 asset pipeline, for very speedy static websites.
+
+- Voldemort
+
+http://foobarnbaz.com/2011/10/05/voldemort-a-jinja-powered-static-site-generator/
+
+Scratch-an-itch deal in Python. Only supports Markdown and Jinja2.
+
+## Thoughts
+
+When people say that there's a lot of static site generators out there, that's not necessarily true. Building a blog is a fun little project for most programmers, and so there's many code dumps or very tiny generators out there that were made "just for the heck of it" – but very few that actually have a community around them and do more than what the author needed for his or her own blog. Moreover, even when you look at just the popular ones, a lot of them are Jekyll clones, made because the author wanted Jekyll in <insert language>.
+
+Django-bakery and Flask-static are the ones to look at in the "bake a dynamic site" sphere.
+
+Jekyll, Hyde and Nanoc have the most mindshare among the blog engines.
+
+Phrozn, Middleman and to a lesser extent Bonsai and Machined have some mindshare as static site (not blog) generation.
+
+Octopress and Jekyll are leading the way in Git/GitHub-based systems.
+
+So the static site generator sphere is really not as crowded as it looks.
+
+## Other resources
+
+http://www.odopod.com/blog/introduction-static-website-generators/
+http://rrees.wordpress.com/2009/06/01/semi-static-cmss/
+http://www.quora.com/How-a-does-a-static-site-generator-like-Jekyll-work
+
+## Conclusions
+
+=> allow any kind of templating language and markup language
+=> support more complex sites
+ - real, proper routing
+ - because there's no concept of controllers (except through plugins),
+ you generally want to provide more conveniences out of the box so you can do more in the template (without overloading it with logic): template helpers, context sliced up in a couple of different ways (e.g. archive in a dict per year/month) etc.
+=> try to build something that tries to stick close to good MVC practices,
+ but simply happens to work with static data sources rather than dynamic ones
+=> partial rebuilds if possible: if nothing in /layouts has changed since the last build,
+ just regenerate pages from data that has changed (new or changed blogposts, say)
3  README.md
@@ -0,0 +1,3 @@
+Hector is a static site generator, perfect for blogs and promotional websites.
+
+Because Hector is built on Tilt.js, you can use just about any template language and markup language you want. Here's a list of languages we currently support......
14 bin/hector
@@ -0,0 +1,14 @@
+#!/usr/bin/env coffee
+
+program = require 'commander'
+hector = require '../src/index'
+
+program
+ .usage '<source> <destination> [options]'
+
+program
+ .option '-r, --routes [path]',
+ 'provide a custom routing file', ''
+ .parse process.argv
+
+hector.build program.args..., program.routes
0  examples/basic/layouts/base.jade
No changes.
6 examples/basic/layouts/blogpost.jade
@@ -0,0 +1,6 @@
+//extends base
+html
+ head
+ body
+ h1= meta.title
+ != body
5 examples/basic/pages/about.md
@@ -0,0 +1,5 @@
+---
+title: About me
+---
+
+Here's a thing or two you might like to know about me.
5 examples/basic/posts/2012-08-01-hello-world.md
@@ -0,0 +1,5 @@
+---
+title: Hello world
+---
+
+A dummy blogpost.
5 examples/basic/posts/2012-08-03-hello-wisconsin.md
@@ -0,0 +1,5 @@
+---
+title: Hello, Wisconsin!
+---
+
+A dummy blogpost.
5 examples/basic/posts/2012-08-05-hello-waukesha.md
@@ -0,0 +1,5 @@
+---
+title: Hi there, Waukesha
+---
+
+A dummy blogpost.
20 examples/basic/routes.yml
@@ -0,0 +1,20 @@
+defaults:
+ language: 'en'
+
+routes:
+ posts:
+ route: '{year}/{month}/{day}/{permalink}'
+ layout: '{layout}'
+ context: 'posts/{year}-{month}-{day}-{permalink}'
+ defaults:
+ layout: 'blogpost'
+ pages:
+ route: '{permalink}'
+ layout: '{layout}'
+ context: 'pages/{permalink}'
+ defaults:
+ layout: 'page'
+ feed:
+ route: 'feed.xml'
+ layout: 'feed'
+ context: 'posts/{year}/{month}-{day}-{permalink}'
36 src/index.coffee
@@ -0,0 +1,36 @@
+context = require 'espy'
+tilt = require 'tilt'
+railgun = require 'railgun'
+_ = require 'underscore'
+fs = require 'fs'
+fs.path = require 'path'
+yaml = require 'js-yaml'
+routing = require './routing'
+
+exports.build = (paths..., routes) ->
+ unless routes.length then routes = 'routes.yml'
+
+ cwd = process.cwd()
+
+ switch paths.length
+ when 2
+ [source, destination] = paths
+ when 1
+ source = paths[0]
+ destination = 'public'
+ when 0
+ source = ''
+ destination = 'public'
+ else
+ throw new Error 'Wrong arguments'
+
+ source = fs.path.join cwd, source
+ destination = fs.path.join cwd, destination
+ routes = fs.path.join source, routes
+
+ settings = require routes
+ router = new routing.Router settings.routes, settings.defaults, source
+ #console.log router.routes
+ router.generate (err, bundle) ->
+ console.log bundle
+ railgun.package bundle, destination
135 src/routing.coffee
@@ -0,0 +1,135 @@
+context = require 'espy'
+tilt = require 'tilt'
+railgun = require 'railgun'
+_ = require 'underscore'
+fs = require 'fs'
+fs.path = require 'path'
+fs.glob = require 'glob'
+async = require 'async'
+
+class exports.Format
+ constructor: (@raw, @defaults = {}) ->
+ # is this a fully-specified path or a template with placeholders?
+ if (@raw.indexOf '{') > -1
+ @isTemplate = yes
+ else
+ @isTemplate = no
+
+ # analog to matching a regular expression against a string
+ match: (str) ->
+ keys = @raw.match /\{([^\}]+)\}/g
+ keys = keys.map (match) ->
+ match.slice 1, -1
+ keys.push 'extension'
+
+ regex = @raw.replace /\{([^\}]+)\}/g, '(.+?)'
+ regex = new RegExp "#{regex}\.([^.]+)$"
+ matches = regex.exec(str)[1..]
+ context = @defaults
+ for key in keys
+ context[key] = matches.shift()
+
+ context
+
+ toTemplate: ->
+ (context) =>
+ str = @raw
+ for key, value of context
+ str = str.replace "{#{key}}", value, 'g'
+ str
+
+ # fill the placeholders in our formatted string with
+ # the context variables
+ fill: (context) ->
+ @toTemplate()(context)
+
+class exports.Route
+ constructor: (spec, @router) ->
+ @defaults = _.extend {}, @router.defaults, spec.defaults
+ @route = new exports.Format spec.route, @defaults
+ @layout = new exports.Format spec.layout, @defaults
+ @context = new exports.Format spec.context, @defaults
+ @root = @getBaseDir @context.raw
+
+ # this looks a bit strange, but what it does is, provided with a path to context like
+ # `posts/{year}/{month}-{day}-{title}`, figure out that we want to start looking in
+ # `posts`.
+ getBaseDir: (str) ->
+ end = str.indexOf('{')
+ format = str.slice 0, end
+ format.split('/').slice(0, -1).join('/')
+
+ getData: (callback) ->
+ # TODO: should really be (errors, context) =>
+ context.findFilesFor @router.fileRoot, @root, (files) ->
+ context.getContext files, (errors, context) ->
+ callback context
+
+ findLayout: (templateName) ->
+ path = fs.path.join @router.fileRoot, 'layouts', templateName
+ pattern = path + '.*'
+ # we need to glob for this basepath, and among the options
+ # (if any) we need to figure out if there are any with extensions
+ # for template languages we can deal with
+ matches = fs.glob.sync pattern
+ for match in matches
+ file = new tilt.File path: match
+ handler = tilt.findHandler file
+ # it's not enough to have a handler available,
+ # the file we're dealing with should be a template language and
+ # not e.g. a CSS preprocessor
+ if handler? and handler.mime.output is 'text/html'
+ return (context, callback) ->
+ file.load ->
+ handler.compiler file, context, callback
+
+ generate: (bundle, callback) ->
+ # TODO
+ contextFromGlobals = _.extend {}, @defaults
+
+ gen = (item, done) =>
+ [name, set] = item
+
+ relpath = set.meta.origin.filename.replace @router.fileRoot + '/', ''
+ contextFromFilename = @context.match set.meta.origin.filename
+ contextFromFile = set.meta
+
+ locals =
+ meta: _.extend {}, contextFromGlobals, contextFromFilename, contextFromFile
+ body: set.body
+
+ #data[name] = locals
+
+ # locals.meta.layout can be variable, so we want to run
+ # it through a formatter first
+ layoutName = new exports.Format(locals.meta.layout).fill(locals.meta)
+ layout = @findLayout layoutName
+ path = @route.fill(locals.meta) + '/index.html'
+
+ # TESTING
+ # abspath here is sort of silly, since the bundle will cut it right
+ # off again -- but we can worry about the fine details of the interop
+ # later
+ abspath = fs.path.join bundle.root, path
+
+ layout locals, (err, output) ->
+ bundle.push abspath, {content: output, compilerType: 'noop'}
+ done err
+
+ @getData (data) =>
+ if @route.isTemplate
+ # render once for every context set
+ async.forEach (_.pairs data), gen, (err) ->
+ callback err, bundle
+ else
+ # render once
+ 'todo'
+
+class exports.Router
+ constructor: (@routes, @defaults, @fileRoot) ->
+ (@routes[name] = new exports.Route spec, @) for name, spec of @routes
+
+ generate: (callback) ->
+ bundle = new railgun.Bundle @fileRoot + 'index'
+ console.log 'root', bundle.root
+ @routes.posts.generate bundle, callback
70 src/sandbox.coffee
@@ -0,0 +1,70 @@
+context = require 'espy'
+tilt = require 'tilt'
+railgun = require 'railgun'
+_ = require 'underscore'
+routing = require './routing'
+
+
+# DUMMY DATA
+
+routes =
+ posts:
+ route: "{year}/{month}/{day}/{title}"
+ template: "{layout}"
+ context: "posts/{year}/{month}-{day}-{title}"
+ defaults:
+ language: "en"
+
+file =
+ filename: "posts/2012/03-27-hello-beautiful-world.textile"
+ context:
+ layout: 'quote'
+ content: 'This is a quote from somebody.'
+
+# turn a filename into context
+format = new router.Format(routes.posts.context, routes.posts.defaults)
+context = format.match file.filename
+console.log _.extend {}, file.context, context
+
+# generate a path
+format = new router.Format(routes.posts.route)
+template = format.toTemplate()
+path = template context
+
+console.log path
+console.log format.fill context
+
+bundle = new railgun.Bundle 'index.html', 'production'
+
+# this looks a bit strange, but what it does is, provided with a path to context like
+# `posts/{year}/{month}-{day}-{title}`, figure out that we want to start looking in
+# `posts`.
+getBaseDir = (format) ->
+ end = format.indexOf('{')
+ format = format.slice 0, end
+ format.split('/').slice(0, -1).join('/')
+
+console.log getBaseDir "data/posts/{year}/{month}-{day}-{title}"
+
+# Go through each route and find appropriate context (TODO)
+for name, route of {} # routes
+ # espy lacks some of the features we need, like giving
+ # us the original filenames associated with context,
+ # going through subdirectories (not necessarily for context sets -- those should be optional),
+ # finding templates from a glob (`blogpost` whether it's `blogpost.jade` or `blogpost.haml` or whatever)
+ # and lastly getting context from the filename itself (though I think it makes sense to have
+ # that in `Format#match` in Hector)
+ # -- anyhow, this is pseudocode
+
+ espy.find getBaseDir route.context, (errors, context) ->
+ for name, set of context
+ templateName = new Format(route.template).fill(context)
+ template = espy.findTemplate root, templateName
+ path = new Format(route.route).fill(context)
+ content = tilt.compile route.template
+ compilerType: 'noop'
+ bundle.push {path, content, compilerType}
+
+#railgun.optimize bundle, ->
+# railgun.package bundle, dest, ->
+# console.log 'done'
3  test/test.coffee
@@ -0,0 +1,3 @@
+tilt = require 'tilt'
+f = new tilt.File path: '../example/posts/2012-08-05-hello-waukesha.md'
+tilt.compile f, null, (content) -> console.log content
Please sign in to comment.
Something went wrong with that request. Please try again.