blogs
Version:
A simple blog generator.
289 lines (277 loc) • 6.76 kB
JavaScript
var fs = require('fs');
var path = require('path');
var async = require('async');
var glob = require('glob');
var nunjucks = require('nunjucks');
var bMods = require('blogs_modules');
var removeExt = bMods.removeExt;
var camelCase = bMods.camelCase;
var forEachKey = bMods.forEachKey;
var isUndef = bMods.isUndef;
var Cache = bMods.Cache;
var rm = bMods.rm;
var writeFile = bMods.writeFile;
var cp = bMods.cp;
var Post = require('./-post');
var EventEmitter = require('events').EventEmitter;
class Blog extends EventEmitter {
constructor(dir) {
super();
this.dir = dir;
this.cache = new Cache();
forEachKey(require(this.path('config')), (value, key) => {
if (isUndef(this[key])) {
this[key] = value;
}
});
forEachKey({
dest: 'public',
static: [],
root: '/',
permalink: '{{ YYYY }}/{{ MM }}/{{ T }}.html'
}, (value, key) => {
if (isUndef(this[key])) {
this[key] = value;
}
});
if (!Array.isArray(this.static)) {
this.static = [this.static];
}
}
/**
* Wraps path.join
* @param {...String} ...paths
* @return {String}
* @example
* var blog = new Blog('/home/jelonzo/blog');
* blog.path('layout');
* // '/home/jelonzo/blog/layout'
*/
path(...paths) {
paths.unshift(this.dir);
return path.join.apply(path, paths);
}
/**
* Gets posts
* @param {Function} callback
* @return {undefined}
*/
posts(callback) {
this.cache.get('posts', (callback) => {
async.waterfall([
(callback) => {
glob(this.path('posts/**/*'), {
nodir: true
}, callback);
},
(files, callback) => {
async.map(files, (file, callback) => {
callback(null, new Post(this, file));
}, callback);
}
], callback);
}, callback);
}
/**
* Gets a list of layout files
* @param {Function} callback
* @return {undefined}
*/
layouts(callback) {
this.cache.get('layouts', (callback) => {
async.waterfall([
(callback) => {
async.map([
this.path('layout/**/*'),
path.join(__dirname, '../layout/**/*')
], (pattern, callback) => {
glob(pattern, {
nodir: true
}, callback);
}, callback);
},
(files, callback) => {
files = [].concat(...files);
callback(null, [
'error',
'index',
'post'
].reduce((layout, name) => {
layout[name] = files.find((file) => {
return removeExt(path.basename(file)) === name;
});
return layout;
}, {}));
}
], callback);
}, callback);
}
/**
* Gets plugins
* @param {String} type - 'processor', 'filter', 'global', 'extension'
* @param {Function} callback
* @return {undefined}
*/
plugins(type, callback) {
this.cache.get('plugins:' + type, (callback) => {
async.waterfall([
(callback) => {
var query = type + '-*';
async.map([
path.join(__dirname, '../node_modules', 'blogs-' + query),
this.path('scripts', query)
], (pattern, callback) => {
glob(pattern, callback);
}, callback);
},
(files, callback) => {
var expr = new RegExp('^.*?' + type + '-');
files = [].concat(...files);
callback(null, files.reduce((filters, file) => {
var name = camelCase(removeExt(path.basename(file)).replace(expr, ''));
filters[name] = require(file);
return filters;
}, {}));
}
], callback);
}, callback);
}
/**
* Renders a template string.
* @param {String} template
* @param {String} dir
* @param {Object} bindings
* @param {Function} callback
* @return {undefined}
*/
render(template, dir, bindings, callback) {
async.waterfall([
(callback) => {
this.cache.get('render', (callback) => {
async.map([
'filter',
'extension',
'global'
], (type, callback) => {
this.plugins(type, callback);
}, callback);
}, callback);
},
(results, callback) => {
var error = null;
var [filters, extensions, globals] = results;
var Loader = nunjucks.FileSystemLoader;
var Environment = nunjucks.Environment;
var Template = nunjucks.Template;
var env = new Environment(dir && new Loader(dir));
var result;
forEachKey(filters, (filter, name) => {
env.addFilter(name, filter);
});
forEachKey(extensions, (extension, name) => {
env.addExtension(name, extension);
});
forEachKey(globals, (value, name) => {
env.addGlobal(name, value);
});
forEachKey(bindings, (value, name) => {
env.addGlobal(name, value);
});
try {
result = new Template(template, env).render();
} catch (err) {
error = err;
}
callback(error, result);
}
], callback);
}
/**
* Compiles the blog
* @param {Function} callback
* @return {undefined}
*/
compile(callback) {
this.cache.get('compile', (callback) => {
async.waterfall([
(callback) => {
this.posts(callback);
},
(posts, callback) => {
async.map(posts, (post, callback) => {
post.load(callback);
}, callback);
},
(posts, callback) => {
posts.sort((a, b) => {
return b.date - a.date;
});
posts.forEach((post, index, posts) => {
post.newer = posts[index - 1];
post.older = posts[index + 1];
});
this.posts = posts;
this.layouts(callback);
},
(layouts, callback) => {
async.waterfall([
(callback) => {
fs.readFile(layouts.post, callback);
},
(template, callback) => {
template = template.toString();
async.each(this.posts, (post, callback) => {
async.waterfall([
(callback) => {
this.render(template, path.dirname(layouts.post), {
post: post,
blog: this
}, callback);
},
(result, callback) => {
post.rendered = result;
callback(null, post);
}
], callback);
}, callback);
},
(callback) => {
callback(null, this);
}
], callback);
}
], callback);
}, callback);
}
/**
* Save files.
* @param {Function} callback
* @return {undefined}
*/
deploy(callback) {
async.waterfall([
(callback) => {
async.parallel([
(callback) => {
this.compile(callback);
},
(callback) => {
rm(this.path(this.dest), callback);
}
], callback);
this.compile(callback);
},
(results, callback) => {
async.each(this.static, (dir, callback) => {
cp(this.path(dir), this.path(this.dest, dir), callback);
}, callback);
},
(callback) => {
async.each(this.posts, (post, callback) => {
writeFile(this.path(this.dest, post.permalink), post.rendered, callback);
}, callback);
}
], callback);
}
}
module.exports = Blog;