├── .gitignore ├── .npmrc ├── .npmignore ├── CONTRIBUTING.md ├── lib ├── parsers │ ├── api_private.js │ ├── api_error_example.js │ ├── api_header_example.js │ ├── api_param_example.js │ ├── api_success_example.js │ ├── api_permission.js │ ├── api_group.js │ ├── api_sample_request.js │ ├── api_name.js │ ├── api_use.js │ ├── api_body.js │ ├── api_query.js │ ├── api_header.js │ ├── api_description.js │ ├── api_error.js │ ├── api_success.js │ ├── api_deprecated.js │ ├── api.js │ ├── api_version.js │ ├── api_example.js │ ├── api_define.js │ └── api_param.js ├── languages │ ├── py.js │ ├── coffee.js │ ├── clj.js │ ├── ex.js │ ├── lua.js │ ├── rb.js │ ├── default.js │ ├── pm.js │ └── erl.js ├── filters │ ├── api_error.js │ ├── api_header.js │ ├── api_success.js │ └── api_param.js ├── errors │ ├── parameter_error.js │ ├── parser_error.js │ ├── worker_error.js │ └── file_error.js ├── utils │ ├── unindent.js │ └── find_files.js ├── workers │ ├── api_body_title.js │ ├── api_query_title.js │ ├── api_structure.js │ ├── api_error_title.js │ ├── api_header_title.js │ ├── api_success_title.js │ ├── api_error_structure.js │ ├── api_header_structure.js │ ├── api_success_structure.js │ ├── api_sample_request.js │ ├── api_name.js │ ├── api_permission.js │ ├── api_param_title.js │ ├── api_use.js │ └── api_group.js ├── filter.js ├── plugin_loader.js ├── worker.js ├── index.js └── parser.js ├── .editorconfig ├── README.md ├── SECURITY.md ├── .github └── workflows │ └── validating.yml ├── LICENSE ├── hooks.md ├── CHANGELOG.md ├── test ├── util_unindent_test.js ├── parser_api_description_test.js ├── parse_source_test.js ├── parser_api_query_test.js ├── parsers │ └── api_param.js ├── parser_api_body_test.js ├── parser_api_param_test.js ├── apidoc_test.js └── worker_api_use_test.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | node_modules/ 3 | test/ 4 | .gitignore 5 | .npmignore 6 | .travis.yml 7 | npm-debug.log 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to apidoc-core 2 | 3 | * Target the `dev` branch for your PR 4 | * Add a test if possible 5 | * Use `npm test` 6 | -------------------------------------------------------------------------------- /lib/parsers/api_private.js: -------------------------------------------------------------------------------- 1 | function parse() { 2 | return { 3 | private: true 4 | }; 5 | } 6 | 7 | /** 8 | * Exports 9 | */ 10 | module.exports = { 11 | parse : parse, 12 | path : 'local', 13 | method: 'insert' 14 | }; 15 | -------------------------------------------------------------------------------- /lib/parsers/api_error_example.js: -------------------------------------------------------------------------------- 1 | // Same as @apiExample 2 | var apiParser = require('./api_example.js'); 3 | 4 | /** 5 | * Exports 6 | */ 7 | module.exports = { 8 | parse : apiParser.parse, 9 | path : 'local.error.examples', 10 | method: apiParser.method 11 | }; 12 | -------------------------------------------------------------------------------- /lib/languages/py.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Python 3 | */ 4 | module.exports = { 5 | // find document blocks between """ and """ 6 | docBlocksRegExp: /\"\"\"\uffff?(.+?)\uffff?(?:\s*)?\"\"\"/g, 7 | // remove not needed tabs at the beginning 8 | inlineRegExp: /^(\t*)?/gm 9 | }; 10 | -------------------------------------------------------------------------------- /lib/parsers/api_header_example.js: -------------------------------------------------------------------------------- 1 | // Same as @apiExample 2 | var apiParser = require('./api_example.js'); 3 | 4 | /** 5 | * Exports 6 | */ 7 | module.exports = { 8 | parse : apiParser.parse, 9 | path : 'local.header.examples', 10 | method: apiParser.method 11 | }; 12 | -------------------------------------------------------------------------------- /lib/parsers/api_param_example.js: -------------------------------------------------------------------------------- 1 | // Same as @apiExample 2 | var apiParser = require('./api_example.js'); 3 | 4 | /** 5 | * Exports 6 | */ 7 | module.exports = { 8 | parse : apiParser.parse, 9 | path : 'local.parameter.examples', 10 | method: apiParser.method 11 | }; 12 | -------------------------------------------------------------------------------- /lib/parsers/api_success_example.js: -------------------------------------------------------------------------------- 1 | // Same as @apiExample 2 | var apiParser = require('./api_example.js'); 3 | 4 | /** 5 | * Exports 6 | */ 7 | module.exports = { 8 | parse : apiParser.parse, 9 | path : 'local.success.examples', 10 | method: apiParser.method 11 | }; 12 | -------------------------------------------------------------------------------- /lib/languages/coffee.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CoffeeScript 3 | */ 4 | module.exports = { 5 | // find document blocks between '###' and '###' 6 | docBlocksRegExp: /###\uffff?(.+?)\uffff?(?:\s*)?###/g, 7 | // remove not needed tabs at the beginning 8 | inlineRegExp: /^(\t*)?/gm 9 | }; 10 | -------------------------------------------------------------------------------- /lib/languages/clj.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Clojure 3 | */ 4 | module.exports = { 5 | // find document blocks between ';;;;' and ';;;;' 6 | docBlocksRegExp: /\;{4}\uffff?(.+?)\uffff?(?:\s*)?;{4}/g, 7 | // remove not needed ' ;; ' at the beginning 8 | inlineRegExp: /^(\s*)?(;{2})[ ]?/gm 9 | }; 10 | -------------------------------------------------------------------------------- /lib/languages/ex.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Elixir 3 | */ 4 | module.exports = { 5 | // Find document blocks between '#{' and '#}' 6 | docBlocksRegExp: /\#*\s?\{\uffff?(.+?)\uffff?(?:\s*)?\#+\s?\}/g, 7 | // Remove not needed '#' and tabs at the beginning 8 | inlineRegExp: /^(\s*)?(\#*)[ ]?/gm 9 | }; 10 | -------------------------------------------------------------------------------- /lib/languages/lua.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Lua 3 | */ 4 | module.exports = { 5 | // find document blocks between '--[[' and '--]]' 6 | docBlocksRegExp: /--\[\[\uffff?(.+?)\uffff?(?:\s*)?\]\]/g, 7 | // remove not needed ' * ' and tabs at the beginning 8 | inlineRegExp: /^(\s*)?(\*)[ ]?/gm 9 | }; 10 | -------------------------------------------------------------------------------- /lib/parsers/api_permission.js: -------------------------------------------------------------------------------- 1 | // Same as @apiUse 2 | var apiParser = require('./api_use.js'); 3 | 4 | /** 5 | * Exports 6 | */ 7 | module.exports = { 8 | parse : apiParser.parse, 9 | path : 'local.permission', 10 | method : apiParser.method, 11 | preventGlobal: true 12 | }; 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.js,*.json] 13 | indent_size = 4 14 | 15 | [*.html] 16 | indent_size = 2 17 | 18 | [*.css] 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /lib/languages/rb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ruby 3 | */ 4 | module.exports = { 5 | // find document blocks between '=begin' and '=end' 6 | docBlocksRegExp: /#\*\*\uffff?(.+?)\uffff?(?:\s*)?#\*|=begin\uffff?(.+?)\uffff?(?:\s*)?=end/g, 7 | // remove not needed ' # ' and tabs at the beginning 8 | inlineRegExp: /^(\s*)?(#)[ ]?/gm 9 | }; 10 | -------------------------------------------------------------------------------- /lib/parsers/api_group.js: -------------------------------------------------------------------------------- 1 | function parse(content) { 2 | var group = content.trim(); 3 | 4 | if (group.length === 0) 5 | return null; 6 | 7 | return { 8 | group: group.replace(/(\s+)/g, '_') 9 | }; 10 | } 11 | 12 | /** 13 | * Exports 14 | */ 15 | module.exports = { 16 | parse : parse, 17 | path : 'local', 18 | method: 'insert' 19 | }; 20 | -------------------------------------------------------------------------------- /lib/languages/default.js: -------------------------------------------------------------------------------- 1 | /** 2 | * C#, Go, Dart, Java, JavaScript, PHP (all DocStyle capable languages) 3 | */ 4 | module.exports = { 5 | // find document blocks between '#**' and '#*' 6 | docBlocksRegExp: /\/\*\*\uffff?(.+?)\uffff?(?:\s*)?\*\//g, 7 | // remove not needed ' * ' and tabs at the beginning 8 | inlineRegExp: /^(\s*)?(\*)[ ]?/gm 9 | }; 10 | -------------------------------------------------------------------------------- /lib/languages/pm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Perl 3 | */ 4 | module.exports = { 5 | // find document blocks between '#**' and '#*' 6 | // or between '=pod' and '=cut' 7 | docBlocksRegExp: /#\*\*\uffff?(.+?)\uffff?(?:\s*)?#\*|=pod\uffff?(.+?)\uffff?(?:\s*)?=cut/g, 8 | // remove not needed ' # ' and tabs at the beginning 9 | inlineRegExp: /^(\s*)?(#)[ ]?/gm 10 | }; 11 | -------------------------------------------------------------------------------- /lib/parsers/api_sample_request.js: -------------------------------------------------------------------------------- 1 | function parse(content) { 2 | var url = content.trim(); 3 | 4 | if(url.length === 0) 5 | return null; 6 | 7 | return { 8 | url: url 9 | }; 10 | } 11 | 12 | /** 13 | * Exports 14 | */ 15 | module.exports = { 16 | parse : parse, 17 | path : 'local.sampleRequest', 18 | method: 'push' 19 | }; 20 | -------------------------------------------------------------------------------- /lib/parsers/api_name.js: -------------------------------------------------------------------------------- 1 | function parse(content) { 2 | var name = content.trim(); 3 | 4 | if(name.length === 0) 5 | return null; 6 | 7 | return { 8 | name: name.replace(/(\s+)/g, '_') 9 | }; 10 | } 11 | 12 | /** 13 | * Exports 14 | */ 15 | module.exports = { 16 | parse : parse, 17 | path : 'local', 18 | method: 'insert' 19 | }; 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # apidoc-core 2 | 3 | ## ARCHIVED: this repository has been archived because it is now part of the apidoc main repository 4 | 5 | Core parser library to generate apidoc result following the [apidoc-spec](https://github.com/apidoc/apidoc-spec). 6 | 7 | If you are an end user, please proceed to [apidoc](https://github.com/apidoc/apidoc) or [apidoc-documentation](https://apidocjs.com). 8 | -------------------------------------------------------------------------------- /lib/languages/erl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Erlang 3 | */ 4 | module.exports = { 5 | // Find document blocks between '%{' and '%}' 6 | docBlocksRegExp: /\%*\{\uffff?(.+?)\uffff?(?:\s*)?\%+\}/g, 7 | // remove not needed ' % ' and tabs at the beginning 8 | // HINT: Not sure if erlang developer use the %, but i think it should be no problem 9 | inlineRegExp: /^(\s*)?(\%*)[ ]?/gm 10 | }; 11 | -------------------------------------------------------------------------------- /lib/parsers/api_use.js: -------------------------------------------------------------------------------- 1 | function parse(content) { 2 | var name = content.trim(); 3 | 4 | if (name.length === 0) 5 | return null; 6 | 7 | return { 8 | name: name 9 | }; 10 | } 11 | 12 | /** 13 | * Exports 14 | */ 15 | module.exports = { 16 | parse : parse, 17 | path : 'local.use', 18 | method : 'push', 19 | preventGlobal: true 20 | }; 21 | -------------------------------------------------------------------------------- /lib/parsers/api_body.js: -------------------------------------------------------------------------------- 1 | // Same as @apiParam 2 | var apiParser = require('./api_param.js'); 3 | 4 | function parse(content, source) { 5 | return apiParser.parse(content, source, 'Body'); 6 | } 7 | 8 | /** 9 | * Exports 10 | */ 11 | module.exports = { 12 | parse : parse, 13 | path : 'local.body', 14 | method : apiParser.method, 15 | markdownFields: [ 'description' ] 16 | }; 17 | -------------------------------------------------------------------------------- /lib/parsers/api_query.js: -------------------------------------------------------------------------------- 1 | // Same as @apiParam 2 | var apiParser = require('./api_param.js'); 3 | 4 | function parse(content, source) { 5 | return apiParser.parse(content, source, 'Query'); 6 | } 7 | 8 | /** 9 | * Exports 10 | */ 11 | module.exports = { 12 | parse : parse, 13 | path : 'local.query', 14 | method : apiParser.method, 15 | markdownFields: [ 'description' ] 16 | }; 17 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | To report a vulnerability please send an email with the details to info@apidocjs.com but ONLY in case it is a real problem. apiDoc generates static files, which should be used and hosted on a separate webspace, so it could not harm anything else. 6 | 7 | For normal issues or problems with dependent libraries use the [Issue tracker](https://github.com/apidoc/apidoc/issues). 8 | -------------------------------------------------------------------------------- /lib/filters/api_error.js: -------------------------------------------------------------------------------- 1 | // Same as @apiParam 2 | var filterApiParam = require('./api_param.js'); 3 | 4 | /** 5 | * Post Filter parsed results. 6 | * 7 | * @param {Object[]} parsedFiles 8 | * @param {String[]} filenames 9 | */ 10 | function postFilter(parsedFiles, filenames) { 11 | filterApiParam.postFilter(parsedFiles, filenames, 'error'); 12 | } 13 | 14 | /** 15 | * Exports 16 | */ 17 | module.exports = { 18 | postFilter: postFilter 19 | }; 20 | -------------------------------------------------------------------------------- /lib/filters/api_header.js: -------------------------------------------------------------------------------- 1 | // Same as @apiParam 2 | var filterApiParam = require('./api_param.js'); 3 | 4 | /** 5 | * Post Filter parsed results. 6 | * 7 | * @param {Object[]} parsedFiles 8 | * @param {String[]} filenames 9 | */ 10 | function postFilter(parsedFiles, filenames) { 11 | filterApiParam.postFilter(parsedFiles, filenames, 'header'); 12 | } 13 | 14 | /** 15 | * Exports 16 | */ 17 | module.exports = { 18 | postFilter: postFilter 19 | }; 20 | -------------------------------------------------------------------------------- /lib/filters/api_success.js: -------------------------------------------------------------------------------- 1 | // Same as @apiParam 2 | var filterApiParam = require('./api_param.js'); 3 | 4 | /** 5 | * Post Filter parsed results. 6 | * 7 | * @param {Object[]} parsedFiles 8 | * @param {String[]} filenames 9 | */ 10 | function postFilter(parsedFiles, filenames) { 11 | filterApiParam.postFilter(parsedFiles, filenames, 'success'); 12 | } 13 | 14 | /** 15 | * Exports 16 | */ 17 | module.exports = { 18 | postFilter: postFilter 19 | }; 20 | -------------------------------------------------------------------------------- /.github/workflows/validating.yml: -------------------------------------------------------------------------------- 1 | name: validate 2 | on: [push, pull_request] 3 | jobs: 4 | run-test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: actions/setup-node@v1 9 | - run: npm i 10 | - run: npm run test 11 | run-jshint: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | - run: npm i 17 | - run: npm run jshint 18 | -------------------------------------------------------------------------------- /lib/parsers/api_header.js: -------------------------------------------------------------------------------- 1 | // Same as @apiParam 2 | var apiParser = require('./api_param.js'); 3 | 4 | function parse(content, source) { 5 | return apiParser.parse(content, source, 'Header'); 6 | } 7 | 8 | function path() { 9 | return 'local.header.fields.' + apiParser.getGroup(); 10 | } 11 | 12 | /** 13 | * Exports 14 | */ 15 | module.exports = { 16 | parse : parse, 17 | path : path, 18 | method : apiParser.method, 19 | markdownFields: [ 'description' ] 20 | }; 21 | -------------------------------------------------------------------------------- /lib/parsers/api_description.js: -------------------------------------------------------------------------------- 1 | var unindent = require('../utils/unindent'); 2 | 3 | function parse(content) { 4 | var description = content.trim(); 5 | 6 | if (description.length === 0) 7 | return null; 8 | 9 | return { 10 | description: unindent(description) 11 | }; 12 | } 13 | 14 | /** 15 | * Exports 16 | */ 17 | module.exports = { 18 | parse : parse, 19 | path : 'local', 20 | method : 'insert', 21 | markdownFields: [ 'description' ] 22 | }; 23 | -------------------------------------------------------------------------------- /lib/parsers/api_error.js: -------------------------------------------------------------------------------- 1 | // Same as @apiParam 2 | var apiParser = require('./api_param.js'); 3 | 4 | function parse(content, source) { 5 | return apiParser.parse(content, source, 'Error 4xx'); 6 | } 7 | 8 | function path() { 9 | return 'local.error.fields.' + apiParser.getGroup(); 10 | } 11 | 12 | /** 13 | * Exports 14 | */ 15 | module.exports = { 16 | parse : parse, 17 | path : path, 18 | method : apiParser.method, 19 | markdownFields: [ 'description', 'type' ], 20 | markdownRemovePTags: [ 'type' ] 21 | }; 22 | -------------------------------------------------------------------------------- /lib/parsers/api_success.js: -------------------------------------------------------------------------------- 1 | // Same as @apiParam 2 | var apiParser = require('./api_param.js'); 3 | 4 | function parse(content, source) { 5 | return apiParser.parse(content, source, 'Success 200'); 6 | } 7 | 8 | function path() { 9 | return 'local.success.fields.' + apiParser.getGroup(); 10 | } 11 | 12 | /** 13 | * Exports 14 | */ 15 | module.exports = { 16 | parse : parse, 17 | path : path, 18 | method : apiParser.method, 19 | markdownFields: [ 'description', 'type' ], 20 | markdownRemovePTags: [ 'type' ] 21 | }; 22 | -------------------------------------------------------------------------------- /lib/errors/parameter_error.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | function ParameterError(message, element, definition, example) { 4 | // enable stack trace 5 | Error.call(this); 6 | Error.captureStackTrace(this, this.constructor); 7 | this.name = this.constructor.name; 8 | 9 | this.message = message; 10 | this.element = element; 11 | this.definition = definition; 12 | this.example = example; 13 | } 14 | 15 | /** 16 | * Inherit from Error 17 | */ 18 | util.inherits(ParameterError, Error); 19 | 20 | /** 21 | * Exports 22 | */ 23 | module.exports = ParameterError; 24 | -------------------------------------------------------------------------------- /lib/parsers/api_deprecated.js: -------------------------------------------------------------------------------- 1 | var unindent = require('../utils/unindent'); 2 | 3 | function parse(content) { 4 | var deprecated = content.trim(); 5 | 6 | if (deprecated.length > 0) { 7 | return { 8 | deprecated: { 9 | content: unindent(deprecated) 10 | } 11 | }; 12 | } 13 | 14 | return { 15 | deprecated: true 16 | }; 17 | } 18 | 19 | /** 20 | * Exports 21 | */ 22 | module.exports = { 23 | parse : parse, 24 | path : 'local', 25 | method: 'insert', 26 | markdownFields: [ 'deprecated.content' ], 27 | markdownRemovePTags: [ 'deprecated.content' ] 28 | }; 29 | -------------------------------------------------------------------------------- /lib/parsers/api.js: -------------------------------------------------------------------------------- 1 | function parse(content) { 2 | content = content.trim(); 3 | 4 | // Search: type, url and title 5 | // Example: {get} /user/:id Get User by ID. 6 | var parseRegExp = /^(?:(?:\{(.+?)\})?\s*)?(.+?)(?:\s+(.+?))?$/g; 7 | var matches = parseRegExp.exec(content); 8 | 9 | if ( ! matches) 10 | return null; 11 | 12 | return { 13 | type : matches[1], 14 | url : matches[2], 15 | title: matches[3] || '' 16 | }; 17 | } 18 | 19 | /** 20 | * Exports 21 | */ 22 | module.exports = { 23 | parse : parse, 24 | path : 'local', 25 | method: 'insert' 26 | }; 27 | -------------------------------------------------------------------------------- /lib/errors/parser_error.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | function ParserError(message, file, block, element, source, extra) { 4 | // enable stack trace 5 | Error.call(this); 6 | Error.captureStackTrace(this, this.constructor); 7 | this.name = this.constructor.name; 8 | 9 | this.message = message; 10 | this.file = file; 11 | this.block = block; 12 | this.element = element; 13 | this.source = source; 14 | this.extra = extra || []; 15 | } 16 | 17 | /** 18 | * Inherit from Error 19 | */ 20 | util.inherits(ParserError, Error); 21 | 22 | /** 23 | * Exports 24 | */ 25 | module.exports = ParserError; 26 | -------------------------------------------------------------------------------- /lib/errors/worker_error.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | function WorkerError(message, file, block, element, definition, example, extra) { 4 | // enable stack trace 5 | Error.call(this); 6 | Error.captureStackTrace(this, this.constructor); 7 | this.name = this.constructor.name; 8 | 9 | this.message = message; 10 | this.file = file; 11 | this.block = block; 12 | this.element = element; 13 | this.definition = definition; 14 | this.example = example; 15 | this.extra = extra; 16 | } 17 | 18 | /** 19 | * Inherit from Error 20 | */ 21 | util.inherits(WorkerError, Error); 22 | 23 | /** 24 | * Exports 25 | */ 26 | module.exports = WorkerError; 27 | -------------------------------------------------------------------------------- /lib/errors/file_error.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var fs = require('path'); 3 | 4 | function FileError(message, file, path) { 5 | // enable stack trace 6 | Error.call(this); 7 | Error.captureStackTrace(this, this.constructor); 8 | this.name = this.constructor.name; 9 | 10 | this.message = message; 11 | this.file = file || ''; 12 | this.path = path || file; 13 | 14 | if (this.path && this.path.charAt(this.path.length - 1) !== '/') { 15 | this.path = fs.dirname(this.path); 16 | } 17 | } 18 | 19 | /** 20 | * Inherit from Error 21 | */ 22 | util.inherits(FileError, Error); 23 | 24 | /** 25 | * Exports 26 | */ 27 | module.exports = FileError; 28 | -------------------------------------------------------------------------------- /lib/parsers/api_version.js: -------------------------------------------------------------------------------- 1 | var semver = require('semver'); 2 | 3 | var ParameterError = require('../errors/parameter_error'); 4 | 5 | function parse(content) { 6 | content = content.trim(); 7 | 8 | if (content.length === 0) 9 | return null; 10 | 11 | if ( ! semver.valid(content)) 12 | throw new ParameterError('Version format not valid.', 13 | 'apiVersion', '@apiVersion major.minor.patch', '@apiVersion 1.2.3'); 14 | 15 | return { 16 | version: content 17 | }; 18 | } 19 | 20 | /** 21 | * Exports 22 | */ 23 | module.exports = { 24 | parse : parse, 25 | path : 'local', 26 | method : 'insert', 27 | extendRoot: true 28 | }; 29 | -------------------------------------------------------------------------------- /lib/utils/unindent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Strips from each line any leading whitespace that is shared by all lines. 3 | * 4 | * @param str string 5 | * @returns string 6 | */ 7 | module.exports = function unindent(str) { 8 | var lines = str.split('\n'); 9 | 10 | var xs = lines.filter(function (x) { 11 | return /\S/.test(x); 12 | }).sort(); 13 | 14 | if (xs.length === 0) 15 | return str; 16 | 17 | var a = xs[0]; 18 | var b = xs[xs.length - 1]; 19 | 20 | var maxLength = Math.min(a.length, b.length); 21 | 22 | var i = 0; 23 | while (i < maxLength && 24 | /\s/.test(a.charAt(i)) && 25 | a.charAt(i) === b.charAt(i)) { 26 | i += 1; 27 | } 28 | 29 | if (i === 0) 30 | return str; 31 | 32 | return lines.map(function (line) { 33 | return line.substr(i); 34 | }).join('\n'); 35 | }; 36 | -------------------------------------------------------------------------------- /lib/parsers/api_example.js: -------------------------------------------------------------------------------- 1 | var unindent = require('../utils/unindent'); 2 | 3 | function parse(content, source) { 4 | source = source.trim(); 5 | 6 | var title = ''; 7 | var text = ''; 8 | var type; 9 | 10 | // Search for @apiExample "[{type}] title and content 11 | // /^(@\w*)?\s?(?:(?:\{(.+?)\})\s*)?(.*)$/gm; 12 | var parseRegExpFirstLine = /(@\w*)?(?:(?:\s*\{\s*([a-zA-Z0-9\.\/\\\[\]_-]+)\s*\}\s*)?\s*(.*)?)?/; 13 | var parseRegExpFollowing = /(^.*\s?)/gm; 14 | 15 | var matches; 16 | if ( (matches = parseRegExpFirstLine.exec(source)) ) { 17 | type = matches[2]; 18 | title = matches[3]; 19 | } 20 | 21 | parseRegExpFollowing.exec(content); // ignore line 1 22 | while ( (matches = parseRegExpFollowing.exec(source)) ) { 23 | text += matches[1]; 24 | } 25 | 26 | if (text.length === 0) 27 | return null; 28 | 29 | return { 30 | title : title, 31 | content: unindent(text), 32 | type : type || 'json' 33 | }; 34 | } 35 | 36 | /** 37 | * Exports 38 | */ 39 | module.exports = { 40 | parse : parse, 41 | path : 'local.examples', 42 | method: 'push' 43 | }; 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2017 inveris OHG 2 | Author Peter Rottmann 3 | Licensed under the MIT license. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /lib/workers/api_body_title.js: -------------------------------------------------------------------------------- 1 | var apiWorker = require('./api_param_title.js'); 2 | 3 | // Additional information for error log 4 | var _messages = { 5 | common: { 6 | element: 'apiBody', 7 | usage : '@apiBody (group) varname', 8 | example: '@apiDefine MyValidParamGroup Some title\n@apiBody (MyValidParamGroup) username' 9 | } 10 | }; 11 | 12 | /** 13 | * PreProcess 14 | * 15 | * @param {Object[]} parsedFiles 16 | * @param {String[]} filenames 17 | * @param {Object} packageInfos 18 | * @returns {Object} 19 | */ 20 | function preProcess(parsedFiles, filenames, packageInfos) { 21 | return apiWorker.preProcess(parsedFiles, filenames, packageInfos, 'defineBodyTitle'); 22 | } 23 | 24 | /** 25 | * PostProcess 26 | * 27 | * @param {Object[]} parsedFiles 28 | * @param {String[]} filenames 29 | * @param {Object[]} preProcess 30 | * @param {Object} packageInfos 31 | */ 32 | function postProcess(parsedFiles, filenames, preProcess, packageInfos) { 33 | apiWorker.postProcess(parsedFiles, filenames, preProcess, packageInfos, 'defineBodyTitle', 'body', _messages); 34 | } 35 | 36 | /** 37 | * Exports 38 | */ 39 | module.exports = { 40 | preProcess : preProcess, 41 | postProcess: postProcess 42 | }; 43 | -------------------------------------------------------------------------------- /lib/workers/api_query_title.js: -------------------------------------------------------------------------------- 1 | var apiWorker = require('./api_param_title.js'); 2 | 3 | // Additional information for error log 4 | var _messages = { 5 | common: { 6 | element: 'apiQuery', 7 | usage : '@apiQuery (group) varname', 8 | example: '@apiDefine MyValidParamGroup Some title\n@apiQuery (MyValidParamGroup) username' 9 | } 10 | }; 11 | 12 | /** 13 | * PreProcess 14 | * 15 | * @param {Object[]} parsedFiles 16 | * @param {String[]} filenames 17 | * @param {Object} packageInfos 18 | * @returns {Object} 19 | */ 20 | function preProcess(parsedFiles, filenames, packageInfos) { 21 | return apiWorker.preProcess(parsedFiles, filenames, packageInfos, 'defineQueryTitle'); 22 | } 23 | 24 | /** 25 | * PostProcess 26 | * 27 | * @param {Object[]} parsedFiles 28 | * @param {String[]} filenames 29 | * @param {Object[]} preProcess 30 | * @param {Object} packageInfos 31 | */ 32 | function postProcess(parsedFiles, filenames, preProcess, packageInfos) { 33 | apiWorker.postProcess(parsedFiles, filenames, preProcess, packageInfos, 'defineQueryTitle', 'query', _messages); 34 | } 35 | 36 | /** 37 | * Exports 38 | */ 39 | module.exports = { 40 | preProcess : preProcess, 41 | postProcess: postProcess 42 | }; 43 | -------------------------------------------------------------------------------- /lib/workers/api_structure.js: -------------------------------------------------------------------------------- 1 | // Same as @apiUse 2 | var apiWorker = require('./api_use.js'); 3 | 4 | // Additional information for error log 5 | var _messages = { 6 | common: { 7 | element: 'apiStructure', 8 | usage : '@apiStructure group', 9 | example: '@apiDefine MyValidStructureGroup Some title\n@apiStructure MyValidStructureGroup' 10 | } 11 | }; 12 | 13 | /** 14 | * PreProcess 15 | * 16 | * @param {Object[]} parsedFiles 17 | * @param {String[]} filenames 18 | * @param {Object} packageInfos 19 | * @returns {Object} 20 | */ 21 | function preProcess(parsedFiles, filenames, packageInfos) { 22 | return apiWorker.preProcess(parsedFiles, filenames, packageInfos, 'defineStructure'); 23 | } 24 | 25 | /** 26 | * PostProcess 27 | * 28 | * @param {Object[]} parsedFiles 29 | * @param {String[]} filenames 30 | * @param {Object[]} preProcess 31 | * @param {Object} packageInfos 32 | */ 33 | function postProcess(parsedFiles, filenames, preProcess, packageInfos) { 34 | apiWorker.postProcess(parsedFiles, filenames, preProcess, packageInfos, 'defineStructure', 'structure', _messages); 35 | } 36 | 37 | /** 38 | * Exports 39 | */ 40 | module.exports = { 41 | preProcess : preProcess, 42 | postProcess: postProcess 43 | }; 44 | -------------------------------------------------------------------------------- /lib/workers/api_error_title.js: -------------------------------------------------------------------------------- 1 | // Same as @apiParamTitle 2 | var apiWorker = require('./api_param_title.js'); 3 | 4 | // Additional information for error log 5 | var _messages = { 6 | common: { 7 | element: 'apiError', 8 | usage : '@apiError (group) varname', 9 | example: '@apiDefine MyValidErrorGroup Some title or 40X Error\n@apiError (MyValidErrorGroup) username' 10 | } 11 | }; 12 | 13 | /** 14 | * PreProcess 15 | * 16 | * @param {Object[]} parsedFiles 17 | * @param {String[]} filenames 18 | * @param {Object} packageInfos 19 | * @returns {Object} 20 | */ 21 | function preProcess(parsedFiles, filenames, packageInfos) { 22 | return apiWorker.preProcess(parsedFiles, filenames, packageInfos, 'defineErrorTitle'); 23 | } 24 | 25 | /** 26 | * PostProcess 27 | * 28 | * @param {Object[]} parsedFiles 29 | * @param {String[]} filenames 30 | * @param {Object[]} preProcess 31 | * @param {Object} packageInfos 32 | */ 33 | function postProcess(parsedFiles, filenames, preProcess, packageInfos) { 34 | apiWorker.postProcess(parsedFiles, filenames, preProcess, packageInfos, 'defineErrorTitle', 'error', _messages); 35 | } 36 | 37 | /** 38 | * Exports 39 | */ 40 | module.exports = { 41 | preProcess : preProcess, 42 | postProcess: postProcess 43 | }; 44 | -------------------------------------------------------------------------------- /lib/workers/api_header_title.js: -------------------------------------------------------------------------------- 1 | // Same as @apiParamTitle 2 | var apiWorker = require('./api_param_title.js'); 3 | 4 | // Additional information for error log 5 | var _messages = { 6 | common: { 7 | element: 'apiHeader', 8 | usage : '@apiHeader (group) varname', 9 | example: '@apiDefine MyValidHeaderGroup Some title\n@apiHeader (MyValidHeaderGroup) Content-Type' 10 | } 11 | }; 12 | 13 | /** 14 | * PreProcess 15 | * 16 | * @param {Object[]} parsedFiles 17 | * @param {String[]} filenames 18 | * @param {Object} packageInfos 19 | * @returns {Object} 20 | */ 21 | function preProcess(parsedFiles, filenames, packageInfos) { 22 | return apiWorker.preProcess(parsedFiles, filenames, packageInfos, 'defineHeaderTitle'); 23 | } 24 | 25 | /** 26 | * PostProcess 27 | * 28 | * @param {Object[]} parsedFiles 29 | * @param {String[]} filenames 30 | * @param {Object[]} preProcess 31 | * @param {Object} packageInfos 32 | */ 33 | function postProcess(parsedFiles, filenames, preProcess, packageInfos) { 34 | apiWorker.postProcess(parsedFiles, filenames, preProcess, packageInfos, 'defineHeaderTitle', 'header', _messages); 35 | } 36 | 37 | /** 38 | * Exports 39 | */ 40 | module.exports = { 41 | preProcess : preProcess, 42 | postProcess: postProcess 43 | }; 44 | -------------------------------------------------------------------------------- /lib/workers/api_success_title.js: -------------------------------------------------------------------------------- 1 | // Same as @apiParamTitle 2 | var apiWorker = require('./api_param_title.js'); 3 | 4 | // Additional information for error log 5 | var _messages = { 6 | common: { 7 | element: 'apiSuccess', 8 | usage : '@apiSuccess (group) varname', 9 | example: '@apiDefine MyValidSuccessGroup Some title or 200 OK\n@apiSuccess (MyValidSuccessGroup) username' 10 | } 11 | }; 12 | 13 | /** 14 | * PreProcess 15 | * 16 | * @param {Object[]} parsedFiles 17 | * @param {String[]} filenames 18 | * @param {Object} packageInfos 19 | * @returns {Object} 20 | */ 21 | function preProcess(parsedFiles, filenames, packageInfos) { 22 | return apiWorker.preProcess(parsedFiles, filenames, packageInfos, 'defineSuccessTitle'); 23 | } 24 | 25 | /** 26 | * PostProcess 27 | * 28 | * @param {Object[]} parsedFiles 29 | * @param {String[]} filenames 30 | * @param {Object[]} preProcess 31 | * @param {Object} packageInfos 32 | */ 33 | function postProcess(parsedFiles, filenames, preProcess, packageInfos) { 34 | apiWorker.postProcess(parsedFiles, filenames, preProcess, packageInfos, 'defineSuccessTitle', 'success', _messages); 35 | } 36 | 37 | /** 38 | * Exports 39 | */ 40 | module.exports = { 41 | preProcess : preProcess, 42 | postProcess: postProcess 43 | }; 44 | -------------------------------------------------------------------------------- /lib/workers/api_error_structure.js: -------------------------------------------------------------------------------- 1 | // Same as @apiUse 2 | var apiWorker = require('./api_use.js'); 3 | 4 | // Additional information for error log 5 | var _messages = { 6 | common: { 7 | element: 'apiErrorStructure', 8 | usage : '@apiErrorStructure group', 9 | example: '@apiDefine MyValidErrorStructureGroup Some title\n@apiErrorStructure MyValidErrorStructureGroup' 10 | } 11 | }; 12 | 13 | /** 14 | * PreProcess 15 | * 16 | * @param {Object[]} parsedFiles 17 | * @param {String[]} filenames 18 | * @param {Object} packageInfos 19 | * @returns {Object} 20 | */ 21 | function preProcess(parsedFiles, filenames, packageInfos) { 22 | return apiWorker.preProcess(parsedFiles, filenames, packageInfos, 'defineErrorStructure'); 23 | } 24 | 25 | /** 26 | * PostProcess 27 | * 28 | * @param {Object[]} parsedFiles 29 | * @param {String[]} filenames 30 | * @param {Object[]} preProcess 31 | * @param {Object} packageInfos 32 | */ 33 | function postProcess(parsedFiles, filenames, preProcess, packageInfos) { 34 | apiWorker.postProcess(parsedFiles, filenames, preProcess, packageInfos, 'defineErrorStructure', 'errorStructure', _messages); 35 | } 36 | 37 | /** 38 | * Exports 39 | */ 40 | module.exports = { 41 | preProcess : preProcess, 42 | postProcess: postProcess 43 | }; 44 | -------------------------------------------------------------------------------- /lib/workers/api_header_structure.js: -------------------------------------------------------------------------------- 1 | // Same as @apiUse 2 | var apiWorker = require('./api_use.js'); 3 | 4 | // Additional information for error log 5 | var _messages = { 6 | common: { 7 | element: 'apiHeaderStructure', 8 | usage : '@apiHeaderStructure group', 9 | example: '@apiDefine MyValidHeaderStructureGroup Some title\n@apiHeaderStructure MyValidHeaderStructureGroup' 10 | } 11 | }; 12 | 13 | /** 14 | * PreProcess 15 | * 16 | * @param {Object[]} parsedFiles 17 | * @param {String[]} filenames 18 | * @param {Object} packageInfos 19 | * @returns {Object} 20 | */ 21 | function preProcess(parsedFiles, filenames, packageInfos) { 22 | return apiWorker.preProcess(parsedFiles, filenames, packageInfos, 'defineHeaderStructure'); 23 | } 24 | 25 | /** 26 | * PostProcess 27 | * 28 | * @param {Object[]} parsedFiles 29 | * @param {String[]} filenames 30 | * @param {Object[]} preProcess 31 | * @param {Object} packageInfos 32 | */ 33 | function postProcess(parsedFiles, filenames, preProcess, packageInfos) { 34 | apiWorker.postProcess(parsedFiles, filenames, preProcess, packageInfos, 'defineHeaderStructure', 'headerStructure', _messages); 35 | } 36 | 37 | /** 38 | * Exports 39 | */ 40 | module.exports = { 41 | preProcess : preProcess, 42 | postProcess: postProcess 43 | }; 44 | -------------------------------------------------------------------------------- /lib/workers/api_success_structure.js: -------------------------------------------------------------------------------- 1 | // Same as @apiUse 2 | var apiWorker = require('./api_use.js'); 3 | 4 | // Additional information for error log 5 | var _messages = { 6 | common: { 7 | element: 'apiSuccessStructure', 8 | usage : '@apiSuccessStructure group', 9 | example: '@apiDefine MyValidSuccessStructureGroup Some title\n@apiSuccessStructure MyValidSuccessStructureGroup' 10 | } 11 | }; 12 | 13 | /** 14 | * PreProcess 15 | * 16 | * @param {Object[]} parsedFiles 17 | * @param {String[]} filenames 18 | * @param {Object} packageInfos 19 | * @returns {Object} 20 | */ 21 | function preProcess(parsedFiles, filenames, packageInfos) { 22 | return apiWorker.preProcess(parsedFiles, filenames, packageInfos, 'defineSuccessStructure'); 23 | } 24 | 25 | /** 26 | * PostProcess 27 | * 28 | * @param {Object[]} parsedFiles 29 | * @param {String[]} filenames 30 | * @param {Object[]} preProcess 31 | * @param {Object} packageInfos 32 | */ 33 | function postProcess(parsedFiles, filenames, preProcess, packageInfos) { 34 | apiWorker.postProcess(parsedFiles, filenames, preProcess, packageInfos, 'defineSuccessStructure', 'successStructure', _messages); 35 | } 36 | 37 | /** 38 | * Exports 39 | */ 40 | module.exports = { 41 | preProcess : preProcess, 42 | postProcess: postProcess 43 | }; 44 | -------------------------------------------------------------------------------- /hooks.md: -------------------------------------------------------------------------------- 1 | # apiDoc Hooks 2 | 3 | If you need a hook in apidoc-core please add your hook and provide a pull request. 4 | 5 | How to add a hook into apidoc-core view [source code](https://github.com/apidoc/apidoc-core/blob/20921efd32f95e7934333d633c56ff6f60722123/lib/parser.js#L454-L458). 6 | 7 | How to use hook in your plugin view (https://github.com/apidoc/apidoc-plugin-test/blob/master/index.js) 8 | 9 | 10 | ## parser-find-elements 11 | 12 | Called on each found element. Returns a new list of elements (replace elements). 13 | Used to inject annotationes from an external schema. 14 | 15 | Parameter: `(elements, element, block, filename)` 16 | * {array} elements Found elements in a block without the current element. 17 | * {array} element Contains the source, name (lowercase), sourceName (original), content. 18 | * {string} block Current source block. 19 | * {string} filename Current filename. 20 | 21 | File: `parser.js` 22 | Function: `_findElements` 23 | 24 | 25 | 26 | ## parser-find-element-{name} 27 | 28 | Called on each found element and returns the modified element. 29 | Used to modify a specific element. 30 | 31 | {name} is the found element.name (lowercase). 32 | 33 | Parameter: `(element, block, filename)` 34 | * {array} element Contains the source, name (lowercase), sourceName (original), content. 35 | * {string} block Current source block. 36 | * {string} filename Current filename. 37 | 38 | File: `parser.js` 39 | Function: `_findElements` 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for apidoc-core 2 | 3 | ## 0.15.1 4 | * Use str.trim() instead of own regex vulnerable to ReDOS. 5 | Note that this vulnerability is not impacting this project as an attacker would need to control your source code from which you generate the documentation. 6 | Done in #120 by @ready-research. 7 | 8 | ## 0.15.0 9 | 10 | ### Fixed 11 | * Fix incorrect return type in @apiPrivate parser (PR #112 from nomoon) 12 | 13 | ## 0.14.0 14 | 15 | ### Added 16 | 17 | * Implement apiBody and apiQuery tags by @SWheeler17 #104 18 | * Expose parseSource function by @woodgear #109 19 | * Add GitHub actions by @tommy87 #107 20 | 21 | ## 0.13.0 22 | 23 | ### Added 24 | 25 | * Enable the use of an apiUse block in an apiDefine block (#99 by tommy87) 26 | 27 | ## 0.12.0 28 | 29 | ### Changed 30 | 31 | * Allow '#' and '@' in apiParam names (#102 by Fulvio Gentile) 32 | 33 | ## 0.11.1 34 | 35 | * Correctly merge the dev branch 36 | 37 | ## 0.11.0 38 | 39 | ### Added 40 | 41 | * Allow filtering by tag (PR #91 by @omaretna) 42 | 43 | ### Fixed 44 | 45 | * Fix apiprivate parser issue (PR #81 by @jason-gao) 46 | 47 | ## 0.10.0 48 | 49 | ### Added 50 | 51 | * Allow the use of | to specify different types: {String|Number} (#62) 52 | * Allow the use of $ in the param name (#36) 53 | * Allow special characters in name and group 54 | 55 | ### Changed 56 | 57 | * Update dependencies 58 | * Use os.EOL instead of own logic for line endings 59 | 60 | ### Fixed 61 | 62 | * Update elixir's syntax (#65) 63 | -------------------------------------------------------------------------------- /lib/filters/api_param.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Post Filter parsed results 3 | * Remove double fields, happen when overwrite a global inherited field with a local definition. 4 | * 5 | * @param {Object[]} parsedFiles 6 | * @param {String[]} filenames 7 | * @param {String} tagName Example: 'parameter' 8 | * @returns {Object} 9 | */ 10 | function postFilter(parsedFiles, filenames, tagName) { 11 | tagName = tagName || 'parameter'; 12 | 13 | parsedFiles.forEach(function(parsedFile) { 14 | parsedFile.forEach(function(block) { 15 | if (block.local[tagName] && block.local[tagName].fields) { 16 | var blockFields = block.local[tagName].fields; 17 | Object.keys(blockFields).forEach(function(blockFieldKey) { 18 | var fields = block.local[tagName].fields[blockFieldKey]; 19 | var newFields = []; 20 | var existingKeys = {}; 21 | fields.forEach(function(field) { 22 | var key = field.field; // .field (=id) is the key to check if it exists twice 23 | if ( ! existingKeys[key]) { 24 | existingKeys[key] = 1; 25 | newFields.push(field); 26 | } 27 | }); 28 | block.local[tagName].fields[blockFieldKey] = newFields; 29 | }); 30 | } 31 | }); 32 | }); 33 | } 34 | 35 | /** 36 | * Exports 37 | */ 38 | module.exports = { 39 | postFilter: postFilter 40 | }; 41 | -------------------------------------------------------------------------------- /test/util_unindent_test.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false*/ 2 | 3 | /** 4 | * Test: Util unindent 5 | */ 6 | 7 | // node modules 8 | var should = require('should'); 9 | 10 | // lib modules 11 | var unindent = require('../lib/utils/unindent'); 12 | 13 | describe('Util: unindent', function() { 14 | 15 | it('should strip common leading spaces', function(done) { 16 | unindent(' a\n b\n c').should.equal('a\n b\n c'); 17 | done(); 18 | }); 19 | 20 | it('should strip common leading tabs', function(done) { 21 | unindent('\t\ta\n\t\t\t\tb\n\t\t\tc').should.equal('a\n\t\tb\n\tc'); 22 | done(); 23 | }); 24 | 25 | it('should strip all leading whitespace from a single line', function(done) { 26 | unindent(' \t a').should.equal('a'); 27 | done(); 28 | }); 29 | 30 | it('should not modify the empty string', function(done) { 31 | var s = ''; 32 | unindent(s).should.equal(s); 33 | done(); 34 | }); 35 | 36 | it('should not modify if any line starts with non-whitespace', function(done) { 37 | var s = ' a\n b\nc d\n e'; 38 | unindent(s).should.equal(s); 39 | done(); 40 | }); 41 | 42 | it('should strip common leading tabs and keep spaces', function(done) { 43 | unindent('\ta\n\t b\n\t c').should.equal('a\n b\n c'); 44 | done(); 45 | }); 46 | 47 | it('should strip common leading tabs and 1 space on each line', function(done) { 48 | unindent('\t a\n\t b\n\t c').should.equal(' a\n b\nc'); 49 | done(); 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /lib/parsers/api_define.js: -------------------------------------------------------------------------------- 1 | var unindent = require('../utils/unindent'); 2 | 3 | var ParameterError = require('../errors/parameter_error'); 4 | 5 | // Additional information for error log 6 | var _messages = { 7 | common: { 8 | element: 'apiDefine', 9 | usage : '@apiDefine name', 10 | example: '@apiDefine MyValidName' 11 | } 12 | }; 13 | 14 | function parse(content, source, messages) { 15 | messages = messages || _messages; 16 | 17 | content = content.trim(); 18 | 19 | var parseRegExp = /^(\w*)(.*?)(?:\s+|$)(.*)$/gm; 20 | var matches = parseRegExp.exec(content); 21 | 22 | if ( ! matches) 23 | return null; 24 | 25 | if (matches[0] === '') 26 | throw new ParameterError('No arguments found.', 27 | messages.common.element, messages.common.usage, messages.common.example); 28 | 29 | if (matches[2] !== '') 30 | throw new ParameterError('Name must contain only alphanumeric characters.', 31 | messages.common.element, messages.common.usage, messages.common.example); 32 | 33 | var name = matches[1]; 34 | var title = matches[3]; 35 | var description = ''; 36 | 37 | while ( (matches = parseRegExp.exec(content)) ) { 38 | description += matches[0] + '\n'; 39 | } 40 | 41 | return { 42 | name : name, 43 | title : title, 44 | description: unindent(description) 45 | }; 46 | } 47 | 48 | /** 49 | * Exports 50 | */ 51 | module.exports = { 52 | parse : parse, 53 | path : 'global.define', 54 | method : 'insert', 55 | markdownFields: [ 'description' ] 56 | }; 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apidoc-core", 3 | "version": "0.15.1", 4 | "description": "Core parser library to generate apidoc result following the apidoc-spec", 5 | "author": "Peter Rottmann ", 6 | "license": "MIT", 7 | "main": "./lib/index.js", 8 | "homepage": "https://github.com/apidoc/apidoc-core", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/apidoc/apidoc-core.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/apidoc/apidoc-core/issues" 15 | }, 16 | "scripts": { 17 | "test": "npm run jshint && mocha test/**/*_test.js", 18 | "jshint": "jshint lib/ test/" 19 | }, 20 | "keywords": [ 21 | "api", 22 | "apidoc", 23 | "doc", 24 | "documentation", 25 | "rest", 26 | "restful" 27 | ], 28 | "engines": { 29 | "node": ">= 0.10.0" 30 | }, 31 | "dependencies": { 32 | "fs-extra": "^9.0.1", 33 | "glob": "^7.1.6", 34 | "iconv-lite": "^0.6.2", 35 | "klaw-sync": "^6.0.0", 36 | "lodash": "^4.17.20", 37 | "semver": "~7.3.2" 38 | }, 39 | "devDependencies": { 40 | "apidoc-example": "^0.2.3", 41 | "deep-equal": "^2.0.3", 42 | "jshint": "^2.12.0", 43 | "markdown-it": "^11.0.0", 44 | "mocha": "^8.1.1", 45 | "should": "~13.2.3" 46 | }, 47 | "jshintConfig": { 48 | "camelcase": true, 49 | "curly": false, 50 | "eqeqeq": true, 51 | "forin": true, 52 | "latedef": false, 53 | "newcap": true, 54 | "undef": true, 55 | "unused": true, 56 | "trailing": true, 57 | "node": true, 58 | "browser": true, 59 | "predef": [ 60 | "it", 61 | "describe", 62 | "before", 63 | "after" 64 | ] 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/filter.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var _ = require('lodash'); 3 | 4 | var app = {}; 5 | 6 | /** 7 | * Filter 8 | * Cleanup the data, e.g.: remove double fields, happen when overwrite a global inherited field with a local definition. 9 | * 10 | * @param {Object} _app 11 | */ 12 | function Filter(_app) { 13 | var self = this; 14 | 15 | // global variables 16 | app = _app; 17 | 18 | // class variables 19 | this.filters = {}; 20 | 21 | // load filters 22 | var filters = Object.keys(app.filters); 23 | filters.forEach(function(filter) { 24 | if (_.isObject( app.filters[filter] )) { 25 | app.log.debug('inject filter: ' + filter); 26 | self.addFilter(filter, app.filters[filter] ); 27 | } else { 28 | var filename = app.filters[filter]; 29 | app.log.debug('load filter: ' + filter + ', ' + filename); 30 | self.addFilter(filter, require(filename)); 31 | } 32 | }); 33 | } 34 | 35 | /** 36 | * Inherit 37 | */ 38 | util.inherits(Filter, Object); 39 | 40 | /** 41 | * Exports 42 | */ 43 | module.exports = Filter; 44 | 45 | /** 46 | * Add Filter 47 | */ 48 | Filter.prototype.addFilter = function(name, filter) { 49 | this.filters[name] = filter; 50 | }; 51 | 52 | /** 53 | * Execute filter 54 | */ 55 | Filter.prototype.process = function(parsedFiles, parsedFilenames) { 56 | // filter each @api-Parameter 57 | _.each(this.filters, function(filter, name) { 58 | if (filter.postFilter) { 59 | app.log.verbose('filter postFilter: ' + name); 60 | filter.postFilter(parsedFiles, parsedFilenames); 61 | } 62 | }); 63 | 64 | // reduce to local blocks where global is empty 65 | var blocks = []; 66 | parsedFiles.forEach(function(parsedFile) { 67 | parsedFile.forEach(function(block) { 68 | if (Object.keys(block.global).length === 0 && Object.keys(block.local).length > 0) 69 | blocks.push(block.local); 70 | }); 71 | }); 72 | return blocks; 73 | }; 74 | -------------------------------------------------------------------------------- /lib/workers/api_sample_request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * PostProcess 3 | * 4 | * @param {Object[]} parsedFiles 5 | * @param {String[]} filenames 6 | * @param {Object[]} preProcess 7 | * @param {Object} packageInfos 8 | */ 9 | function postProcess(parsedFiles, filenames, preProcess, packageInfos) { 10 | var targetName = 'sampleRequest'; 11 | 12 | parsedFiles.forEach(function(parsedFile) { 13 | parsedFile.forEach(function(block) { 14 | if (block.local[targetName]) { 15 | var newBlock = []; 16 | block.local[targetName].forEach(function(entry) { 17 | if (entry.url !== 'off') { 18 | // Check if is an internal url 19 | if (packageInfos.sampleUrl && entry.url.length >= 4 && entry.url.substr(0, 4).toLowerCase() !== 'http') { 20 | // Prepend sampleUrl 21 | entry.url = packageInfos.sampleUrl + entry.url; 22 | } 23 | newBlock.push(entry); 24 | } 25 | }); // forEach 26 | 27 | if (newBlock.length === 0) 28 | delete block.local[targetName]; 29 | else 30 | block.local[targetName] = newBlock; 31 | } else { 32 | var url; 33 | if (packageInfos.sampleUrl && block.local && block.local.url) { 34 | // if the block local url is absolute, just use this don't append to the sampleUrl 35 | if (block.local.url.length >= 4 && block.local.url.substr(0, 4).toLowerCase() !== 'http') { 36 | url = packageInfos.sampleUrl + block.local.url; 37 | } else { 38 | url = block.local.url; 39 | } 40 | 41 | block.local[targetName] = [{ 42 | 'url': url 43 | }]; 44 | } 45 | } 46 | }); 47 | }); 48 | } 49 | 50 | /** 51 | * Exports 52 | */ 53 | module.exports = { 54 | postProcess: postProcess 55 | }; 56 | -------------------------------------------------------------------------------- /test/parser_api_description_test.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false*/ 2 | 3 | /** 4 | * Test: Parser apiDescription 5 | */ 6 | 7 | // node modules 8 | var should = require('should'); 9 | 10 | // lib modules 11 | var parser = require('../lib/parsers/api_description'); 12 | 13 | describe('Parser: apiDescription', function() { 14 | 15 | // TODO: Add 1.000 more possible cases ;-) 16 | var testCases = [ 17 | { 18 | title: 'Word only', 19 | content: 'Text', 20 | expected: { 21 | description: 'Text' 22 | } 23 | }, 24 | { 25 | title: 'Trim single line', 26 | content: ' Text line 1 (Begin: 3xSpaces (3 removed), End: 1xSpace). ', 27 | expected: { 28 | description: 'Text line 1 (Begin: 3xSpaces (3 removed), End: 1xSpace).' 29 | } 30 | }, 31 | { 32 | title: 'Trim multi line (spaces)', 33 | content: ' Text line 1 (Begin: 4xSpaces (3 removed)).\n Text line 2 (Begin: 3xSpaces (3 removed), End: 2xSpaces). ', 34 | expected: { 35 | description: 'Text line 1 (Begin: 4xSpaces (3 removed)).\n Text line 2 (Begin: 3xSpaces (3 removed), End: 2xSpaces).' 36 | } 37 | }, 38 | { 39 | title: 'Trim multi line (tabs)', 40 | content: '\t\t\tText line 1 (Begin: 3xTab (2 removed)).\n\t\tText line 2 (Begin: 2x Tab (2 removed), End: 1xTab).\t', 41 | expected: { 42 | description: 'Text line 1 (Begin: 3xTab (2 removed)).\n\t\tText line 2 (Begin: 2x Tab (2 removed), End: 1xTab).' 43 | } 44 | }, 45 | { 46 | title: 'Trim multi line (tabs and space)', 47 | content: '\t Text line 1 (Begin: 1xTab, 2xSpaces).\n Text line 2 (Begin: 3xSpaces, End: 1xTab).\t', 48 | expected: { 49 | description: 'Text line 1 (Begin: 1xTab, 2xSpaces).\n Text line 2 (Begin: 3xSpaces, End: 1xTab).' 50 | } 51 | } 52 | ]; 53 | 54 | // create 55 | it('case 1: should pass all regexp test cases', function(done) { 56 | testCases.forEach(function(testCase) { 57 | var parsed = parser.parse(testCase.content); 58 | (parsed !== null).should.equal(true, 'Title: ' + testCase.title + ', Source: ' + testCase.content); 59 | parsed.should.eql(testCase.expected); 60 | }); 61 | done(); 62 | }); 63 | 64 | }); 65 | -------------------------------------------------------------------------------- /test/parse_source_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test: parseSource 3 | */ 4 | var deepEqual = require('deep-equal'); 5 | var apidoc = require("../lib/index"); 6 | 7 | function log() { 8 | } 9 | 10 | var logger = { 11 | debug: log, 12 | verbose: log, 13 | info: log, 14 | warn: log, 15 | error: log, 16 | }; 17 | 18 | describe("parseSource", function () { 19 | var testCases = [ 20 | { 21 | source: 22 | "/**" + 23 | "\n * @api {post} /api/school/students/:studentId/cloth " + 24 | "\n * @apiName createCloth " + 25 | "\n * @apiGroup cloth " + 26 | "\n * @apiParam (body) {String} [name] " + 27 | "\n * @apiSuccess {Number} code 200 " + 28 | "\n * @apiSuccessExample {json} Success-Response: " + 29 | "\n * { " + 30 | "\n * status: 200 " + 31 | "\n * } " + 32 | "\n*/ ", 33 | expected: { 34 | global: {}, 35 | local: { 36 | type: "post", 37 | url: "/api/school/students/:studentId/cloth", 38 | title: "", 39 | name: "createCloth", 40 | group: "cloth", 41 | parameter: { 42 | fields: { 43 | body: [ 44 | { 45 | group: "body", 46 | type: "String", 47 | field: "name", 48 | optional: true, 49 | description: "", 50 | }, 51 | ], 52 | }, 53 | }, 54 | success: { 55 | fields: { 56 | "Success 200": [ 57 | { 58 | group: "Success 200", 59 | type: "Number", 60 | optional: false, 61 | field: "code", 62 | description: "200", 63 | }, 64 | ], 65 | }, 66 | examples: [ 67 | { 68 | title: "Success-Response: ", 69 | content: "{ \n status: 200 \n}", 70 | type: "json", 71 | }, 72 | ], 73 | }, 74 | }, 75 | index: 1, 76 | }, 77 | }, 78 | ]; 79 | it("case 1: should pass all test cases", function (done) { 80 | testCases.forEach(function (testCase) { 81 | apidoc.setLogger(logger); 82 | var parsed = apidoc.parseSource(Buffer.from(testCase.source)); 83 | deepEqual(parsed, testCase.expected); 84 | }); 85 | done(); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /lib/plugin_loader.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var path = require('path'); 3 | var util = require('util'); 4 | var glob = require('glob'); 5 | 6 | var app = {}; 7 | 8 | function PluginLoader(_app) { 9 | var self = this; 10 | 11 | // global variables 12 | app = _app; 13 | 14 | // class variables 15 | self.plugins = {}; 16 | 17 | // Try to load global apidoc-plugins (if apidoc is installed locally it tries only local) 18 | this.detectPugins(__dirname); 19 | 20 | // Try to load local apidoc-plugins 21 | this.detectPugins( path.join(process.cwd(), '/node_modules') ); 22 | 23 | if (Object.keys(this.plugins).length === 0) { 24 | app.log.debug('No plugins found.'); 25 | } 26 | 27 | this.loadPlugins(); 28 | } 29 | /** 30 | * Inherit 31 | */ 32 | util.inherits(PluginLoader, Object); 33 | 34 | /** 35 | * Exports 36 | */ 37 | module.exports = PluginLoader; 38 | 39 | /** 40 | * Detect modules start with "apidoc-plugin-". 41 | * Search up to root until found a plugin. 42 | */ 43 | PluginLoader.prototype.detectPugins = function(dir) { 44 | var self = this; 45 | 46 | // Every dir start with "apidoc-plugin-", because for the tests of apidoc-plugin-test. 47 | var plugins; 48 | try { 49 | plugins = glob.sync(dir + '/apidoc-plugin-*') 50 | .concat( glob.sync(dir + '/@*/apidoc-plugin-*') ); 51 | } catch (e) { 52 | app.log.warn(e); 53 | return; 54 | } 55 | 56 | if (plugins.length === 0) { 57 | dir = path.join(dir, '..'); 58 | if (dir === '/' || dir.substr(1) === ':\\') { 59 | return; 60 | } 61 | return this.detectPugins(dir); 62 | } 63 | 64 | var offset = dir.length + 1; 65 | plugins.forEach( function(plugin) { 66 | var name = plugin.substr(offset); 67 | var filename = path.relative(__dirname, plugin); 68 | app.log.debug('add plugin: ' + name + ', ' + filename); 69 | self.addPlugin(name, plugin); 70 | }); 71 | }; 72 | 73 | /** 74 | * Add Plugin to plugin list. 75 | */ 76 | PluginLoader.prototype.addPlugin = function(name, filename) { 77 | if (this.plugins[name]) { 78 | app.log.debug('overwrite plugin: ' + name + ', ' + this.plugins[name]); 79 | } 80 | 81 | this.plugins[name] = filename; 82 | }; 83 | 84 | /** 85 | * Load and initialize Plugins. 86 | */ 87 | PluginLoader.prototype.loadPlugins = function() { 88 | _.forEach(this.plugins, function(filename, name) { 89 | app.log.debug('load plugin: ' + name + ', ' + filename); 90 | var plugin; 91 | try { 92 | plugin = require(filename); 93 | } catch(e) { 94 | } 95 | if (plugin && plugin.init) { 96 | plugin.init(app); 97 | } else { 98 | app.log.debug('Ignored, no init function found.'); 99 | } 100 | }); 101 | }; 102 | -------------------------------------------------------------------------------- /test/parser_api_query_test.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false*/ 2 | 3 | /** 4 | * Test: Parser apiParam 5 | */ 6 | 7 | // node modules 8 | var should = require('should'); 9 | 10 | // lib modules 11 | var parser = require('../lib/parsers/api_query'); 12 | 13 | describe('Parser: apiQuery', function() { 14 | 15 | // TODO: Add 1.000 more possible cases ;-) 16 | var testCases = [ 17 | { 18 | title: 'Type, Fieldname, Description', 19 | content: '{String} name The users name.', 20 | expected: { 21 | group: 'Query', 22 | type: 'String', 23 | size: undefined, 24 | allowedValues: undefined, 25 | optional: false, 26 | field: 'name', 27 | defaultValue: undefined, 28 | description: 'The users name.' 29 | } 30 | }, 31 | { 32 | title: 'Type, Fieldname, Description', 33 | content: '{String|String[]} name The users name.', 34 | expected: { 35 | group: 'Query', 36 | type: 'String|String[]', 37 | size: undefined, 38 | allowedValues: undefined, 39 | optional: false, 40 | field: 'name', 41 | defaultValue: undefined, 42 | description: 'The users name.' 43 | } 44 | }, 45 | { 46 | title: '$Simple fieldname only', 47 | content: '$simple', 48 | expected: { 49 | group: 'Query', 50 | type: undefined, 51 | size: undefined, 52 | allowedValues: undefined, 53 | optional: false, 54 | field: '$simple', 55 | defaultValue: undefined, 56 | description: '' 57 | } 58 | }, 59 | { 60 | title: 'All options, with optional defaultValue', 61 | content: ' ( MyGroup ) { \\Object\\String.uni-code_char[] { 1..10 } = \'abc\', \'def\' } ' + 62 | '[ \\MyClass\\field.user_first-name = \'John Doe\' ] Some description.', 63 | expected: { 64 | group: 'MyGroup', 65 | type: '\\Object\\String.uni-code_char[]', 66 | size: '1..10', 67 | allowedValues: [ '\'abc\'', '\'def\'' ], 68 | optional: true, 69 | field: '\\MyClass\\field.user_first-name', 70 | defaultValue: 'John Doe', 71 | description: 'Some description.' 72 | } 73 | }, 74 | ]; 75 | 76 | // create 77 | it('case 1: should pass all regexp test cases', function(done) { 78 | testCases.forEach(function(testCase) { 79 | var parsed = parser.parse(testCase.content); 80 | (parsed !== null).should.equal(true, 'Title: ' + testCase.title + ', Source: ' + testCase.content); 81 | parsed.should.eql(testCase.expected); 82 | }); 83 | done(); 84 | }); 85 | 86 | }); 87 | -------------------------------------------------------------------------------- /test/parsers/api_param.js: -------------------------------------------------------------------------------- 1 | var deepEqual = require('deep-equal'); 2 | var parser = require('../../lib/parsers/api_param'); 3 | 4 | var supportedSpecialCharacters = [ 5 | '#', 6 | '@', 7 | '.' 8 | ]; 9 | 10 | function getTestDataForCharacter(character) { 11 | return [ 12 | { 13 | input: '(params) {String} [' + character + 'id] Id of the product', 14 | expected: { 15 | group: 'params', 16 | type: 'String', 17 | size: undefined, 18 | allowedValues: undefined, 19 | optional: true, 20 | field: character + 'id', 21 | defaultValue: undefined, 22 | description: 'Id of the product' 23 | } 24 | }, 25 | { 26 | input: '(params) {String} [' + character + 'i' + character+ 'd] Id of the product', 27 | expected: { 28 | group: 'params', 29 | type: 'String', 30 | size: undefined, 31 | allowedValues: undefined, 32 | optional: true, 33 | field: character + 'i' + character+ 'd', 34 | defaultValue: undefined, 35 | description: 'Id of the product' 36 | } 37 | }, 38 | { 39 | input: '(params) {String} ' + character + 'id Id of the product', 40 | expected: { 41 | group: 'params', 42 | type: 'String', 43 | size: undefined, 44 | allowedValues: undefined, 45 | optional: false, 46 | field: character + 'id', 47 | defaultValue: undefined, 48 | description: 'Id of the product' 49 | } 50 | }, 51 | { 52 | input: '(params) {String=foo,bar} ' + character + 'id="foo" Id of the product', 53 | expected: { 54 | group: 'params', 55 | type: 'String', 56 | size: undefined, 57 | allowedValues: [ 58 | 'foo', 59 | 'bar' 60 | ], 61 | optional: false, 62 | field: character + 'id', 63 | defaultValue: 'foo', 64 | description: 'Id of the product' 65 | } 66 | } 67 | ]; 68 | } 69 | 70 | describe('api param parser', function() { 71 | supportedSpecialCharacters.forEach(function(character) { 72 | it('Should be able to correctly parse params with special character ' + character, function(done) { 73 | var dataObjects = getTestDataForCharacter(character); 74 | 75 | dataObjects.forEach(function(data) { 76 | var result = parser.parse(data.input); 77 | 78 | if (!deepEqual(result, data.expected)) { 79 | done(new Error('Expected result to be:\n' + JSON.stringify(data.expected, undefined, '\t') + '\n got:\n ' + JSON.stringify(result, undefined, '\t'))); 80 | return; 81 | } 82 | }); 83 | 84 | done(); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /lib/worker.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var _ = require('lodash'); 3 | 4 | var app = {}; 5 | 6 | /** 7 | * Worker 8 | * 9 | * Attaches defined data to parameter which inherit the data. 10 | * It uses 2 functions, preProcess and postProcess (with the result of preProcess). 11 | * 12 | * preProcess Generates a list with [defineName][name][version] = value 13 | * postProcess Attach the preProcess data with the nearest version to the tree. 14 | * 15 | * @param {Object} _app 16 | */ 17 | function Worker(_app) { 18 | var self = this; 19 | 20 | // global variables 21 | app = _app; 22 | 23 | // class variables 24 | this.workers = {}; 25 | 26 | // load worker 27 | var workers = Object.keys(app.workers); 28 | workers.forEach(function(worker) { 29 | if (_.isObject( app.workers[worker] )) { 30 | app.log.debug('inject worker: ' + worker); 31 | self.addWorker(worker, app.workers[worker] ); 32 | } else { 33 | var filename = app.workers[worker]; 34 | app.log.debug('load worker: ' + worker + ', ' + filename); 35 | self.addWorker(worker, require(filename)); 36 | } 37 | }); 38 | } 39 | 40 | /** 41 | * Inherit 42 | */ 43 | util.inherits(Worker, Object); 44 | 45 | /** 46 | * Exports 47 | */ 48 | module.exports = Worker; 49 | 50 | /** 51 | * Add Worker 52 | */ 53 | Worker.prototype.addWorker = function(name, worker) { 54 | this.workers[name] = worker; 55 | }; 56 | 57 | /** 58 | * Execute worker 59 | * 60 | * @todo Add priority system (if needed), if a plugin need an other operation to be done before. 61 | */ 62 | Worker.prototype.process = function(parsedFiles, parsedFilenames, packageInfos) { 63 | // some smaller operation that are not outsourced to extra workers 64 | // TODO: add priority system first and outsource them then 65 | parsedFiles.forEach(function(parsedFile, fileIndex) { 66 | parsedFile.forEach(function(block) { 67 | if (Object.keys(block.global).length === 0 && Object.keys(block.local).length > 0) { 68 | if ( ! block.local.type) 69 | block.local.type = ''; 70 | 71 | if ( ! block.local.url) 72 | block.local.url = ''; 73 | 74 | if ( ! block.local.version) 75 | block.local.version = packageInfos.defaultVersion; 76 | 77 | if ( ! block.local.filename) 78 | block.local.filename = parsedFilenames[fileIndex]; 79 | 80 | // convert dir delimeter \\ to / 81 | block.local.filename = block.local.filename.replace(/\\/g, '/'); 82 | } 83 | 84 | }); 85 | }); 86 | 87 | // process transformations and assignments for each @api-Parameter 88 | var preProcessResults = {}; 89 | 90 | _.each(this.workers, function(worker, name) { 91 | if (worker.preProcess) { 92 | app.log.verbose('worker preProcess: ' + name); 93 | var result = worker.preProcess(parsedFiles, parsedFilenames, packageInfos); 94 | _.extend(preProcessResults, result); 95 | } 96 | }); 97 | _.each(this.workers, function(worker, name) { 98 | if (worker.postProcess) { 99 | app.log.verbose('worker postProcess: ' + name); 100 | worker.postProcess(parsedFiles, parsedFilenames, preProcessResults, packageInfos); 101 | } 102 | }); 103 | }; 104 | -------------------------------------------------------------------------------- /lib/utils/find_files.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs-extra'); 2 | var klawSync = require('klaw-sync'); 3 | var os = require('os'); 4 | var path = require('path'); 5 | 6 | var FileError = require('../errors/file_error'); 7 | 8 | /** 9 | * Search files recursivly and filter with include / exlude filters 10 | */ 11 | function FindFiles() { 12 | this.path = process.cwd(); 13 | this.excludeFilters = []; 14 | this.includeFilters = []; 15 | } 16 | 17 | /** 18 | * Exports 19 | */ 20 | module.exports = new FindFiles(); 21 | 22 | /** 23 | * Set path to source-files 24 | * 25 | * @param {String} path 26 | */ 27 | FindFiles.prototype.setPath = function(newPath) { 28 | if (path) { 29 | this.path = path.resolve(newPath); 30 | } 31 | }; 32 | 33 | /** 34 | * Set exclude filters 35 | * 36 | * @param {string[]} excludeFilters 37 | */ 38 | FindFiles.prototype.setExcludeFilters = function(excludeFilters) { 39 | if (excludeFilters) { 40 | this.excludeFilters = excludeFilters; 41 | } 42 | }; 43 | 44 | /** 45 | * Set include filters 46 | * 47 | * @param {string[]} isSilent 48 | */ 49 | FindFiles.prototype.setIncludeFilters = function(includeFilters) { 50 | if (includeFilters) { 51 | this.includeFilters = includeFilters; 52 | } 53 | }; 54 | 55 | /** 56 | * Search files recursivly and filter by include / exlude filters 57 | * 58 | * @returns {String[]} 59 | */ 60 | FindFiles.prototype.search = function() { 61 | var self = this; 62 | var files = []; 63 | 64 | try { 65 | files = klawSync(self.path).map( function(entry) { 66 | return entry.path; 67 | }); 68 | 69 | // create RegExp Include Filter List 70 | var regExpIncludeFilters = []; 71 | var filters = self.includeFilters; 72 | if (typeof(filters) === 'string') { 73 | filters = [ filters ]; 74 | } 75 | 76 | filters.forEach(function(filter) { 77 | if (filter.length > 0) { 78 | regExpIncludeFilters.push( new RegExp(filter) ); 79 | } 80 | }); 81 | 82 | // RegExp Include Filter 83 | var length = regExpIncludeFilters.length; 84 | files = files.filter(function(filename) { 85 | // not include Directories like 'dirname.js/' 86 | if (fs.statSync(filename).isDirectory()) { 87 | return 0; 88 | } 89 | 90 | if (os.platform() === 'win32') { 91 | filename = filename.replace(/\\/g, '/'); 92 | } 93 | 94 | // apply every filter 95 | for (var i = 0; i < length; i += 1) { 96 | if(regExpIncludeFilters[i].test(filename)) { 97 | return 1; 98 | } 99 | } 100 | 101 | return 0; 102 | }); 103 | 104 | // create RegExp Exclude Filter List 105 | var regExpExcludeFilters = []; 106 | filters = self.excludeFilters; 107 | if (typeof(filters) === 'string') { 108 | filters = [ filters ]; 109 | } 110 | 111 | filters.forEach(function(filter) { 112 | if (filter.length > 0) { 113 | regExpExcludeFilters.push( new RegExp(filter) ); 114 | } 115 | }); 116 | 117 | // RegExp Exclude Filter 118 | length = regExpExcludeFilters.length; 119 | files = files.filter(function(filename) { 120 | if (os.platform() === 'win32') { 121 | filename = filename.replace(/\\/g, '/'); 122 | } 123 | 124 | // apply every filter 125 | for(var i = 0; i < length; i += 1) { 126 | if(regExpExcludeFilters[i].test(filename)) { 127 | return 0; 128 | } 129 | } 130 | 131 | return 1; 132 | }); 133 | } catch (e) { 134 | throw e; 135 | } finally { 136 | if ( ! files || files.length === 0) { 137 | throw new FileError('No files found.', self.path); 138 | } 139 | 140 | // remove source path prefix 141 | files = files.map( function(filename) { 142 | if (filename.startsWith(self.path)) { 143 | return filename.substr(self.path.length + 1); 144 | } 145 | return filename; 146 | }); 147 | } 148 | return files; 149 | }; 150 | -------------------------------------------------------------------------------- /test/parser_api_body_test.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false*/ 2 | 3 | /** 4 | * Test: Parser apiParam 5 | */ 6 | 7 | // node modules 8 | var should = require('should'); 9 | 10 | // lib modules 11 | var parser = require('../lib/parsers/api_body'); 12 | 13 | describe('Parser: apiBody', function() { 14 | 15 | // TODO: Add 1.000 more possible cases ;-) 16 | var testCases = [ 17 | { 18 | title: 'Simple fieldname only', 19 | content: 'simple', 20 | expected: { 21 | group: 'Body', 22 | type: undefined, 23 | size: undefined, 24 | allowedValues: undefined, 25 | optional: false, 26 | field: 'simple', 27 | defaultValue: undefined, 28 | description: '' 29 | } 30 | }, 31 | { 32 | title: 'Type, Fieldname, Description', 33 | content: '{String} name The users name.', 34 | expected: { 35 | group: 'Body', 36 | type: 'String', 37 | size: undefined, 38 | allowedValues: undefined, 39 | optional: false, 40 | field: 'name', 41 | defaultValue: undefined, 42 | description: 'The users name.' 43 | } 44 | }, 45 | { 46 | title: 'Type, Fieldname, Description', 47 | content: '{String|String[]} name The users name.', 48 | expected: { 49 | group: 'Body', 50 | type: 'String|String[]', 51 | size: undefined, 52 | allowedValues: undefined, 53 | optional: false, 54 | field: 'name', 55 | defaultValue: undefined, 56 | description: 'The users name.' 57 | } 58 | }, 59 | { 60 | title: '$Simple fieldname only', 61 | content: '$simple', 62 | expected: { 63 | group: 'Body', 64 | type: undefined, 65 | size: undefined, 66 | allowedValues: undefined, 67 | optional: false, 68 | field: '$simple', 69 | defaultValue: undefined, 70 | description: '' 71 | } 72 | }, 73 | { 74 | title: 'All options, with optional defaultValue', 75 | content: ' ( MyGroup ) { \\Object\\String.uni-code_char[] { 1..10 } = \'abc\', \'def\' } ' + 76 | '[ \\MyClass\\field.user_first-name = \'John Doe\' ] Some description.', 77 | expected: { 78 | group: 'MyGroup', 79 | type: '\\Object\\String.uni-code_char[]', 80 | size: '1..10', 81 | allowedValues: [ '\'abc\'', '\'def\'' ], 82 | optional: true, 83 | field: '\\MyClass\\field.user_first-name', 84 | defaultValue: 'John Doe', 85 | description: 'Some description.' 86 | } 87 | }, 88 | { 89 | title: 'All options, without optional-marker', 90 | content: ' ( MyGroup ) { \\Object\\String.uni-code_char[] { 1..10 } = \'abc\', \'def\' } ' + 91 | '\\MyClass\\field.user_first-name = \'John Doe\' Some description.', 92 | expected: { 93 | group: 'MyGroup', 94 | type: '\\Object\\String.uni-code_char[]', 95 | size: '1..10', 96 | allowedValues: [ '\'abc\'', '\'def\'' ], 97 | optional: false, 98 | field: '\\MyClass\\field.user_first-name', 99 | defaultValue: 'John Doe', 100 | description: 'Some description.' 101 | } 102 | }, 103 | { 104 | title: 'All options, without optional-marker, without default value quotes', 105 | content: ' ( MyGroup ) { \\Object\\String.uni-code_char[] { 1..10 } = \'abc\', \'def\' } ' + 106 | '\\MyClass\\field.user_first-name = John_Doe Some description.', 107 | expected: { 108 | group: 'MyGroup', 109 | type: '\\Object\\String.uni-code_char[]', 110 | size: '1..10', 111 | allowedValues: [ '\'abc\'', '\'def\'' ], 112 | optional: false, 113 | field: '\\MyClass\\field.user_first-name', 114 | defaultValue: 'John_Doe', 115 | description: 'Some description.' 116 | } 117 | }, 118 | ]; 119 | 120 | // create 121 | it('case 1: should pass all regexp test cases', function(done) { 122 | testCases.forEach(function(testCase) { 123 | var parsed = parser.parse(testCase.content); 124 | (parsed !== null).should.equal(true, 'Title: ' + testCase.title + ', Source: ' + testCase.content); 125 | parsed.should.eql(testCase.expected); 126 | }); 127 | done(); 128 | }); 129 | 130 | }); 131 | -------------------------------------------------------------------------------- /test/parser_api_param_test.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false*/ 2 | 3 | /** 4 | * Test: Parser apiParam 5 | */ 6 | 7 | // node modules 8 | var should = require('should'); 9 | 10 | // lib modules 11 | var parser = require('../lib/parsers/api_param'); 12 | 13 | describe('Parser: apiParam', function() { 14 | 15 | // TODO: Add 1.000 more possible cases ;-) 16 | var testCases = [ 17 | { 18 | title: 'Simple fieldname only', 19 | content: 'simple', 20 | expected: { 21 | group: 'Parameter', 22 | type: undefined, 23 | size: undefined, 24 | allowedValues: undefined, 25 | optional: false, 26 | field: 'simple', 27 | defaultValue: undefined, 28 | description: '' 29 | } 30 | }, 31 | { 32 | title: 'Type, Fieldname, Description', 33 | content: '{String} name The users name.', 34 | expected: { 35 | group: 'Parameter', 36 | type: 'String', 37 | size: undefined, 38 | allowedValues: undefined, 39 | optional: false, 40 | field: 'name', 41 | defaultValue: undefined, 42 | description: 'The users name.' 43 | } 44 | }, 45 | { 46 | title: 'Type, Fieldname, Description', 47 | content: '{String|String[]} name The users name.', 48 | expected: { 49 | group: 'Parameter', 50 | type: 'String|String[]', 51 | size: undefined, 52 | allowedValues: undefined, 53 | optional: false, 54 | field: 'name', 55 | defaultValue: undefined, 56 | description: 'The users name.' 57 | } 58 | }, 59 | { 60 | title: '$Simple fieldname only', 61 | content: '$simple', 62 | expected: { 63 | group: 'Parameter', 64 | type: undefined, 65 | size: undefined, 66 | allowedValues: undefined, 67 | optional: false, 68 | field: '$simple', 69 | defaultValue: undefined, 70 | description: '' 71 | } 72 | }, 73 | { 74 | title: 'All options, with optional defaultValue', 75 | content: ' ( MyGroup ) { \\Object\\String.uni-code_char[] { 1..10 } = \'abc\', \'def\' } ' + 76 | '[ \\MyClass\\field.user_first-name = \'John Doe\' ] Some description.', 77 | expected: { 78 | group: 'MyGroup', 79 | type: '\\Object\\String.uni-code_char[]', 80 | size: '1..10', 81 | allowedValues: [ '\'abc\'', '\'def\'' ], 82 | optional: true, 83 | field: '\\MyClass\\field.user_first-name', 84 | defaultValue: 'John Doe', 85 | description: 'Some description.' 86 | } 87 | }, 88 | { 89 | title: 'All options, without optional-marker', 90 | content: ' ( MyGroup ) { \\Object\\String.uni-code_char[] { 1..10 } = \'abc\', \'def\' } ' + 91 | '\\MyClass\\field.user_first-name = \'John Doe\' Some description.', 92 | expected: { 93 | group: 'MyGroup', 94 | type: '\\Object\\String.uni-code_char[]', 95 | size: '1..10', 96 | allowedValues: [ '\'abc\'', '\'def\'' ], 97 | optional: false, 98 | field: '\\MyClass\\field.user_first-name', 99 | defaultValue: 'John Doe', 100 | description: 'Some description.' 101 | } 102 | }, 103 | { 104 | title: 'All options, without optional-marker, without default value quotes', 105 | content: ' ( MyGroup ) { \\Object\\String.uni-code_char[] { 1..10 } = \'abc\', \'def\' } ' + 106 | '\\MyClass\\field.user_first-name = John_Doe Some description.', 107 | expected: { 108 | group: 'MyGroup', 109 | type: '\\Object\\String.uni-code_char[]', 110 | size: '1..10', 111 | allowedValues: [ '\'abc\'', '\'def\'' ], 112 | optional: false, 113 | field: '\\MyClass\\field.user_first-name', 114 | defaultValue: 'John_Doe', 115 | description: 'Some description.' 116 | } 117 | }, 118 | ]; 119 | 120 | // create 121 | it('case 1: should pass all regexp test cases', function(done) { 122 | testCases.forEach(function(testCase) { 123 | var parsed = parser.parse(testCase.content); 124 | (parsed !== null).should.equal(true, 'Title: ' + testCase.title + ', Source: ' + testCase.content); 125 | parsed.should.eql(testCase.expected); 126 | }); 127 | done(); 128 | }); 129 | 130 | }); 131 | -------------------------------------------------------------------------------- /test/apidoc_test.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false, expr:true */ 2 | 3 | /** 4 | * Test: apiDoc full parse 5 | */ 6 | 7 | // node modules 8 | var fs = require('fs'); 9 | var path = require('path'); 10 | var semver = require('semver'); 11 | var should = require('should'); 12 | var Markdown = require('markdown-it'); 13 | 14 | var versions = require('apidoc-example').versions; 15 | 16 | // lib modules 17 | var apidoc = require('../lib/index'); 18 | 19 | describe('apiDoc full parse', function() { 20 | 21 | // get latest example for the used apidoc-spec 22 | var latestExampleVersion = semver.maxSatisfying(versions, '~' + apidoc.getSpecificationVersion()); // ~0.2.0 = >=0.2.0 <0.3.0 23 | 24 | var exampleBasePath = 'node_modules/apidoc-example/' + latestExampleVersion; 25 | var fixturePath = exampleBasePath + '/fixtures'; 26 | 27 | function log() { 28 | // can add an emitter here and capture it in the tests with chai-spies 29 | } 30 | 31 | var logger = { 32 | debug : log, 33 | verbose: log, 34 | info : log, 35 | warn : log, 36 | error : log, 37 | }; 38 | 39 | var markdown = new Markdown({ 40 | breaks : false, 41 | html : true, 42 | linkify : false, 43 | typographer: false 44 | }); 45 | 46 | var fixtureFiles = [ 47 | { key: 'data' , filename: 'api_data.json' }, 48 | { key: 'project', filename: 'api_project.json' } 49 | ]; 50 | 51 | var api = {}; 52 | 53 | before(function(done) { 54 | done(); 55 | }); 56 | 57 | after(function(done) { 58 | done(); 59 | }); 60 | 61 | // version found 62 | it('should find latest example version', function(done) { 63 | should(latestExampleVersion).be.ok; 64 | done(); 65 | }); 66 | 67 | // create 68 | it('should create example in memory', function(done) { 69 | apidoc.setLogger(logger); 70 | apidoc.setGeneratorInfos({}); 71 | apidoc.setMarkdownParser(markdown); 72 | apidoc.setPackageInfos({ 73 | 'name': 'test', 74 | 'version': '0.5.0', 75 | 'description': 'RESTful web API Documentation Generator', 76 | 'url' : 'https://api.github.com/v1', 77 | 'sampleUrl': 'https://api.github.com/v1', 78 | 'header': { 79 | 'title': 'My own header title', 80 | 'content': '

Header .md File

\n

Content of header.md file.

\n' 81 | }, 82 | 'footer': { 83 | 'title': 'My own footer title', 84 | 'content': '

Footer .md File

\n

Content of footer.md file.

\n' 85 | }, 86 | 'order': [ 87 | 'Error', 88 | 'Define', 89 | 'PostTitleAndError', 90 | 'NotExistingEntry', 91 | 'PostError', 92 | 'GetParam' 93 | ] 94 | }); 95 | 96 | api = apidoc.parse({ 97 | src: exampleBasePath + '/src/', 98 | lineEnding: '\n' 99 | }); 100 | 101 | if (api === false) 102 | throw new Error('Parse failed.'); 103 | 104 | done(); 105 | }); 106 | 107 | // compare 108 | it('memory should compare to fixtures', function(done) { 109 | var timeRegExp = /\"time\"\:\s\"(.*)\"/g; 110 | var versionRegExp = /\"version\"\:\s\"(.*)\"/g; 111 | var filenameRegExp = new RegExp('(?!"filename":\\s")(' + exampleBasePath + '/)', 'g'); 112 | 113 | fixtureFiles.forEach(function(file) { 114 | var key = file.key; 115 | var name = fixturePath + '/' + file.filename; 116 | 117 | var fixtureContent = fs.readFileSync(name, 'utf8'); 118 | var createdContent = api[key] + '\n'; // add linebreak at the end 119 | 120 | // creation time remove (never equal) 121 | fixtureContent = fixtureContent.replace(timeRegExp, ''); 122 | createdContent = createdContent.replace(timeRegExp, ''); 123 | 124 | // creation time remove (or fixtures must be updated every time the version change) 125 | fixtureContent = fixtureContent.replace(versionRegExp, ''); 126 | createdContent = createdContent.replace(versionRegExp, ''); 127 | 128 | // remove the base path 129 | createdContent = createdContent.replace(filenameRegExp, ''); 130 | 131 | // split and compare each line 132 | // TODO: compare objects not line by line 133 | var fixtureLines = fixtureContent.split(/\n/); 134 | var createdLines = createdContent.split(/\n/); 135 | 136 | // if (fixtureLines.length !== createdLines.length) 137 | // throw new Error(key + ' not equals to ' + name); 138 | 139 | for (var lineNumber = 0; lineNumber < fixtureLines.length; lineNumber += 1) { 140 | if (fixtureLines[lineNumber] !== createdLines[lineNumber]) 141 | throw new Error(key + ' not equals to ' + name + ' in line ' + (lineNumber + 1) + 142 | '\nfixture: ' + fixtureLines[lineNumber] + 143 | '\ncreated: ' + createdLines[lineNumber] 144 | ); 145 | } 146 | }); 147 | done(); 148 | }); 149 | 150 | }); 151 | -------------------------------------------------------------------------------- /lib/parsers/api_param.js: -------------------------------------------------------------------------------- 1 | var unindent = require('../utils/unindent'); 2 | 3 | var group = ''; 4 | 5 | // Search: group, type, optional, fieldname, defaultValue, size, description 6 | // Example: {String{1..4}} [user.name='John Doe'] Users fullname. 7 | // 8 | // Naming convention: 9 | // b -> begin 10 | // e -> end 11 | // name -> the field value 12 | // oName -> wrapper for optional field 13 | // wName -> wrapper for field 14 | var regExp = { 15 | b: '^', // start 16 | oGroup: { // optional group: (404) 17 | b: '\\s*(?:\\(\\s*', // starting with '(', optional surrounding spaces 18 | group: '(.+?)', // 1 19 | e: '\\s*\\)\\s*)?' // ending with ')', optional surrounding spaces 20 | }, 21 | oType: { // optional type: {string} 22 | b: '\\s*(?:\\{\\s*', // starting with '{', optional surrounding spaces 23 | type: '([a-zA-Z0-9\(\)#:\\.\\/\\\\\\[\\]_\|-]+)', // 2 24 | oSize: { // optional size within type: {string{1..4}} 25 | b: '\\s*(?:\\{\\s*', // starting with '{', optional surrounding spaces 26 | size: '(.+?)', // 3 27 | e: '\\s*\\}\\s*)?' // ending with '}', optional surrounding spaces 28 | }, 29 | oAllowedValues: { // optional allowed values within type: {string='abc','def'} 30 | b: '\\s*(?:=\\s*', // starting with '=', optional surrounding spaces 31 | possibleValues: '(.+?)', // 4 32 | e: '(?=\\s*\\}\\s*))?' // ending with '}', optional surrounding spaces 33 | }, 34 | e: '\\s*\\}\\s*)?' // ending with '}', optional surrounding spaces 35 | }, 36 | wName: { 37 | b: '(\\[?\\s*', // 5 optional optional-marker 38 | name: '([#@a-zA-Z0-9\\$\\:\\.\\/\\\\_-]+', // 6 39 | withArray: '(?:\\[[a-zA-Z0-9\\.\\/\\\\_-]*\\])?)', // https://github.com/apidoc/apidoc-core/pull/4 40 | oDefaultValue: { // optional defaultValue 41 | b: '(?:\\s*=\\s*(?:', // starting with '=', optional surrounding spaces 42 | withDoubleQuote: '"([^"]*)"', // 7 43 | withQuote: '|\'([^\']*)\'', // 8 44 | withoutQuote: '|(.*?)(?:\\s|\\]|$)', // 9 45 | e: '))?' 46 | }, 47 | e: '\\s*\\]?\\s*)' 48 | }, 49 | description: '(.*)?', // 10 50 | e: '$|@' 51 | }; 52 | 53 | function _objectValuesToString(obj) { 54 | var str = ''; 55 | for (var el in obj) { 56 | if (typeof obj[el] === 'string') 57 | str += obj[el]; 58 | else 59 | str += _objectValuesToString(obj[el]); 60 | } 61 | return str; 62 | } 63 | 64 | var parseRegExp = new RegExp(_objectValuesToString(regExp)); 65 | 66 | var allowedValuesWithDoubleQuoteRegExp = new RegExp(/\"[^\"]*[^\"]\"/g); 67 | var allowedValuesWithQuoteRegExp = new RegExp(/\'[^\']*[^\']\'/g); 68 | var allowedValuesRegExp = new RegExp(/[^,\s]+/g); 69 | 70 | function parse(content, source, defaultGroup) { 71 | content = content.trim(); 72 | 73 | // replace Linebreak with Unicode 74 | content = content.replace(/\n/g, '\uffff'); 75 | 76 | var matches = parseRegExp.exec(content); 77 | 78 | if ( ! matches) 79 | return null; 80 | 81 | // reverse Unicode Linebreaks 82 | matches.forEach(function (val, index, array) { 83 | if (val) { 84 | array[index] = val.replace(/\uffff/g, '\n'); 85 | } 86 | }); 87 | 88 | var allowedValues = matches[4]; 89 | if (allowedValues) { 90 | var regExp; 91 | if (allowedValues.charAt(0) === '"') 92 | regExp = allowedValuesWithDoubleQuoteRegExp; 93 | else if (allowedValues.charAt(0) === '\'') 94 | regExp = allowedValuesWithQuoteRegExp; 95 | else 96 | regExp = allowedValuesRegExp; 97 | 98 | var allowedValuesMatch; 99 | var list = []; 100 | 101 | while ( (allowedValuesMatch = regExp.exec(allowedValues)) ) { 102 | list.push(allowedValuesMatch[0]); 103 | } 104 | allowedValues = list; 105 | } 106 | 107 | // Set global group variable 108 | group = matches[1] || defaultGroup || 'Parameter'; 109 | 110 | return { 111 | group : group, 112 | type : matches[2], 113 | size : matches[3], 114 | allowedValues: allowedValues, 115 | optional : (matches[5] && matches[5][0] === '[') ? true : false, 116 | field : matches[6], 117 | defaultValue : matches[7] || matches[8] || matches[9], 118 | description : unindent(matches[10] || '') 119 | }; 120 | } 121 | 122 | function path() { 123 | return 'local.parameter.fields.' + getGroup(); 124 | } 125 | 126 | function getGroup() { 127 | return group; 128 | } 129 | 130 | /** 131 | * Exports 132 | */ 133 | module.exports = { 134 | parse : parse, 135 | path : path, 136 | method : 'push', 137 | getGroup : getGroup, 138 | markdownFields: [ 'description', 'type' ], 139 | markdownRemovePTags: [ 'type' ] 140 | }; 141 | -------------------------------------------------------------------------------- /lib/workers/api_name.js: -------------------------------------------------------------------------------- 1 | /** 2 | * PostProcess 3 | * 4 | * Priority: process after use and api 5 | * 6 | * @param {Object[]} parsedFiles 7 | * @param {String[]} filenames 8 | * @param {Object[]} preProcess 9 | * @param {Object} packageInfos 10 | */ 11 | function postProcess(parsedFiles/*, filenames, preProcess, packageInfos*/) { 12 | var target = 'name'; 13 | 14 | parsedFiles.forEach(function(parsedFile) { 15 | parsedFile.forEach(function(block) { 16 | // Ignore global name, or non existing global names (that will be generated with this func) 17 | // could overwrite local names on a later starting worker process from e.g. @apiUse 18 | if (Object.keys(block.global).length === 0) { 19 | var name = block.local[target]; 20 | if ( ! name) { 21 | // TODO: Add a warning 22 | 23 | // HINT: document that name SHOULD always be used 24 | // if no name is set, the name will be generated from type and url. 25 | var type = block.local.type; 26 | var url = block.local.url; 27 | name = type.charAt(0).toUpperCase() + type.slice(1).toLowerCase(); 28 | 29 | var matches = url.match(/[\w]+/g); 30 | if (matches) { 31 | for (var i = 0; i < matches.length; i+= 1) { 32 | var part = matches[i]; 33 | name += part.charAt(0).toUpperCase() + part.slice(1).toLowerCase(); 34 | } 35 | } 36 | } 37 | 38 | // replace special chars 39 | name.replace(/[^\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/g, '_'); 40 | 41 | block.local[target] = name; 42 | } 43 | }); 44 | }); 45 | } 46 | 47 | /** 48 | * Exports 49 | */ 50 | module.exports = { 51 | postProcess: postProcess 52 | }; 53 | -------------------------------------------------------------------------------- /lib/workers/api_permission.js: -------------------------------------------------------------------------------- 1 | var semver = require('semver'); 2 | var WorkerError = require('../errors/worker_error'); 3 | 4 | // Additional information for error log 5 | var _messages = { 6 | common: { 7 | element: 'apiPermission', 8 | usage : '@apiPermission group', 9 | example: '@apiDefine MyValidPermissionGroup Some title\n@apiPermission MyValidPermissionGroup' 10 | } 11 | }; 12 | 13 | /** 14 | * PreProcess 15 | * 16 | * @param {Object[]} parsedFiles 17 | * @param {String[]} filenames 18 | * @param {Object} packageInfos 19 | * @param {String} target Target path in preProcess-Object (returned result), where the data should be set. 20 | * @returns {Object} 21 | */ 22 | function preProcess(parsedFiles, filenames, packageInfos, target) { 23 | target = target || 'definePermission'; 24 | var source = 'define'; // relative path to the tree (global.), from where the data should be fetched. 25 | 26 | var result = {}; 27 | result[target] = {}; 28 | 29 | parsedFiles.forEach(function(parsedFile) { 30 | parsedFile.forEach(function(block) { 31 | if (block.global[source]) { 32 | var name = block.global[source].name; 33 | var version = block.version || packageInfos.defaultVersion; 34 | 35 | if ( ! result[target][name]) 36 | result[target][name] = {}; 37 | 38 | // fetch from local 39 | result[target][name][version] = block.global[source]; 40 | } 41 | }); 42 | }); 43 | 44 | if (result[target].length === 0) 45 | delete result[target]; 46 | 47 | return result; 48 | } 49 | 50 | /** 51 | * PostProcess 52 | * 53 | * @param {Object[]} parsedFiles 54 | * @param {String[]} filenames 55 | * @param {Object[]} preProcess 56 | * @param {Object} packageInfos 57 | * @param {String} source Source path in preProcess-Object 58 | * @param {String} target Relative path to the tree (local.), where the data should be modified. 59 | * @param {String} messages 60 | */ 61 | function postProcess(parsedFiles, filenames, preProcess, packageInfos, source, target, messages) { 62 | source = source || 'definePermission'; 63 | target = target || 'permission'; 64 | messages = messages || _messages; 65 | 66 | parsedFiles.forEach(function(parsedFile, parsedFileIndex) { 67 | parsedFile.forEach(function(block) { 68 | if ( ! block.local[target]) 69 | return; 70 | 71 | var newPermissions = []; 72 | block.local[target].forEach(function(definition) { 73 | var name = definition.name; 74 | var version = block.version || packageInfos.defaultVersion; 75 | var matchedData = {}; 76 | 77 | if ( ! preProcess[source] || ! preProcess[source][name]) { 78 | // TODO: Enable in the next version 79 | // At the moment the (groupname) is optional and must not be defined. 80 | /* 81 | var extra = [ 82 | { 'Groupname': name } 83 | ]; 84 | throw new WorkerError('Referenced groupname does not exist / it is not defined with @apiDefine.', 85 | filenames[parsedFileIndex], 86 | block.index, 87 | messages.common.element, 88 | messages.common.usage, 89 | messages.common.example, 90 | extra); 91 | */ 92 | // TODO: Remove in the next version 93 | matchedData.name = name; 94 | matchedData.title = definition.title; 95 | matchedData.description = definition.description; 96 | 97 | } 98 | 99 | // TODO: Remove in the next version 100 | else { 101 | 102 | if (preProcess[source][name][version]) { 103 | // found the version 104 | matchedData = preProcess[source][name][version]; 105 | } else { 106 | // find nearest matching version 107 | var foundIndex = -1; 108 | var lastVersion = packageInfos.defaultVersion; 109 | 110 | var versionKeys = Object.keys(preProcess[source][name]); 111 | versionKeys.forEach(function(currentVersion, versionIndex) { 112 | if (semver.gte(version, currentVersion) && semver.gte(currentVersion, lastVersion)) { 113 | lastVersion = currentVersion; 114 | foundIndex = versionIndex; 115 | } 116 | }); 117 | 118 | if (foundIndex === -1) { 119 | var extra = [ 120 | { 'Groupname': name }, 121 | { 'Version': version }, 122 | { 'Defined versions': versionKeys }, 123 | ]; 124 | throw new WorkerError('Referenced definition has no matching or a higher version. ' + 125 | 'Check version number in referenced define block.', 126 | filenames[parsedFileIndex], 127 | block.index, 128 | messages.common.element, 129 | messages.common.usage, 130 | messages.common.example, 131 | extra); 132 | } 133 | 134 | var versionName = versionKeys[foundIndex]; 135 | matchedData = preProcess[source][name][versionName]; 136 | } 137 | 138 | // TODO: Remove in the next version 139 | } 140 | 141 | newPermissions.push(matchedData); 142 | }); 143 | 144 | // replace permissions with new permissions 145 | // TODO: reduce complexity and remove group 146 | block.local[target] = newPermissions; 147 | }); 148 | }); 149 | } 150 | 151 | /** 152 | * Exports 153 | */ 154 | module.exports = { 155 | preProcess : preProcess, 156 | postProcess: postProcess 157 | }; 158 | -------------------------------------------------------------------------------- /test/worker_api_use_test.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false*/ 2 | 3 | /** 4 | * Test: Parser apiParam 5 | */ 6 | 7 | // node modules 8 | var should = require('should'); 9 | 10 | // lib modules 11 | var worker = require('../lib/workers/api_use'); 12 | 13 | describe('Worker: apiUse', function() { 14 | 15 | var packageInfos = { 16 | name: 'Test recursive apiUse', 17 | version: '0.0.1', 18 | description: 'i am a dummy description', 19 | title: 'Test recursive apiUse', 20 | url: 'http://localhost:18080', 21 | order: [ 'GetUser', 'PostUser' ], 22 | template: { withCompare: false, withGenerator: true }, 23 | sampleUrl: false, 24 | defaultVersion: '0.0.0' 25 | }; 26 | 27 | var filenames = [ 'fileA', 'fileB', 'fileC' ]; 28 | 29 | // TODO: Add 1.000 more possible cases ;-) 30 | // the tree is build like 31 | // root 32 | // l 33 | var parsedFilesSimpleTest = [ 34 | //file 35 | [ 36 | { 37 | global: { }, 38 | local: { 39 | use: [ 40 | { name: 'leaf_l' } 41 | ], 42 | name: 'root', 43 | test: ['root'] 44 | }, 45 | expected: 'root', 46 | }, 47 | { 48 | global: { 49 | define: { 50 | name: 'leaf_l', 51 | title: '', 52 | description: '', 53 | } 54 | }, 55 | local: { 56 | test: ['l'] 57 | }, 58 | expected: 'l' 59 | } 60 | ] 61 | ]; 62 | 63 | // the tree is build like 64 | // root 65 | // l, r 66 | // ll, rr 67 | // rrl, rrr 68 | var parsedFilesRecursiveTest = [ 69 | //file 70 | [ 71 | { 72 | global: { }, 73 | local: { 74 | use: [ 75 | { name: 'leaf_l' }, 76 | { name: 'leaf_r' }, 77 | ], 78 | name: 'root', 79 | test: ['root'] 80 | }, 81 | expected: 'root', 82 | } 83 | ], 84 | [ 85 | { 86 | global: { 87 | define: { 88 | name: 'leaf_l', 89 | title: '', 90 | description: '', 91 | } 92 | }, 93 | local: { 94 | test: ['l'], 95 | use: [ 96 | { name: 'leaf_ll' } 97 | ], 98 | }, 99 | expected: 'l' 100 | }, 101 | { 102 | global: { 103 | define: { 104 | name: 'leaf_rr', 105 | title: '', 106 | description: '', 107 | } 108 | }, 109 | local: { 110 | test: ['rr'], 111 | use: [ 112 | { name: 'leaf_rrr' }, 113 | { name: 'leaf_rrl' } 114 | ], 115 | }, 116 | expected: 'rr' 117 | } 118 | ], 119 | [ 120 | { 121 | global: { 122 | define: { 123 | name: 'leaf_ll', 124 | title: '', 125 | description: '', 126 | } 127 | }, 128 | local: { 129 | test: ['ll'] 130 | }, 131 | expected: 'll' 132 | }, 133 | { 134 | global: { 135 | define: { 136 | name: 'leaf_r', 137 | title: '', 138 | description: '', 139 | } 140 | }, 141 | local: { 142 | test: ['r'], 143 | use: [ 144 | { name: 'leaf_rr' } 145 | ], 146 | }, 147 | expected: 'r' 148 | }, 149 | { 150 | global: { 151 | define: { 152 | name: 'leaf_rrr', 153 | title: '', 154 | description: '', 155 | } 156 | }, 157 | local: { 158 | test: ['rrr'] 159 | }, 160 | expected: 'rrr' 161 | }, 162 | { 163 | global: { 164 | define: { 165 | name: 'leaf_rrl', 166 | title: '', 167 | description: '', 168 | } 169 | }, 170 | local: { 171 | test: ['rrl'] 172 | }, 173 | expected: 'rrl' 174 | } 175 | ] 176 | ]; 177 | 178 | // create 179 | it('case 1: simple test', function(done) { 180 | var preProcess = worker.preProcess(parsedFilesSimpleTest, filenames, packageInfos); 181 | worker.postProcess(parsedFilesSimpleTest, filenames, preProcess, packageInfos); 182 | 183 | var rootBlock = parsedFilesSimpleTest[0][0]; 184 | rootBlock.local.name.should.eql('root'); 185 | 186 | //check if the root block contains the expected value from every other block 187 | parsedFilesSimpleTest.forEach(function(parsedFile, parsedFileIndex) { 188 | parsedFile.forEach(function(block) { 189 | rootBlock.local.test.should.containEql(block.expected); 190 | }); 191 | }); 192 | done(); 193 | }); 194 | 195 | it('case 2: recursive test', function(done) { 196 | var preProcess = worker.preProcess(parsedFilesRecursiveTest, filenames, packageInfos); 197 | worker.postProcess(parsedFilesRecursiveTest, filenames, preProcess, packageInfos); 198 | 199 | var rootBlock = parsedFilesRecursiveTest[0][0]; 200 | rootBlock.local.name.should.eql('root'); 201 | 202 | //check if the root block contains the expected value from every other block 203 | parsedFilesRecursiveTest.forEach(function(parsedFile, parsedFileIndex) { 204 | parsedFile.forEach(function(block) { 205 | rootBlock.local.test.should.containEql(block.expected); 206 | }); 207 | }); 208 | done(); 209 | }); 210 | 211 | }); 212 | -------------------------------------------------------------------------------- /lib/workers/api_param_title.js: -------------------------------------------------------------------------------- 1 | var semver = require('semver'); 2 | var WorkerError = require('../errors/worker_error'); 3 | 4 | // Additional information for error log 5 | var _messages = { 6 | common: { 7 | element: 'apiParam', 8 | usage : '@apiParam (group) varname', 9 | example: '@apiDefine MyValidParamGroup Some title\n@apiParam (MyValidParamGroup) username' 10 | } 11 | }; 12 | 13 | /** 14 | * PreProcess 15 | * 16 | * @param {Object[]} parsedFiles 17 | * @param {String[]} filenames 18 | * @param {Object} packageInfos 19 | * @param {String} target Target path in preProcess-Object (returned result), where the data should be set. 20 | * @returns {Object} 21 | */ 22 | function preProcess(parsedFiles, filenames, packageInfos, target) { 23 | target = target || 'defineParamTitle'; 24 | var source = 'define'; // relative path to the tree (global.), from where the data should be fetched. 25 | 26 | var result = {}; 27 | result[target] = {}; 28 | 29 | parsedFiles.forEach(function(parsedFile) { 30 | parsedFile.forEach(function(block) { 31 | if (block.global[source]) { 32 | var name = block.global[source].name; 33 | var version = block.version || packageInfos.defaultVersion; 34 | 35 | if ( ! result[target][name]) 36 | result[target][name] = {}; 37 | 38 | // fetch from global 39 | result[target][name][version] = block.global[source]; 40 | } 41 | }); 42 | }); 43 | 44 | // remove empty target 45 | if (result[target].length === 0) 46 | delete result[target]; 47 | 48 | return result; 49 | } 50 | 51 | /** 52 | * PostProcess 53 | * 54 | * @param {Object[]} parsedFiles 55 | * @param {String[]} filenames 56 | * @param {Object[]} preProcess 57 | * @param {Object} packageInfos 58 | * @param {String} source Source path in preProcess-Object 59 | * @param {String} target Relative path to the tree (local.), where the data should be modified. 60 | * @param {String} messages 61 | */ 62 | function postProcess(parsedFiles, filenames, preProcess, packageInfos, source, target, messages) { 63 | source = source || 'defineParamTitle'; 64 | target = target || 'parameter'; 65 | messages = messages || _messages; 66 | 67 | parsedFiles.forEach(function(parsedFile, parsedFileIndex) { 68 | parsedFile.forEach(function(block) { 69 | if ( ! block.local[target] || ! block.local[target].fields) 70 | return; 71 | 72 | var newFields = {}; 73 | var fields = block.local[target].fields; 74 | Object.keys(fields).forEach(function(fieldGroup) { 75 | var params = block.local[target].fields[fieldGroup]; 76 | 77 | params.forEach(function(definition) { 78 | var name = definition.group; 79 | var version = block.version || packageInfos.defaultVersion; 80 | var matchedData = {}; 81 | 82 | if ( ! preProcess[source] || ! preProcess[source][name]) { 83 | // TODO: Enable in the next version 84 | // At the moment the (groupname) is optional and must not be defined. 85 | /* 86 | var extra = [ 87 | { 'Groupname': name } 88 | ]; 89 | throw new WorkerError('Referenced groupname does not exist / it is not defined with @apiDefine.', 90 | filenames[parsedFileIndex], 91 | block.index, 92 | messages.common.element, 93 | messages.common.usage, 94 | messages.common.example, 95 | extra); 96 | */ 97 | // TODO: Remove in the next version 98 | matchedData.name = name; 99 | matchedData.title = name; 100 | 101 | } 102 | 103 | // TODO: Remove in the next version 104 | else { 105 | 106 | if (preProcess[source][name][version]) { 107 | // found the version 108 | matchedData = preProcess[source][name][version]; 109 | } else { 110 | // find nearest matching version 111 | var foundIndex = -1; 112 | var lastVersion = packageInfos.defaultVersion; 113 | 114 | var versionKeys = Object.keys(preProcess[source][name]); 115 | versionKeys.forEach(function(currentVersion, versionIndex) { 116 | if (semver.gte(version, currentVersion) && semver.gte(currentVersion, lastVersion)) { 117 | lastVersion = currentVersion; 118 | foundIndex = versionIndex; 119 | } 120 | }); 121 | 122 | if (foundIndex === -1) { 123 | var extra = [ 124 | { 'Groupname': name }, 125 | { 'Version': version }, 126 | { 'Defined versions': versionKeys }, 127 | ]; 128 | throw new WorkerError('Referenced definition has no matching or a higher version. ' + 129 | 'Check version number in referenced define block.', 130 | filenames[parsedFileIndex], 131 | block.index, 132 | messages.common.element, 133 | messages.common.usage, 134 | messages.common.example, 135 | extra); 136 | } 137 | 138 | var versionName = versionKeys[foundIndex]; 139 | matchedData = preProcess[source][name][versionName]; 140 | } 141 | 142 | // TODO: Remove in the next version 143 | } 144 | 145 | if ( ! newFields[matchedData.title]) 146 | newFields[matchedData.title] = []; 147 | 148 | newFields[matchedData.title].push(definition); 149 | }); 150 | }); 151 | 152 | // replace fields with new field header 153 | // TODO: reduce complexity and remove group 154 | block.local[target].fields = newFields; 155 | }); 156 | }); 157 | } 158 | 159 | /** 160 | * Exports 161 | */ 162 | module.exports = { 163 | preProcess : preProcess, 164 | postProcess: postProcess 165 | }; 166 | -------------------------------------------------------------------------------- /lib/workers/api_use.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var semver = require('semver'); 3 | var WorkerError = require('../errors/worker_error'); 4 | 5 | // Additional information for error log 6 | var _messages = { 7 | common: { 8 | element: 'apiUse', 9 | usage : '@apiUse group', 10 | example: '@apiDefine MyValidGroup Some title\n@apiUse MyValidGroup' 11 | } 12 | }; 13 | 14 | /** 15 | * PreProcess 16 | * 17 | * @param {Object[]} parsedFiles 18 | * @param {String[]} filenames 19 | * @param {Object} packageInfos 20 | * @param {String} target Target path in preProcess-Object (returned result), where the data should be set. 21 | * @returns {Object} 22 | */ 23 | function preProcess(parsedFiles, filenames, packageInfos, target) { 24 | target = target || 'define'; 25 | var source = target; // relative path to the tree (global.), from where the data should be fetched. 26 | 27 | var result = {}; 28 | result[target] = {}; 29 | 30 | parsedFiles.forEach(function(parsedFile) { 31 | parsedFile.forEach(function(block) { 32 | if (block.global[source]) { 33 | var name = block.global[source].name; 34 | var version = block.version || packageInfos.defaultVersion; 35 | 36 | if ( ! result[target][name]) 37 | result[target][name] = {}; 38 | 39 | // fetch from local 40 | result[target][name][version] = block.local; 41 | } 42 | }); 43 | }); 44 | 45 | if (result[target].length === 0) 46 | delete result[target]; 47 | 48 | return result; 49 | } 50 | 51 | /** 52 | * PostProcess 53 | * 54 | * @param {Object[]} parsedFiles 55 | * @param {String[]} filenames 56 | * @param {Object[]} preProcess 57 | * @param {Object} packageInfos 58 | * @param {String} source Source path in preProcess-Object 59 | * @param {String} target Target path in preProcess-Object (returned result), where the data should be set. 60 | * @param {String} messages 61 | */ 62 | function postProcess(parsedFiles, filenames, preProcess, packageInfos, source, target, messages) { 63 | source = source || 'define'; 64 | target = target || 'use'; 65 | messages = messages || _messages; 66 | 67 | parsedFiles.forEach(function(parsedFile, parsedFileIndex) { 68 | parsedFile.forEach(function(block) { 69 | var loopCounter = 0; //add a loop counter to have a break condition when the recursion depth exceed a predifined limit 70 | while (block.local[target]) { 71 | if (loopCounter > 10) { 72 | throw new WorkerError('recursion depth exceeds limit with @apiUse', 73 | filenames[parsedFileIndex], 74 | block.index, 75 | messages.common.element, 76 | messages.common.usage, 77 | messages.common.example, 78 | [ 79 | { 'Groupname': block.name } 80 | ] 81 | ); 82 | } 83 | 84 | //create a copy of the elements for save iterating of the elements 85 | var blockClone = block.local[target].slice(); 86 | 87 | // remove unneeded target before starting the loop, to allow a save insertion of new elements 88 | // TODO: create a cleanup filter 89 | delete block.local[target]; 90 | 91 | for (var blockIndex = 0; blockIndex < blockClone.length; ++blockIndex) { 92 | var definition = blockClone[blockIndex]; 93 | var name = definition.name; 94 | var version = block.version || packageInfos.defaultVersion; 95 | 96 | if ( ! preProcess[source] || ! preProcess[source][name]) { 97 | throw new WorkerError('Referenced groupname does not exist / it is not defined with @apiDefine.', 98 | filenames[parsedFileIndex], 99 | block.index, 100 | messages.common.element, 101 | messages.common.usage, 102 | messages.common.example, 103 | [ 104 | { 'Groupname': name } 105 | ] 106 | ); 107 | } 108 | 109 | var matchedData = {}; 110 | if (preProcess[source][name][version]) { 111 | // found the version 112 | matchedData = preProcess[source][name][version]; 113 | } else { 114 | // find nearest matching version 115 | var foundIndex = -1; 116 | var lastVersion = packageInfos.defaultVersion; 117 | 118 | var versionKeys = Object.keys(preProcess[source][name]); 119 | for (var versionIndex = 0; versionIndex < versionKeys.length; ++versionIndex) { 120 | var currentVersion = versionKeys[versionIndex]; 121 | if (semver.gte(version, currentVersion) && semver.gte(currentVersion, lastVersion)) { 122 | lastVersion = currentVersion; 123 | foundIndex = versionIndex; 124 | } 125 | } 126 | 127 | if (foundIndex === -1) { 128 | throw new WorkerError('Referenced definition has no matching or a higher version. ' + 129 | 'Check version number in referenced define block.', 130 | filenames[parsedFileIndex], 131 | block.index, 132 | messages.common.element, 133 | messages.common.usage, 134 | messages.common.example, 135 | [ 136 | { 'Groupname': name }, 137 | { 'Version': version }, 138 | { 'Defined versions': versionKeys }, 139 | ] 140 | ); 141 | } 142 | 143 | var versionName = versionKeys[foundIndex]; 144 | matchedData = preProcess[source][name][versionName]; 145 | } 146 | 147 | // copy matched elements into parsed block 148 | _recursiveMerge(block.local, matchedData); 149 | } 150 | } 151 | }); 152 | }); 153 | } 154 | 155 | /** 156 | * Recursive Merge of Objects with Arrays. 157 | * 158 | * @param block 159 | * @param matchedData 160 | * @todo Bad Hack - watch for something better 161 | */ 162 | function _recursiveMerge(block, matchedData) { 163 | _.mergeWith(block, matchedData, function(a, b) { 164 | if(a instanceof Array) { 165 | return a.concat(b); 166 | } 167 | if(_.isObject(a)) { 168 | _recursiveMerge(a, b); 169 | } 170 | return a; 171 | }); 172 | } 173 | 174 | /** 175 | * Exports 176 | */ 177 | module.exports = { 178 | preProcess : preProcess, 179 | postProcess: postProcess 180 | }; 181 | -------------------------------------------------------------------------------- /lib/workers/api_group.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var semver = require('semver'); 3 | var WorkerError = require('../errors/worker_error'); 4 | 5 | // Additional information for error log 6 | var _messages = { 7 | common: { 8 | element: 'apiGroup', 9 | usage : '@apiGroup group', 10 | example: '@apiDefine MyValidGroup Some title\n@apiGroup MyValidGroup' 11 | } 12 | }; 13 | 14 | /** 15 | * PreProcess 16 | * 17 | * @param {Object[]} parsedFiles 18 | * @param {String[]} filenames 19 | * @param {Object} packageInfos 20 | * @param {String} target Target path in preProcess-Object (returned result), where the data should be set. 21 | * @returns {Object} 22 | */ 23 | function preProcess(parsedFiles, filenames, packageInfos, target) { 24 | target = target || 'defineGroup'; 25 | var source = 'define'; // relative path to the tree (global.), from where the data should be fetched. 26 | 27 | var result = {}; 28 | result[target] = {}; 29 | 30 | parsedFiles.forEach(function(parsedFile) { 31 | parsedFile.forEach(function(block) { 32 | if (block.global[source]) { 33 | var name = block.global[source].name; 34 | var version = block.version || packageInfos.defaultVersion; 35 | 36 | if ( ! result[target][name]) 37 | result[target][name] = {}; 38 | 39 | // fetch from global 40 | result[target][name][version] = block.global[source]; 41 | } 42 | }); 43 | }); 44 | 45 | // remove empty target 46 | if (result[target].length === 0) 47 | delete result[target]; 48 | 49 | return result; 50 | } 51 | 52 | /** 53 | * PostProcess 54 | * 55 | * @param {Object[]} parsedFiles 56 | * @param {String[]} filenames 57 | * @param {Object[]} preProcess 58 | * @param {Object} packageInfos 59 | * @param {String} source Source path in preProcess-Object 60 | * @param {String} target Relative path to the tree (local.), where the data should be modified. 61 | * @param {String} messages 62 | */ 63 | function postProcess(parsedFiles, filenames, preProcess, packageInfos, source, target, messages) { 64 | source = source || 'defineGroup'; 65 | target = target || 'group'; 66 | messages = messages || _messages; 67 | 68 | // set group name if empty 69 | parsedFiles.forEach(function(parsedFile, parsedFileIndex) { 70 | parsedFile.forEach(function(block) { 71 | // Ignore global groups, or non existing global group names (that will be generated with this func) 72 | // could overwrite local names on a later starting worker process from e.g. @apiUse 73 | if (Object.keys(block.global).length === 0) { 74 | var group = block.local[target]; 75 | if ( ! group) { 76 | // TODO: Add a warning 77 | 78 | // if no group is set, the filename will be the group-name 79 | group = path.resolve(filenames[parsedFileIndex]); 80 | } 81 | 82 | // replace special chars 83 | group.replace(/[^\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/g, '_'); 84 | 85 | block.local[target] = group; 86 | } 87 | }); 88 | }); 89 | 90 | // add group description and title 91 | parsedFiles.forEach(function(parsedFile, parsedFileIndex) { 92 | parsedFile.forEach(function(block) { 93 | if ( ! block.local[target]) 94 | return; 95 | 96 | var name = block.local[target]; 97 | var version = block.version || packageInfos.defaultVersion; 98 | var matchedData = {}; 99 | 100 | if ( ! preProcess[source] || ! preProcess[source][name]) { 101 | // TODO: Enable in the next version 102 | // At the moment the (groupname) is optional and must not be defined. 103 | /* 104 | var extra = [ 105 | { 'Groupname': name } 106 | ]; 107 | throw new WorkerError('Referenced groupname does not exist / it is not defined with @apiDefine.', 108 | filenames[parsedFileIndex], 109 | block.index, 110 | messages.common.element, 111 | messages.common.usage, 112 | messages.common.example, 113 | extra); 114 | */ 115 | // TODO: Remove in the next version 116 | matchedData.title = block.local[target]; 117 | matchedData.description = undefined; 118 | 119 | } 120 | 121 | // TODO: Remove in the next version 122 | else { 123 | 124 | if (preProcess[source][name][version]) { 125 | // found the version 126 | matchedData = preProcess[source][name][version]; 127 | } else { 128 | // find nearest matching version 129 | var foundIndex = -1; 130 | var lastVersion = packageInfos.defaultVersion; 131 | 132 | var versionKeys = Object.keys(preProcess[source][name]); 133 | versionKeys.forEach(function(currentVersion, versionIndex) { 134 | if (semver.gte(version, currentVersion) && semver.gte(currentVersion, lastVersion)) { 135 | lastVersion = currentVersion; 136 | foundIndex = versionIndex; 137 | } 138 | }); 139 | 140 | if (foundIndex === -1) { 141 | var extra = [ 142 | { 'Groupname': name }, 143 | { 'Version': version }, 144 | { 'Defined versions': versionKeys }, 145 | ]; 146 | throw new WorkerError('Referenced definition has no matching or a higher version. ' + 147 | 'Check version number in referenced define block.', 148 | filenames[parsedFileIndex], 149 | block.index, 150 | messages.common.element, 151 | messages.common.usage, 152 | messages.common.example, 153 | extra); 154 | } 155 | 156 | var versionName = versionKeys[foundIndex]; 157 | matchedData = preProcess[source][name][versionName]; 158 | } 159 | 160 | // TODO: Remove in the next version 161 | } 162 | 163 | block.local.groupTitle = matchedData.title; 164 | 165 | if (matchedData.description) 166 | block.local.groupDescription = matchedData.description; // keep original block.local 167 | }); 168 | }); 169 | } 170 | 171 | /** 172 | * Exports 173 | */ 174 | module.exports = { 175 | preProcess : preProcess, 176 | postProcess: postProcess 177 | }; 178 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var fs = require('fs'); 3 | var os = require('os'); 4 | var path = require('path'); 5 | var semver = require('semver'); 6 | 7 | /*jshint -W079 */ 8 | var Filter = require('./filter'); 9 | var Parser = require('./parser'); 10 | var Worker = require('./worker'); 11 | 12 | var PluginLoader = require('./plugin_loader'); 13 | 14 | var FileError = require('./errors/file_error'); 15 | var ParserError = require('./errors/parser_error'); 16 | var WorkerError = require('./errors/worker_error'); 17 | 18 | // const 19 | var SPECIFICATION_VERSION = '0.3.0'; 20 | 21 | var defaults = { 22 | excludeFilters: [], 23 | includeFilters: [ '.*\\.(clj|cls|coffee|cpp|cs|dart|erl|exs?|go|groovy|ino?|java|js|jsx|litcoffee|lua|p|php?|pl|pm|py|rb|scala|ts|vue)$' ], 24 | 25 | src: path.join(__dirname, '../example/'), 26 | 27 | filters: {}, 28 | languages: {}, 29 | parsers: {}, 30 | workers: {}, 31 | 32 | lineEnding: os.EOL, 33 | encoding: 'utf8' 34 | }; 35 | 36 | var app = { 37 | options : {}, // see defaults 38 | log : logger, 39 | generator : {}, 40 | packageInfos: {}, 41 | markdownParser: false, 42 | filters: { 43 | apierror : './filters/api_error.js', 44 | apiheader : './filters/api_header.js', 45 | apiparam : './filters/api_param.js', 46 | apisuccess : './filters/api_success.js' 47 | }, 48 | languages: { 49 | '.clj' : './languages/clj.js', 50 | '.coffee' : './languages/coffee.js', 51 | '.erl' : './languages/erl.js', 52 | '.ex' : './languages/ex.js', 53 | '.exs' : './languages/ex.js', 54 | '.litcoffee' : './languages/coffee.js', 55 | '.lua' : './languages/lua.js', 56 | '.pl' : './languages/pm.js', 57 | '.pm' : './languages/pm.js', 58 | '.py' : './languages/py.js', 59 | '.rb' : './languages/rb.js', 60 | 'default' : './languages/default.js' 61 | }, 62 | parsers: { 63 | api : './parsers/api.js', 64 | apibody : './parsers/api_body.js', 65 | apiquery : './parsers/api_query.js', 66 | apidefine : './parsers/api_define.js', 67 | apidescription : './parsers/api_description.js', 68 | apierror : './parsers/api_error.js', 69 | apierrorexample : './parsers/api_error_example.js', 70 | apiexample : './parsers/api_example.js', 71 | apiheader : './parsers/api_header.js', 72 | apiheaderexample : './parsers/api_header_example.js', 73 | apigroup : './parsers/api_group.js', 74 | apiname : './parsers/api_name.js', 75 | apiparam : './parsers/api_param.js', 76 | apiparamexample : './parsers/api_param_example.js', 77 | apipermission : './parsers/api_permission.js', 78 | apisuccess : './parsers/api_success.js', 79 | apisuccessexample : './parsers/api_success_example.js', 80 | apiuse : './parsers/api_use.js', 81 | apiversion : './parsers/api_version.js', 82 | apisamplerequest : './parsers/api_sample_request.js', 83 | apideprecated : './parsers/api_deprecated.js', 84 | apiprivate : './parsers/api_private.js', 85 | }, 86 | workers: { 87 | apierrorstructure : './workers/api_error_structure.js', 88 | apierrortitle : './workers/api_error_title.js', 89 | apigroup : './workers/api_group.js', 90 | apiheaderstructure : './workers/api_header_structure.js', 91 | apiheadertitle : './workers/api_header_title.js', 92 | apiname : './workers/api_name.js', 93 | apiparamtitle : './workers/api_param_title.js', 94 | apipermission : './workers/api_permission.js', 95 | apisamplerequest : './workers/api_sample_request.js', 96 | apistructure : './workers/api_structure.js', 97 | apisuccessstructure : './workers/api_success_structure.js', 98 | apisuccesstitle : './workers/api_success_title.js', 99 | apiuse : './workers/api_use.js' 100 | }, 101 | hooks: {}, 102 | addHook: addHook, 103 | hook: applyHook 104 | }; 105 | 106 | var defaultGenerator = { 107 | name : 'apidoc', 108 | time : new Date(), 109 | url : 'https://apidocjs.com', 110 | version: '0.0.0' 111 | }; 112 | 113 | // TODO: find abetter name for PackageInfos (-> apidoc-conf) 114 | var defaultPackageInfos = { 115 | description: '', 116 | name : '', 117 | sampleUrl : false, 118 | version : '0.0.0', 119 | defaultVersion: '0.0.0' 120 | }; 121 | 122 | // Simple logger interace 123 | var logger = { 124 | debug : function() { console.log(arguments); }, 125 | verbose: function() { console.log(arguments); }, 126 | info : function() { console.log(arguments); }, 127 | warn : function() { console.log(arguments); }, 128 | error : function() { console.log(arguments); } 129 | }; 130 | 131 | /** 132 | * Return the used specification version 133 | * 134 | * @returns {String} 135 | */ 136 | function getSpecificationVersion() { 137 | return SPECIFICATION_VERSION; 138 | } 139 | 140 | /** 141 | * Parser 142 | * 143 | * @param {Object} options Overwrite default options. 144 | * @param {Object} logger Logger (with methods: debug, verbose, info, warn and error is necessary). 145 | 146 | * @returns {Mixed} true = ok, but nothing todo | false = error | Object with parsed data and project-informations. 147 | * { 148 | * data : { ... } 149 | * project: { ... } 150 | * } 151 | */ 152 | function parse(options) { 153 | try { 154 | initApp(options); 155 | options = app.options; 156 | var parsedFiles = []; 157 | var parsedFilenames = []; 158 | // if input option for source is an array of folders, 159 | // parse each folder in the order provided. 160 | app.log.verbose('run parser'); 161 | if (options.src instanceof Array) { 162 | options.src.forEach(function(folder) { 163 | // Keep same options for each folder, but ensure the 'src' of options 164 | // is the folder currently being processed. 165 | var folderOptions = options; 166 | folderOptions.src = path.join(folder, './'); 167 | app.parser.parseFiles(folderOptions, parsedFiles, parsedFilenames); 168 | }); 169 | } 170 | else { 171 | // if the input option for source is a single folder, parse as usual. 172 | options.src = path.join(options.src, './'); 173 | app.parser.parseFiles(options, parsedFiles, parsedFilenames); 174 | } 175 | 176 | if (parsedFiles.length > 0) { 177 | // process transformations and assignments 178 | app.log.verbose('run worker'); 179 | app.worker.process(parsedFiles, parsedFilenames, app.packageInfos); 180 | 181 | // cleanup 182 | app.log.verbose('run filter'); 183 | var blocks = app.filter.process(parsedFiles, parsedFilenames); 184 | 185 | // sort by group ASC, name ASC, version DESC 186 | blocks.sort(function(a, b) { 187 | var nameA = a.group + a.name; 188 | var nameB = b.group + b.name; 189 | if (nameA === nameB) { 190 | if (a.version === b.version) 191 | return 0; 192 | return (semver.gte(a.version, b.version)) ? -1 : 1; 193 | } 194 | return (nameA < nameB) ? -1 : 1; 195 | }); 196 | 197 | // add apiDoc specification version 198 | app.packageInfos.apidoc = SPECIFICATION_VERSION; 199 | 200 | // add apiDoc specification version 201 | app.packageInfos.generator = app.generator; 202 | 203 | // api_data 204 | var apiData = JSON.stringify(blocks, null, 2); 205 | apiData = apiData.replace(/(\r\n|\n|\r)/g, app.options.lineEnding); 206 | 207 | // api_project 208 | var apiProject = JSON.stringify(app.packageInfos, null, 2); 209 | apiProject = apiProject.replace(/(\r\n|\n|\r)/g, app.options.lineEnding); 210 | 211 | return { 212 | data : apiData, 213 | project: apiProject 214 | }; 215 | } 216 | return true; 217 | } catch(e) { 218 | // display error by instance 219 | var extra; 220 | var meta = {}; 221 | if (e instanceof FileError) { 222 | meta = { 'Path': e.path }; 223 | app.log.error(e.message, meta); 224 | } else if (e instanceof ParserError) { 225 | extra = e.extra; 226 | if (e.source) 227 | extra.unshift({ 'Source': e.source }); 228 | if (e.element) 229 | extra.unshift({ 'Element': '@' + e.element }); 230 | if (e.block) 231 | extra.unshift({ 'Block': e.block }); 232 | if (e.file) 233 | extra.unshift({ 'File': e.file }); 234 | 235 | extra.forEach(function(obj) { 236 | var key = Object.keys(obj)[0]; 237 | meta[key] = obj[key]; 238 | }); 239 | 240 | app.log.error(e.message, meta); 241 | } 242 | else if (e instanceof WorkerError) { 243 | extra = e.extra; 244 | if (e.definition) 245 | extra.push({ 'Definition': e.definition }); 246 | if (e.example) 247 | extra.push({ 'Example': e.example }); 248 | extra.unshift({ 'Element': '@' + e.element }); 249 | extra.unshift({ 'Block': e.block }); 250 | extra.unshift({ 'File': e.file }); 251 | 252 | extra.forEach(function(obj) { 253 | var key = Object.keys(obj)[0]; 254 | meta[key] = obj[key]; 255 | }); 256 | 257 | app.log.error(e.message, meta); 258 | } 259 | else { 260 | app.log.error(e.message); 261 | if (e.stack) 262 | app.log.debug(e.stack); 263 | } 264 | return false; 265 | } 266 | } 267 | 268 | /** 269 | * parseSource 270 | * 271 | * @param {string} source the source code string. 272 | * @param {Object} options Overwrite default options. 273 | */ 274 | function parseSource(source, options) { 275 | try { 276 | initApp(options); 277 | return app.parser.parseSource(source, app.options.encoding, app.options.filename); 278 | } catch (e) { 279 | app.log.error(e.message); 280 | } 281 | } 282 | 283 | /** 284 | * initApp 285 | * 286 | * @param {Object} options Overwrite default options. 287 | */ 288 | function initApp(options) { 289 | 290 | options = _.defaults({}, options, defaults); 291 | // extend with custom functions 292 | app.filters = _.defaults({}, options.filters, app.filters); 293 | app.languages = _.defaults({}, options.languages, app.languages); 294 | app.parsers = _.defaults({}, options.parsers, app.parsers); 295 | app.workers = _.defaults({}, options.workers, app.workers); 296 | app.hooks = _.defaults({}, options.hooks, app.hooks); 297 | 298 | // options 299 | app.options = options; 300 | 301 | // generator 302 | app.generator = _.defaults({}, app.generator, defaultGenerator); 303 | 304 | // packageInfos 305 | app.packageInfos = _.defaults({}, app.packageInfos, defaultPackageInfos); 306 | 307 | // Log version information 308 | var filename = path.join(__dirname, '../', './package.json'); 309 | var packageJson = JSON.parse( fs.readFileSync( filename , 'utf8') ); 310 | app.log.verbose('apidoc-generator name: ' + app.generator.name); 311 | app.log.verbose('apidoc-generator version: ' + app.generator.version); 312 | app.log.verbose('apidoc-core version: ' + packageJson.version); 313 | app.log.verbose('apidoc-spec version: ' + getSpecificationVersion()); 314 | 315 | new PluginLoader(app); 316 | 317 | var parser = new Parser(app); 318 | var worker = new Worker(app); 319 | var filter = new Filter(app); 320 | 321 | // Make them available for plugins 322 | app.parser = parser; 323 | app.worker = worker; 324 | app.filter = filter; 325 | } 326 | 327 | /** 328 | * Set generator informations. 329 | * 330 | * @param {Object} [generator] Generator informations. 331 | * @param {String} [generator.name] Generator name (UI-Name). 332 | * @param {String} [generator.time] Time for the generated doc 333 | * @param {String} [generator.version] Version (semver) of the generator, e.g. 1.2.3 334 | * @param {String} [generator.url] Url to the generators homepage 335 | */ 336 | function setGeneratorInfos(generator) { 337 | app.generator = generator; 338 | } 339 | 340 | /** 341 | * Set a logger. 342 | * 343 | * @param {Object} logger A Logger (@see https://github.com/flatiron/winston for details) 344 | * Interface: 345 | * debug(msg, meta) 346 | * verbose(msg, meta) 347 | * info(msg, meta) 348 | * warn(msg, meta) 349 | * error(msg, meta) 350 | */ 351 | function setLogger(logger) { 352 | app.log = logger; 353 | } 354 | 355 | /** 356 | * Set the markdown parser. 357 | * 358 | * @param {Object} [markdownParser] Markdown parser. 359 | */ 360 | function setMarkdownParser(markdownParser) { 361 | app.markdownParser = markdownParser; 362 | } 363 | 364 | /** 365 | * Set package infos. 366 | * 367 | * @param {Object} [packageInfos] Collected from apidoc.json / package.json. 368 | * @param {String} [packageInfos.name] Project name. 369 | * @param {String} [packageInfos.version] Version (semver) of the project, e.g. 1.0.27 370 | * @param {String} [packageInfos.description] A short description. 371 | * @param {String} [packageInfos.sampleUrl] @see https://apidocjs.com/#param-api-sample-request 372 | */ 373 | function setPackageInfos(packageInfos) { 374 | app.packageInfos = packageInfos; 375 | } 376 | 377 | /** 378 | * Register a hook function. 379 | * 380 | * @param {String} name Name of the hook. Hook overview: https://github.com/apidoc/apidoc-core/hooks.md 381 | * @param {Function} func Callback function. 382 | * @param {Integer} [priority=100] Hook priority. Lower value will be executed first. 383 | * Same value overwrite a previously defined hook. 384 | */ 385 | function addHook(name, func, priority) { 386 | priority = priority || 100; 387 | 388 | if ( ! app.hooks[name]) 389 | app.hooks[name] = []; 390 | 391 | app.log.debug('add hook: ' + name + ' [' + priority + ']'); 392 | 393 | // Find position and overwrite same priority 394 | var replace = 0; 395 | var pos = 0; 396 | app.hooks[name].forEach( function(entry, index) { 397 | if (priority === entry.priority) { 398 | pos = index; 399 | replace = 1; 400 | } else if (priority > entry.priority) { 401 | pos = index + 1; 402 | } 403 | }); 404 | 405 | app.hooks[name].splice(pos, replace, { 406 | func: func, 407 | priority: priority 408 | }); 409 | } 410 | 411 | /** 412 | * Execute a hook. 413 | */ 414 | function applyHook(name /* , ...args */) { 415 | if ( ! app.hooks[name]) 416 | return Array.prototype.slice.call(arguments, 1, 2)[0]; 417 | 418 | var args = Array.prototype.slice.call(arguments, 1); 419 | app.hooks[name].forEach( function(hook) { 420 | hook.func.apply(this, args); 421 | }); 422 | return args[0]; 423 | } 424 | 425 | 426 | module.exports = { 427 | getSpecificationVersion: getSpecificationVersion, 428 | parse : parse, 429 | parseSource : parseSource, 430 | setGeneratorInfos : setGeneratorInfos, 431 | setLogger : setLogger, 432 | setMarkdownParser : setMarkdownParser, 433 | setPackageInfos : setPackageInfos 434 | }; 435 | -------------------------------------------------------------------------------- /lib/parser.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var util = require('util'); 5 | var iconv = require('iconv-lite'); 6 | 7 | var findFiles = require('./utils/find_files'); 8 | 9 | var ParameterError = require('./errors/parameter_error'); 10 | var ParserError = require('./errors/parser_error'); 11 | 12 | var app = {}; 13 | var filterTag = null; // define the tag to filter by 14 | 15 | function Parser(_app) { 16 | var self = this; 17 | 18 | // global variables 19 | app = _app; 20 | 21 | // class variables 22 | self.languages = {}; 23 | self.parsers = {}; 24 | self.parsedFileElements = []; 25 | self.parsedFiles = []; 26 | self.countDeprecated = {}; 27 | 28 | // load languages 29 | var languages = Object.keys(app.languages); 30 | languages.forEach(function(language) { 31 | if (_.isObject( app.languages[language] )) { 32 | app.log.debug('inject parser language: ' + language); 33 | self.addLanguage(language, app.languages[language] ); 34 | } else { 35 | var filename = app.languages[language]; 36 | app.log.debug('load parser language: ' + language + ', ' + filename); 37 | self.addLanguage(language, require(filename)); 38 | } 39 | }); 40 | 41 | // load parser 42 | var parsers = Object.keys(app.parsers); 43 | parsers.forEach(function(parser) { 44 | if (_.isObject( app.parsers[parser] )) { 45 | app.log.debug('inject parser: ' + parser); 46 | self.addParser(parser, app.parsers[parser] ); 47 | } else { 48 | var filename = app.parsers[parser]; 49 | app.log.debug('load parser: ' + parser + ', ' + filename); 50 | self.addParser(parser, require(filename)); 51 | } 52 | }); 53 | 54 | // check app.options.filterBy and define the tag to filter by 55 | if (app.options.filterBy) { 56 | var tag = app.options.filterBy.split('=')[0]; 57 | filterTag = (tag.indexOf('api') !== -1) ? tag : null; 58 | } 59 | } 60 | 61 | /** 62 | * Inherit 63 | */ 64 | util.inherits(Parser, Object); 65 | 66 | /** 67 | * Exports 68 | */ 69 | module.exports = Parser; 70 | 71 | /** 72 | * Add a Language 73 | */ 74 | Parser.prototype.addLanguage = function(name, language) { 75 | this.languages[name] = language; 76 | }; 77 | 78 | /** 79 | * Add a Parser 80 | */ 81 | Parser.prototype.addParser = function(name, parser) { 82 | this.parsers[name] = parser; 83 | }; 84 | 85 | /** 86 | * Parse files in specified folder 87 | * 88 | * @param {Object} options The options used to parse and filder the files. 89 | * @param {Object[]} parsedFiles List of parsed files. 90 | * @param {String[]} parsedFilenames List of parsed files, with full path. 91 | */ 92 | Parser.prototype.parseFiles = function(options, parsedFiles, parsedFilenames) { 93 | var self = this; 94 | 95 | findFiles.setPath(options.src); 96 | findFiles.setExcludeFilters(options.excludeFilters); 97 | findFiles.setIncludeFilters(options.includeFilters); 98 | var files = findFiles.search(); 99 | 100 | // Parser 101 | for (var i = 0; i < files.length; i += 1) { 102 | var filename = options.src + files[i]; 103 | var parsedFile = self.parseFile(filename, options.encoding); 104 | if (parsedFile) { 105 | app.log.verbose('parse file: ' + filename); 106 | parsedFiles.push(parsedFile); 107 | parsedFilenames.push(filename); 108 | } 109 | } 110 | }; 111 | 112 | /** 113 | * Execute Fileparsing 114 | */ 115 | Parser.prototype.parseFile = function(filename, encoding) { 116 | var self = this; 117 | 118 | if (typeof(encoding) === 'undefined') 119 | encoding = 'utf8'; 120 | 121 | app.log.debug('inspect file: ' + filename); 122 | 123 | self.filename = filename; 124 | self.extension = path.extname(filename).toLowerCase(); 125 | // TODO: Not sure if this is correct. Without skipDecodeWarning we got string errors 126 | // https://github.com/apidoc/apidoc-core/pull/25 127 | var fileContent = fs.readFileSync(filename, { encoding: 'binary' }); 128 | return self.parseSource(fileContent,encoding,filename); 129 | }; 130 | 131 | /** 132 | * Execute Sourceparsing 133 | */ 134 | Parser.prototype.parseSource = function(fileContent,encoding,filename) { 135 | var self = this; 136 | iconv.skipDecodeWarning = true; 137 | self.src = iconv.decode(fileContent, encoding); 138 | app.log.debug('size: ' + self.src.length); 139 | 140 | // unify line-breaks 141 | self.src = self.src.replace(/\r\n/g, '\n'); 142 | 143 | self.blocks = []; 144 | self.indexApiBlocks = []; 145 | 146 | // determine blocks 147 | self.blocks = self._findBlocks(); 148 | if (self.blocks.length === 0) 149 | return; 150 | 151 | app.log.debug('count blocks: ' + self.blocks.length); 152 | 153 | // determine elements in blocks 154 | self.elements = self.blocks.map(function(block, i) { 155 | var elements = self.findElements(block, filename); 156 | app.log.debug('count elements in block ' + i + ': ' + elements.length); 157 | return elements; 158 | }); 159 | if (self.elements.length === 0) 160 | return; 161 | 162 | // determine list of blocks with API elements 163 | self.indexApiBlocks = self._findBlockWithApiGetIndex(self.elements); 164 | if (self.indexApiBlocks.length === 0) 165 | return; 166 | 167 | return self._parseBlockElements(self.indexApiBlocks, self.elements, filename); 168 | }; 169 | 170 | /** 171 | * Parse API Elements with Plugins 172 | * 173 | * @param indexApiBlocks 174 | * @param detectedElements 175 | * @returns {Array} 176 | */ 177 | Parser.prototype._parseBlockElements = function(indexApiBlocks, detectedElements, filename) { 178 | var self = this; 179 | var parsedBlocks = []; 180 | 181 | for (var i = 0; i < indexApiBlocks.length; i += 1) { 182 | var blockIndex = indexApiBlocks[i]; 183 | var elements = detectedElements[blockIndex]; 184 | var blockData = { 185 | global: {}, 186 | local : {} 187 | }; 188 | var countAllowedMultiple = 0; 189 | 190 | for (var j = 0; j < elements.length; j += 1) { 191 | var element = elements[j]; 192 | var elementParser = self.parsers[element.name]; 193 | 194 | if ( ! elementParser) { 195 | app.log.warn('parser plugin \'' + element.name + '\' not found in block: ' + blockIndex + ' in file: ' + filename); 196 | } else { 197 | app.log.debug('found @' + element.sourceName + ' in block: ' + blockIndex); 198 | 199 | // Deprecation warning 200 | if (elementParser.deprecated) { 201 | self.countDeprecated[element.sourceName] = self.countDeprecated[element.sourceName] ? self.countDeprecated[element.sourceName] + 1 : 1; 202 | 203 | var message = '@' + element.sourceName + ' is deprecated'; 204 | if (elementParser.alternative) 205 | message = '@' + element.sourceName + ' is deprecated, please use ' + elementParser.alternative; 206 | 207 | if (self.countDeprecated[element.sourceName] === 1) 208 | // show deprecated message only 1 time as warning 209 | app.log.warn(message); 210 | else 211 | // show deprecated message more than 1 time as verbose message 212 | app.log.verbose(message); 213 | 214 | app.log.verbose('in file: ' + filename + ', block: ' + blockIndex); 215 | } 216 | 217 | var values; 218 | var preventGlobal; 219 | var allowMultiple; 220 | var pathTo; 221 | var attachMethod; 222 | try { 223 | // parse element and retrieve values 224 | values = elementParser.parse(element.content, element.source); 225 | 226 | // HINT: pathTo MUST be read after elementParser.parse, because of dynamic paths 227 | // Add all other options after parse too, in case of a custom plugin need to modify params. 228 | 229 | // check if it is allowed to add to global namespace 230 | preventGlobal = elementParser.preventGlobal === true; 231 | 232 | // allow multiple inserts into pathTo 233 | allowMultiple = true; 234 | 235 | 236 | // path to an array, where the values should be attached 237 | pathTo = ''; 238 | if (elementParser.path) { 239 | if (typeof elementParser.path === 'string') 240 | pathTo = elementParser.path; 241 | else 242 | pathTo = elementParser.path(); // for dynamic paths 243 | } 244 | 245 | if ( ! pathTo) 246 | throw new ParserError('pathTo is not defined in the parser file.', '', '', element.sourceName); 247 | 248 | // method how the values should be attached (insert or push) 249 | attachMethod = elementParser.method || 'push'; 250 | 251 | if (attachMethod !== 'insert' && attachMethod !== 'push') 252 | throw new ParserError('Only push or insert are allowed parser method values.', '', '', element.sourceName); 253 | 254 | // TODO: put this into "converters" 255 | if (values) { 256 | // Markdown. 257 | if ( app.markdownParser && 258 | elementParser.markdownFields && 259 | elementParser.markdownFields.length > 0 260 | ) { 261 | for (var markdownIndex = 0; markdownIndex < elementParser.markdownFields.length; markdownIndex += 1) { 262 | var field = elementParser.markdownFields[markdownIndex]; 263 | var value = _.get(values, field); 264 | if (value) { 265 | value = app.markdownParser.render(value); 266 | // remove line breaks 267 | value = value.replace(/(\r\n|\n|\r)/g, ' '); 268 | 269 | value = value.trim(); 270 | _.set(values, field, value); 271 | 272 | // TODO: Little hacky, not sure to handle this here or in template 273 | if ( elementParser.markdownRemovePTags && 274 | elementParser.markdownRemovePTags.length > 0 && 275 | elementParser.markdownRemovePTags.indexOf(field) !== -1 276 | ) { 277 | // Remove p-Tags 278 | value = value.replace(/(

|<\/p>)/g, ''); 279 | _.set(values, field, value); 280 | } 281 | } 282 | } 283 | } 284 | } 285 | } catch(e) { 286 | if (e instanceof ParameterError) { 287 | var extra = []; 288 | if (e.definition) 289 | extra.push({ 'Definition': e.definition }); 290 | if (e.example) 291 | extra.push({ 'Example': e.example }); 292 | throw new ParserError(e.message, 293 | self.filename, (blockIndex + 1), element.sourceName, element.source, extra); 294 | } 295 | throw new ParserError('Undefined error.', 296 | self.filename, (blockIndex + 1), element.sourceName, element.source); 297 | } 298 | 299 | if ( ! values) 300 | throw new ParserError('Empty parser result.', 301 | self.filename, (blockIndex + 1), element.sourceName, element.source); 302 | 303 | if (preventGlobal) { 304 | // Check if count global namespace entries > count allowed 305 | // (e.g. @successTitle is global, but should co-exist with @apiErrorStructure) 306 | if (Object.keys(blockData.global).length > countAllowedMultiple) 307 | throw new ParserError('Only one definition or usage is allowed in the same block.', 308 | self.filename, (blockIndex + 1), element.sourceName, element.source); 309 | } 310 | 311 | // only one global allowed per block 312 | if (pathTo === 'global' || pathTo.substr(0, 7) === 'global.') { 313 | if (allowMultiple) { 314 | countAllowedMultiple += 1; 315 | } else { 316 | if (Object.keys(blockData.global).length > 0) 317 | throw new ParserError('Only one definition is allowed in the same block.', 318 | self.filename, (blockIndex + 1), element.sourceName, element.source); 319 | 320 | if (preventGlobal === true) 321 | throw new ParserError('Only one definition or usage is allowed in the same block.', 322 | self.filename, (blockIndex + 1), element.sourceName, element.source); 323 | } 324 | } 325 | 326 | if ( ! blockData[pathTo]) 327 | self._createObjectPath(blockData, pathTo, attachMethod); 328 | 329 | var blockDataPath = self._pathToObject(pathTo, blockData); 330 | 331 | // insert Fieldvalues in Path-Array 332 | if (attachMethod === 'push') 333 | blockDataPath.push(values); 334 | else 335 | _.extend(blockDataPath, values); 336 | 337 | // insert Fieldvalues in Mainpath 338 | if (elementParser.extendRoot === true) 339 | _.extend(blockData, values); 340 | 341 | blockData.index = blockIndex + 1; 342 | } 343 | } 344 | if (blockData.index && blockData.index > 0) 345 | parsedBlocks.push(blockData); 346 | } 347 | return parsedBlocks; 348 | }; 349 | 350 | /** 351 | * Create a not existing Path in an Object 352 | * 353 | * @param src 354 | * @param path 355 | * @param {String} attachMethod Create last element as object or array: 'insert', 'push' 356 | * @returns {Object} 357 | */ 358 | Parser.prototype._createObjectPath = function(src, path, attachMethod) { 359 | if ( ! path) 360 | return src; 361 | var pathParts = path.split('.'); 362 | var current = src; 363 | for (var i = 0; i < pathParts.length; i += 1) { 364 | var part = pathParts[i]; 365 | if ( ! current[part]) { 366 | if (i === (pathParts.length - 1) && attachMethod === 'push' ) 367 | current[part] = []; 368 | else 369 | current[part] = {}; 370 | } 371 | current = current[part]; 372 | } 373 | return current; 374 | }; 375 | 376 | 377 | /** 378 | * Return Path to Object 379 | */ 380 | Parser.prototype._pathToObject = function(path, src) { 381 | if ( ! path) 382 | return src; 383 | var pathParts = path.split('.'); 384 | var current = src; 385 | for (var i = 0; i < pathParts.length; i += 1) { 386 | var part = pathParts[i]; 387 | current = current[part]; 388 | } 389 | return current; 390 | }; 391 | 392 | /** 393 | * Determine Blocks 394 | */ 395 | Parser.prototype._findBlocks = function() { 396 | var self = this; 397 | var blocks = []; 398 | var src = self.src; 399 | 400 | // Replace Linebreak with Unicode 401 | src = src.replace(/\n/g, '\uffff'); 402 | 403 | var regexForFile = this.languages[self.extension] || this.languages['default']; 404 | var matches = regexForFile.docBlocksRegExp.exec(src); 405 | while (matches) { 406 | var block = matches[2] || matches[1]; 407 | 408 | // Reverse Unicode Linebreaks 409 | block = block.replace(/\uffff/g, '\n'); 410 | 411 | block = block.replace(regexForFile.inlineRegExp, ''); 412 | blocks.push(block); 413 | 414 | // Find next 415 | matches = regexForFile.docBlocksRegExp.exec(src); 416 | } 417 | return blocks; 418 | }; 419 | 420 | /** 421 | * Return block indexes with active API-elements 422 | * 423 | * An @apiIgnore ignores the block. 424 | * Other, non @api elements, will be ignored. 425 | */ 426 | Parser.prototype._findBlockWithApiGetIndex = function(blocks) { 427 | var foundIndexes = []; 428 | // get value to filter by 429 | var valueTofilter = (filterTag) ? app.options.filterBy.split('=')[1] : null; 430 | for (var i = 0; i < blocks.length; i += 1) { 431 | var found = false; 432 | var isToFilterBy = false; 433 | var isDefine = false; 434 | for (var j = 0; j < blocks[i].length; j += 1) { 435 | // check apiIgnore 436 | if (blocks[i][j].name.substr(0, 9) === 'apiignore') { 437 | app.log.debug('apiIgnore found in block: ' + i); 438 | found = false; 439 | break; 440 | } 441 | 442 | // check app.options.apiprivate and apiPrivate 443 | if (!app.options.apiprivate && blocks[i][j].name.substr(0, 10) === 'apiprivate') { 444 | app.log.debug('private flag is set to false and apiPrivate found in block: ' + i); 445 | found = false; 446 | break; 447 | } 448 | 449 | // check if the user want to filter by some specific tag 450 | if (filterTag) { 451 | // we need to add all apidefine 452 | if (blocks[i][j].name.substr(0, 9) === 'apidefine') { 453 | isDefine = true; 454 | } 455 | if (blocks[i][j].name.substr(0, filterTag.length) === filterTag && blocks[i][j].content === valueTofilter) { 456 | isToFilterBy = true; 457 | } 458 | } 459 | 460 | if (blocks[i][j].name.substr(0, 3) === 'api') 461 | found = true; 462 | } 463 | 464 | // add block if it's apidefine or the tag is equal to the value defined in options 465 | if (filterTag) { 466 | found = found && (isToFilterBy || isDefine); 467 | } 468 | 469 | if (found) { 470 | foundIndexes.push(i); 471 | app.log.debug('api found in block: ' + i); 472 | } 473 | } 474 | return foundIndexes; 475 | }; 476 | 477 | /** 478 | * Get Elements of Blocks 479 | */ 480 | Parser.prototype.findElements = function(block, filename) { 481 | var elements = []; 482 | 483 | // Replace Linebreak with Unicode 484 | block = block.replace(/\n/g, '\uffff'); 485 | 486 | // Elements start with @ 487 | var elementsRegExp = /(@(\w*)\s?(.*?)(?=\uffff[\s\*]*@|$))/gm; 488 | var matches = elementsRegExp.exec(block); 489 | while (matches) { 490 | var element = { 491 | source : matches[1], 492 | name : matches[2].toLowerCase(), 493 | sourceName: matches[2], 494 | content : matches[3] 495 | }; 496 | 497 | // reverse Unicode Linebreaks 498 | element.content = element.content.replace(/\uffff/g, '\n'); 499 | element.source = element.source.replace(/\uffff/g, '\n'); 500 | 501 | app.hook('parser-find-element-' + element.name, element, block, filename); 502 | 503 | elements.push(element); 504 | 505 | app.hook('parser-find-elements', elements, element, block, filename); 506 | 507 | // next Match 508 | matches = elementsRegExp.exec(block); 509 | } 510 | return elements; 511 | }; 512 | --------------------------------------------------------------------------------