From bd5d2776d6f150b19614c5ebb8bf7ab6787e0775 Mon Sep 17 00:00:00 2001 From: yamadapc Date: Wed, 21 May 2014 18:29:41 -0300 Subject: [PATCH 1/2] Refactor the whole code base. --- .jshintrc | 60 +++----------- bin/apidoc-markdown.js | 36 +++++++++ examples/api_data.json | 10 +-- examples/example.md | 64 ++++++++------- index.js | 59 -------------- lib/index.js | 175 +++++++++++++++++++++++++++++++++++++++++ package.json | 16 ++-- templates/default.md | 95 ++++++++++++---------- 8 files changed, 331 insertions(+), 184 deletions(-) create mode 100644 bin/apidoc-markdown.js delete mode 100755 index.js create mode 100644 lib/index.js diff --git a/.jshintrc b/.jshintrc index 6c8d52e..938b73f 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,48 +1,14 @@ { - "browser" : true, - "devel" : true, - "jquery" : true, - "couch" : false, - "es5" : false, - "node" : true, - "rhino" : false, - "prototypejs" : false, - "mootools" : false, - - "asi" : false, - "bitwise" : false, - "boss" : false, - "curly" : true, - "debug" : false, - "eqeqeq" : true, - "eqnull" : true, - "evil" : false, - "forin" : false, - "immed" : true, - "laxbreak" : false, - "maxerr" : 100, - "maxlen" : 0, - "newcap" : true, - "noarg" : true, - "noempty" : false, - "nonew" : false, - "nomen" : false, - "onevar" : false, - "passfail" : false, - "plusplus" : false, - "regexp" : false, - "undef" : false, - "unused" : "vars", - "sub" : true, - "strict" : true, - "globalstrict": true, - "white" : true, - "expr" : true, - "smarttabs": true, - "predef" : [ - "describe", - "it", - "beforeEach", - "afterEach" - ] -} \ No newline at end of file + "globals": [ + "exports" + ], + "eqnull": true, + "expr": true, + "noarg": true, + "node": true, + "trailing": true, + "unused": true, + "laxbreak": true, + "laxcomma": true, + "browser": true +} diff --git a/bin/apidoc-markdown.js b/bin/apidoc-markdown.js new file mode 100644 index 0000000..a40ded0 --- /dev/null +++ b/bin/apidoc-markdown.js @@ -0,0 +1,36 @@ +#!/usr/bin/env node +'use strict'; +var fs = require('fs'), + path = require('path'), + optimist = require('optimist'), + apidocMarkdown = require('..'); + +var join = path.join; + +var argv = optimist + .usage('Generate markdown documentation from apidoc data.' + '\n' + + 'Usage: apidoc-markdown -p [path] -o [output file]') + .demand(['output']) + .alias({ + 'path': 'p', + 'output': 'o', + 'template': 't' + }) + .describe({ + path: 'Path to generated apidoc output. Where api_data.json & api_project.json resides.', + output: 'Output file to write.', + template: 'Path to EJS template file, if not specified the default template will be used.', + prepend: 'Prepend file after TOC' + }).argv; + +var apidocRoot = argv.path || join(process.cwd(), 'doc'); + +var dataPath = join(apidocRoot, 'api_data.json'), + templatePath = argv.template, + projectPath = join(apidocRoot, 'api_project.json'), + prependPath = argv.prepend; + +var markdown = apidocMarkdown(dataPath, projectPath, templatePath, prependPath); + +fs.writeFileSync(argv.output, markdown); +console.log('Wrote apidoc-markdown to: ' + argv.output); diff --git a/examples/api_data.json b/examples/api_data.json index f399a3c..3c8111c 100644 --- a/examples/api_data.json +++ b/examples/api_data.json @@ -264,7 +264,7 @@ }, "examples": [ { - "title": " Response (example):", + "title": "Response (example):", "content": " HTTP/1.1 400 Bad Request\n {\n \"error\": \"UserNameTooShort\"\n }\n" } ] @@ -325,7 +325,7 @@ }, "examples": [ { - "title": " Response (example):", + "title": "Response (example):", "content": " HTTP/1.1 400 Bad Request\n {\n \"error\": \"UserNameTooShort\"\n }\n" } ] @@ -373,7 +373,7 @@ }, "examples": [ { - "title": " Response (example):", + "title": "Response (example):", "content": " HTTP/1.1 400 Bad Request\n {\n \"error\": \"UserNameTooShort\"\n }\n" } ] @@ -408,7 +408,7 @@ }, "examples": [ { - "title": " Response (example):", + "title": "Response (example):", "content": " HTTP/1.1 400 Bad Request\n {\n \"error\": \"UserNameTooShort\"\n }\n" } ] @@ -425,4 +425,4 @@ "url": "", "filename": "example/example.js" } -] \ No newline at end of file +] diff --git a/examples/example.md b/examples/example.md index ff609fb..9bdb67b 100644 --- a/examples/example.md +++ b/examples/example.md @@ -2,31 +2,36 @@ apidoc example project -- [User](#user) - - [Read data of a User](#read-data-of-a-user) - - [Create a new User](#create-a-new-user) - - [Change a new User](#change-a-new-user) - +- [example/example.js](#example-example-js) + - [Read data of a User](#read-data-of-a-user) + - [Create a new User](#create-a-new-user) + - [Change a new User](#change-a-new-user) - -# User +# example/example.js ## Read data of a User Compare Verison 0.3.0 with 0.2.0 and you will see the green markers with new items in version 0.3.0 and red markers with removed items since 0.2.0. - GET /user/:id +`GET /user/:id` ### Parameters +| Name | Type | Description | +|------|------|-------------| +| `id` | _String_ | The Users-ID. | + +### Success Parameters -| Name | Type | Description | -|---------|-----------|--------------------------------------| -| id | String | The Users-ID. | +#### Success 200 +| Name | Type | Description | +|------|------|-------------| +| `id` | _String_ | The Users-ID. | +| `registered` | _Date_ | Registration Date. | +| `name` | _Date_ | Fullname of the User. | ### Examples CURL example: - ``` curl -i -X POST http://localhost:3001/example -H 'Content-Type: application/json' \ @@ -37,7 +42,6 @@ CURL example: ### Success Response Success-Response (example): - ``` HTTP/1.1 200 OK { @@ -47,10 +51,10 @@ Success-Response (example): } ``` + ### Error Response Error-Response (example): - ``` HTTP/1.1 401 Not Authenticated { @@ -58,23 +62,29 @@ Error-Response (example): } ``` + ## Create a new User In this case "apiErrorStructure" is defined and used. Define blocks with params that will be used in several functions, so you dont have to rewrite them. - POST /user +`POST /user` ### Parameters +| Name | Type | Description | +|------|------|-------------| +| `name` | _String_ | Name of the User. | -| Name | Type | Description | -|---------|-----------|--------------------------------------| -| name | String | Name of the User. | +### Success Parameters -### Error Response +#### Success 200 +| Name | Type | Description | +|------|------|-------------| +| `id` | _String_ | The new Users-ID. | - Response (example): +### Error Response +Response (example): ``` HTTP/1.1 400 Bad Request { @@ -82,22 +92,21 @@ Define blocks with params that will be used in several functions, so you dont ha } ``` + ## Change a new User This function has same errors like POST /user, but errors not defined again, they were included with "apiErrorStructure" - PUT /user/:id +`PUT /user/:id` ### Parameters - -| Name | Type | Description | -|---------|-----------|--------------------------------------| -| name | String | Name of the User. | +| Name | Type | Description | +|------|------|-------------| +| `name` | _String_ | Name of the User. | ### Error Response - Response (example): - +Response (example): ``` HTTP/1.1 400 Bad Request { @@ -105,4 +114,3 @@ This function has same errors like POST /user, but errors not defined again, the } ``` - diff --git a/index.js b/index.js deleted file mode 100755 index 0e404be..0000000 --- a/index.js +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env node - -"use strict"; - -var fs = require('fs'), - ejs = require('ejs'), - _ = require('underscore'); - -var argv = require('optimist') - .usage('Generate documentation from apidoc data.\nUsage: apidoc-markdown -p [path] -o [output file]') - .demand(['path', 'output']) - .alias({ - 'path': 'p', - 'output': 'o', - 'template': 't' - }) - .describe({ - 'path': 'Path to generated apidoc output. Where api_data.json & api_project.json resides.', - 'output': 'Output file to write.', - 'template': 'Path to EJS template file, if not specified default template will be used.', - 'prepend': 'Prepend file after TOC.' - }).argv; - -ejs.filters.undef = function (obj) { - return obj ? obj : ''; -}; - -ejs.filters.mlink = function (obj) { - return (obj || '').toLowerCase().replace(/\s/g, '-'); -}; - -var tmplFile = argv.template ? argv.template : __dirname + '/templates/default.md', - apiData = JSON.parse(fs.readFileSync(argv.path + '/api_data.json')), - projData = JSON.parse(fs.readFileSync(argv.path + '/api_project.json')), - template = ejs.compile(fs.readFileSync(tmplFile).toString()); - -apiData = _.filter(apiData, function (entry) { - return entry.type; -}); - -var apiByGroup = _.groupBy(apiData, function (entry) { - return entry.group; -}); - -var apiByGroupAndName = {}; -Object.keys(apiByGroup).forEach(function (key) { - apiByGroupAndName[key] = _.groupBy(apiByGroup[key], function (entry) { - return entry.name; - }); -}); - -var data = { - project: projData, - data: apiByGroupAndName -}; - -data.prepend = argv.prepend ? fs.readFileSync(argv.prepend).toString() : null; -fs.writeFileSync(argv.output, template(data)); -console.log('Wrote apidoc-markdown template output to: ' + argv.output); \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..7652967 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,175 @@ +'use strict'; +/*! + * Dependencies + * --------------------------------------------------------------------------*/ + +var fs = require('fs'), + path = require('path'), + semver = require('semver'), + ejs = require('ejs'), + _ = require('lodash'); + +var parseJSON = JSON.parse.bind(JSON); +var compileEjs = ejs.compile.bind(ejs); + +exports = module.exports = apidocMarkdown; + +var DEFAULT_TEMPLATE_PATH = path.join(__dirname, '..', 'templates', 'default.md'); + +/** + * Reads the apidoc data files and returns a string representation of it passed + * through a markdown ejs template. + * + * @api public + * @param {String} dataPath The path to `api_data.json` + * @param {String} projectPath The path to `api_project.json` + * @param {String} [templatePath=templates/default.md] The path to the `ejs` + * template to use + * @param {String} [prependPath] The path to the `prepend` file - supposedly + * used by the template to add text before the generated documentation + * @return {String} markdown The resulting markdown string + */ + +function apidocMarkdown(dataPath, projectPath, templatePath, prependPath) { + // Read the template + var template = readParse(compileEjs, templatePath || DEFAULT_TEMPLATE_PATH); + // Read api_data.json + var data = readParse(parseJSON, dataPath); + // Read api_project.json + var project = readParse(parseJSON, projectPath); + // Read the prepend file (if provided) + var prepend = prependPath && readParse(prependPath); + + var latestData = getLatestData(data); + var fileNames = _.uniq(_.pluck(latestData, 'filename')); + var sections = _.groupBy(_.filter(latestData, function(entry) { + return !!entry.title; + }), 'filename'); + + var markdown = template({ + data: data, // `data` isn't necessary since `sections` are easier to handle + project: project, + prepend: prepend, + + files: fileNames, + sections: sections, + helpers: helpers, + _: _ + }); + + return markdown; +} + +/** + * Reads a file with utf8 encoding from the file system synchronously and tries + * parsing it with a `parse` function if provided. Both reading and parsing are + * wrapped in a try catch block, displaying a pretty error message and exit on + * errors. + * + * @api private + * @param {Function} [parse] A parsing function + * @param {String} path A path to a file + * @return {Mixed} The result of reading and parsing the file. + */ + +var readParse = exports._readParse = function readParse(parse, path) { + if(!path) { + path = parse; + parse = undefined; + } + + var file, + parsed; + + // Read the file + try { + file = fs.readFileSync(path, { + encoding: 'utf8' + }); + } catch(err) { + console.error('Error reading file "' + path + '".'); + (process.env.NODE_ENV === 'development') && console.error(err); + process.exit(1); + } + + // Stop if no parsing function was provided + if(!parse) { + return file; + } + + // Parse the file with the parsing function + try { + parsed = parse(file); + } catch(err) { + console.error('Error parsing file "' + path + '".'); + (process.env.NODE_ENV === 'development') && console.error(err); + process.exit(1); + } + + return parsed; +}; + +/** + * Gets the latest version of a api data object representation. + * i.e. Filters out old versions from the output of apidoc. + * + * @api private + * @param {Object} data An object representation of `api_data.json` + * @return {Object} version A filtered object with only the latest version nodes + */ + +var getLatestData = exports._getLatestData = function getLatestData(data) { + var versions = _.groupBy(data, 'version'), + versionNumbers = _.keys(versions), + version = latestVersion(versionNumbers); + + return versions[version]; +}; + +/** + * Takes an array of semantic version numbers and returns the greatest. + * + * @api private + * @param {Array.} versionNumbers + * @return {String} latest + */ + +var latestVersion = exports._latestVersion = function latestVersion(versionNumbers) { + return _.reduce(versionNumbers, function(chosen, versionNumber) { + return semver.gt(versionNumber, chosen) ? versionNumber : chosen; + }, _.first(versionNumbers)); +}; + +/** + * EJS helpers to be passed into the template. + * @type {Object} + */ + +var helpers = exports._helpers = { + /** + * Generates a markdown link to a section, given a section `name`. + * I'm actually not sure this regexp will work for all links. + * + * @param {String} name A section name + * @return {String} link The generated link + */ + + markdownLink: function markdownLink(name) { + return '[' + name + ']' + + '(#' + (name || '').toLowerCase().replace(/[^\w]/g, '-') + ')'; + }, + + /** + * Helper to get an API parameters description. + * + * @param {Object} param An API parameter + * @param {Boolean} [param.optional] Whether the parameter is optional + * @param {String} [param.description] The parameter's description + * @return {String} desc The pretty description + */ + + paramDescription: function paramDescription(param) { + return param.optional ? '**optional** ' + (param.description || ''): + (param.description || ''); + } +}; diff --git a/package.json b/package.json index 80889fa..4030833 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,16 @@ "name": "apidoc-markdown", "version": "0.1.3", "description": "Generate API documentation in markdown format from apidoc data.", - "main": "index.js", + "main": "lib/index.js", + "bin": { + "apidoc-markdown": "./bin/apidoc-markdown.js" + }, "directories": { "example": "examples" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, - "bin": { "apidoc-markdown" : "./index.js" }, "repository": { "type": "git", "url": "https://github.com/martinj/node-apidoc-markdown" @@ -24,8 +26,12 @@ "url": "https://github.com/martinj/node-apidoc-markdown/issues" }, "dependencies": { - "optimist": "~0.6.0", - "underscore": "~1.5.2", - "ejs": "~0.8.5" + "ejs": "^1.0.0", + "handlebars": "^2.0.0-alpha.2", + "lodash": "^2.4.1", + "mocha": "^1.18.2", + "optimist": "^0.6.1", + "semver": "^2.3.0", + "should": "^3.3.1" } } diff --git a/templates/default.md b/templates/default.md index 8a620de..51fbeee 100644 --- a/templates/default.md +++ b/templates/default.md @@ -2,71 +2,86 @@ <%= project.description %> -<% Object.keys(data).forEach(function (group) { -%> -- [<%= group %>](#<%=: group | mlink %>) - <% Object.keys(data[group]).forEach(function (sub) { -%> -- [<%= data[group][sub][0].title %>](#<%=: data[group][sub][0].title | mlink %>) - <% }); -%> +<% _.each(files, function(file) { -%> +- <%= helpers.markdownLink(file) %> +<% _.each(sections[file], function(section) { -%> + - <%= helpers.markdownLink(section.title) %> +<% }); -%> +<% }); -%> +<% if(prepend) { -%> -<% }); %> - -<% if (prepend) { -%> <%- prepend %> + <% } -%> -<% Object.keys(data).forEach(function (group) { -%> -# <%= group %> +<% _.each(files, function(file) { -%> -<% Object.keys(data[group]).forEach(function (sub) { -%> -## <%= data[group][sub][0].title %> +# <%= file %> +<% _.each(sections[file], function(entry) { -%> -<%-: data[group][sub][0].description | undef %> +## <%= entry.title %> - <%-: data[group][sub][0].type | upcase %> <%= data[group][sub][0].url %> +<%- entry.description || "" %> -<% if (data[group][sub][0].parameter && data[group][sub][0].parameter.fields.Parameter.length) { -%> -### Parameters +`<%- entry.type.toUpperCase() %> <%= entry.url %>` +<% var parameters = (entry.parameter && entry.parameter.fields.Parameter) || []; -%> +<% if(parameters.length) { -%> -| Name | Type | Description | -|---------|-----------|--------------------------------------| -<% data[group][sub][0].parameter.fields.Parameter.forEach(function (param) { -%> -| <%- param.field %> | <%- param.type %> | <%- param.optional ? '**optional**' : '' %> <%- param.description %> | -<% }); //forech parameter -%> -<% } //if parameters -%> -<% if (data[group][sub][0].examples && data[group][sub][0].examples.length) { -%> +### Parameters +| Name | Type | Description | +|------|------|-------------| +<% _.each(parameters, function(param) { -%> +| `<%- param.field %>` | _<%- param.type %>_ | <%= helpers.paramDescription(param) %> | +<% }); -%> +<% } -%> +<% var successParameters = (entry.success && entry.success.fields) -%> +<% if(successParameters) { -%> + +### Success Parameters + +<% _.each(successParameters, function(group, groupName) { -%> +#### <%= groupName %> +| Name | Type | Description | +|------|------|-------------| +<% _.each(group, function(param) { -%> +| `<%- param.field %>` | _<%- param.type %>_ | <%= helpers.paramDescription(param) %> | +<% }); -%> +<% }); -%> +<% } -%> +<% var examples = entry.examples || []; -%> +<% if(examples && examples.length) { -%> ### Examples -<% data[group][sub][0].examples.forEach(function (example) { -%> +<% _.each(examples, function(example) { -%> <%= example.title %> - ``` <%- example.content %> ``` -<% }); //foreach example -%> -<% } //if example -%> +<% }); -%> +<% } -%> +<% var successExamples = (entry.success && entry.success.examples) || []; -%> +<% if(successExamples.length) { -%> -<% if (data[group][sub][0].success && data[group][sub][0].success.examples && data[group][sub][0].success.examples.length) { -%> ### Success Response -<% data[group][sub][0].success.examples.forEach(function (example) { -%> +<% _.each(successExamples, function(example) { -%> <%= example.title %> - ``` <%- example.content %> ``` -<% }); //foreach success example -%> -<% } //if examples -%> -<% if (data[group][sub][0].error && data[group][sub][0].error.examples && data[group][sub][0].error.examples.length) { -%> +<% }); -%> +<% } -%> +<% var errorExamples = (entry.error && entry.error.examples) || []; -%> +<% if(errorExamples.length) { -%> + ### Error Response -<% data[group][sub][0].error.examples.forEach(function (example) { -%> +<% _.each(errorExamples, function(example) { -%> <%= example.title %> - ``` <%- example.content %> ``` -<% }); //foreach error example -%> -<% } //if examples -%> -<% }); //foreach sub -%> -<% }); //foreach group -%> - +<% }); -%> +<% } -%> +<% }); -%> +<% }); -%> From 43574ff387dbf2f7d84d52aa482d93ef37a433a6 Mon Sep 17 00:00:00 2001 From: yamadapc Date: Tue, 2 Feb 2016 14:52:34 -0300 Subject: [PATCH 2/2] Add donation BTC address --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6657120..5a30f4e 100644 --- a/README.md +++ b/README.md @@ -23,4 +23,6 @@ Generate from included example data apidoc-markdown -p examples -o examples/example.md -[View generated example](https://github.com/martinj/node-apidoc-markdown/blob/master/examples/example.md) \ No newline at end of file +[View generated example](https://github.com/martinj/node-apidoc-markdown/blob/master/examples/example.md) +## Donations +Would you like to buy me a beer? Send bitcoin to 3JjxJydvoJjTrhLL86LGMc8cNB16pTAF3y