Skip to content

Commit

Permalink
Switching from handlebars to handybars
Browse files Browse the repository at this point in the history
  • Loading branch information
Jocelyn Badgley (Twipped) committed May 13, 2020
1 parent 9b5428d commit 7d03658
Show file tree
Hide file tree
Showing 59 changed files with 206 additions and 197 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ If you wish to re-used this blogging engine, download this repository and run `n

- `posts`: Every blog post goes into here, in its own folder. Each folder must contain an `index.md` file and at least a `1.jpeg` file to serve as the titlecard for the post. A `poster.jpeg` may also be provided to use on the site index and for oembed images. `titlecard.jpeg` may also be provided to override _just_ the oembed titlecard. Images or movies numbered in the pattern of `(N)N.(jpeg|jpg|gif|png)` will all automatically be scaled and inserted in the post. Numbered videos may also be provided in the format of `(N)N.m4v`, but must already be web-ready (no ffmpeg juju is performed). The `index.md` file is standard markdown, but supports inline HTML as well.

- `templates` contains the handlebars templates used for the site chrome, individual post pages, and the post cells on the index and tags pages.
- `templates` contains the handybars templates used for the site chrome, individual post pages, and the post cells on the index and tags pages.

- `pages` contains the handlebars templates for root level pages such as `index.html` and the `sitemap.xml` file. Handlebars is extended with helpers from the [Helper Hoard](http://npm.im/helper-hoard) js library (one of my own). Content regions define segments for embedding into the site chrome template. The `{{rev}}` helper is also provided to replace any asset paths with their respective cache busted urls.
- `pages` contains the handybars templates for root level pages such as `index.html` and the `sitemap.xml` file. Handybars is extended with helpers from the [Helper Hoard](http://npm.im/helper-hoard) js library (one of my own). Content regions define segments for embedding into the site chrome template. The `{{rev}}` helper is also provided to replace any asset paths with their respective cache busted urls.

- `scss` contains the site SCSS templates. Files prefixed with underscores are skipped by the build process, as those are just for imports. The build process converts these into standard CSS, minified when in production mode.

Expand Down
123 changes: 66 additions & 57 deletions build/engines.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ const fs = require('fs-extra');
const log = require('fancy-log');
const { resolve, readFile, ENGINE, TYPE } = require('./resolve');

const Handlebars = require('handlebars');
const HandlebarsKit = require('hbs-kit');
HandlebarsKit.load(Handlebars);
const handybars = require('handybars');
const Kit = require('handybars/kit');

const slugify = require('./lib/slugify');
const striptags = require('string-strip-html');
Expand Down Expand Up @@ -39,7 +38,7 @@ const markdownEngines = {
.use(require('./lib/markdown-token-filter')),
};

function markdown (mode, input, env) {
function markdown (mode, input, data, hbs) {

if (mode === 'preview') {
input = striptags(input
Expand All @@ -52,31 +51,21 @@ function markdown (mode, input, env) {
} else {

input = input.replace(/\{!\{([\s\S]*?)\}!\}/mg, (match, contents) => {
try {
const result = Handlebars.compile(contents)(env);
return 'æææ' + result + 'æææ';
} catch (e) {
log.error(e);
return '';
}
const result = hbs(contents, data);
return 'æææ' + result + 'æææ';
});

input = input.replace(/<!--[[\]]-->/g, '');
}

try {
return input ? markdownEngines[mode].render(input, env) : '';
return input ? markdownEngines[mode].render(input, data) : '';
} catch (e) {
log(input);
throw e;
}
}

function handlebars (input, env) {
const template = Handlebars.compile(input);
return template(env);
}

function stripIndent (input) {
const match = input.match(/^[^\S\n]*(?=\S)/gm);
const indent = match && Math.min(...match.map((el) => el.length));
Expand All @@ -89,47 +78,56 @@ function stripIndent (input) {
return input;
}

const HANDLEBARS_PARTIALS = {
const HANDYBARS_PARTIALS = {
layout: 'templates/layout.hbs',
};

const HANDYBARS_TEMPLATES = {
list: 'templates/list.hbs',
page: 'templates/page.hbs',
post: 'templates/post.hbs',
};

module.exports = exports = async function (prod) {
const templates = {};
for (const [ name, file ] of Object.entries(HANDLEBARS_PARTIALS)) {

const revManifest = prod && await fs.readJson(resolve('rev-manifest.json')).catch(() => {}).then((r) => r || {});
const injectables = new Injectables(prod, revManifest);

const env = { ...Kit, ...injectables.helpers() };

for (const [ name, file ] of Object.entries(HANDYBARS_PARTIALS)) {
try {
const contents = await readFile(file);
const template = Handlebars.compile(contents.toString('utf8'));
templates[name] = template;
Handlebars.registerPartial(name, template);
env[name] = handybars.partial(contents.toString('utf8'));
} catch (e) {
log.error('Could not execute load partial ' + file, e);
log.error('Could not load partial ' + file, e);
}
}

const revManifest = prod && await fs.readJson(resolve('rev-manifest.json')).catch(() => {}).then((r) => r || {});
const templates = {};
for (const [ name, file ] of Object.entries(HANDYBARS_TEMPLATES)) {
try {
const contents = await readFile(file);
templates[name] = handybars(contents.toString('utf8'), env);
} catch (e) {
log.error('Could not load template ' + file, e);
}
}

const helpers = new Injectables(prod, revManifest);
Handlebars.registerHelper('import', helpers.import());
Handlebars.registerHelper('markdown', helpers.markdown());
Handlebars.registerHelper('icon', helpers.icon());
Handlebars.registerHelper('prod', helpers.production());
Handlebars.registerHelper('rev', helpers.rev());
const hbs = (source, data) => handybars(source, env)(data);

const result = {
[TYPE.HANDLEBARS]: handlebars,
[TYPE.MARKDOWN]: (source, env) => markdown('full', source, env),
[TYPE.HANDYBARS]: hbs,
[TYPE.MARKDOWN]: (source, data) => markdown('full', source, data, hbs),
[TYPE.OTHER]: (source) => source,

[ENGINE.LIST]: (source, env) => templates.list({ ...env, contents: markdown('full', source, env) }),
[ENGINE.PAGE]: (source, env) => templates.page({ ...env, contents: markdown('full', source, env) }),
[ENGINE.POST]: (source, env) => templates.post({ ...env, contents: markdown('full', source, env) }),
[ENGINE.LIST]: (source, data) => templates.list({ ...data, contents: markdown('full', source, data, hbs) }),
[ENGINE.PAGE]: (source, data) => templates.page({ ...data, contents: markdown('full', source, data, hbs) }),
[ENGINE.POST]: (source, data) => templates.post({ ...data, contents: markdown('full', source, data, hbs) }),
[ENGINE.HTML]: (source) => source,
[ENGINE.OTHER]: (source) => source,

preview: (source, env) => markdown('preview', source, env),
preview: (source, data) => markdown('preview', source, data, hbs),
};

return result;
Expand Down Expand Up @@ -172,6 +170,16 @@ class Injectables {
return '';
}

helpers () {
return {
import: this.import(),
markdown: this.markdown(),
icon: this.icon(),
prod: this.production(),
rev: this.rev(),
};
}

rev () {
const self = this;
return function (url) {
Expand All @@ -184,48 +192,47 @@ class Injectables {

production () {
const self = this;
return function (options) {
if (!options.fn) return self.prod;
return self.prod ? options.fn(this) : options.inverse(this);
return function ({ fn, inverse }) {
if (!fn) return self.prod;
return self.prod ? fn(this) : inverse && inverse(this);
};
}

markdown () {
const self = this;
return function (...args) {
const { fn, data } = args.pop();
const { fn, data, resolve: rval } = args.pop();
const local = rval('@root.this.local');
let contents;

if (fn) {
contents = stripIndent(fn(data.root));
} else {
let tpath = args.shift();
tpath = self._parsePath(tpath, data.root.local, 'md');
tpath = self._parsePath(tpath, local, 'md');

contents = self._template(tpath);
}

contents = markdown('full', contents, data);
contents = markdown('full', contents, data, () => { throw new Error('You went too deep!'); });

return new Handlebars.SafeString(contents);
return { value: contents };
};
}

import () {
const self = this;
return function (tpath, ...args) {
const { hash, data } = args.pop();
const { hash, env, resolve: rval } = args.pop();
const value = args.shift() || this;
const frame = Handlebars.createFrame(data);
const context = (typeof value === 'object')
? { ...value, ...(hash || {}), _parent: this }
: value;
const frame = handybars.makeContext(value, env, { hash });
const local = rval('@root.this.local');

tpath = self._parsePath(tpath, data.root.local, 'hbs');
tpath = self._parsePath(tpath, local, 'hbs');

try {
const contents = self._template(tpath, Handlebars.compile)(context, { data: frame });
return new Handlebars.SafeString(contents);
const contents = self._template(tpath, handybars.parse).evaluate(value, frame);
return handybars.safe(contents);
} catch (e) {
log.error('Could not execute import template ' + tpath, e);
return '';
Expand All @@ -236,15 +243,17 @@ class Injectables {
icon () {
const self = this;
return function (name, ...args) {
const { hash, data } = args.pop();
const tpath = path.join(data.root.local.root, 'svg', name + '.svg');
const { hash, env, resolve: rval } = args.pop();
const local = rval('@root.this.local');
const tpath = path.join(local.root, 'svg', name + '.svg');
const frame = handybars.makeContext(hash, env);

try {
const contents = self._template(tpath, (s) =>
Handlebars.compile(`<span class="svg-icon" {{#if size}}style="width:{{size}}px;height:{{size}}px"{{/if}}>${s}</span>`),
)({ size: hash && hash.size });
handybars(`<span class="svg-icon" {{#if size}}style="width:{{size}}px;height:{{size}}px"{{/if}}>${s}</span>`),
)(frame);

return new Handlebars.SafeString(contents);
return handybars.safe(contents);
} catch (e) {
log.error('Could not execute import template ' + tpath, e);
return '';
Expand Down
4 changes: 2 additions & 2 deletions build/lists.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ class List extends Page {

_engine () {
switch (this.type) {
case TYPE.HANDLEBARS:
return TYPE.HANDLEBARS;
case TYPE.HANDYBARS:
return TYPE.HANDYBARS;
case TYPE.MARKDOWN:
return ENGINE.LIST;
default:
Expand Down
4 changes: 2 additions & 2 deletions build/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ module.exports = exports = class Page extends File {

_engine () {
switch (this.type) {
case TYPE.HANDLEBARS:
return TYPE.HANDLEBARS;
case TYPE.HANDYBARS:
return TYPE.HANDYBARS;
case TYPE.MARKDOWN:
return ENGINE.PAGE;
default:
Expand Down
4 changes: 2 additions & 2 deletions build/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ module.exports = exports = class Post extends Page {

_engine () {
switch (this.type) {
case TYPE.HANDLEBARS:
return TYPE.HANDLEBARS;
case TYPE.HANDYBARS:
return TYPE.HANDYBARS;
case TYPE.MARKDOWN:
return ENGINE.POST;
default:
Expand Down
8 changes: 4 additions & 4 deletions build/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ const normalizedExt = exports.normalizedExt = (ext) => {

const isVideo = exports.isVideo = is(MP4, M4V);
const isImage = exports.isImage = is(JPG, JPEG, PNG, GIF);
const isHandlebars = exports.isHandlebars = is(XML, HBS, HTML);
const isHandybars = exports.isHandybars = is(XML, HBS, HTML);
const isMarkdown = exports.isMarkdown = is(MD);
const isPage = exports.isPage = is(isHandlebars, isMarkdown);
const isPage = exports.isPage = is(isHandybars, isMarkdown);
const isAsset = exports.isAsset = is(isImage, isVideo);
const isArtifact = exports.isArtifact = is(CSS, SCSS, JS, JSX);
exports.isCleanUrl = is(HBS, MD);
Expand All @@ -83,7 +83,7 @@ exports.isCleanUrl = is(HBS, MD);
const TYPE = exports.TYPE = {
IMAGE: 'TYPE_IMAGE',
VIDEO: 'TYPE_VIDEO',
HANDLEBARS: 'TYPE_HANDLEBARS',
HANDYBARS: 'TYPE_HANDYBARS',
MARKDOWN: 'TYPE_MARKDOWN',
SCRIPT: 'TYPE_SCRIPT',
STYLE: 'TYPE_STYLE',
Expand All @@ -92,7 +92,7 @@ const TYPE = exports.TYPE = {

exports.type = dictMatch({
[TYPE.IMAGE]: isImage,
[TYPE.HANDLEBARS]: isHandlebars,
[TYPE.HANDYBARS]: isHandybars,
[TYPE.MARKDOWN]: isMarkdown,
[TYPE.VIDEO]: isVideo,
[TYPE.SCRIPT]: is(JS, JSX),
Expand Down
2 changes: 1 addition & 1 deletion posts/2017-08-29.1023.44A20F/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ no-images: true
---

{!{
{{import '~/img' images.[1]
{{import '~/img' images['1']
className="card span3 right"
}}
}!}
Expand Down
2 changes: 1 addition & 1 deletion posts/2017-09-21.1022.7B173D/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ tags:
---

{!{
{{import '~/img' images.[1]
{{import '~/img' images['1']
className="card span3 right"
}}
}!}
Expand Down
2 changes: 1 addition & 1 deletion posts/2018-03-04.0927.8B7BD6/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ captions:
---

{!{
{{import '~/img' images.[1]
{{import '~/img' images['1']
className="card span3 right"
}}
}!}
Expand Down
2 changes: 1 addition & 1 deletion posts/2018-05-12.0923.0D371F/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ products:
"Come as You Are, by Emily Nagoski": https://www.amazon.com/exec/obidos/ASIN/B00LD1ORBI/curvyandtrans-20
---
{!{
{{import '~/img' images.[1]
{{import '~/img' images['1']
className="card span3 right"
}}
}!}
Expand Down
2 changes: 1 addition & 1 deletion posts/2018-06-12.0916.9002B0/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ no-images: true
---

{!{
{{import '~/img' images.[1]
{{import '~/img' images['1']
className="card span3 right"
}}
}!}
Expand Down
Loading

0 comments on commit 7d03658

Please sign in to comment.