345 lines
10 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
|