rsi.cli
Version:
The swiss army knife for RSI related tasks.
318 lines (299 loc) • 10.8 kB
text/typescript
import * as path from 'path';
import { readFileSync, readdirSync, fstat, lstatSync } from 'fs';
import { Readable } from 'stream';
import Handlebars from "handlebars";
import File from "vinyl";
import { Logger } from '../../../utils/Logger';
// Create reference instance
const marked = require('marked');
import { SERVICE_NAME_REGEX } from '../../../utils/regex';
let ASSESTS_PATH;
// Handelbars helpers
export function toUpperCase(value: string) {
return value.toUpperCase();
}
Handlebars.registerHelper('toUpperCase', toUpperCase);
export function extractServiceName(serviceName: string) {
if (!serviceName) return "_undefined_"
const match = serviceName.match(SERVICE_NAME_REGEX);
if (null === match) throw new Error(`service name ${serviceName} does not match the ${SERVICE_NAME_REGEX} pattern`)
return match[1];
}
Handlebars.registerHelper('extractServiceName', extractServiceName);
Handlebars.registerHelper('ifEqCi', (a, b, opts) => {
if(a.toUpperCase() === b.toUpperCase())
return opts.fn(this);
else
return opts.inverse(this);
});
Handlebars.registerHelper('ifnEqCi', (a, b, opts) => {
if(a.toUpperCase() !== b.toUpperCase())
return opts.fn(this);
else
return opts.inverse(this);
});
Handlebars.registerHelper('isMandatory', (name, required, totalLength) => {
totalLength = totalLength || 3;
const mandatory = (required && required.indexOf(name) >-1) ? 'yes' : 'no';
return new Handlebars.SafeString(mandatory + new Array(totalLength-mandatory.length).join(" "));
});
Handlebars.registerHelper('strech', (str, totalLength) => {
str = str || "";
var length = (totalLength - str.length + 1);
return new Handlebars.SafeString(str + new Array((length >= 0) ? length : 0).join(" "));
});
Handlebars.registerHelper('getValue', function(property) {
var resolveObject = (property) => {
if (property.hasOwnProperty("oneOf")) {
var refs=[];
for (var key in property.oneOf) {
var objname = /((\w+)Object|(\w+)Type)/.exec(property.oneOf[key]['#ref']);
if (null !== objname) {
refs.push("[/" + property.oneOf[key]['#ref'].replace(/\./g, '/') + '](#' + objname[0] + ')');
}
}
return new Handlebars.SafeString(refs.join("<br />"));
}
return "n/a";
}
const expandEnum = (property) => {
if (property.hasOwnProperty("enum")) {
return new Handlebars.SafeString(property.enum.join("<br />"));
}
else {
return "-";
}
}
switch (property.type.toLowerCase()) {
case "string":
return expandEnum(property);
break;
case "object":
return resolveObject(property);
break;
case "array":
if (property.hasOwnProperty("items")) {
switch (property.items.type.toLowerCase()) {
case "string":
return expandEnum(property.items);
break;
case "object":
return resolveObject(property.items);
break;
}
}
return "-";
break;
case "number":
return "[" + (property.minimum || "-inf") + ".." + (property.maximum || "inf") + "] " + (property.resolution ? "@" + property.resolution : "")
break;
default:
return "-";
}
});
Handlebars.registerHelper('safeString', function(str) {
if (str === undefined) {
return "No change log found. Does not exist.";
} else {
return new Handlebars.SafeString(str);
}
});
Handlebars.registerHelper('resourceLevelAction', function(method) {
// Handlebars.log("ERROR", "hi");
switch (method.toUpperCase()) {
case "GET":
return "retrieve a list of available elements of type"
break;
case "SUBSCRIBE":
return "subscribe to updates on a list of available elements of type"
break;
case "POST":
return "create a new element of type"
break;
default:
}
});
Handlebars.registerHelper('methodToText', function(method) {
switch (method.toUpperCase()) {
case "SUBSCRIBE":
return "WebSocket Subscription"
break;
case "GET":
case "PUT":
case "POST":
case "DELETE":
return `HTTP ${method.toUpperCase()} request`
}
});
Handlebars.registerHelper('elementLevelAction', function(method) {
switch (method.toUpperCase()) {
case "GET":
return "retrieve a particular"
break;
case "SUBSCRIBE":
return "subscribe to updates on a particular"
break;
case "PUT":
return "create a custom"
break;
case "POST":
return "update a particular"
break;
case "DELETE":
return "update a particular"
break;
default:
}
});
/**
* load templates
* @param {String} pathToTemplates the template directory (relative)
* @param {Function} cb callback function (error, template literal)
* @private
*/
export async function loadTemplates(pathToTemplates):Promise<{}> {
const names = readdirSync(pathToTemplates);
var files = names.map((it) => { return path.join(pathToTemplates, it)} );
let tmpls = {};
files.map((tmpl,idx) => {
if(names[idx].indexOf("$") !== 0) return
tmpls[names[idx].split('.hbs')[0]] = readFileSync(files[idx], "utf-8");
});
return tmpls;
};
/**
* renderes markdown from rsi schema
*
* @param {Object} data data object representation
* @param {string} pathToTemplates path to all templates used to render
*/
// export async function compileMD (data:Object, pathToTemplates:string) {
export async function compileMD (data: Object, pathToTemplates:string) {
if (!data || null === data) throw new Error("missing data");
if (!pathToTemplates) throw new Error("missing pathToTemplates");
const templates = await loadTemplates(pathToTemplates);
var serviceOverview = templates["$index.md"];
return Handlebars.compile(serviceOverview)(data);
}
// export async function renderDoc(schemaPath:string, packagePath: string, changelogPath?: string):Promise<Readable> {
export async function renderDoc(obj: Object, packageInfo: Object, _compileHTML: boolean):Promise<Readable> {
return new Promise<Readable>((resolve, reject) => {
// ASSESTS_PATH = ((bundle) ? path.join(path.dirname(__filename), "../../../../assets/documentation.bundle.templates") : path.join(path.dirname(__filename), "../../../../assets/documentation.templates"))
ASSESTS_PATH = path.join(path.dirname(__filename), "../../../../assets/documentation.bundle.templates")
marked.setOptions({
renderer: new marked.Renderer(),
highlight: function(code) {
return require('highlight.js').highlightAuto(code).value;
},
// pedantic: false,
gfm: true,
// breaks: false,
// sanitize: false,
// smartLists: true,
// smartypants: false,
// xhtml: false
});
// create an returnable ReadStream that is in object mode, because we want to put File objects on it
const outStream = new Readable({ objectMode: true });
outStream._read = () => {}; // implemenmts a read() function
let bundlePackages = { packageInfo: packageInfo, serviceDefinitions: [], changelog: ((obj['changelog']) ? readFileSync(obj['changelog'], "utf-8") : undefined) };
for (let prop in obj) {
if (prop !== 'changelog') {
bundlePackages.serviceDefinitions.push({
spec: JSON.parse(readFileSync(obj[prop].serviceDefinition, 'utf-8')),
meta: JSON.parse(readFileSync(obj[prop].package, 'utf-8')),
changelog: obj[prop].changelog ? readFileSync(obj[prop].changelog, "utf-8") : undefined
});
}
}
compileMD(bundlePackages, ASSESTS_PATH).then( async (data) => {
if (_compileHTML) {
const html = await compileHTML({html: marked(data), ...bundlePackages}, ASSESTS_PATH);
outStream.push(new File({
cwd: '/',
base: '/',
path: `/${packageInfo['name'] || "index"}_${packageInfo['version'] || "XYZ"}.html`,
contents: Buffer.from(html.replace(new RegExp("<table>", "g"), "<table class=\"table\">") ,"utf-8")
}));
// css files
outStream.push(new File({
cwd: '/',
base: '/',
path: "/styles/bootstrap.3.3.5.min.css",
contents: readFileSync(path.join(ASSESTS_PATH, "styles", "bootstrap.3.3.5.min.css"))
}));
outStream.push(new File({
cwd: '/',
base: '/',
path: "/styles/doc.css",
contents: readFileSync(path.join(ASSESTS_PATH, "styles", "doc.css"))
}));
outStream.push(new File({
cwd: '/',
base: '/',
path: "/styles/hljs_monokai_sublime.min.css",
contents: readFileSync(path.join(ASSESTS_PATH, "styles", "hljs_monokai_sublime.min.css"))
}));
outStream.push(new File({
cwd: '/',
base: '/',
path: "/styles/vueFont.css",
contents: readFileSync(path.join(ASSESTS_PATH, "styles", "vueFont.css"))
}));
outStream.push(new File({
cwd: '/',
base: '/',
path: "/styles/vueMat.css",
contents: readFileSync(path.join(ASSESTS_PATH, "styles", "vueMat.css"))
}));
outStream.push(new File({
cwd: '/',
base: '/',
path: "/styles/vueTheme.css",
contents: readFileSync(path.join(ASSESTS_PATH, "styles", "vueTheme.css"))
}));
// javascript files
outStream.push(new File({
cwd: '/',
base: '/',
path: "/js/vue.js",
contents: readFileSync(path.join(ASSESTS_PATH, "js", "vue.js"))
}));
outStream.push(new File({
cwd: '/',
base: '/',
path: "/js/vueMat.js",
contents: readFileSync(path.join(ASSESTS_PATH, "js", "vueMat.js"))
}));
} else {
// publish schema.json
outStream.push(new File({
cwd: '/',
base: '/',
path: `/${packageInfo['name'] || "index"}_${packageInfo['version'] || "XYZ"}.md`,
contents: Buffer.from(data ,"utf-8")
}));
}
// image
outStream.push(new File({
cwd: '/',
base: '/',
path: "/curlies.png",
contents: readFileSync(path.join(ASSESTS_PATH, "curlies.png"))
}));
resolve(outStream);
})
})
}
export async function compileHTML(data, pathToTemplates):Promise<string> {
if (!data || null === data) throw new Error("missing data");
if (!pathToTemplates) throw new Error("missing pathToTemplates");
let html = "can not find $index.html template"
try {
const templates = await loadTemplates(pathToTemplates);
html = templates["$index.html"];
} catch (error) {
Logger.error("compileHTML(): Can not load templates")
}
return Handlebars.compile(html)(data);
}