Skip to content
This repository was archived by the owner on Sep 6, 2021. It is now read-only.

Commit 20921ef

Browse files
committed
Add Plugin Loader. Extend filter, parser, worker for plugin usage. Add hooks for found elements.
1 parent e13e06b commit 20921ef

File tree

7 files changed

+227
-15
lines changed

7 files changed

+227
-15
lines changed

hooks.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# apiDoc Hooks
2+
3+
4+
## parser-find-elements
5+
6+
Called on each found element. Returns a new list of elements (replace elements).
7+
Used to inject annotationes from an external schema.
8+
9+
Parameter: `(elements, element, block, filename)`
10+
* {array} elements Found elements in a block without the current element.
11+
* {array} element Contains the source, name (lowercase), sourceName (original), content.
12+
* {string} block Current source block.
13+
* {string} filename Current filename.
14+
15+
File: `parser.js`
16+
Function: `_findElements`
17+
18+
19+
20+
## parser-find-element-{name}
21+
22+
Called on each found element and returns the modified element.
23+
Used to modify a specific element.
24+
25+
{name} is the found element.name (lowercase).
26+
27+
Parameter: `(element, block, filename)`
28+
* {array} element Contains the source, name (lowercase), sourceName (original), content.
29+
* {string} block Current source block.
30+
* {string} filename Current filename.
31+
32+
File: `parser.js`
33+
Function: `_findElements`

lib/filter.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,14 @@ function Filter(_app) {
2121
// load filters
2222
var filters = Object.keys(app.filters);
2323
filters.forEach(function(filter) {
24-
var filename = app.filters[filter];
25-
app.log.debug('load filter: ' + filter + ', ' + filename);
26-
self.addFilter(filter, require(filename));
24+
if (_.isObject( app.filters[filter] )) {
25+
app.log.debug('inject filter: ' + parser);
26+
self.addFilter(worker, 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+
}
2732
});
2833
}
2934

lib/index.js

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ var Filter = require('./filter');
99
var Parser = require('./parser');
1010
var Worker = require('./worker');
1111

12+
var PluginLoader = require('./plugin_loader');
13+
1214
var FileError = require('./errors/file_error');
1315
var ParserError = require('./errors/parser_error');
1416
var WorkerError = require('./errors/worker_error');
@@ -90,7 +92,10 @@ var app = {
9092
apisuccessstructure : './workers/api_success_structure.js',
9193
apisuccesstitle : './workers/api_success_title.js',
9294
apiuse : './workers/api_use.js'
93-
}
95+
},
96+
hooks: {},
97+
addHook: addHook,
98+
hook: applyHook
9499
};
95100

96101
var defaultGenerator = {
@@ -159,6 +164,7 @@ function parse(options) {
159164
app.languages = _.defaults({}, options.languages, app.languages);
160165
app.parsers = _.defaults({}, options.parsers, app.parsers);
161166
app.workers = _.defaults({}, options.workers, app.workers);
167+
app.hooks = _.defaults({}, options.hooks, app.hooks);
162168

163169
// options
164170
app.options = options;
@@ -181,10 +187,17 @@ function parse(options) {
181187
app.log.verbose('apidoc-core version: ' + packageJson.version);
182188
app.log.verbose('apidoc-spec version: ' + getSpecificationVersion());
183189

190+
new PluginLoader(app);
191+
184192
var parser = new Parser(app);
185193
var worker = new Worker(app);
186194
var filter = new Filter(app);
187195

196+
// Make them available for plugins
197+
app.parser = parser;
198+
app.worker = worker;
199+
app.filter = filter;
200+
188201
// if input option for source is an array of folders,
189202
// parse each folder in the order provided.
190203
app.log.verbose('run parser');
@@ -345,6 +358,55 @@ function setPackageInfos(packageInfos) {
345358
app.packageInfos = packageInfos;
346359
}
347360

361+
/**
362+
* Register a hook function.
363+
*
364+
* @param {String} name Name of the hook. Hook overview: https://github.com/apidoc/apidoc-core/hooks.md
365+
* @param {Function} func Callback function.
366+
* @param {Integer} [priority=100] Hook priority. Lower value will be executed first.
367+
* Same value overwrite a previously defined hook.
368+
*/
369+
function addHook(name, func, priority) {
370+
priority = priority || 100;
371+
372+
if ( ! app.hooks[name])
373+
app.hooks[name] = [];
374+
375+
app.log.debug('add hook: ' + name + ' [' + priority + ']');
376+
377+
// Find position and overwrite same priority
378+
var replace = 0;
379+
var pos = 0;
380+
app.hooks[name].forEach( function(entry, index) {
381+
if (priority === entry.priority) {
382+
pos = index;
383+
replace = 1;
384+
} else if (priority > entry.priority) {
385+
pos = index + 1;
386+
}
387+
});
388+
389+
app.hooks[name].splice(pos, replace, {
390+
func: func,
391+
priority: priority
392+
});
393+
}
394+
395+
/**
396+
* Execute a hook.
397+
*/
398+
function applyHook(name /* , ...args */) {
399+
if ( ! app.hooks[name])
400+
return Array.prototype.slice.call(arguments, 1, 2)[0];
401+
402+
var args = Array.prototype.slice.call(arguments, 1);
403+
app.hooks[name].forEach( function(hook) {
404+
hook['func'].apply(this, args);
405+
});
406+
return args[0];
407+
}
408+
409+
348410
module.exports = {
349411
getSpecificationVersion: getSpecificationVersion,
350412
parse : parse,

lib/parser.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,27 @@ function Parser(_app) {
2727
// load languages
2828
var languages = Object.keys(app.languages);
2929
languages.forEach(function(language) {
30-
var filename = app.languages[language];
31-
app.log.debug('load parser language: ' + language + ', ' + filename);
32-
self.addLanguage(language, require(filename));
30+
if (_.isObject( app.languages[language] )) {
31+
app.log.debug('inject parser language: ' + language);
32+
self.addLanguage(language, app.languages[language] );
33+
} else {
34+
var filename = app.languages[language];
35+
app.log.debug('load parser language: ' + language + ', ' + filename);
36+
self.addLanguage(language, require(filename));
37+
}
3338
});
3439

3540
// load parser
3641
var parsers = Object.keys(app.parsers);
3742
parsers.forEach(function(parser) {
38-
var filename = app.parsers[parser];
39-
app.log.debug('load parser: ' + parser + ', ' + filename);
40-
self.addParser(parser, require(filename));
43+
if (_.isObject( app.parsers[parser] )) {
44+
app.log.debug('inject parser: ' + parser);
45+
self.addParser(parser, app.parsers[parser] );
46+
} else {
47+
var filename = app.parsers[parser];
48+
app.log.debug('load parser: ' + parser + ', ' + filename);
49+
self.addParser(parser, require(filename));
50+
}
4151
});
4252
}
4353

@@ -127,7 +137,7 @@ Parser.prototype.parseFile = function(filename, encoding) {
127137

128138
// determine elements in blocks
129139
self.elements = self.blocks.map(function(block, i) {
130-
var elements = self._findElements(block);
140+
var elements = self.findElements(block, filename);
131141
app.log.debug('count elements in block ' + i + ': ' + elements.length);
132142
return elements;
133143
});
@@ -420,7 +430,7 @@ Parser.prototype._findBlockWithApiGetIndex = function(blocks) {
420430
/**
421431
* Get Elements of Blocks
422432
*/
423-
Parser.prototype._findElements = function(block) {
433+
Parser.prototype.findElements = function(block, filename) {
424434
var elements = [];
425435

426436
// Replace Linebreak with Unicode
@@ -441,8 +451,12 @@ Parser.prototype._findElements = function(block) {
441451
element.content = element.content.replace(/\uffff/g, '\n');
442452
element.source = element.source.replace(/\uffff/g, '\n');
443453

454+
app.hook('parser-find-element-' + element.name, element, block, filename);
455+
444456
elements.push(element);
445457

458+
app.hook('parser-find-elements', elements, element, block, filename);
459+
446460
// next Match
447461
matches = elementsRegExp.exec(block);
448462
}

lib/plugin_loader.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
var _ = require('lodash');
2+
var fs = require('fs');
3+
var path = require('path');
4+
var util = require('util');
5+
var glob = require('glob');
6+
7+
var app = {};
8+
9+
function PluginLoader(_app) {
10+
var self = this;
11+
12+
// global variables
13+
app = _app;
14+
15+
// class variables
16+
self.plugins = {};
17+
18+
// Try to load global apidoc-plugins (if apidoc is installed locally it tries only local)
19+
this.detectPugins(__dirname);
20+
21+
// Try to load local apidoc-plugins
22+
this.detectPugins( path.join(process.cwd(), '/node_modules') );
23+
24+
if (Object.keys(this.plugins).length === 0)
25+
app.log.debug('No plugins found.');
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+
// Search from the given dir up to root.
47+
// Every dir start with "apidoc-plugin-", because for the tests of apidoc-plugin-test.
48+
var plugins = glob.sync(dir + '/apidoc-plugin-*');
49+
if (plugins.length === 0) {
50+
dir = path.join(dir, '..');
51+
if (dir === '/')
52+
return;
53+
return this.detectPugins(dir);
54+
}
55+
56+
var offset = dir.length + 1;
57+
plugins.forEach( function(plugin) {
58+
var name = plugin.substr(offset);
59+
var filename = path.relative(__dirname, plugin);
60+
app.log.debug('add plugin: ' + name + ', ' + filename);
61+
self.addPlugin(name, plugin);
62+
});
63+
};
64+
65+
/**
66+
* Add Plugin to plugin list.
67+
*/
68+
PluginLoader.prototype.addPlugin = function(name, filename) {
69+
if (this.plugins[name])
70+
app.log.debug('overwrite plugin: ' + name + ', ' + this.plugins[name]);
71+
72+
this.plugins[name] = filename;
73+
};
74+
75+
/**
76+
* Load and initialize Plugins.
77+
*/
78+
PluginLoader.prototype.loadPlugins = function() {
79+
_.forEach(this.plugins, function(filename, name) {
80+
app.log.debug('load plugin: ' + name + ', ' + filename);
81+
var plugin;
82+
try {
83+
plugin = require(filename);
84+
} catch(e) {
85+
}
86+
if (plugin && plugin.init) {
87+
plugin.init(app);
88+
} else {
89+
app.log.debug('Ignored, no init function found.');
90+
}
91+
});
92+
};

lib/worker.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,14 @@ function Worker(_app) {
2626
// load worker
2727
var workers = Object.keys(app.workers);
2828
workers.forEach(function(worker) {
29-
var filename = app.workers[worker];
30-
app.log.debug('load worker: ' + worker + ', ' + filename);
31-
self.addWorker(worker, require(filename));
29+
if (_.isObject( app.workers[worker] )) {
30+
app.log.debug('inject worker: ' + parser);
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+
}
3237
});
3338
}
3439

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"node": ">= 0.10.0"
3434
},
3535
"dependencies": {
36+
"glob": "^7.0.3",
3637
"iconv-lite": "^0.4.13",
3738
"lodash": "~4.5.0",
3839
"semver": "~5.1.0",

0 commit comments

Comments
 (0)