bridge-node-server/node_server/tools/docgen/docgen.js
Martin Donnelly 57bd6c8e6a init
2018-06-24 21:15:03 +01:00

345 lines
10 KiB
JavaScript

/**
* Code generation of the AngularJS client from a swagger file
*/
'use strict';
var SwaggerParser = require('swagger-parser');
var Handlebars = require('handlebars');
var handlebarsHelpers = require('handlebars-helpers')({
handlebars: Handlebars
});
var _ = require('lodash');
var fs = require('fs');
var os = require('os');
//
// Define the exports
//
module.exports = {
Swagger2AsciiDoc: docgen
};
/**
* Function to generate the AsciiDoc documents from a Swagger definition file
*
* @param {String | Object} src - The swagger API as Object or path to file
* @param {Object} options - The docgen options
* @property {Object} options.parser - Swagger-parser options
* @property {Object} options.templates - Moustache templates
*
* @returns {Promise} Promise that is resolved/rejected on success/failure
*/
function docgen(src, options) {
var p1 = new Promise(function(resolve, reject) {
//
// Register some Handlebars helpers
//
registerHandlebarsHelpers();
//
// Register Handlebars partials
//
var handlebarsOpts = {noEscape: true};
registerHandlebarsPartials(options, handlebarsOpts);
//
// Parse the swagger file, then use it as the data for applying the functions
//
var promise = SwaggerParser.parse(src);
promise.then(function(swagger) {
//
// Define all the data we need for the templates
//
var templateData = {
paths: options.pathsName,
definitions: options.definitionsName,
swagger: swagger
};
try {
for (var name in options.pages) {
var dest = options.dest + name + '.adoc';
console.log('Building %s -> %s', options.pages[name], dest);
var templateText = fs.readFileSync(options.pages[name], 'utf-8');
var func = Handlebars.compile(templateText, handlebarsOpts);
var result = func(templateData);
fs.writeFileSync(dest, result);
}
//
// Resolve the promise to report success
//
resolve();
} catch (err) {
reject(err);
}
});
promise.catch(function(err) {
reject(err);
});
});
return p1;
}
/**
* Beautify and format the basic javascript.
* Uses JS-Beautify and JSCS to tidy up the code
*
* @param {String} original - the original JS strings
* @param {Object} options - options for the tidY
*
* @return {String} - the beautified string
*/
function formatAndTidy(original, options) {
return original;
}
/**
* Register handlebars partials
*
* @param {Object} options - configuration options
* @param {Object} handlebarsOpts - options for handlebars
*/
function registerHandlebarsPartials(options, handlebarsOpts) {
for (var name in options.templates) {
registerHandlebarsPartial(options.templates[name], name, handlebarsOpts);
}
}
function registerHandlebarsPartial(file, name, handlebarsOpts) {
var templateText = fs.readFileSync(file, 'utf-8');
var template = Handlebars.compile(templateText, handlebarsOpts);
Handlebars.registerPartial(name, template);
}
/**
* Registers handlebars helper functions
*/
function registerHandlebarsHelpers() {
Handlebars.registerHelper('swaggerType', swaggerTypeHelper);
Handlebars.registerHelper('swaggerStringify', swaggerStringifyHelper);
Handlebars.registerHelper('validIdentifier', validIdentiferHelper);
Handlebars.registerHelper('multilineComment', multilineCommentHelper);
Handlebars.registerHelper('refToLink', refToLink);
Handlebars.registerHelper('swaggerRef', swaggerRef);
Handlebars.registerHelper('swaggerStringify', swaggerStringify);
Handlebars.registerHelper('ifUndefined', ifUndefined);
Handlebars.registerHelper('escapeAsciidocTable', escapeAsciidocTable);
}
/**
* Handlebars helper function to find the best 'type' for a parameter. It
* is either the explicit `type` field, or the name of the `$ref` (with the
* '#/definitions/' stripped off).
*
* @param {Object} item - the item to look at.
*
* @return {Object} - a safestring of the type string
*/
function swaggerTypeHelper(item) {
var type = '';
if (item.hasOwnProperty('$ref')) {
var ref = item.$ref;
type = ref.replace('#/definitions/', '');
type += 'T';
} else if (item.hasOwnProperty('type')) {
type = item.type;
// If we have an array, then find out what the type of items in the
// array is.
if (type === 'array') {
if (Array.isArray(item.items)) {
return swaggerTypeHelper(item.items[0]);
} else {
return swaggerTypeHelper(item.items);
}
}
}
return new Handlebars.SafeString(type);
}
/**
* Simple helper to stringify a value and return it.
*
* @return {Object} - a stringified version of the string
*/
function swaggerStringifyHelper() {
return new Handlebars.SafeString(JSON.stringify(this));
}
/**
* Converts a string into a valid JS identifier (e.g. function name) by
* removing any invalid characters.
*
* @param {String} string - The string to validate/convert
*
* @return {Object} - a valid identifier
*/
function validIdentiferHelper(string) {
var result = string.replace(/([^a-zA-Z0-9]+)/g, '');
return new Handlebars.SafeString(result);
}
/**
* Converts a schema $ref to an asciidoc link to that schema name
*
* @param {String} string - The string to validate/convert
*
* @return {Object} - a valid identifier
*/
function refToLink(string) {
if (string && string.length) {
var result = /#\/.*\/(.*)/.exec(string)[1];
result = '<<' + result + '>>';
return new Handlebars.SafeString(result);
} else {
return new Handlebars.SafeString('!!UNDEFINED!!');
}
}
/**
* Converts a long line into a number of comment strings across several lines.
*
* @param {String} string - The string to validate/convert
*
* @return {Object} - a multi-line comment
*/
function multilineCommentHelper(string) {
var lineLength = 65;
var result = ' *';
var currentLineLength = 2;
//
// Swap CRLF or LF with a known character sequence. This sequence is
// space separated so it will be split on its own, even if it was tight
// against another word.
//
var CRLFReplacement = '~[NEWLINE]~';
string = string.replace(/\r\n/g, ' ' + CRLFReplacement + ' ');
string = string.replace(/\n/g, ' ' + CRLFReplacement + ' ');
//
// Split the string by spaces, so we will line wrap by word (rather than
// at an arbitrary point)
//
var splitLine = string.split(/\s+/);
//
// Go through all the words, adding them to the line, and inserting new
// comment lines as the line length becomes too long.
//
for (var i = 0; i < splitLine.length; ++i) {
var word = splitLine[i];
if (!word) {
//
// Sometimes split will give a null entry (e.g. if the string
// ends on the split character). Just ignore it.
//
continue;
} else if (word === CRLFReplacement) {
//
// Fixup the CRLF replacement to put in a CRLF and the start of the
// next line. Then move on to the next word.
//
result += os.EOL + ' *';
currentLineLength = 2;
continue;
} else if (currentLineLength + word.length + 1 > lineLength) {
//
// If we would exceed the line length, start a new line.
//
result += os.EOL + ' *';
currentLineLength = 2;
}
//
// Add the current word to the current line.
//
result += ' ' + word;
currentLineLength += word.length + 1;
}
return new Handlebars.SafeString(result);
}
/**
* Follows the (internal) reference and sets that object as the context
*
* @param {Object} context - The context passed into the function
* @param {Object} options - The options object
*
* @returns {Object} - The result of the options.fn, or an error string
*/
function swaggerRef(context, options) {
//console.log("Context: ", context, options);
if (!context) {
return Handlebars.SafeString('REF LOOKUP FAILED');
}
var type = /#\/(.*)\//.exec(context)[1];
var ref = /#\/.*\/(.*)/.exec(context)[1];
if (
options.data.root.swagger.hasOwnProperty(type) &&
options.data.root.swagger[type].hasOwnProperty(ref)
) {
var newContext = options.data.root.swagger[type][ref];
return options.fn(newContext);
} else {
return Handlebars.SafeString('REF LOOKUP FAILED');
}
};
/**
* Stringify's an object (if it isn't undefined)
*
* @param {Object} context - The string
*
* @return {Object} - a valid identifier
*/
function swaggerStringify(context) {
return new Handlebars.SafeString(
_.isUndefined(context) ? '' : JSON.stringify(context)
);
}
/**
* Simple helper to escape asciidoc table separators
*
* @param {String} string - The string to validate/convert
* @return {Object} - a stringified version of the string
*/
function escapeAsciidocTable(string) {
const asciiDocChars = /([|])/g;
const result1 = string.replace(asciiDocChars, '\\$1');
//
// Also remove incorrect bolding
//
const escapeBold = /(\*\S*\*)/g;
const result2 = result1.replace(escapeBold, '\\$1');
//console.log('escapeAsciidoc: ', string);
//console.log(' ->: ', result);
return new Handlebars.SafeString(result2);
}
/**
* Checks if an object exists (even if it is 'empty'
*
* @param {Object} conditional - The object we are testing
* @param {Object} options - The options object
*
* @return {Object} - the result
*/
function ifUndefined(conditional, options) {
if (_.isUndefined(conditional)) {
return options.fn(this);
} else {
return options.inverse(this);
}
}