wbs-markdown
Version:
Work Breakdown Structure (WBS) in markdown format for software development projects.
174 lines (159 loc) • 6.57 kB
JavaScript
// 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
};