-
Notifications
You must be signed in to change notification settings - Fork 1
/
plugins.js
142 lines (119 loc) · 3.66 KB
/
plugins.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
const assert = require('assert')
// const { locate } = require('func-loc');
const { isPromise, bindAll, debug } = require('./utils')
// const TESTING = process.env.NODE_ENV === 'test'
module.exports = function createPluginManager (defaults) {
return new PluginManager(defaults)
}
function PluginManager (defaults) {
bindAll(this)
this._plugins = {}
if (defaults) this.use(defaults)
}
PluginManager.prototype.register = function register (method, handlers, unshift) {
handlers = [].concat(handlers).map(wrapHandler)
assert(
handlers.every(handler => typeof handler.fn === 'function'),
'expected wrapped handlers'
)
const current = this._plugins[method] || []
const first = unshift ? handlers : current
const second = unshift ? current : handlers
this._plugins[method] = first.concat(second)
return this.unregister.bind(this, method, handlers)
}
PluginManager.prototype.unregister = function unregister (method, handlers) {
if (!this._plugins[method]) return
handlers = [].concat(handlers).map(unwrapHandler)
this._plugins[method] = this._plugins[method]
.filter(({ fn }) => !handlers.includes(fn))
}
PluginManager.prototype.use = function use (plugin, unshift) {
for (let method in plugin) {
let val = plugin[method]
if (typeof val === 'function') {
this.register(method, wrapHandler(val, plugin), unshift)
} else if (Array.isArray(val) && val.every(sub => typeof sub === 'function')) {
this.register(method, wrapHandler(val), unshift)
}
}
return this.remove.bind(this, plugin)
}
PluginManager.prototype.clear = function clear (method) {
if (method) {
this._plugins[method] = []
} else {
this._plugins = {}
}
}
PluginManager.prototype.remove = function remove (plugin) {
for (let method in plugin) {
this.unregister(method, plugin[method])
}
}
PluginManager.prototype.exec = function ({
method,
args,
waterfall,
returnResult,
allowExit
}) {
if (typeof arguments[0] === 'string') {
method = arguments[0]
args = Array.prototype.slice.call(arguments, 1)
}
const handlers = this._plugins[method] || []
this._debug(`${handlers.length} handlers found for "${method}"`)
if (!handlers.length) return Promise.resolve()
return execute({
handlers,
args,
allowExit,
returnResult,
waterfall
})
}
PluginManager.prototype.count = function (method) {
if (method) {
return Object.keys(this._plugins[method])
}
return Object.keys(this._plugins).reduce((total, method) => {
return total + this.count(method)
}, 0)
}
PluginManager.prototype._debug = function (...args) {
args.unshift('plugins')
return debug(...args)
}
/**
* execute in series, with synchronous and promise support
*/
function execute ({ handlers, args, waterfall, allowExit, returnResult }) {
let ret
handlers = handlers.slice()
while (handlers.length) {
let handler = handlers.shift()
const start = Date.now()
ret = handler.fn.call(handler.context || this, ...args)
if (!isPromise(ret))
return continueExec(ret)
return ret.then(async result => {
let interval = Date.now() - start
if (interval > 100) console.log('plugins.time: ', interval)
return continueExec(result)
})
}
function continueExec (ret) {
if (allowExit && ret === false) return ret
if (returnResult && ret != null) return ret
if (!handlers.length) return ret
if (waterfall) args = [ret]
return execute({ handlers, args, waterfall, allowExit, returnResult })
}
}
function wrapHandler (fn, context) {
return typeof fn === 'function' ? { fn, context } : fn
}
function unwrapHandler (handler) {
return typeof handler === 'function' ? handler : handler.fn
}