UNPKG

hikaru-coffee

Version:

A static site generator that generates routes based on directories naturally.

521 lines (492 loc) 19.9 kB
// 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);