UNPKG

blot

Version:

The DRY documentation builder

376 lines (303 loc) 10.9 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.log = exports.jsonLike = exports.interpolator = exports.Blueprint = undefined; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); exports.src = src; exports.dest = dest; exports.glob = glob; exports.read = read; var _env = require('./env'); var env = _interopRequireWildcard(_env); var _io = require('./io'); var _hazy = require('hazy'); var _hazy2 = _interopRequireDefault(_hazy); var _protagonist = require('protagonist'); var _protagonist2 = _interopRequireDefault(_protagonist); var _hercule = require('hercule'); var _glob2 = require('glob'); var _glob3 = _interopRequireDefault(_glob2); var _path = require('path'); var _path2 = _interopRequireDefault(_path); var _log = require('./log'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * Parses, transcludes and compiles API blueprint files * and their associated fixtures. Allows compiled * API blueprints to be exported to the filesystem */ var Blueprint = exports.Blueprint = function () { /** * @param {String} markdown Valid API blueprint markdown */ function Blueprint(markdown) { var _this = this; _classCallCheck(this, Blueprint); this.markdown = markdown; var markdownFuncs = ['compile', 'fixtures', 'groups', 'interpolate', 'parse', 'transclude', 'validate']; markdownFuncs.forEach(function (method) { _this[method] = function () { return Blueprint[method](_this.markdown); }; }); } /** * Compiles API blueprint markdown data by translcuding it and then parsing it for fixtures * * @param {String} markdown * @returns {Promise} */ _createClass(Blueprint, null, [{ key: 'compile', value: function compile(markdown) { log().info('compiling'); return Blueprint.transclude(markdown).then(function (embedMd) { return Blueprint.interpolate(embedMd); }).then(function (staticMd) { return Blueprint.validate(staticMd); }).then(function (finalMd) { return Blueprint.fixtures(finalMd).then(function (fixtures) { return Object.assign(new Blueprint(markdown), { compiled: { fixtures: fixtures, markdown: finalMd } }); }); }); } /** * Extracts JSON fixtures from API blueprint markdown * * @param {String} markdown * @returns {Promise} */ // TODO - add option that allows fixtures to be parsed from hazy pool }, { key: 'fixtures', value: function fixtures(markdown) { log().info('extracting fixtures'); return new Promise(function (resolve, reject) { var fixtures = []; var jsonStrMatches = markdown.match instanceof Function ? markdown.match(jsonLike) : []; jsonStrMatches && jsonStrMatches.forEach(function (jsonStr) { try { var fixture = JSON.parse(jsonStr); fixtures.push(fixture); } catch (e) {} }); resolve(fixtures); }); } /** * Compiles API blueprint markdown data by translcuding it and then parsing it for fixtures * * @param {String} markdown * @returns {Promise} */ }, { key: 'parse', value: function parse(markdown) { log().info('parsing'); return new Promise(function (resolve, reject) { if (markdown) { _protagonist2.default.parse(markdown, function (err, blueprint) { if (!err) { resolve(blueprint); } else { log().error('invalid API blueprint', err); reject('failed to parse file as valid API blueprint: ' + err); } }); } else { reject('markdown data required'); } }); } /** * Parses API blueprint markdown data and determines if it's valid * * @param {String} markdown * @returns {Promise} */ }, { key: 'validate', value: function validate(markdown) { log().info('validating'); return new Promise(function (resolve, reject) { Blueprint.parse(markdown).then(function (obj) { resolve(markdown); }).catch(function (err) { log().error('invalid', err); reject(markdown); }); }); } /** * Scans API blueprint markdown for Hercule references and transcludes them * * @param {String} markdown * @returns {Promise} */ }, { key: 'transclude', value: function transclude(markdown) { return new Promise(function (resolve, reject) { log().info('transcluding'); if (markdown) { (0, _hercule.transcludeString)(markdown, function (err, transMd) { if (err) { reject('failed to parse hercule transclusions'); } resolve(transMd); }); } else { reject('valid markdown required for transclusion'); } }); } /** * Interpolates markdown with relevant replacements. Uses hazy's random * fixture data interpolator by default * * @param {String} markdown * @returns {Promise} */ }, { key: 'interpolate', value: function interpolate(markdown) { log().info('interpolating'); var locals = { config: env.current() // FIXME - not accessible in hazy for some reason };return new Promise(function (resolve, reject) { return resolve(interpolator(markdown, locals)); }); } /** * Attempts to marshall API blueprint content into a specific filetype * * @param {String} markdown * @param {String} filetype 'apib' or 'json' * @returns {Promise} */ }, { key: 'marshall', value: function marshall(markdown, filetype) { return new Promise(function (resolve, reject) { log().info('marshalling to ' + filetype); var filetypes = { md: function md() { return resolve(markdown); }, apib: function apib() { return resolve(markdown); }, json: function json() { return Blueprint.fixtures(markdown).then(resolve); } }; if (filetype in filetypes) { return filetypes[filetype || 'apib'](); } else { reject('unsupported filetype: ' + filetype); } }); } // TODO - juggle (obfiscates fixture data by randomizing it with hazy tokens) // TODO - struct (pretty prints your API blueprint by components (like `ps auxf`)) }]); return Blueprint; }(); /** * Parses and compiles an API blueprint from the file system * * @param {String} filepath * @returns {Promise} */ function src(filepath) { log().info('reading content from ' + filepath); return new Promise(function (resolve, reject) { if (filepath) { _io.util.fs.src(filepath, function (data) { return new Blueprint(data).compile(); }).then(resolve).catch(reject); } else { reject('Failed to read file, filepath required'); } }); } /** * Reads in and then exports API blueprints as either a static html or apib file * to the provided file path * * @param {String} markdown * @param {String} filepath * @returns {Promise} */ function dest(blueprint, filepath) { return new Promise(function (resolve, reject) { log().info('writing content to ' + filepath); var ext = _path2.default.extname(filepath).substr(1); read(blueprint).then(function (consumed) { return Blueprint.marshall(consumed.compiled.markdown, ext); }).then(function (marshalled) { return _io.util.fs.dest(filepath, marshalled); }).then(resolve).catch(reject); }); } /** * Globs and reads in valid API blueprint(s) from the filesystem and compiles them * * @param {Array|String} blueprints * @returns {Promise} */ function glob(pattern, options) { log().info('globbing against ' + pattern); return new Promise(function (resolve, reject) { (0, _glob3.default)(pattern, options, function (err, files) { if (!err) { Promise.all(files.map(function (filepath) { return src(filepath); })).then(resolve).catch(reject); } else { reject('failed to load globbed file: ' + err); } }); }); } /** * Reads in valid API blueprint(s) from Strings or * Arrays of Strings and compiles them * * @param {Array|String} blueprints * @returns {Promise} */ function read(blueprints) { log().info('reading in content'); return new Promise(function (resolve, reject) { if (blueprints instanceof Blueprint) { resolve(blueprints); } else if (blueprints.constructor === String) { resolve(new Blueprint(blueprints).compile()); } else if (blueprints instanceof Array) { Promise.all(blueprints.map(function (bp) { return new Blueprint(bp).compile(); })).then(resolve).catch(reject); } else { reject('documents must be represented as a String or Array, got ' + (typeof blueprints === 'undefined' ? 'undefined' : _typeof(blueprints))); } }); } /** * Allows developers to configure and override the default interpolator (hazy.lang.process) */ var interpolator = exports.interpolator = _hazy2.default.lang.process; /** * My janky regex for finding "JSON" objects in markdown. Need a more loveable solution, has obvious * issues with validating nested structures (regex isn't suited for this) */ var jsonLike = exports.jsonLike = /\{(.*?)\}/gm; /** * Module-level bunyan logger */ var log = exports.log = function log() { return (0, _log.logger)().child({ module: 'blueprint' }); };