/** * 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); } }