UNPKG

wbs-markdown

Version:

Work Breakdown Structure (WBS) in markdown format for software development projects.

174 lines (159 loc) 6.57 kB
#! /usr/bin/env node // Functions and behavior for compiling the report. // RESOURCES: // - https://www.npmjs.com/package/commander // - https://developer.atlassian.com/blog/2015/11/scripting-with-node/ // - https://github.com/tj/commander.js/tree/master/examples // - https://nodejs.org/api/fs.html // - https://nodejs.org/api/path.html // - https://github.com/paulmillr/chokidar var path = require('path'); var chokidar = require('chokidar'); var _ = require('lodash'); var md5 = require('md5') var version = require("../version").version; var fileUtils = require("../file-utils"); var settings = require("../settings"); /** * Given a config object, apply defaults supplied in ../config/default.json. * This function returns a new merged config object. * @param {Object} config The user-supplied .wbsm-config.json file */ function applyConfigDefaults(config) { var defaultConfig = JSON.parse(fileUtils.read(path.join(__dirname, '../config/default.json'))); return _.merge({}, defaultConfig, config); } /** * Given a config object, apply a computed MD5 hash as a "document_id" so it * will be accessible to the report at runtime. * This function returns a new config merged object. * @param {Object} config The user-supplied .wbsm-config.json file * @param {String} md5Hash Computed MD5 hash of the full project filename */ function applyMd5HashToConfigDefaults(config, md5Hash) { return _.merge({}, config, {document_id: md5Hash}) } /** * Convert markdown to HTML, respecting any markdown-it plugins and html plugins * @param {String} mdContents The contents of the markdown file * @param {Object} config The wbsm config file values * @returns {String} */ function mdToHtml(mdContents, config) { // create markdown instance with given options var md = require('markdown-it')(config.markdownItOptions); // load any plugins config.markdownItPlugins.forEach(function(plugin) { var pluginFunction = require(plugin.require); md.use(pluginFunction); }); // render var html = md.render(mdContents); // run post-render plugins config.htmlPlugins.forEach(function(plugin) { var pluginFunction = require(plugin.require); html = pluginFunction(html, config); }); // return final html return html; } /** * Get html need to render js and css links and blocks in the document <head> element * @param {Object} config The wbsm config file values * @returns {string} */ function getAssets(config) { var lines = []; // gather script, link, and style tags config.assets.forEach(function(asset) { if (asset.script) { lines.push('<script src="' + _.escape(asset.script) + '"></script>'); } else if (asset.stylesheet) { lines.push('<link rel="stylesheet" href="' + _.escape(asset.stylesheet) + '">'); } else if (asset.style) { var cssPath = asset.style.match(/^\.\/components\//) ? path.join(__dirname, asset.style) : asset.style; var css = fileUtils.read(cssPath); lines.push('<style>\n' + css + '\n</style>'); } else if (asset.js) { var jsPath = asset.js.match(/^\.\/components\//) ? path.join(__dirname, asset.js) : asset.js; var js = fileUtils.read(jsPath); lines.push('<script>\n' + js + '\n</script>'); } }); return lines.join('\n'); } /** * Take the final html file and populate placeholders * @param {String} template The base HTML file * @param {Object} config The wbsm config file values * @param {Object} data An object containing the replacement values * @property {String} data.content The content generated by mdToHtml() * @property {String} data.assets The assets html generated by getAssets() * @property {String} data.version The wbsm version as stored in version.js * @property {String} data.dateText The report generation date * @returns {*} */ function compileHtml(template, config, data) { var report = template; report = report.replace(/{{content}}/, data.content); report = report.replace(/{{assets}}/, data.assets); // stamp the wbsm version into the report report = report.replace(/{{version}}/, data.version); // replace "{{reportDate}}" with a text string of the date and time when generated report = report.replace(/{{reportDate}}/, data.dateText); // replace "{{reportTitle}}" with the value loaded from the config file report = report.replace(/{{reportTitle}}/, config.reportTitle); return report; } // Read the contents of a file out synchronously and return the file contents. function generate(markdownFilename, reportFilename) { // read markdown file contents var mdContents = fileUtils.read(markdownFilename); if (typeof mdContents === 'undefined') { console.error('No markdown contents found!'); process.exit(1); } // get the full path of the filename. Use to generate an MD5 hash to ID this // file. Used for detecting when localstorage settings don't apply. var md5Filename = md5(path.join(__dirname, markdownFilename)) var renderTemplateFilename = settings.getTemplateFilename(); var dateText = new Date().toLocaleString() var config = JSON.parse(fileUtils.read("./.wbsm-config.json")); config = applyConfigDefaults(config); config = applyMd5HashToConfigDefaults(config, md5Filename) var content = mdToHtml(mdContents, config); var assets = getAssets(config); var template = fileUtils.read(renderTemplateFilename); var report = compileHtml(template, config, {version, dateText, assets, content}); fileUtils.write(report, reportFilename); } // Perform a single file watch. When the file changes, automatically regenerate // the output report file. // // `markdownFilename` - The filename of the input markdown file. // `outputReportFilename` - The filename of the output report file. function watching(markdownFilename, outputReportFilename) { console.log("watching markdown filename", markdownFilename) // watch the markdown file for changes and regenerate the chokidar.watch(markdownFilename).on('all', (event, path) => { // if the markdown project file was deleted, stop the monitoring if (event == "unlink") { console.log("File was moved.") process.exit(0) } // console.log(event, path); // Use a short delay before trying to access the file. Otherwise // it would sometimes try to read the file too quickly and get no contents. _.delay(function() { // File event was "add" or "change". Regenerate the report. generate(markdownFilename, outputReportFilename); }, 100); }); } module.exports = { generate: generate, watching: watching };