aglio-theme-planado
Version:
Custom theme for the Aglio API Blueprint renderer
895 lines (864 loc) • 33.1 kB
JavaScript
// Generated by CoffeeScript 2.1.1
(function() {
var ROOT, benchmark, cache, compileTemplate, crypto, decorate, errMsg, fs, getCached, getCss, getTemplate, highlight, hljs, less, markdownIt, modifyUriTemplate, moment, path, prepareNav, pug, querystring, readLocales, renderExample, renderSchema, sha1, slug;
crypto = require('crypto');
fs = require('fs');
hljs = require('highlight.js');
pug = require('pug');
less = require('less');
markdownIt = require('markdown-it');
moment = require('moment');
path = require('path');
querystring = require('querystring');
renderExample = require('./example');
renderSchema = require('./schema');
// The root directory of this project
ROOT = path.dirname(__dirname);
cache = {};
// Utility for benchmarking
benchmark = {
start: function(message) {
if (process.env.BENCHMARK) {
return console.time(message);
}
},
end: function(message) {
if (process.env.BENCHMARK) {
return console.timeEnd(message);
}
}
};
// Extend an error's message. Returns the modified error.
errMsg = function(message, err) {
err.message = `${message}: ${err.message}`;
return err;
};
// Generate a SHA1 hash
sha1 = function(value) {
return crypto.createHash('sha1').update(value.toString()).digest('hex');
};
readLocales = function(root, language) {
var dest_path, lang_file;
dest_path = path.join(root, language + '.json');
lang_file = fs.readFileSync(dest_path);
return JSON.parse(lang_file);
};
// A function to create ID-safe slugs. If `unique` is passed, then
// unique slugs are returned for the same input. The cache is just
// a plain object where the keys are the sluggified name.
slug = function(cache = {}, value = '', unique = false) {
var sluggified;
sluggified = value.toLowerCase().replace(/[ \t\n\\<>"'=:\/]/g, '-').replace(/-+/g, '-').replace(/^-/, '');
if (unique) {
while (cache[sluggified]) {
// Already exists, so let's try to make it unique.
if (sluggified.match(/\d+$/)) {
sluggified = sluggified.replace(/\d+$/, function(value) {
return parseInt(value) + 1;
});
} else {
sluggified = sluggified + '-1';
}
}
}
cache[sluggified] = true;
return sluggified;
};
// A function to highlight snippets of code. lang is optional and
// if given, is used to set the code language. If lang is no-highlight
// then no highlighting is performed.
highlight = function(code, lang, subset) {
var response;
benchmark.start(`highlight ${lang}`);
response = (function() {
switch (lang) {
case 'no-highlight':
return code;
case void 0:
case null:
case '':
return hljs.highlightAuto(code, subset).value;
default:
return hljs.highlight(lang, code).value;
}
})();
benchmark.end(`highlight ${lang}`);
return response.trim();
};
getCached = function(key, compiledPath, sources, load, done) {
var compiledStats, err, i, len, loadErr, source, sourceStats;
// Disable the template/css caching?
if (process.env.NOCACHE) {
return done(null);
}
// Already loaded? Just return it!
if (cache[key]) {
return done(null, cache[key]);
}
try {
// Next, try to check if the compiled path exists and is newer than all of
// the sources. If so, load the compiled path into the in-memory cache.
if (fs.existsSync(compiledPath)) {
compiledStats = fs.statSync(compiledPath);
for (i = 0, len = sources.length; i < len; i++) {
source = sources[i];
sourceStats = fs.statSync(source);
if (sourceStats.mtime > compiledStats.mtime) {
// There is a newer source file, so we ignore the compiled
// version on disk. It'll be regenerated later.
return done(null);
}
}
try {
return load(compiledPath, function(err, item) {
if (err) {
return done(errMsg('Error loading cached resource', err));
}
cache[key] = item;
return done(null, cache[key]);
});
} catch (error) {
loadErr = error;
return done(errMsg('Error loading cached resource', loadErr));
}
} else {
return done(null);
}
} catch (error) {
err = error;
return done(err);
}
};
getCss = function(variables, styles, verbose, done) {
var compiledPath, customPath, defaultVariablePath, i, item, j, key, len, len1, load, sources, stylePaths, variablePaths;
// Get the CSS for the given variables and style. This method caches
// its output, so subsequent calls will be extremely fast but will
// not reload potentially changed data from disk.
// The CSS is generated via a dummy LESS file with imports to the
// default variables, any custom override variables, and the given
// layout style. Both variables and style support special values,
// for example `flatly` might load `styles/variables-flatly.less`.
// See the `styles` directory for available options.
key = `css-${variables}-${styles}`;
if (cache[key]) {
return done(null, cache[key]);
}
// Not cached in memory, but maybe it's already compiled on disk?
compiledPath = path.join(ROOT, 'cache', `${sha1(key)}.css`);
defaultVariablePath = path.join(ROOT, 'styles', 'variables-default.less');
sources = [defaultVariablePath];
if (!Array.isArray(variables)) {
variables = [variables];
}
if (!Array.isArray(styles)) {
styles = [styles];
}
variablePaths = [defaultVariablePath];
for (i = 0, len = variables.length; i < len; i++) {
item = variables[i];
if (item !== 'default') {
customPath = path.join(ROOT, 'styles', `variables-${item}.less`);
if (!fs.existsSync(customPath)) {
customPath = item;
if (!fs.existsSync(customPath)) {
return done(new Error(`${customPath} does not exist!`));
}
}
variablePaths.push(customPath);
sources.push(customPath);
}
}
stylePaths = [];
for (j = 0, len1 = styles.length; j < len1; j++) {
item = styles[j];
customPath = path.join(ROOT, 'styles', `layout-${item}.less`);
if (!fs.existsSync(customPath)) {
customPath = item;
if (!fs.existsSync(customPath)) {
return done(new Error(`${customPath} does not exist!`));
}
}
stylePaths.push(customPath);
sources.push(customPath);
}
load = function(filename, loadDone) {
return fs.readFile(filename, 'utf-8', loadDone);
};
if (verbose) {
console.log(`Using variables ${variablePaths}`);
console.log(`Using styles ${stylePaths}`);
console.log(`Checking cache ${compiledPath}`);
}
return getCached(key, compiledPath, sources, load, function(err, css) {
var k, l, len2, len3, tmp;
if (err) {
return done(err);
}
if (css) {
if (verbose) {
console.log('Cached version loaded');
}
return done(null, css);
}
// Not cached, so let's create the file.
if (verbose) {
console.log('Not cached or out of date. Generating CSS...');
}
tmp = '';
for (k = 0, len2 = variablePaths.length; k < len2; k++) {
customPath = variablePaths[k];
tmp += `@import "${customPath}";\n`;
}
for (l = 0, len3 = stylePaths.length; l < len3; l++) {
customPath = stylePaths[l];
tmp += `@import "${customPath}";\n`;
}
benchmark.start('less-compile');
return less.render(tmp, {
compress: true
}, function(err, result) {
var writeErr;
if (err) {
return done(msgErr('Error processing LESS -> CSS', err));
}
try {
css = result.css;
fs.writeFileSync(compiledPath, css, 'utf-8');
} catch (error) {
writeErr = error;
return done(errMsg('Error writing cached CSS to file', writeErr));
}
benchmark.end('less-compile');
cache[key] = css;
return done(null, cache[key]);
});
});
};
compileTemplate = function(filename, options) {
var compiled;
return compiled = `var pug = require('pug');\n${pug.compileFileClient(filename, options)}\nmodule.exports = compiledFunc;`;
};
getTemplate = function(name, verbose, done) {
var builtin, compiledPath, key, load;
// Check if this is a built-in template name
builtin = path.join(ROOT, 'templates', `${name}.pug`);
if (!fs.existsSync(name) && fs.existsSync(builtin)) {
name = builtin;
}
// Get the template function for the given path. This will load and
// compile the template if necessary, and cache it for future use.
key = `template-${name}`;
// Check if it is cached in memory. If not, then we'll check the disk.
if (cache[key]) {
return done(null, cache[key]);
}
// Check if it is compiled on disk and not older than the template file.
// If not present or outdated, then we'll need to compile it.
compiledPath = path.join(ROOT, 'cache', `${sha1(key)}.js`);
load = function(filename, loadDone) {
var loadErr, loaded;
try {
loaded = require(filename);
} catch (error) {
loadErr = error;
return loadDone(errMsg('Unable to load template', loadErr));
}
return loadDone(null, require(filename));
};
if (verbose) {
console.log(`Using template ${name}`);
console.log(`Checking cache ${compiledPath}`);
}
return getCached(key, compiledPath, [name], load, function(err, template) {
var compileErr, compileOptions, compiled, writeErr;
if (err) {
return done(err);
}
if (template) {
if (verbose) {
console.log('Cached version loaded');
}
return done(null, template);
}
if (verbose) {
console.log('Not cached or out of date. Generating template JS...');
}
// We need to compile the template, then cache it. This is interesting
// because we are compiling to a client-side template, then adding some
// module-specific code to make it work here. This allows us to save time
// in the future by just loading the generated javascript function.
benchmark.start('pug-compile');
compileOptions = {
filename: name,
name: 'compiledFunc',
self: true,
compileDebug: false
};
try {
compiled = compileTemplate(name, compileOptions);
} catch (error) {
compileErr = error;
return done(errMsg('Error compiling template', compileErr));
}
if (compiled.indexOf('self.') === -1) {
// Not using self, so we probably need to recompile into compatibility
// mode. This is slower, but keeps things working with pug files
// designed for Aglio 1.x.
compileOptions.self = false;
try {
compiled = compileTemplate(name, compileOptions);
} catch (error) {
compileErr = error;
return done(errMsg('Error compiling template', compileErr));
}
}
try {
fs.writeFileSync(compiledPath, compiled, 'utf-8');
} catch (error) {
writeErr = error;
return done(errMsg('Error writing cached template file', writeErr));
}
benchmark.end('pug-compile');
cache[key] = require(compiledPath);
return done(null, cache[key]);
});
};
modifyUriTemplate = function(templateUri, parameters, colorize) {
var block, closeIndex, index, lastIndex, param, parameterBlocks, parameterNames, parameterSet, parameterValidator;
// Modify a URI template to only include the parameter names from
// the given parameters. For example:
// URI template: /pages/{id}{?verbose}
// Parameters contains a single `id` parameter
// Output: /pages/{id}
parameterValidator = function(b) {
// Compare the names, removing the special `*` operator
return parameterNames.indexOf(querystring.unescape(b.replace(/^\*|\*$/, ''))) !== -1;
};
parameterNames = (function() {
var i, len, results;
results = [];
for (i = 0, len = parameters.length; i < len; i++) {
param = parameters[i];
results.push(param.name);
}
return results;
})();
parameterBlocks = [];
lastIndex = index = 0;
while ((index = templateUri.indexOf("{", index)) !== -1) {
parameterBlocks.push(templateUri.substring(lastIndex, index));
block = {};
closeIndex = templateUri.indexOf("}", index);
block.querySet = templateUri.indexOf("{?", index) === index;
block.formSet = templateUri.indexOf("{&", index) === index;
block.reservedSet = templateUri.indexOf("{+", index) === index;
lastIndex = closeIndex + 1;
index++;
if (block.querySet || block.formSet || block.reservedSet) {
index++;
}
parameterSet = templateUri.substring(index, closeIndex);
block.parameters = parameterSet.split(",").filter(parameterValidator);
if (block.parameters.length) {
parameterBlocks.push(block);
}
}
parameterBlocks.push(templateUri.substring(lastIndex, templateUri.length));
return parameterBlocks.reduce(function(uri, v) {
var segment;
if (typeof v === "string") {
uri.push(v);
} else {
segment = !colorize ? ["{"] : [];
if (v.querySet) {
segment.push("?");
}
if (v.formSet) {
segment.push("&");
}
if (v.reservedSet && !colorize) {
segment.push("+");
}
segment.push(v.parameters.map(function(name) {
if (!colorize) {
return name;
} else {
// TODO: handle errors here?
name = name.replace(/^\*|\*$/, '');
param = parameters[parameterNames.indexOf(querystring.unescape(name))];
if (v.querySet || v.formSet) {
return `<span class="hljs-attribute">${name}=</span>` + `<span class="hljs-literal">${param.example || ''}</span>`;
} else {
return `<span class="hljs-attribute" title="${name}">${param.example || name}</span>`;
}
}
}).join(colorize ? '&' : ','));
if (!colorize) {
segment.push("}");
}
uri.push(segment.join(""));
}
return uri;
}, []).join('').replace(/\/+/g, '/');
};
prepareNav = function(navigation) {
return navigation.map(function([tag, title, id, children]) {
return {
link: "#" + id[1],
title: title,
children: prepareNav(children)
};
});
};
decorate = function(api, md, slugCache, verbose) {
var action, category, content_sections, dataStructure, dataStructures, err, example, fn, i, item, j, k, knownParams, l, len, len1, len2, len3, len4, m, meta, name, newParams, param, ref, ref1, ref2, ref3, ref4, resource, resourceGroup, results, reversed, schema, section, slugify;
// Decorate an API Blueprint AST with various pieces of information that
// will be useful for the theme. Anything that would significantly
// complicate the pug template should probably live here instead!
// Use the slug caching mechanism
slugify = slug.bind(slug, slugCache);
// Find data structures. This is a temporary workaround until Drafter is
// updated to support JSON Schema again.
// TODO: Remove me when Drafter is released.
dataStructures = {};
ref = api.content || [];
for (i = 0, len = ref.length; i < len; i++) {
category = ref[i];
ref1 = category.content || [];
for (j = 0, len1 = ref1.length; j < len1; j++) {
item = ref1[j];
if (item.element === 'dataStructure') {
dataStructure = item.content[0];
dataStructures[dataStructure.meta.id] = dataStructure;
}
}
}
if (verbose) {
console.log(`Known data structures: ${Object.keys(dataStructures)}`);
}
// API overview description”
if (api.description) {
content_sections = api.description.split('<!-- LHSCONTENT -->');
api.descriptionHtml = [];
api.descriptionHtml.push([md.render(content_sections[0])]);
ref2 = content_sections.slice(1);
fn = function() {
var rest, side, sides, sidesContent;
sides = section.split('<!-- RHSCONTENT -->');
[sides[1], rest] = sides[1].split('<!-- ENDCONTENT -->');
sidesContent = (function() {
var l, len3, results;
results = [];
for (l = 0, len3 = sides.length; l < len3; l++) {
side = sides[l];
results.push(md.render(side));
}
return results;
})();
api.descriptionHtml.push(sidesContent);
return api.descriptionHtml.push([md.render(rest)]);
};
for (k = 0, len2 = ref2.length; k < len2; k++) {
section = ref2[k];
fn();
}
api.navItems = prepareNav(slugCache._nav);
slugCache._nav = [];
}
ref3 = api.metadata || [];
for (l = 0, len3 = ref3.length; l < len3; l++) {
meta = ref3[l];
if (meta.name === 'HOST') {
api.host = meta.value;
}
}
ref4 = api.resourceGroups || [];
results = [];
for (m = 0, len4 = ref4.length; m < len4; m++) {
resourceGroup = ref4[m];
// Element ID and link
resourceGroup.elementId = slugify(resourceGroup.name, true);
resourceGroup.elementLink = `#${resourceGroup.elementId}`;
// Description
if (resourceGroup.description) {
resourceGroup.descriptionHtml = md.render(resourceGroup.description);
resourceGroup.navItems = slugCache._nav;
slugCache._nav = [];
}
results.push((function() {
var len5, n, ref5, results1;
ref5 = resourceGroup.resources || [];
results1 = [];
for (n = 0, len5 = ref5.length; n < len5; n++) {
resource = ref5[n];
// Element ID and link
resource.elementId = slugify(`${resourceGroup.name}-${resource.name}`, true);
resource.elementLink = `#${resource.elementId}`;
results1.push((function() {
var len6, len7, o, p, ref6, results2;
ref6 = resource.actions || [];
results2 = [];
for (o = 0, len6 = ref6.length; o < len6; o++) {
action = ref6[o];
// Element ID and link
action.elementId = slugify(`${resourceGroup.name}-${resource.name}-${action.method}`, true);
action.elementLink = `#${action.elementId}`;
// Lowercase HTTP method name
action.methodLower = action.method.toLowerCase();
// Parameters may be defined on the action or on the
// parent resource. Resource parameters should be concatenated
// to the action-specific parameters if set.
if (!(action.attributes || {}).uriTemplate) {
if (!action.parameters || !action.parameters.length) {
action.parameters = resource.parameters;
} else if (resource.parameters) {
action.parameters = resource.parameters.concat(action.parameters);
}
}
// Remove any duplicates! This gives precedence to the parameters
// defined on the action.
knownParams = {};
newParams = [];
reversed = (action.parameters || []).concat([]).reverse();
for (p = 0, len7 = reversed.length; p < len7; p++) {
param = reversed[p];
if (knownParams[param.name]) {
continue;
}
knownParams[param.name] = true;
newParams.push(param);
}
action.parameters = newParams.reverse();
// Set up the action's template URI
action.uriTemplate = modifyUriTemplate((action.attributes || {}).uriTemplate || resource.uriTemplate || '', action.parameters);
action.colorizedUriTemplate = modifyUriTemplate((action.attributes || {}).uriTemplate || resource.uriTemplate || '', action.parameters, true);
// Examples have a content section only if they have a
// description, headers, body, or schema.
action.hasRequest = false;
results2.push((function() {
var len8, q, ref7, results3;
ref7 = action.examples || [];
results3 = [];
for (q = 0, len8 = ref7.length; q < len8; q++) {
example = ref7[q];
results3.push((function() {
var len9, r, ref8, results4;
ref8 = ['requests', 'responses'];
results4 = [];
for (r = 0, len9 = ref8.length; r < len9; r++) {
name = ref8[r];
results4.push((function() {
var len10, len11, len12, ref10, ref11, ref9, results5, s, u, w;
ref9 = example[name] || [];
results5 = [];
for (s = 0, len10 = ref9.length; s < len10; s++) {
item = ref9[s];
if (name === 'requests' && !action.hasRequest) {
action.hasRequest = true;
}
// If there is no schema, but there are MSON attributes, then try
// to generate the schema. This will fail sometimes.
// TODO: Remove me when Drafter is released.
if (!item.schema && item.content) {
ref10 = item.content;
for (u = 0, len11 = ref10.length; u < len11; u++) {
dataStructure = ref10[u];
if (dataStructure.element === 'dataStructure') {
try {
schema = renderSchema(dataStructure.content[0], dataStructures);
schema['$schema'] = 'http://json-schema.org/draft-04/schema#';
item.schema = JSON.stringify(schema, null, 2);
} catch (error) {
err = error;
if (verbose) {
console.log(JSON.stringify(dataStructure.content[0], null, 2));
console.log(err);
}
}
}
}
}
if (!item.body && item.content) {
ref11 = item.content;
for (w = 0, len12 = ref11.length; w < len12; w++) {
dataStructure = ref11[w];
if (dataStructure.element === 'dataStructure') {
try {
item.body = JSON.stringify(renderExample(dataStructure.content[0], dataStructures), null, 2);
} catch (error) {
err = error;
if (verbose) {
console.log(JSON.stringify(dataStructure.content[0], null, 2));
console.log(err);
}
}
}
}
}
item.hasContent = item.description || Object.keys(item.headers).length || item.body || item.schema;
try {
// If possible, make the body/schema pretty
if (item.body) {
item.body = JSON.stringify(JSON.parse(item.body), null, 2);
}
if (item.schema) {
results5.push(item.schema = JSON.stringify(JSON.parse(item.schema), null, 2));
} else {
results5.push(void 0);
}
} catch (error) {
err = error;
results5.push(false);
}
}
return results5;
})());
}
return results4;
})());
}
return results3;
})());
}
return results2;
})());
}
return results1;
})());
}
return results;
};
// Get the theme's configuration, used by Aglio to present available
// options and confirm that the input blueprint is a supported
// version.
exports.getConfig = function() {
return {
formats: ['1A'],
options: [
{
name: 'variables',
description: 'Color scheme name or path to custom variables',
default: 'default'
},
{
name: 'condense-nav',
description: 'Condense navigation links',
boolean: true,
default: true
},
{
name: 'full-width',
description: 'Use full window width',
boolean: true,
default: false
},
{
name: 'template',
description: 'Template name or path to custom template',
default: 'default'
},
{
name: 'style',
description: 'Layout style name or path to custom stylesheet'
},
{
name: 'emoji',
description: 'Enable support for emoticons',
boolean: true,
default: true
},
{
name: 'lang',
description: 'Language code'
},
{
name: 'lang-dir',
description: 'Language directory'
}
]
};
};
// Render the blueprint with the given options using pug and LESS
exports.render = function(input, options, done) {
var collectNavigationData, locale, md, slugCache, themeStyle, themeVariables, verbose;
if (done == null) {
done = options;
options = {};
}
// Disable the template/css caching?
if (process.env.NOCACHE) {
cache = {};
}
// This is purely for backward-compatibility
if (options.condenseNav) {
options.themeCondenseNav = options.condenseNav;
}
if (options.fullWidth) {
options.themeFullWidth = options.fullWidth;
}
// Setup defaults
if (options.themeVariables == null) {
options.themeVariables = 'default';
}
if (options.themeStyle == null) {
options.themeStyle = 'default';
}
if (options.themeTemplate == null) {
options.themeTemplate = 'default';
}
if (options.themeCondenseNav == null) {
options.themeCondenseNav = true;
}
if (options.themeFullWidth == null) {
options.themeFullWidth = false;
}
if (options.themeLang == null) {
options.themeLang = null;
}
if (options.themeLangDir == null) {
options.themeLangDir = null;
}
// Transform built-in layout names to paths
if (options.themeTemplate === 'default') {
options.themeTemplate = path.join(ROOT, 'templates', 'index.pug');
}
collectNavigationData = function(md, opts) {
var getHeadingLevel, navigation, noop, originalHeadingOpen, recursivelyAdd, setupCallback;
// How it works:
// 1. We attach the function *after* all H-tags are processed
// 2. Those tags have `id` as an attribute
// 3. We build a nested list of items (pretty lispy)
// 4. We convert the list into a dict-like structure and render it
noop = function() {
return [];
};
setupCallback = opts && opts.callback ? opts.callback : noop;
originalHeadingOpen = md.renderer.rules.heading_open;
navigation = [];
setupCallback(navigation);
getHeadingLevel = function(heading) {
return parseInt(heading[1]);
};
recursivelyAdd = function(navigation, item) {
var item_level, item_tag, parent, parent_children, parent_id, parent_level, parent_tag, parent_title;
parent = navigation[navigation.length - 1];
if (parent && parent.length > 0) {
[parent_tag, parent_title, parent_id, parent_children] = parent;
item_tag = item[0];
parent_level = getHeadingLevel(parent_tag);
item_level = getHeadingLevel(item_tag);
if (parent_level < item_level) { // higher level = child
return recursivelyAdd(parent_children, item);
} else {
return navigation.push(item);
}
} else {
return navigation.push(item);
}
};
return md.renderer.rules.heading_open = function(tokens, idx, something, somethingelse, self) {
var id, item, tag, title;
title = tokens[idx + 1].children.reduce(function(acc, t) {
return acc + t.content;
}, "").replace(" ¶", "");
tag = tokens[idx].tag;
id = tokens[idx].attrs.find(function([key, _]) {
return key === "id";
});
item = [tag, title, id, []];
recursivelyAdd(navigation, item);
if (originalHeadingOpen) {
return originalHeadingOpen.apply(this, arguments);
} else {
return self.renderToken.apply(self, arguments);
}
};
};
// Setup markdown with code highlighting and smartypants. This also enables
// automatically inserting permalinks for headers.
slugCache = {
_nav: []
};
md = markdownIt({
html: true,
linkify: true,
typographer: true,
highlight: highlight
}).use(require('markdown-it-anchor'), {
slugify: function(value) {
var output;
output = `header-${slug(slugCache, value, true)}`;
return output;
},
permalink: true,
permalinkClass: 'permalink'
}).use(collectNavigationData, {
callback: function(navigation) {
return slugCache._nav = navigation;
}
}).use(require('markdown-it-checkbox')).use(require('markdown-it-container'), 'note').use(require('markdown-it-container'), 'warning');
if (options.themeEmoji) {
md.use(require('markdown-it-emoji'));
}
if (options.themeLang && options.themeLangDir) {
locale = readLocales(options.themeLangDir, options.themeLang);
} else {
locale = {};
}
// Enable code highlighting for unfenced code blocks
md.renderer.rules.code_block = md.renderer.rules.fence;
benchmark.start('decorate');
decorate(input, md, slugCache, options.verbose);
benchmark.end('decorate');
benchmark.start('css-total');
({themeVariables, themeStyle, verbose} = options);
return getCss(themeVariables, themeStyle, verbose, function(err, css) {
var key, locals, ref, value;
if (err) {
return done(errMsg('Could not get CSS', err));
}
benchmark.end('css-total');
locals = {
api: input,
condenseNav: options.themeCondenseNav,
css: css,
fullWidth: options.themeFullWidth,
date: moment,
hash: function(value) {
return crypto.createHash('md5').update(value.toString()).digest('hex');
},
highlight: highlight,
markdown: function(content) {
return md.render(content);
},
slug: slug.bind(slug, slugCache),
urldec: function(value) {
return querystring.unescape(value);
},
locale: locale
};
ref = options.locals || {};
for (key in ref) {
value = ref[key];
locals[key] = value;
}
benchmark.start('get-template');
return getTemplate(options.themeTemplate, verbose, function(getTemplateErr, renderer) {
var html;
if (getTemplateErr) {
return done(errMsg('Could not get template', getTemplateErr));
}
benchmark.end('get-template');
benchmark.start('call-template');
try {
html = renderer(locals);
} catch (error) {
err = error;
return done(errMsg('Error calling template during rendering', err));
}
benchmark.end('call-template');
return done(null, html);
});
});
};
}).call(this);