var fs = require("fs"); var path = require("path"); var program = require("commander"); var lodash = require("lodash"); var jsyaml = require("js-yaml"); var remark = require("remark"); var frontMatter = require("remark-frontmatter"); var mdCompact = require("mdast-util-compact"); var si = require("./SourceInfoClasses"); // "Aggregate" data collected over the whole file set. var aggData = {}; var toolsFolderName = "tools"; var configFileName = "doctool.config.json"; var defaultFolder = path.resolve("docs"); var sourceInfoFolder = path.resolve("docs", "sourceinfo"); function updatePhase(mdCache, aggData) { var errorMessages; toolList.forEach(toolName => { errorMessages = []; console.log(`Tool: ${toolName}`); toolModules[toolName].processDocs(mdCache, aggData, errorMessages); }); var filenames = Object.keys(mdCache); for (var i = 0; i < filenames.length; i++) { var pathname = filenames[i]; var tree = mdCache[pathname].mdOutTree; var original = mdCache[pathname].mdInTree; if (program.json) { let filename = path.basename(pathname); console.log(`\nFile "${filename}" before processing:`); console.log(JSON.stringify(original)); console.log(`\nFile "${filename}" after processing:`); console.log(JSON.stringify(tree)); } if (!lodash.isEqual(tree, original)) { if (program.verbose) { console.log(`Modified: ${pathname}`); } fs.writeFileSync(filenames[i], remark().use(frontMatter, {type: 'yaml', fence: '---'}).data("settings", {paddedTable: false, gfm: false}).stringify(tree)); } } } function deepCopy(obj) { // Despite how it looks, this technique is apparently quite efficient // because the JSON routines are implemented in C code and faster // than the equivalent JavaScript loops ;-) return JSON.parse(JSON.stringify(obj)); } function minimiseTree(tree) { let minPropsTree = JSON.parse(JSON.stringify(tree, (key, value) => key === "position" ? undefined : value)); mdCompact(minPropsTree); return minPropsTree; } function loadToolModules() { var mods = {}; var toolsFolderPath = path.resolve(__dirname, toolsFolderName); var modFiles = fs.readdirSync(toolsFolderPath); for (var i = 0; i < modFiles.length; i++) { var modPath = path.resolve(toolsFolderPath, modFiles[i]) if (path.extname(modPath) === ".js") { var toolName = path.basename(modPath, ".js"); mods[toolName] = require(modPath); } } return mods; } function loadConfig() { var configFilePath = path.resolve(__dirname, configFileName) return JSON.parse(fs.readFileSync(configFilePath)); } function getAllDocFilePaths(docFolder, files) { var items = fs.readdirSync(docFolder); for (var i = 0; i < items.length; i++) { var itemPath = path.resolve(docFolder, items[i]); var itemInfo = fs.statSync(itemPath); if (itemInfo.isFile()){ files.push(itemPath); } else if (itemInfo.isDirectory()) { getAllDocFilePaths(itemPath, files); } } } function initMdCache(filenames) { var mdCache = {}; for (var i = 0; i < filenames.length; i++) { var pathname = filenames[i]; mdCache[pathname] = {}; var src = fs.readFileSync(pathname); var tree = remark().use(frontMatter, ["yaml"]).parse(src); mdCache[pathname].mdInTree = minimiseTree(tree); mdCache[pathname].mdOutTree = minimiseTree(tree); } return mdCache; } function initClassInfo(aggData) { var yamlFilenames = fs.readdirSync(path.resolve(sourceInfoFolder)); aggData.classInfo = {}; yamlFilenames.forEach(yamlFilename => { var classYamlText = fs.readFileSync(path.resolve(sourceInfoFolder, yamlFilename), "utf8"); var classYaml = jsyaml.safeLoad(classYamlText); if (program.verbose) { console.log(classYaml.items[0].name); } aggData.classInfo[classYaml.items[0].name] = new si.ComponentInfo(classYaml); }); } program .usage("[options] ") .option("-p, --profile [profileName]", "Select named config profile", "default") .option("-j, --json", "Output JSON data for Markdown syntax tree") .option("-v, --verbose", "Log doc files as they are processed") .option("-t, --timing", "Output time taken for run") .parse(process.argv); var startTime; if (program.timing) { startTime = process.hrtime(); } var sourcePath; if (program.args.length === 0) { sourcePath = defaultFolder; } else { sourcePath = path.resolve(program.args[0]); } var sourceInfo = fs.statSync(sourcePath); var toolModules = loadToolModules(); var config = loadConfig(); aggData['config'] = config; var toolList; if (config.profiles[program.profile]){ toolList = config.profiles[program.profile]; var toolListText = toolList.join(", "); console.log(`Using '${program.profile}' profile: ${toolListText}`); } else { console.log(`Aborting: unknown profile '${program.profile}`); return 0; } var files = []; if (sourceInfo.isDirectory()) { getAllDocFilePaths(sourcePath, files); aggData['rootFolder'] = path.dirname(sourcePath); } else if (sourceInfo.isFile()) { files = [ sourcePath ]; } files = files.filter(filename => (filename !== undefined) && (path.extname(filename) === ".md") && (filename !== "README.md") ); var mdCache = initMdCache(files); console.log("Loading source data..."); initClassInfo(aggData); console.log("Updating Markdown files..."); updatePhase(mdCache, aggData); if (program.timing) { var endTime = process.hrtime(startTime); console.log(`Run complete in ${endTime[0]} sec`); }