const PJSON = require("./pseudo_json") let fs = require("fs"), mold = new (require("mold-template")) let {transformTokens} = require("./transform") let CodeMirror = require("codemirror/addon/runmode/runmode.node.js") require("codemirror/mode/javascript/javascript.js") require("codemirror/mode/xml/xml.js") require("codemirror/mode/css/css.js") require("codemirror/mode/htmlmixed/htmlmixed.js") let file, epub = false for (let arg of process.argv.slice(2)) { if (arg == "--epub") epub = true else if (file) throw new Error("Multiple input files") else file = arg == "-" ? "/dev/stdin" : arg } if (!file) throw new Error("No input file") let chapter = /^\d{2}_([^\.]+)/.exec(file) || [null, "hints"] let {tokens, metadata} = transformTokens(require("./markdown").parse(fs.readFileSync(file, "utf8"), {}), { defined: epub ? ["book", "html"] : ["interactive", "html"], strip: epub ? "hints" : "", takeTitle: true, index: false }) let close = epub ? "/" : "" let chapters = fs.readdirSync(__dirname + "/..") .filter(file => /^\d{2}_\w+\.md$/.test(file)) .sort() .map(file => /^\d{2}_(\w+)\.md$/.exec(file)[1]) if (epub) chapters.push("hints") function escapeChar(ch) { return ch == "<" ? "<" : ch == ">" ? ">" : ch == "&" ? "&" : """ } function escape(str) { return str.replace(/[<>&"]/g, escapeChar) } function highlight(lang, text) { if (lang == "html") lang = "text/html" let result = "" CodeMirror.runMode(text, lang, (text, style) => { let esc = escape(text) result += style ? `${esc}` : esc }) return result } function maybeSplitInlineCode(html) { if (html.length <= 16) return html return html.replace(/[.\/](?!\/)/g, `$&`) } const seenIDs = Object.create(null) function anchor(token) { let id = token.hashID if (!id || id in seenIDs) return "" seenIDs[id] = true return `` } function attrs(token) { return token.attrs ? token.attrs.map(([name, val]) => ` ${name}="${escape(String(val))}"`).join("") : "" } let linkedChapter = null let renderer = { fence(token) { let config = /\S/.test(token.info) ? PJSON.parse(token.info) : {} if (config.hidden) return ""; let lang = config.lang || "javascript" return `\n\n${anchor(token)}${highlight(lang, token.content.trimRight())}` }, hardbreak() { return `` }, softbreak() { return " " }, text(token) { let {content} = token if (linkedChapter != null) content = content.replace(/\?/g, linkedChapter) return escape(content) }, paragraph_open(token) { return `\n\n${anchor(token)}` }, paragraph_close() { return "

" }, heading_open(token) { return `\n\n<${token.tag}${attrs(token)}>${anchor(token)}` }, heading_close(token) { return `` }, bullet_list_open(token) { return `\n\n` }, bullet_list_close() { return `` }, ordered_list_open(token) { return `\n\n` }, ordered_list_close() { return `\n\n` }, list_item_open() { return "\n\n
  • " }, list_item_close() { return "
  • " }, table_open() { return "\n\n" }, table_close() { return "\n\n
    " }, tbody_open() { return "" }, tbody_close() { return "" }, tr_open() { return "\n\n" }, tr_close() { return "\n\n" }, td_open() { return "" }, td_close() { return "" }, html_block(token) { return token.content }, code_inline(token) { return `${maybeSplitInlineCode(escape(token.content))}` }, strong_open() { return "" }, strong_close() { return "" }, em_open() { return "" }, em_close() { return "" }, sub_open() { return "" }, sub_close() { return "" }, sup_open() { return "" }, sup_close() { return "" }, link_open(token) { let alt = token.attrGet("alt"), href= token.attrGet("href") let maybeChapter = /^(\w+)(#.*)?$/.exec(href) if (maybeChapter && chapters.includes(maybeChapter[1])) { let number = "" if (maybeChapter[1] != "hints") { linkedChapter = chapters.indexOf(maybeChapter[1]) number = pad(linkedChapter) + "_" } href = number + maybeChapter[1] + (epub ? ".xhtml" : ".html") + (maybeChapter[2] || "") } return `` }, link_close() { linkedChapter = null; return "" }, inline(token) { return renderArray(token.children) }, meta_figure(token) { let {url, alt, chapter} = token.args[0] let className = !chapter ? null : "chapter" + (chapter == "true" ? "" : " " + chapter) return `${escape(alt)}` }, meta_quote_open() { return "\n\n
    " }, meta_quote_close(token) { let {author, title} = token.args[0] || {} return (author ? `\n\n
    ${escape(author)}${title ? `, ${escape(title)}` : ""}
    ` : "") + "\n\n
    " }, meta_keyname_open() { return "" }, meta_keyname_close() { return "" }, meta_hint_open() { return "\n\n
    " }, meta_hint_close() { return "\n\n
    " } } function renderArray(tokens) { let result = "" for (let i = 0; i < tokens.length; i++) { let token = tokens[i], f = renderer[token.type] if (!f) throw new Error("No render function for " + token.type) result += f(token) } return result } function pad(n) { return (n < 10 ? "0" : "") + n } metadata.content = renderArray(tokens) let index if (chapter && (index = chapters.indexOf(chapter[1])) > -1) { metadata.chap_num = index if (index > 0) metadata.prev_link = `${pad(index - 1)}_${chapters[index - 1]}` if (index < chapters.length - 1) metadata.next_link = `${pad(index + 1)}_${chapters[index + 1]}` } let template = mold.bake("chapter", fs.readFileSync(__dirname + `/${epub ? "epub_" : ""}chapter.html`, "utf8")) console.log(template(metadata))