hikaru-coffee
Version:
A static site generator that generates routes based on directories naturally.
521 lines (492 loc) • 19.9 kB
JavaScript
// Generated by CoffeeScript 2.3.1
(function() {
var Generator, Hikaru, Logger, Renderer, Router, Translator, URL, cheerio, coffee, dateStrCompare, escapeHTML, fse, getAbsPathFn, getURLFn, glob, highlight, marked, moment, nib, nunjucks, paginate, paginateCategories, path, removeControlChars, sortCategories, stylus, yaml;
fse = require("fs-extra");
path = require("path");
({URL} = require("url"));
glob = require("glob");
cheerio = require("cheerio");
moment = require("moment");
yaml = require("js-yaml");
nunjucks = require("nunjucks");
marked = require("marked");
stylus = require("stylus");
nib = require("nib");
coffee = require("coffeescript");
highlight = require("./highlight");
Logger = require("./logger");
Renderer = require("./renderer");
Generator = require("./generator");
Translator = require("./translator");
Router = require("./router");
({escapeHTML, removeControlChars, paginate, dateStrCompare, sortCategories, paginateCategories, getAbsPathFn, getURLFn} = require("./utils"));
module.exports = Hikaru = class Hikaru {
constructor(debug = false) {
this.init = this.init.bind(this);
this.clean = this.clean.bind(this);
this.generate = this.generate.bind(this);
this.registerInternalRenderers = this.registerInternalRenderers.bind(this);
this.registerInternalGenerators = this.registerInternalGenerators.bind(this);
this.registerInternalRoutes = this.registerInternalRoutes.bind(this);
this.debug = debug;
this.logger = new Logger(this.debug);
this.logger.debug("Hikaru is starting...");
process.on("exit", () => {
return this.logger.debug("Hikaru is stopping...");
});
}
init(workDir = ".", configPath) {
return fse.mkdirp(workDir).then(() => {
this.logger.debug(`Hikaru started initialization in \`${path.join(workDir, path.sep)}\`.`);
return fse.copy(path.join(__dirname, "..", "dist", "config.yml"), configPath || path.join(workDir, "config.yml"));
}).then(() => {
this.logger.debug(`Hikaru copyed \`${configPath || path.join(workDir, "config.yml")}\`.`);
return fse.mkdirp(path.join(workDir, "src"));
}).then(() => {
this.logger.debug(`Hikaru created \`${path.join(workDir, "src", path.sep)}\`.`);
return fse.copy(path.join(__dirname, "..", "dist", "archives.md"), path.join(workDir, "src", "archives", "index.md"));
}).then(() => {
this.logger.debug(`Hikaru copyed \`${path.join(workDir, "src", "archives", "index.md")}\`.`);
return fse.copy(path.join(__dirname, "..", "dist", "categories.md"), path.join(workDir, "src", "categories", "index.md"));
}).then(() => {
this.logger.debug(`Hikaru copyed \`${path.join(workDir, "src", "categories", "index.md")}\`.`);
return fse.copy(path.join(__dirname, "..", "dist", "tags.md"), path.join(workDir, "src", "tags", "index.md"));
}).then(() => {
this.logger.debug(`Hikaru copyed \`${path.join(workDir, "src", "tags", "index.md")}\`.`);
return fse.mkdirp(path.join(workDir, "doc"));
}).then(() => {
this.logger.debug(`Hikaru created \`${path.join(workDir, "doc", path.sep)}\`.`);
return fse.mkdirp(path.join(workDir, "themes"));
}).then(() => {
this.logger.debug(`Hikaru created \`${path.join(workDir, "themes", path.sep)}\`.`);
return this.logger.debug(`Hikaru finished initialization in \`${workDir}\`.`);
}).catch(this.logger.error);
}
clean(workDir = ".", configPath) {
var siteConfig;
configPath = configPath || path.join(workDir, "config.yml");
siteConfig = yaml.safeLoad(fse.readFileSync(configPath, "utf8"));
if ((siteConfig != null ? siteConfig["docDir"] : void 0) != null) {
return glob("*", {
"cwd": path.join(workDir, siteConfig["docDir"])
}, (err, res) => {
var j, len, r, results1;
if (err) {
return err;
}
results1 = [];
for (j = 0, len = res.length; j < len; j++) {
r = res[j];
results1.push(((r) => {
return fse.remove(path.join(workDir, siteConfig["docDir"], r)).then(() => {
return this.logger.debug(`Hikaru removed \`${path.join(workDir, siteConfig["docDir"], r)}\`.`);
}).catch(this.logger.error);
})(r));
}
return results1;
});
}
}
generate(workDir = ".", configPath) {
var defaultLanguage, err;
this.site = {
"workDir": workDir,
"templates": {},
"assets": [],
"pages": [],
"posts": [],
"data": []
};
configPath = configPath || path.join(this.site["workDir"], "config.yml");
this.site["siteConfig"] = yaml.safeLoad(fse.readFileSync(configPath, "utf8"));
this.site["srcDir"] = path.join(this.site["workDir"], this.site["siteConfig"]["srcDir"]);
this.site["docDir"] = path.join(this.site["workDir"], this.site["siteConfig"]["docDir"]);
this.site["themeDir"] = path.join(this.site["workDir"], "themes", this.site["siteConfig"]["themeDir"]);
this.site["themeSrcDir"] = path.join(this.site["themeDir"], "src");
try {
this.site["themeConfig"] = yaml.safeLoad(fse.readFileSync(path.join(this.site["themeDir"], "config.yml")));
} catch (error) {
err = error;
if (err["code"] === "ENOENT") {
this.logger.info("Hikaru continues with a empty theme config...");
this.site["themeConfig"] = {};
}
}
this.renderer = new Renderer(this.logger, this.site["siteConfig"]["skipRender"]);
this.generator = new Generator(this.logger);
this.translator = new Translator(this.logger);
defaultLanguage = yaml.safeLoad(fse.readFileSync(path.join(this.site["themeDir"], "languages", "default.yml")));
this.translator.register("default", defaultLanguage);
this.router = new Router(this.logger, this.renderer, this.generator, this.translator, this.site);
this.registerInternalRenderers();
this.registerInternalGenerators();
this.registerInternalRoutes();
return this.router.route();
}
registerInternalRenderers() {
var markedConfig, njkConfig, njkEnv, renderer, stylConfig;
njkConfig = Object.assign({
"autoescape": false
}, this.site["siteConfig"]["nunjucks"]);
njkEnv = nunjucks.configure(this.site["themeSrcDir"], njkConfig);
this.renderer.register([".njk", ".j2"], null, function(data, ctx) {
var template;
// For template you must give a render function.
template = nunjucks.compile(data["text"], njkEnv, data["srcPath"]);
return data["content"] = function(ctx) {
return new Promise(function(resolve, reject) {
return template.render(ctx, function(err, res) {
if (err) {
return reject(err);
}
return resolve(res);
});
});
};
});
markedConfig = Object.assign({
"gfm": true
}, this.site["siteConfig"]["marked"]);
renderer = new marked.Renderer();
renderer.heading = function(text, level) {
var escaped;
escaped = escapeHTML(text);
return `<h${level} id="${escaped}">` + `<a class="headerlink" href="#${escaped}" title="${escaped}">` + "</a>" + `${text}` + `</h${level}>`;
};
marked.setOptions({
"langPrefix": "",
"highlight": function(code, lang) {
return highlight(code, {
"lang": lang != null ? lang.toLowerCase() : void 0,
"hljs": markedConfig["hljs"] || true,
"gutter": markedConfig["gutter"] || true
});
}
});
this.renderer.register(".md", ".html", function(data, ctx) {
return new Promise(function(resolve, reject) {
var err;
try {
data["content"] = marked(data["text"], Object.assign({
"renderer": renderer
}, markedConfig));
return resolve(data);
} catch (error) {
err = error;
return reject(err);
}
});
});
stylConfig = this.site["siteConfig"]["stylus"] || {};
this.renderer.register(".styl", ".css", (data, ctx) => {
return new Promise((resolve, reject) => {
return stylus(data["text"]).use(nib()).use((style) => {
return style.define("getSiteConfig", (data) => {
var j, k, keys, len, res;
keys = data["val"].toString().split(".");
res = this.site["siteConfig"];
for (j = 0, len = keys.length; j < len; j++) {
k = keys[j];
if (!(k in res)) {
return null;
}
res = res[k];
}
return res;
});
}).use((style) => {
return style.define("getThemeConfig", (data) => {
var j, k, keys, len, res;
keys = data["val"].toString().split(".");
res = this.site["themeConfig"];
for (j = 0, len = keys.length; j < len; j++) {
k = keys[j];
if (!(k in res)) {
return null;
}
res = res[k];
}
return res;
});
}).set("filename", path.join(this.site["themeSrcDir"], data["srcPath"])).set("sourcemap", stylConfig["sourcemap"]).set("compress", stylConfig["compress"]).set("include css", true).render(function(err, res) {
if (err) {
return reject(err);
}
data["content"] = res;
return resolve(data);
});
});
});
// TODO: CoffeeScript render.
return this.renderer.register(".coffee", ".js", function(data, ctx) {});
}
registerInternalGenerators() {
this.generator.register("index", (page, posts, ctx) => {
posts.sort(dateStrCompare);
return paginate(page, posts, ctx, this.site["siteConfig"]["perPage"]);
});
this.generator.register("archives", (page, posts, ctx) => {
posts.sort(dateStrCompare);
return paginate(page, posts, ctx, this.site["siteConfig"]["perPage"]);
});
this.generator.register("categories", (page, posts, ctx) => {
var j, len, ref, results, sub;
results = [];
ref = ctx["site"]["categories"];
for (j = 0, len = ref.length; j < len; j++) {
sub = ref[j];
results = results.concat(paginateCategories(sub, page, path.dirname(page["docPath"]), this.site["siteConfig"]["perPage"], ctx));
}
results.push(Object.assign({}, page, ctx, {
"categories": ctx["site"]["categories"]
}));
return results;
});
this.generator.register("tags", (page, posts, ctx) => {
var j, len, p, ref, results, tag;
results = [];
ref = ctx["site"]["tags"];
for (j = 0, len = ref.length; j < len; j++) {
tag = ref[j];
p = Object.assign({}, page);
p["layout"] = "tag";
p["docPath"] = path.join(path.dirname(page["docPath"]), `${tag["name"]}`, "index.html");
tag["docPath"] = p["docPath"];
p["title"] = "tag";
p["name"] = tag["name"].toString();
results = results.concat(paginate(p, tag["posts"], ctx, this.site["siteConfig"]["perPage"]));
}
results.push(Object.assign({}, page, ctx, {
"tags": ctx["site"]["tags"]
}));
return results;
});
return this.generator.register(["post", "page"], (page, posts, ctx) => {
var $, a, getURL, h, hNames, headings, href, i, imgs, j, l, len, len1, len2, level, links, m, split, src, toc;
$ = cheerio.load(page["content"]);
// TOC generate.
hNames = ["h1", "h2", "h3", "h4", "h5", "h6"];
headings = $(hNames.join(", "));
toc = [];
for (j = 0, len = headings.length; j < len; j++) {
h = headings[j];
level = toc;
while (level.length > 0 && hNames.indexOf(level[level.length - 1]["name"]) < hNames.indexOf(h["name"])) {
level = level[level.length - 1]["subs"];
}
level.push({
"id": $(h).attr("id"),
"name": h["name"],
"text": $(h).text().trim(),
"subs": []
});
}
// Replace relative path to absolute path.
getURL = getURLFn(this.site["siteConfig"]["baseURL"], this.site["siteConfig"]["rootDir"]);
links = $("a");
for (l = 0, len1 = links.length; l < len1; l++) {
a = links[l];
href = $(a).attr("href");
if (new URL(href, this.site["siteConfig"]["baseURL"]).host !== getURL().host) {
$(a).attr("target", "_blank");
}
if (href.startsWith("https://") || href.startsWith("http://") || href.startsWith("//") || href.startsWith("/") || href.startsWith("#")) {
continue;
}
$(a).attr("href", path.posix.join(path.posix.sep, path.posix.dirname(page["docPath"]), href));
}
imgs = $("img");
for (m = 0, len2 = imgs.length; m < len2; m++) {
i = imgs[m];
src = $(i).attr("src");
if (src.startsWith("https://") || src.startsWith("http://") || src.startsWith("//") || src.startsWith("/") || src.startsWith("data:image")) {
continue;
}
$(i).attr("src", path.posix.join(path.posix.sep, path.posix.dirname(page["docPath"]), src));
}
page["content"] = $("body").html();
if (page["content"].indexOf("<!--more-->") !== -1) {
split = page["content"].split("<!--more-->");
page["excerpt"] = split[0];
page["more"] = split[1];
}
return Object.assign({}, page, ctx, {
"toc": toc
});
});
}
registerInternalRoutes() {
this.router.register("beforeGenerating", function(site) {
var cateName, categories, categoriesLength, category, found, j, l, len, len1, len2, len3, m, n, newCate, post, postCategories, ref, ref1, sub, subCategories;
// Generate categories
/*
[
{
"name":String,
"posts": [Post],
"subs": [
{
"name": String,
"posts": [Post],
"subs": []
}
]
}
]
*/
categories = [];
categoriesLength = 0;
ref = site["posts"];
for (j = 0, len = ref.length; j < len; j++) {
post = ref[j];
if (post["categories"] == null) {
continue;
}
postCategories = [];
subCategories = categories;
ref1 = post["categories"];
for (l = 0, len1 = ref1.length; l < len1; l++) {
cateName = ref1[l];
found = false;
for (m = 0, len2 = subCategories.length; m < len2; m++) {
category = subCategories[m];
if (category["name"] === cateName) {
found = true;
postCategories.push(category);
category["posts"].push(post);
subCategories = category["subs"];
break;
}
}
if (!found) {
newCate = {
"name": cateName,
"posts": [post],
"subs": []
};
++categoriesLength;
postCategories.push(newCate);
subCategories.push(newCate);
subCategories = newCate["subs"];
}
}
post["categories"] = postCategories;
}
categories.sort(function(a, b) {
return a["name"].localeCompare(b["name"]);
});
for (n = 0, len3 = categories.length; n < len3; n++) {
sub = categories[n];
sortCategories(sub);
}
site["categories"] = categories;
site["categoriesLength"] = categoriesLength;
return site;
});
this.router.register("beforeGenerating", function(site) {
var found, j, l, len, len1, len2, len3, m, n, newTag, post, postTags, ref, ref1, tag, tagName, tags, tagsLength;
// Generate tags.
/*
[
{
"name": String,
"posts": [Post]
}
]
*/
tags = [];
tagsLength = 0;
ref = site["posts"];
for (j = 0, len = ref.length; j < len; j++) {
post = ref[j];
if (post["tags"] == null) {
continue;
}
postTags = [];
ref1 = post["tags"];
for (l = 0, len1 = ref1.length; l < len1; l++) {
tagName = ref1[l];
found = false;
for (m = 0, len2 = tags.length; m < len2; m++) {
tag = tags[m];
if (tag["name"] === tagName) {
found = true;
postTags.push(tag);
tag["posts"].push(post);
break;
}
}
if (!found) {
newTag = {
"name": tagName,
"posts": [post]
};
++tagsLength;
postTags.push(newTag);
tags.push(newTag);
}
}
post["tags"] = postTags;
}
tags.sort(function(a, b) {
return a["name"].localeCompare(b["name"]);
});
for (n = 0, len3 = tags.length; n < len3; n++) {
tag = tags[n];
tag["posts"].sort(dateStrCompare);
}
site["tags"] = tags;
site["tagsLength"] = tagsLength;
return site;
});
this.router.register("afterGenerating", function(site) {
var all, getAbsPath, j, len, p, search;
if (!site["siteConfig"]["search"]["enable"]) {
return site;
}
// Generate search index.
search = [];
all = site["pages"].concat(site["posts"]);
getAbsPath = getAbsPathFn(site["siteConfig"]["rootDir"]);
for (j = 0, len = all.length; j < len; j++) {
p = all[j];
search.push({
"title": `${p["title"]}`,
"url": getAbsPath(p["docPath"]),
"content": p["text"]
});
}
site["data"].push({
// "srcPath": site["siteConfig"]["search"]["path"] or "search.json",
"docPath": site["siteConfig"]["search"]["path"] || "search.json",
"content": JSON.stringify(search)
});
return site;
});
return this.router.register("afterGenerating", function(site) {
var content, tmpContent;
if (!site["siteConfig"]["feed"]["enable"]) {
return site;
}
// Generate RSS feed.
tmpContent = fse.readFileSync(path.join(__dirname, "..", "dist", "atom.njk"), "utf8");
content = nunjucks.renderString(tmpContent, {
"site": site,
"siteConfig": site["siteConfig"],
"themeConfig": site["themeConfig"],
"posts": site["posts"],
"moment": moment,
"removeControlChars": removeControlChars,
"getURL": getURLFn(site["siteConfig"]["baseURL"], site["siteConfig"]["rootDir"]),
"getAbsPath": getAbsPathFn(site["siteConfig"]["rootDir"])
});
site["data"].push({
// "srcPath": site["siteConfig"]["feed"]["path"] or "atom.xml",
"docPath": site["siteConfig"]["feed"]["path"] || "atom.xml",
"content": content
});
return site;
});
}
};
}).call(this);