-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathindex.js
168 lines (142 loc) · 5.68 KB
/
index.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
const Promise = require("nodegit-promise");
const args = require("./utils/args");
const cloneFunction = require("./utils/cloneFunction");
const objectAssign = require("object-assign");
// Unfortunately this list is not exhaustive, so if you find that a method does
// not use a "standard"-ish name, you'll have to extend this list.
var callbacks = ["cb", "callback", "callback_", "done"];
/**
* Recursively operate over an object locating "asynchronous" functions by
* inspecting the last argument in the parameter signature for a callback.
*
* @param {*} exports - Should be a function or an object, identity other.
* @param {Function} test - Optional function to identify async methods.
* @param {String} parentKeyName - Tracks the keyName in a digestable format.
* @param {Boolean} noMutate - if set to true then all reference properties are
* cloned to avoid mutating the original object.
* @returns {*} exports - Identity.
*/
function processExports(exports, test, cached, parentKeyName, noMutate) {
if(!exports) {
return exports;
}
if(noMutate || typeof exports === "function") {
// When not mutating we have to cache the original and the wrapped clone.
var cacheResult = cached.filter(function(c) { return c.original === exports; });
if(cacheResult.length) {
return cacheResult[0].wrapped;
}
} else {
// Return early if this object has already been processed.
if (cached.indexOf(exports) > -1) {
return exports;
}
}
// Record this object in the cache, if it is not a function.
if(typeof exports != "function") {
cached.push(exports);
}
// Pass through if not an object or function.
if (typeof exports != "object" && typeof exports != "function") {
return exports;
}
var name = exports.name + "#";
var target;
// If a function, simply return it wrapped.
if (typeof exports === "function") {
var wrapped = exports;
var isAsyncFunction = false;
// Check the callback either passes the test function, or accepts a callback.
if ((test && test(exports, exports.name, parentKeyName))
// If the callback name exists as the last argument, consider it an
// asynchronous function. Brittle? Fragile? Effective.
|| (callbacks.indexOf(args(exports).slice(-1)[0]) > -1)) {
// Assign the new function in place.
wrapped = Promise.denodeify(exports);
isAsyncFunction = true;
} else if(noMutate) {
// If not mutating, then we need to clone the function, even though it isn't async.
wrapped = cloneFunction(exports);
}
// Set which object we'll mutate based upon the noMutate flag.
target = noMutate ? wrapped : exports;
// Here we can push our cloned/wrapped function and original onto cache.
cached.push({
original: exports,
wrapped: wrapped
});
// Find properties added to functions.
for (var keyName in exports) {
target[keyName] = processExports(exports[keyName], test, cached, name, noMutate);
}
// Find methods on the prototype, if there are any.
if (exports.prototype && Object.keys(exports.prototype).length) {
// Attach the augmented prototype.
wrapped.prototype = processExports(exports.prototype, test, cached, name, noMutate);
}
// Ensure attached properties to the previous function are accessible.
// Only do this if it's an async (wrapped) function, else we're setting
// __proto__ to itself, which isn't allowed.
if(isAsyncFunction) {
wrapped.__proto__ = exports;
}
return wrapped;
}
// Make a shallow clone if we're not mutating and set it as the target, else just use exports
target = noMutate ? objectAssign({}, exports) : exports;
// We have our shallow cloned object, so put it (and the original) in the cache
if(noMutate) {
cached.push({
original: exports,
wrapped: target
});
}
Object.keys(target).map(function(keyName) {
// Convert to values.
return [keyName, target[keyName]];
}).filter(function(keyVal) {
var keyName = keyVal[0];
var value = keyVal[1];
// If an object is encountered, recursively traverse.
if (typeof value === "object") {
processExports(value, test, cached, keyName + ".", noMutate);
} else if (typeof value === "function") {
// If a filter function exists, use this to determine if the function
// is asynchronous.
if (test) {
// Pass the function itself, its keyName, and the parent keyName.
return test(value, keyName, parentKeyName);
}
return true;
}
}).forEach(function(keyVal) {
var keyName = keyVal[0];
var func = keyVal[1];
// Wrap this function and reassign.
target[keyName] = processExports(func, test, cached, parentKeyName, noMutate);
});
return target;
}
/**
* Public API for Promisify. Will resolve modules names using `require`.
*
* @param {*} name - Can be a module name, object, or function.
* @param {Function} test - Optional function to identify async methods.
* @param {Boolean} noMutate - Optional set to true to avoid mutating the target.
* @returns {*} exports - The resolved value from require or passed in value.
*/
module.exports = function(name, test, noMutate) {
var exports = name;
// If the name argument is a String, will need to resovle using the built in
// Node require function.
if (typeof name === "string") {
exports = require(name);
// Unless explicitly overridden, don't mutate when requiring modules.
noMutate = !(noMutate === false);
}
// Iterate over all properties and find asynchronous functions to convert to
// promises.
return processExports(exports, test, [], undefined, noMutate);
};
// Export callbacks to the module.
module.exports.callbacks = callbacks;