hikaru-coffee
Version:
A static site generator that generates routes based on directories naturally.
459 lines (422 loc) • 16.6 kB
JavaScript
// 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);