UNPKG

roots-records

Version:

dynamic content functionality for roots

163 lines (133 loc) 5.33 kB
fs = require 'fs' rest = require 'rest' path = require 'path' W = require 'when' node = require 'when/node' _ = require 'lodash' RootsUtil = require 'roots-util' module.exports = (opts) -> class Records ###* * Creates a locals object if one isn't set. * @constructor ### constructor: (@roots) -> @util = new RootsUtil(@roots) @roots.config.locals ||= {} @roots.config.locals.records ||= {} ###* * Setup extension method loops through objects and * returns a promise to get all data and store. ### setup: -> fetch_records = (fetch.call(@, key, conf) for key, conf of opts) W.all(fetch_records) .then (res) -> W.map(res, apply_hook) .tap (res) => W.map(res, add_to_locals.bind(@)) .tap (res) => W.map(res, compile_single_views.bind(@)) ###* * Fetches the JSON data from a url, file, or data and returns it neatly as * an object containing the key, options, and resolved data. * * @param {String} key - name of the record being fetched * @param {Object} opts - options provided under the key * @returns {Promise|Object} - a promise for an object with a `key`, * `options`, and `data` values ### fetch = (key, opts) -> data_promise = switch when _.has(opts, 'url') then resolve_url(opts) when _.has(opts, 'file') then resolve_file.call(@, opts) when _.has(opts, 'data') then resolve_data(opts) else throw new Error("You must provide a 'url', 'file', or 'data' key") data_promise.then (data) -> { key: key, options: opts, data: data } ###* * Makes a request to the provided url, returning the response body as JSON. * * @param {Object} opts - the key's parameters ### resolve_url = (opts) -> mime = require('rest/interceptor/mime') error_code = require('rest/interceptor/errorCode') client = rest .wrap(mime) .wrap(error_code) if typeof opts.url is 'string' conf = { path: opts.url } else conf = opts.url client(conf).then (res) -> if not res.entity throw new Error("URL has not returned any content") if typeof res.entity isnt 'object' throw new Error("URL did not return JSON") res.entity ###* * Reads the file based on a path relative to the project root, returns the * results as JSON. * * @param {Object} obj - the key's parameters ### resolve_file = (opts) -> node.call(fs.readFile.bind(fs), path.join(@roots.root, opts.file), 'utf8') .then (contents) -> JSON.parse(contents) ###* * Ensures data provided is an object, then resolves it through. * * @param {Object} opts - the key's parameters ### resolve_data = (opts) -> type = typeof opts.data if type isnt 'object' throw new Error("Data provided is a #{type} but must be an object") W.resolve(opts.data) ###* * If a hook was provided in the config, runs the response through the hook. * * @param {String} obj - record object with a `key`, `options`, and `data` ### apply_hook = (obj) -> if not obj.options.hook then return obj obj.data = obj.options.hook(obj.data) return obj ###* * Given a resolved records object, adds it to the view's locals. * * @param {Object} obj - records object, containing a `key` and `data` ### add_to_locals = (obj) -> @roots.config.locals.records[obj.key] = obj.data ###* * Given a records object, if that object has `template` and `out` keys, and * its data is an array, iterates through its data, creating a single view * for each item in the array using the template provided in the `template` * value, and writing to the path provided in the `out` value. * * @param {Object} obj - record object with a `key`, `options`, and `data` ### compile_single_views = (obj) -> obj_opts = obj.options if not obj_opts.template and not obj_opts.out then return if obj_opts.template and not obj_opts.out throw new Error("You must also provide an 'out' option") if obj_opts.out and not obj_opts.template throw new Error("You must also provide a 'template' option") if not Array.isArray(obj.data) throw new Error("'#{obj.key}' data must be an array") W.map obj.data, (item) => tpl = if _.isFunction(obj_opts.template) obj_opts.template(item) else obj_opts.template tpl_path = path.join(@roots.root, tpl) output_path = "#{obj_opts.out(item)}.html" compiler = _.find @roots.config.compilers, (c) -> _.contains(c.extensions, path.extname(tpl_path).substring(1)) compiler_opts = _.extend( @roots.config.locals, @roots.config[compiler.name] ? {}, _path: output_path, { item: item } ) compiler.renderFile(tpl_path, compiler_opts) .then (res) => @util.write(output_path, res.result)