Permalink
Browse files

Improvements and bugfixes to pipes. True support for contextless routes.

  • Loading branch information...
1 parent 7cbc870 commit de7d082c1e72745094a0558eca8ec4eb34584f31 @debrouwere committed Jul 28, 2013
View
2 examples/basic/assets/styles.styl
@@ -0,0 +1,2 @@
+body
+ background: #eee
View
7 examples/basic/routes.yml
@@ -26,6 +26,13 @@ globals:
- render
routes:
+ assets:
+ route: 'assets'
+ context: 'assets'
+ pipes:
+ - list-files
+ output:
+ - copy-assets
posts:
route: '{year}/{month}/{day}/{permalink}/'
layout: '{layout}'
View
16 src/format.coffee
@@ -1,5 +1,8 @@
_ = require 'underscore'
+filters =
+ 'd': '([0-9]+)'
+
class exports.Format
constructor: (@raw, @defaults = {}) ->
# checks whether this is a fully-specified path
@@ -16,7 +19,12 @@ class exports.Format
match.slice 1, -1
keys.push 'extension'
- regex = @raw.replace /\{([^\}]+)\}/g, '(.+?)'
+ regex = @raw
+ for key in keys
+ [name, filter] = key.split ':'
+ filter = filters[filter] or '(.+?)'
+ regex = regex.replace /\{([^\}]+)\}/, filter
+
regex = new RegExp "#{regex}\.([^.]+)$"
matchObj = regex.exec(str)
@@ -25,15 +33,17 @@ class exports.Format
matches = matchObj[1..]
context = _.clone @defaults
for key in keys
- context[key] = matches.shift()
+ [name, filter] = key.split ':'
+ context[name] = matches.shift()
context
toTemplate: ->
(context) =>
str = @raw
for key, value of context
- str = str.replace "{#{key}}", value, 'g'
+ typedKey = (new RegExp "{#{key}(\:.)?}")
+ str = str.replace typedKey, value, 'g'
str
# fill the placeholders in our formatted string with
View
43 src/index.coffee
@@ -6,9 +6,12 @@ fs = require 'fs'
fs.path = require 'path'
yaml = require 'js-yaml'
colors = require 'colors'
+async = require 'async'
+express = require 'express'
routing = exports.routing = require './routing'
-exports.build = (paths..., routes) ->
+
+exports._build = (paths..., routes, callback=->) ->
unless routes.length then routes = 'routes.yml'
switch paths.length
@@ -32,15 +35,35 @@ exports.build = (paths..., routes) ->
routes.settings ?= {}
router = new routing.Router routes, source
- router.load (err) ->
+ load = (done) -> router.load done
+ generate = (data, done) -> router.generate done
+ packageBuffer = (buffer, done) -> weaponize.package buffer, './build', done
+
+ async.waterfall [load, generate, packageBuffer], (err) ->
+ console.log 'built / waterfall'
+
+ # TODO: vary user-facing error message
+ # based on the async error
if err
console.log "- could not load data".red
console.log err.message
- return
-
- router.generate (err, buffer) ->
- if err
- console.log "- could not process #{err.path}".red
- console.log err.message
- else
- weaponize.package buffer, './build'
+
+ if err
+ console.log "- could not process #{err.path}".red
+ console.log err.message
+
+ callback err
+
+
+exports._serve = ->
+ exports._build arguments..., (err) ->
+ console.log 'built!'
+ app = express()
+ app.use express.static './build'
+ app.listen 3400
+ console.log 'Listening on 3400'
+
+
+# testing
+exports.build = ->
+ exports._serve arguments...
View
13 src/pipes/copy-assets.coffee
@@ -0,0 +1,13 @@
+fs = require 'fs'
+fs.path = require 'path'
+
+module.exports = (assets, callback) ->
+ # I can't say that I quite understand why we don't always get
+ # a proper array, but there ya go.
+ if typeof assets is 'string' then assets = [assets]
+
+ for asset in assets
+ relativePath = asset.replace (@router.root.assets + '/'), ''
+ @destination.add relativePath, {source: asset}
+
+ callback null
View
33 src/pipes/gather-context.coffee
@@ -2,6 +2,7 @@ _ = require 'underscore'
espy = require 'espy'
async = require 'async'
require 'colors'
+utils = require '../utils'
steps =
findCandidates: (callback) ->
@@ -12,7 +13,6 @@ steps =
espy.findFilesFor @router.root.context, @root, recursive, callback
filterCandidates: (candidates, callback) ->
- console.log "#{@name}".green
files = candidates.filter (file) =>
@context.match (file.replace @router.root.context, '')[1..]
callback null, files
@@ -41,14 +41,17 @@ steps =
{layout, @defaults, path, permalink, root: @router.root}
weaveContext: (sets, callback) ->
- console.log "#{@name}".red
-
getSetMetadata = _.bind steps.getSetMetadata, this
getPageMetadata = _.bind steps.getPageMetadata, this
for name, set of sets
- set.hector = getSetMetadata set.meta
- _.extend set.meta, set.hector.context
+ try
+ set.hector = getSetMetadata set.meta
+ catch error
+ console.log 'error w/ set', set
+ throw new Error()
+
+ set.meta = _.extend set.hector.context, set.meta
if @route.isTemplate
pageMeta = getPageMetadata set.meta
@@ -58,20 +61,26 @@ steps =
sets = _.values sets
else
meta = getPageMetadata {}
- sets = [{context: sets, hector: meta, meta: {}}]
+ sets = [{context: sets, hector: meta, meta: meta.defaults}]
callback null, sets
module.exports = (callback) ->
- procedure = [
- steps.findCandidates
- steps.filterCandidates
- steps.getFileContext
- steps.weaveContext
+ if @context
+ procedure = [
+ steps.findCandidates
+ steps.filterCandidates
+ steps.getFileContext
+ steps.weaveContext
+ ]
+ else
+ seed = utils.functional.seed []
+ procedure = [
+ seed
+ steps.weaveContext
]
procedure = procedure.map (step) => _.bind step, this
-
async.waterfall procedure, (err, sets) =>
console.log "Found #{sets.length} context files for route #{@name}"
callback err, sets
View
13 src/pipes/list-files.coffee
@@ -0,0 +1,13 @@
+fs = require 'fs'
+fs.path = require 'path'
+glob = require 'glob'
+
+module.exports = (callback) ->
+ root = fs.path.join @router.root.assets, @root
+ pattern = root + '/**'
+
+ glob pattern, {mark: yes}, (err, entries) ->
+ # there's probably a better way to filter out directories,
+ # but this works
+ files = entries.filter (entry) -> (entry.slice -1) isnt '/'
+ callback err, files
View
9 src/pipes/render-from-template.coffee
@@ -8,14 +8,17 @@ findLayout = (templateName, root) ->
pattern = path + '.*'
templatePath = utils.template.find pattern
+ return null unless templatePath
+
(context, callback) ->
utils.template.compile templatePath, context, callback
module.exports = (locals, callback) ->
- console.log 'being asked to render context for', locals
-
- layout = findLayout locals.hector.layout, locals.hector.root.app
_.extend locals, {data: @router.data}
+ layout = findLayout locals.hector.layout, locals.hector.root.app
+
+ if not layout
+ return console.log "✗ Couldn't find a layout named #{locals.hector.layout}".red
layout locals, (err, output) =>
console.log "#{locals.hector.path}".grey
View
42 src/routing.coffee
@@ -8,18 +8,24 @@ weaponize = require 'weaponize'
utils = require './utils'
{Format} = require './format'
-
-requireLocal = (name) ->
- name = name.replace '.js', ''
+requireLocal = (name, root) ->
+ name = name
+ .replace('.js', '')
+ .replace('.coffee', '')
+
+ if name[0] is '/'
+ localPath = name
+ else if name[0] is '.'
+ localPath = fs.path.join root, name
+ else
path = fs.path.join 'pipes', name
localPath = './' + path
- require localPath
-
+ require localPath
class Route
constructor: (@name, spec, @router) ->
load = (name) =>
- pipe = requireLocal name
+ pipe = requireLocal name, @router.root.app
_.bind pipe, this
@specification = spec
@@ -30,28 +36,35 @@ class Route
# REFACTOR: rename to `paths`, an array w/ fallbacks
# (and turn into array if we get a plain string from spec.route)
# (perhaps change naming to spec.path also)
- @route = new Format spec.route, @defaults
- @layout = new Format spec.layout, @defaults
- @context = new Format spec.context, @defaults
- @root = @getBaseDir @context.raw
+ if 'route' of spec
+ @route = new Format spec.route, @defaults
+ if 'layout' of spec
+ @layout = new Format spec.layout, @defaults
+ if 'context' of spec
+ @context = new Format spec.context, @defaults
+ @root = @getBaseDir @context.raw
# this looks a bit strange, but what it does is, provided with a path
# like `posts/{year}/{month}-{day}-{title}`, figure out that we want
# to start looking for context in `posts`.
getBaseDir: (str) ->
end = str.indexOf '{'
- format = str.slice 0, end
- format.split('/').slice(0, -1).join('/')
+ if end is -1
+ str
+ else
+ format = str.slice 0, end
+ format.split('/').slice(0, -1).join('/')
load: (callback) ->
save = (err, @data) => callback err, @data
async.waterfall @pipes, save
generate: (@destination, callback) ->
- console.log "Generating #{@name}".bold, "\t#{@context.raw} -> #{@route.raw}"
+ context = @context?.raw or ''
+ context += ' '
+ console.log "Generating #{@name}".bold, "\t#{context}-> #{@route.raw}"
process = (set, done) =>
- console.log 'processing set', set.hector
seed = utils.functional.seed set
async.waterfall [seed, @output...], done
@@ -78,6 +91,7 @@ class Router
@root =
app: absolutize (@options.settings.root?.app or source)
context: absolutize (@options.settings.root?.context or source)
+ assets: absolutize (@options.settings.root?.assets or source)
@routes = {}
(@routes[name] = new Route name, spec, @) for name, spec of @options.routes
View
10 thoughts.txt
@@ -0,0 +1,10 @@
+// sometimes it might make sense to have your context as a hierarchical structure
+// (a directory becomes a key)
+data.posts['2012']['03'].quotes
+// sometimes you want a list of objects that you can query
+// -- in which case you need the full metadata, or, the knowledge that
+// "2012" signifies a year (we already have this in the objects in Hector,
+// so that's not the issue -- mainly it just makes the tree superfluous
+data.posts.all({year: '2012'})
+
+The question becomes, to what extent can you really put all of this stuff in `espy`. Maybe Espy is just fine as a context finder and all enhancements should happen in Hector? True feature parity between context loading in Hector and Draughtsman is impossible anyway, because the latter has to follow certain conventions (data in a `/fixtures` dir, use the same name as the template file etc.) whereas the former can work with arbitrary routes.

0 comments on commit de7d082

Please sign in to comment.