'use strict' const pug = require('pug') const marked = require('marked') const fs = require('fs').promises const unit = (...args) => null const log = process.env.VERBOSE ? console.log : unit const walk = async (path, filename) => { log('[walk]', path) const type = await fs.lstat(path) if (type.isFile()) return {type: 'file', path: path, name: filename || path, tags:[]} if (!type.isDirectory()) return null const files = await fs.readdir(path) return { type: 'folder', path: path, name: filename || path, tags: [], children: await Promise.all(files.map(file => walk(`${path}/${file}`, file))) } } const ext_static = ['.css', '.js', '.otf', '.png', '.svg', '.txt', 'client', 'server'] const ext_md = ['.md', '.markdown'] const ext_pug = ['.pug', '.jade'] const suffix = file => ext => file.substring(file.length - ext.length) == ext ? ext : null const suffixl = (...l) => file => l.find(suffix(file)) const is_static = suffixl(...ext_static) const is_md = suffixl(...ext_md) const is_pug = suffixl(...ext_pug) const is_templated = f => is_md(f) /* || is_rst(f) */ const is_document = f => is_templated(f) || is_pug(f) const prefix = file => ext => file.substring(0, ext.length) == ext ? ext : null const prefixl = (...l) => file => l.find(prefix(file)) const rm_prefix = (...l) => file => file.substring(prefixl(...l)(file).length) const rm_suffix = (...l) => file => file.substring(0, file.length - suffixl(...l)(file).length) const propagate_md_layout = (tree, markdown_template) => { if (tree.type == 'file' && is_templated(tree.name)) { tree.template = markdown_template log('[propagate_md_layout]', tree ? tree.path : null, markdown_template ? markdown_template.path : null) } else if (tree.type == 'folder') { const find_md_tpl = tree.children.filter(c => c.type == 'file' && c.name == '_markdown.pug') const new_md_tpl = find_md_tpl.length > 0 ? find_md_tpl[0] : markdown_template tree.children.forEach(c => propagate_md_layout(c, new_md_tpl)) } return tree } const elagate = tree => { if (tree.type != 'folder') return tree const lh = e => log('[elagate]', e.path) && false tree.children = tree.children.filter(e => !(e.name[0] == '_') || lh(e)) tree.children.forEach(elagate) return tree } const tag_document = tree => { if (tree.type == 'file' && is_document(tree.name) && tree.name[0] != '@') { tree.tags.push('document_leaf', 'document') log('[tag_document]', tree.path, 'document_leaf') } else if (tree.type == 'folder') { tree.children.forEach(tag_document) if(tree.children.some(c => c.tags.includes('document'))) { tree.tags.push('document_branch', 'document') log('[tag_document]', tree.path, 'document_branch') } } return tree } const reference_index = indexes => tree => { if (tree.type != 'folder') return tree; const index = tree.children.find(e => indexes.includes(e.name)) if (index) { tree.index = index tree.tags.push('has_index') log('[reference_index]', tree.path, index.name) index.tags.push('is_index') } tree.children.forEach(reference_index(indexes)) return tree; } const propagate_nice_name = prefix => tree => { const without_prefix = tree.path.substring(prefix.length) const splitted = without_prefix.split('/').filter(v => v.length > 0) if (splitted.length > 0) { tree.nice_path = splitted.slice(0, -1) tree.nice_name = splitted[splitted.length - 1].split('.')[0] tree.url = without_prefix log('[propagate_nice_name]', [...tree.nice_path, tree.nice_name].join('|')) } if (tree.type == 'folder') tree.children.forEach(propagate_nice_name(prefix)) return tree } const prepare_copy = (old_prefix, new_prefix, exts) => tree => { if (tree.type == 'file' && is_static(tree.name)) { tree.generate = { cmd: 'copy', src: tree.path, out: new_prefix + rm_prefix(old_prefix)(tree.path) } log('[prepare_copy]',tree.generate.src,'->',tree.generate.out) } else if (tree.type == 'folder') { tree.children.forEach(prepare_copy(old_prefix, new_prefix, exts)) } return tree } const prepare_pug = (old_prefix, new_prefix) => tree => { if (tree.type == 'file' && is_pug(tree.name)) { tree.old_url = tree.url tree.url = rm_prefix(old_prefix)(rm_suffix(...ext_pug)(tree.path)) + '.html' tree.generate = { cmd: 'pug', src: tree.path, out: new_prefix + tree.url } log('[prepare_pug]',tree.generate.src,'->',tree.generate.out) } else if (tree.type == 'folder') { tree.children.forEach(prepare_pug(old_prefix, new_prefix)) } return tree } const prepare_md = (old_prefix, new_prefix) => tree => { if (tree.type == 'file' && is_md(tree.name)) { tree.old_url = tree.url tree.url = rm_prefix(old_prefix)(rm_suffix(...ext_md)(tree.path)) + '.html' tree.generate = { cmd: 'pug', src: tree.template.path, markdown: tree.path, out: new_prefix + tree.url } log('[prepare_md]',tree.generate.markdown,'+',tree.generate.src,'->',tree.generate.out) } else if (tree.type == 'folder') { tree.children.forEach(prepare_md(old_prefix, new_prefix)) } return tree } const prepare_folder = (old_prefix, new_prefix) => tree => { if (tree.type == 'folder') { tree.generate = { cmd: 'mkdir', out: new_prefix + rm_prefix(old_prefix)(tree.path) } log('[prepare_folder]',tree.generate.out) tree.children.forEach(prepare_folder(old_prefix, new_prefix)) } return tree } const do_folder = async tree => { if (!tree.generate || tree.generate.cmd != 'mkdir') return tree await fs.mkdir(tree.generate.out, { recursive: true }) log('[do_folder]',tree.generate.out) await Promise.all(tree.children.map(do_folder)) return tree } const do_copy = async tree => { if (tree.generate && tree.generate.cmd == 'copy') { await fs.copyFile(tree.generate.src, tree.generate.out) log('[do_copy]',tree.generate.out) } else if (tree.type == 'folder') await Promise.all(tree.children.map(do_copy)) return tree } const do_pug = (prt, root) => async tree => { prt = prt || tree root = root || tree if (tree.generate && tree.generate.cmd == 'pug') { const html = pug.renderFile(tree.generate.src, { markdown: tree.generate.markdown ? marked(await fs.readFile(tree.generate.markdown, 'utf-8')) : null, prt: prt, root: root, element: tree }) await fs.writeFile(tree.generate.out, html) log('[do_pug]',tree.generate.out) } else if (tree.type == 'folder') await Promise.all(tree.children.map(do_pug(tree,root))) return tree } const rm_tree = t => { if (t.type == 'file') { log('[do_clean] file', t.path) return fs.unlink(t.path) } return Promise .all(t.children.map(rm_tree)) .then(_ => { log('[do_clean] path', t.path) return fs.rmdir(t.path) }) } const do_clean = path => tree => walk(path).then(rm_tree).then(_ => tree) const conf = { src: './src', dest: './static'} walk(conf.src) .then(propagate_md_layout) .then(elagate) .then(tag_document) .then(reference_index(['index.md', 'index.pug'])) .then(propagate_nice_name(conf.src)) .then(prepare_copy(conf.src, conf.dest)) .then(prepare_pug(conf.src, conf.dest)) .then(prepare_md(conf.src, conf.dest)) .then(prepare_folder(conf.src, conf.dest)) //.then(v => {log(v) ; return v}) .then(do_clean(conf.dest)) .then(do_folder) .then(do_copy) .then(do_pug()) .catch(console.error)