UNPKG

reclass-doc

Version:

Reclass model documentation generator.

344 lines (343 loc) 12 kB
"use strict"; /** * Reclass doc generator * * @author Jiri Hybek <jiri@hybek.cz> * @license Apache-2.0 (c) 2017 Jiri Hybek */ Object.defineProperty(exports, "__esModule", { value: true }); var fs = require("fs"); var crypto = require("crypto"); var Resolver_1 = require("./Resolver"); var Util_1 = require("./Util"); /** * Inventory class */ var Inventory = (function () { /** * Inventory constructor * * @param config Configuration object * @param logger Logger facility instance */ function Inventory(config, logger) { /** Classes sub-directory */ this.classesDir = '/classes'; /** Nodes sub-directory */ this.nodesDir = '/nodes'; /** Classess cache: id => inventoryClass */ this.classCache = {}; /** Nodes cache: id => inventoryClass */ this.nodeCache = {}; /** Tree overall index */ this.treeIndex = null; this.reclassDir = config.reclassDir; this.classesDir = config.classesDir || "/classes"; this.nodesDir = config.nodesDir || "/nodes"; //Setup resolver this.resolver = new Resolver_1.Resolver(this.reclassDir); this.resolver.classesDir = this.classesDir; this.resolver.nodesDir = this.nodesDir; //Assign logger this.logger = logger; //Create root index this.index = { name: null, path: "__root__", classes: {}, docs: {}, dirs: {}, fingerprint: null, contentFingerprint: null }; } /** * Resolves class and returns inventory object * * @param classPath Relative class path * @param filename Class filename * @param className Class name for resolver */ Inventory.prototype.resolveClass = function (classPath, filename) { var className = Util_1.parseClassName(Util_1.CLASS_TYPE.CLASS, classPath); this.logger.info("Reading class '" + classPath + "' as '" + className.fullName + "'..."); var rClass = { path: classPath, filename: filename, name: className, class: null, error: null }; try { rClass.class = this.resolver.resolveClass(className.fullName); } catch (err) { rClass.error = err; this.logger.warn("Failed to load class '" + filename + "':", String(err), err.stack); } this.classCache[className.fullName] = rClass; return rClass; }; /** * Resolves node and returns inventory object * * @param nodePath Relative node path * @param filename Node filename * @param nodeName Node name for resolver */ Inventory.prototype.resolveNode = function (nodePath, filename) { var nodeName = Util_1.parseClassName(Util_1.CLASS_TYPE.NODE, nodePath); this.logger.info("Reading node '" + nodePath + "' as '" + nodeName.fullName + "'..."); var rClass = { path: nodePath, filename: filename, name: nodeName, class: null, error: null }; try { rClass.class = this.resolver.resolveNode(nodeName.fullName); } catch (err) { rClass.error = err; this.logger.warn("Failed to load node '" + filename + "':", String(err)); } this.nodeCache[nodeName.fullName] = rClass; return rClass; }; /** * Resolves document and returns inventory object * * @param docPath Relative document path * @param filename Class filename * @param name Document name */ Inventory.prototype.resolveDocument = function (docPath, filename, name) { this.logger.info("Reading document '" + docPath + "'..."); var rDoc = { path: docPath, filename: filename, name: name, contents: null, error: null, fingerprint: null }; try { rDoc.contents = fs.readFileSync(filename, { encoding: 'utf-8' }); var stat = fs.statSync(filename); rDoc.fingerprint = crypto.createHash('md5').update(name + ":" + stat.mtime.getTime()).digest('hex'); } catch (err) { rDoc.error = err; this.logger.warn("Failed to load document '" + filename + "':", String(err)); } return rDoc; }; /** * Calculates index fingerprint * * @param index Inventory index */ Inventory.prototype.fingerprintIndex = function (index) { var hashStr = [index.name]; for (var i in index.classes) hashStr.push("cls:" + index.classes[i].name); for (var i in index.docs) hashStr.push("doc:" + index.docs[i].name); for (var i in index.dirs) hashStr.push("dir:" + index.dirs[i].name); index.fingerprint = crypto.createHash('md5').update(hashStr.join(",")).digest('hex'); if (index.classes["init"]) hashStr.push("init:" + (index.classes["init"].class ? index.classes["init"].class.fingerprint : "no-class")); if (index.docs["README"]) hashStr.push("readme:" + index.docs["README"].fingerprint); index.contentFingerprint = crypto.createHash('md5').update(hashStr.join(",")).digest('hex'); }; /** * Scans directory and read classes * * @param dir Relative classes directory * @param name Directory name */ Inventory.prototype.readClasses = function (dir, name) { var path = this.reclassDir + this.classesDir + dir; this.logger.debug("Reading directory '%s'...", path); //Create index var index = { name: name, path: this.classesDir + dir, classes: {}, docs: {}, dirs: {}, fingerprint: null, contentFingerprint: null }; //Iterate over files var files = fs.readdirSync(path); for (var i = 0; i < files.length; i++) { var file = files[i]; var filePath = path + "/" + file; if (file == "." || file == "..") continue; this.logger.debug("Checking '%s'...", filePath); //Is directory? if (fs.statSync(filePath).isDirectory()) { index.dirs[file] = this.readClasses(dir + "/" + file, file); //Is file } else { //Get file extension var fileExt = new RegExp(/^(.+)\.(yaml|yml|md|YAML|YML|MD)$/); var match = fileExt.exec(file); //Skip if not matched if (!match) continue; var basename = match[1]; var ext = String(match[2]).toLowerCase(); //Is doc? if (ext == "md") { var docPath = this.classesDir + dir + (basename.toLowerCase() != "readme" ? "/" + basename : ""); var docName = (basename.toLowerCase() == "readme" ? "README" : basename); index.docs[docName] = this.resolveDocument(docPath, filePath, docName); //Is YAML } else if (ext == "yml" || ext === "yaml") { var classPath = dir + "/" + basename; index.classes[basename] = this.resolveClass(classPath, filePath); } } } //Update fingerprint this.fingerprintIndex(index); return index; }; /** * Scans directory and read nodes * * @param dir Relative nodes directory * @param name Directory name */ Inventory.prototype.readNodes = function (dir, name) { var path = this.reclassDir + this.nodesDir + dir; //Create index var index = { name: name, path: this.nodesDir + dir, classes: {}, docs: {}, dirs: {}, fingerprint: null, contentFingerprint: null }; //Iterate over files var files = fs.readdirSync(path); this.logger.debug("Reading directory '%s'...", path); for (var i = 0; i < files.length; i++) { var file = files[i]; var filePath = path + "/" + file; if (file == "." || file == "..") continue; this.logger.debug("Checking '%s'...", filePath); //Is directory? if (fs.statSync(filePath).isDirectory()) { this.readNodes(dir + "/" + file, file); //Is file } else { //Get file extension var fileExt = new RegExp(/^(.+)\.(yaml|yml|md|YAML|YML|MD)$/); var match = fileExt.exec(file); //Skip if not matched if (!match) continue; var basename = match[1]; var ext = String(match[2]).toLowerCase(); //Is doc? if (ext == "md") { var docPath = this.nodesDir + dir + (basename.toLowerCase() != "readme" ? "/" + basename : ""); var docName = (basename.toLowerCase() == "readme" ? "README" : basename); index.docs[docName] = this.resolveDocument(docPath, filePath, docName); //Is YAML } else if (ext == "yml" || ext === "yaml") { var classPath = dir + "/" + basename; index.classes[basename] = this.resolveNode(classPath, filePath); } } } //Update fingerprint this.fingerprintIndex(index); return index; }; /** * Loads classes in model */ Inventory.prototype.loadClasses = function () { this.index.dirs["classes"] = this.readClasses("", "classes"); }; /** * Loads nodes in model */ Inventory.prototype.loadNodes = function () { this.index.dirs["nodes"] = this.readNodes("", "nodes"); }; /** * Calculates fingerprint of index and all dirs * * @param index Inventory index */ Inventory.prototype.fingerprintTree = function (index) { var hashStr = [index.fingerprint]; for (var i in index.dirs) hashStr.push(this.fingerprintTree(index.dirs[i])); return crypto.createHash('md5').update(hashStr.join(",")).digest('hex'); }; /** * Loads inventory */ Inventory.prototype.load = function () { this.resolver.invalidateModified(); this.loadClasses(); this.loadNodes(); //Load readme var readmePath; if (fs.existsSync(this.reclassDir + "/README.md")) readmePath = "/README.md"; else if (fs.existsSync(this.reclassDir + "readme.md")) readmePath = "/readme.md"; if (readmePath) this.index.docs['README'] = this.resolveDocument(readmePath, this.reclassDir + readmePath, "README"); this.fingerprintIndex(this.index); this.treeIndex = this.fingerprintTree(this.index); }; /** * Returns loaded indexes */ Inventory.prototype.getIndex = function () { return this.index; }; /** * Returns tree index */ Inventory.prototype.getTreeIndex = function () { return this.treeIndex; }; /** * Returns reference to cached class * * @param className Class name */ Inventory.prototype.getClassRef = function (className) { return this.classCache[className]; }; /** * Returns reference to cached node * * @param nodeName Node name */ Inventory.prototype.getNodeRef = function (nodeName) { return this.nodeCache[nodeName]; }; return Inventory; }()); exports.Inventory = Inventory;