UNPKG

hikaru-coffee

Version:

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

459 lines (422 loc) 16.6 kB
// Generated by CoffeeScript 2.3.1 (function() { var Category, File, Promise, Router, Site, Tag, URL, chokidar, colors, fse, getContentType, getPathFn, getURLFn, glob, http, isCurrentPathFn, moment, packageJSON, parseFrontMatter, path, yaml; fse = require("fs-extra"); path = require("path"); yaml = require("js-yaml"); glob = require("glob"); http = require("http"); ({URL} = require("url")); colors = require("colors/safe"); moment = require("moment"); chokidar = require("chokidar"); packageJSON = require("../package.json"); Promise = require("bluebird"); ({Site, File, Category, Tag} = require("./type")); ({getPathFn, getURLFn, getContentType, isCurrentPathFn, parseFrontMatter} = require("./utils")); Router = class Router { constructor(logger, renderer, processer, generator, translator, site) { this.loadFile = this.loadFile.bind(this); this.saveFile = this.saveFile.bind(this); this.processFile = this.processFile.bind(this); this.processPosts = this.processPosts.bind(this); this.processPages = this.processPages.bind(this); this.saveAssets = this.saveAssets.bind(this); this.savePosts = this.savePosts.bind(this); this.savePages = this.savePages.bind(this); this.saveFiles = this.saveFiles.bind(this); this.buildServerRoutes = this.buildServerRoutes.bind(this); this.watchTheme = this.watchTheme.bind(this); this.watchSrc = this.watchSrc.bind(this); this.unwatchAll = this.unwatchAll.bind(this); this.listen = this.listen.bind(this); this.build = this.build.bind(this); this.serve = this.serve.bind(this); this.logger = logger; this.renderer = renderer; this.processer = processer; this.generator = generator; this.translator = translator; this.site = site; this._ = {}; this.srcWatcher = null; this.themeWatcher = null; this.unprocessedSite = new Site(this.site["workDir"]); this.getURL = getURLFn(this.site.get("siteConfig")["baseURL"], this.site.get("siteConfig")["rootDir"]); this.getPath = getPathFn(this.site.get("siteConfig")["rootDir"]); } matchFiles(pattern, options) { return new Promise(function(resolve, reject) { return glob(pattern, options, function(err, res) { if (err) { return reject(err); } return resolve(res); }); }); } async readFile(file) { var raw; raw = (await fse.readFile(path.join(file["srcDir"], file["srcPath"]))); // Auto detect if a file is a binary file or a UTF-8 encoding text file. if (raw.equals(Buffer.from(raw.toString("utf8"), "utf8"))) { raw = raw.toString("utf8"); } file["text"] = raw; file["raw"] = raw; return file; } writeFile(file) { if (file["content"] !== file["raw"]) { return fse.outputFile(path.join(file["docDir"], file["docPath"]), file["content"]); } return fse.copy(path.join(file["srcDir"], file["srcPath"]), path.join(file["docDir"], file["docPath"])); } async loadFile(file) { this.logger.debug(`Hikaru is reading \`${colors.cyan(path.join(file["srcDir"], file["srcPath"]))}\`...`); file = (await this.readFile(file)); if (file["srcDir"] === this.site.get("themeSrcDir")) { file = (await this.renderer.render(file)); if (path.dirname(file["srcPath"]) !== ".") { file["type"] = "asset"; this.site.put("assets", file); } else { file["type"] = "template"; this.site.get("templates")[path.basename(file["srcPath"], path.extname(file["srcPath"]))] = file; } } else if (file["srcDir"] === this.site.get("srcDir")) { file = parseFrontMatter(file); file = (await this.renderer.render(file)); if (file["text"] !== file["raw"]) { if (file["layout"] === "post") { file["type"] = "post"; this.site.put("posts", file); } else { file["layout"] = file["layout"] || "page"; file["type"] = "page"; this.site.put("pages", file); } } else { file["type"] = "asset"; this.site.put("assets", file); } } return file; } saveFile(file) { this.logger.debug(`Hikaru is writing \`${colors.cyan(path.join(file["docDir"], file["docPath"]))}\`...`); return this.writeFile(file); } async processFile(f) { var err, fs, lang, language; lang = f["language"] || this.site.get("siteConfig")["language"]; if (!(lang in this.translator.list())) { try { language = yaml.safeLoad(fse.readFileSync(path.join(this.site.get("themeDir"), "languages", `${lang}.yml`))); this.translator.register(lang, language); } catch (error) { err = error; if (err["code"] === "ENOENT") { this.logger.warn(`Hikaru cannot find \`${lang}\` language file in your theme.`); } } } fs = (await this.processer.process(f, this.site.get("posts"), { "site": this.site.raw(), "siteConfig": this.site.get("siteConfig"), "themeConfig": this.site.get("themeConfig"), "moment": moment, "getURL": this.getURL, "getPath": this.getPath, "isCurrentPath": isCurrentPathFn(this.site.get("siteConfig")["rootDir"], f["docPath"]), "__": this.translator.getTranslateFn(lang) })); if (!(fs instanceof Array)) { return [fs]; } return fs; } async processPosts() { var i, j, l, len, processed, ps, ref, ref1, results; this.site.get("posts").sort(function(a, b) { return -(a["date"] - b["date"]); }); processed = []; ref = this.site.get("posts"); for (j = 0, len = ref.length; j < len; j++) { ps = ref[j]; ps = (await this.processFile(ps)); processed = processed.concat(ps); } this.site.set("posts", processed); results = []; for (i = l = 0, ref1 = this.site.get("posts").length; (0 <= ref1 ? l < ref1 : l > ref1); i = 0 <= ref1 ? ++l : --l) { if (i > 0) { this.site.get("posts")[i]["next"] = this.site.get("posts")[i - 1]; } if (i < this.site.get("posts").length - 1) { results.push(this.site.get("posts")[i]["prev"] = this.site.get("posts")[i + 1]); } else { results.push(void 0); } } return results; } async processPages() { var j, len, p, ps, ref, results; ref = this.site.get("pages"); results = []; for (j = 0, len = ref.length; j < len; j++) { ps = ref[j]; ps = (await this.processFile(ps)); results.push((function() { var l, len1, results1; results1 = []; for (l = 0, len1 = ps.length; l < len1; l++) { p = ps[l]; results1.push(this.site.put("pages", p)); } return results1; }).call(this)); } return results; } saveAssets() { return this.site.get("assets").map((asset) => { this.saveFile(asset); return asset; }); } savePosts() { return this.site.get("posts").map(async(p) => { p["content"] = (await this.site.get("templates")[p["layout"]]["content"](p)); this.saveFile(p); return p; }); } savePages() { return this.site.get("pages").map(async(p) => { if (!(p["layout"] in this.site.get("templates"))) { p["layout"] = "page"; } p["content"] = (await this.site.get("templates")[p["layout"]]["content"](p)); this.saveFile(p); return p; }); } saveFiles() { return this.site.get("files").map((file) => { this.saveFile(file); return file; }); } buildServerRoutes() { var f, j, key, len, ref, results; this._ = {}; ref = this.site.get("assets").concat(this.site.get("posts")).concat(this.site.get("pages")).concat(this.site.get("files")); results = []; for (j = 0, len = ref.length; j < len; j++) { f = ref[j]; key = this.getPath(f["docPath"]); this.logger.debug(`Hikaru is building route \`${colors.cyan(key)}\`...`); results.push(this._[key] = f); } return results; } watchTheme() { var event, j, len, ref, results; this.themeWatcher = chokidar.watch(path.join("**", "*"), { "cwd": this.site.get("themeSrcDir"), "ignoreInitial": true }); ref = ["add", "change", "unlink"]; results = []; for (j = 0, len = ref.length; j < len; j++) { event = ref[j]; results.push(((event) => { return this.themeWatcher.on(event, async(srcPath) => { var file, k, key, l, len1, ref1, ref2, v; this.logger.debug(`Hikaru watched event \`${colors.blue(event)}\` from \`${colors.cyan(path.join(this.site.get("themeSrcDir"), srcPath))}\``); this.site.set("pages", this.unprocessedSite.get("pages")); this.unprocessedSite.set("pages", this.site.get("pages").slice(0)); file = new File(this.site.get("docDir"), this.site.get("themeSrcDir"), srcPath); if (event !== "unlink") { file = (await this.loadFile(file)); if (file["type"] === "template") { ref1 = this.site.get("templates"); for (k in ref1) { v = ref1[k]; this.site.get("templates")[k] = (await this.renderer.render(v)); } } else if (file["type"] === "asset") { this.site.set("assets", (await Promise.all(this.site.get("assets").map((file) => { return this.renderer.render(file); })))); } } else { ref2 = ["assets", "templates"]; for (l = 0, len1 = ref2.length; l < len1; l++) { key = ref2[l]; if (this.site.del(key, file) != null) { break; } } } this.site = (await this.generator.generate("beforeProcessing", this.site)); await this.processPosts(); await this.processPages(); this.site = (await this.generator.generate("afterProcessing", this.site)); return this.buildServerRoutes(); }); })(event)); } return results; } watchSrc() { var event, j, len, ref, results; this.srcWatcher = chokidar.watch(path.join("**", "*"), { "cwd": this.site.get("srcDir"), "ignoreInitial": true }); ref = ["add", "change", "unlink"]; results = []; for (j = 0, len = ref.length; j < len; j++) { event = ref[j]; results.push(((event) => { return this.srcWatcher.on(event, async(srcPath) => { var file, key, l, len1, ref1; this.logger.debug(`Hikaru watched event \`${colors.blue(event)}\` from \`${colors.cyan(path.join(this.site.get("srcDir"), srcPath))}\``); this.site.set("pages", this.unprocessedSite.get("pages")); this.unprocessedSite.set("pages", this.site.get("pages").slice(0)); file = new File(this.site.get("docDir"), this.site.get("srcDir"), srcPath); if (event !== "unlink") { file = (await this.loadFile(file)); if (file["type"] === "asset") { this.site.set("assets", (await Promise.all(this.site.get("assets").map((file) => { return this.renderer.render(file); })))); } } else { ref1 = ["assets", "pages", "posts"]; for (l = 0, len1 = ref1.length; l < len1; l++) { key = ref1[l]; if (this.site.del(key, file) != null) { break; } } } this.site = (await this.generator.generate("beforeProcessing", this.site)); await this.processPosts(); await this.processPages(); this.site = (await this.generator.generate("afterProcessing", this.site)); return this.buildServerRoutes(); }); })(event)); } return results; } unwatchAll() { this.themeWatcher.close(); this.themeWatcher = null; this.srcWatcher.close(); return this.srcWatcher = null; } listen(ip, port) { var server; server = http.createServer(async(request, response) => { var res; if (!(request["url"] in this._)) { this.logger.log(`404: ${request["url"]}`); res = this._[this.getPath("404.html")]; if (res == null) { res = { "content": `<!DOCTYPE html>\n<html>\n <head>\n <meta charset="utf-8">\n <meta http-equiv="X-UA-Compatible" content="IE=edge">\n <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">\n <title>404 Not Found</title>\n </head>\n <body>\n <h1>404 Not Found</h1>\n <p>Hikaru v${packageJSON["version"]}</p>\n </body>\n</html>`, "docPath": this.getPath("404.html") }; } response.writeHead(404, { "Content-Type": getContentType(res["docPath"]) }); } else { this.logger.log(`200: ${request["url"]}`); res = this._[request["url"]]; response.writeHead(200, { "Content-Type": getContentType(res["docPath"]) }); } if (res["layout"] != null) { if (!(res["layout"] in this.site.get("templates"))) { res["layout"] = "page"; } response.write((await this.site.get("templates")[res["layout"]]["content"](res))); } else { response.write(res["content"]); } return response.end(); }); process.prependListener("exit", () => { server.close(); this.logger.log(`Hikaru stopped listening on http://${ip}:${port}${this.getPath()}...`); return this.unwatchAll(); }); this.logger.log(`Hikaru is listening on http://${ip}:${port}${this.getPath()}...`); if (ip !== "localhost") { server.listen(port, ip); } else { server.listen(port); } this.watchTheme(); return this.watchSrc(); } async build() { var allFiles; allFiles = ((await this.matchFiles(path.join("**", "*"), { "nodir": true, "dot": true, "cwd": this.site.get("themeSrcDir") }))).map((srcPath) => { return new File(this.site.get("docDir"), this.site.get("themeSrcDir"), srcPath); }).concat(((await this.matchFiles(path.join("**", "*"), { "nodir": true, "dot": true, "cwd": this.site.get("srcDir") }))).map((srcPath) => { return new File(this.site.get("docDir"), this.site.get("srcDir"), srcPath); })); await Promise.all(allFiles.map(this.loadFile)); this.saveAssets(); this.site = (await this.generator.generate("beforeProcessing", this.site)); await this.processPosts(); await this.processPages(); this.site = (await this.generator.generate("afterProcessing", this.site)); this.savePosts(); this.savePages(); return this.saveFiles(); } async serve(ip, port) { var allFiles; allFiles = ((await this.matchFiles(path.join("**", "*"), { "nodir": true, "dot": true, "cwd": this.site.get("themeSrcDir") }))).map((srcPath) => { return new File(this.site.get("docDir"), this.site.get("themeSrcDir"), srcPath); }).concat(((await this.matchFiles(path.join("**", "*"), { "nodir": true, "dot": true, "cwd": this.site.get("srcDir") }))).map((srcPath) => { return new File(this.site.get("docDir"), this.site.get("srcDir"), srcPath); })); await Promise.all(allFiles.map(this.loadFile)); this.unprocessedSite.set("pages", this.site.get("pages").slice(0)); this.site = (await this.generator.generate("beforeProcessing", this.site)); await this.processPosts(); await this.processPages(); this.site = (await this.generator.generate("afterProcessing", this.site)); this.buildServerRoutes(); return this.listen(ip, port); } }; module.exports = Router; }).call(this);