reclass-doc
Version:
Reclass model documentation generator.
344 lines (343 loc) • 12 kB
JavaScript
"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;