Security update for permissions_by_term
[yaffs-website] / node_modules / hooker / lib / hooker.js
1 /*
2  * JavaScript Hooker
3  * http://github.com/cowboy/javascript-hooker
4  *
5  * Copyright (c) 2012 "Cowboy" Ben Alman
6  * Licensed under the MIT license.
7  * http://benalman.com/about/license/
8  */
9
10 (function(exports) {
11   // Get an array from an array-like object with slice.call(arrayLikeObject).
12   var slice = [].slice;
13   // Get an "[object [[Class]]]" string with toString.call(value).
14   var toString = {}.toString;
15
16   // I can't think of a better way to ensure a value is a specific type other
17   // than to create instances and use the `instanceof` operator.
18   function HookerOverride(v) { this.value = v; }
19   function HookerPreempt(v) { this.value = v; }
20   function HookerFilter(c, a) { this.context = c; this.args = a; }
21
22   // When a pre- or post-hook returns the result of this function, the value
23   // passed will be used in place of the original function's return value. Any
24   // post-hook override value will take precedence over a pre-hook override
25   // value.
26   exports.override = function(value) {
27     return new HookerOverride(value);
28   };
29
30   // When a pre-hook returns the result of this function, the value passed will
31   // be used in place of the original function's return value, and the original
32   // function will NOT be executed.
33   exports.preempt = function(value) {
34     return new HookerPreempt(value);
35   };
36
37   // When a pre-hook returns the result of this function, the context and
38   // arguments passed will be applied into the original function.
39   exports.filter = function(context, args) {
40     return new HookerFilter(context, args);
41   };
42
43   // Execute callback(s) for properties of the specified object.
44   function forMethods(obj, props, callback) {
45     var prop;
46     if (typeof props === "string") {
47       // A single prop string was passed. Create an array.
48       props = [props];
49     } else if (props == null) {
50       // No props were passed, so iterate over all properties, building an
51       // array. Unfortunately, Object.keys(obj) doesn't work everywhere yet, so
52       // this has to be done manually.
53       props = [];
54       for (prop in obj) {
55         if (obj.hasOwnProperty(prop)) {
56           props.push(prop);
57         }
58       }
59     }
60     // Execute callback for every method in the props array.
61     var i = props.length;
62     while (i--) {
63       // If the property isn't a function...
64       if (toString.call(obj[props[i]]) !== "[object Function]" ||
65         // ...or the callback returns false...
66         callback(obj, props[i]) === false) {
67         // ...remove it from the props array to be returned.
68         props.splice(i, 1);
69       }
70     }
71     // Return an array of method names for which the callback didn't fail.
72     return props;
73   }
74
75   // Monkey-patch (hook) a method of an object.
76   exports.hook = function(obj, props, options) {
77     // If the props argument was omitted, shuffle the arguments.
78     if (options == null) {
79       options = props;
80       props = null;
81     }
82     // If just a function is passed instead of an options hash, use that as a
83     // pre-hook function.
84     if (typeof options === "function") {
85       options = {pre: options};
86     }
87
88     // Hook the specified method of the object.
89     return forMethods(obj, props, function(obj, prop) {
90       // The original (current) method.
91       var orig = obj[prop];
92       // The new hooked function.
93       function hooked() {
94         var result, origResult, tmp;
95
96         // Get an array of arguments.
97         var args = slice.call(arguments);
98
99         // If passName option is specified, prepend prop to the args array,
100         // passing it as the first argument to any specified hook functions.
101         if (options.passName) {
102           args.unshift(prop);
103         }
104
105         // If a pre-hook function was specified, invoke it in the current
106         // context with the passed-in arguments, and store its result.
107         if (options.pre) {
108           result = options.pre.apply(this, args);
109         }
110
111         if (result instanceof HookerFilter) {
112           // If the pre-hook returned hooker.filter(context, args), invoke the
113           // original function with that context and arguments, and store its
114           // result.
115           origResult = result = orig.apply(result.context, result.args);
116         } else if (result instanceof HookerPreempt) {
117           // If the pre-hook returned hooker.preempt(value) just use the passed
118           // value and don't execute the original function.
119           origResult = result = result.value;
120         } else {
121           // Invoke the original function in the current context with the
122           // passed-in arguments, and store its result.
123           origResult = orig.apply(this, arguments);
124           // If the pre-hook returned hooker.override(value), use the passed
125           // value, otherwise use the original function's result.
126           result = result instanceof HookerOverride ? result.value : origResult;
127         }
128
129         if (options.post) {
130           // If a post-hook function was specified, invoke it in the current
131           // context, passing in the result of the original function as the
132           // first argument, followed by any passed-in arguments.
133           tmp = options.post.apply(this, [origResult].concat(args));
134           if (tmp instanceof HookerOverride) {
135             // If the post-hook returned hooker.override(value), use the passed
136             // value, otherwise use the previously computed result.
137             result = tmp.value;
138           }
139         }
140
141         // Unhook if the "once" option was specified.
142         if (options.once) {
143           exports.unhook(obj, prop);
144         }
145
146         // Return the result!
147         return result;
148       }
149       // Re-define the method.
150       obj[prop] = hooked;
151       // Fail if the function couldn't be hooked.
152       if (obj[prop] !== hooked) { return false; }
153       // Store a reference to the original method as a property on the new one.
154       obj[prop]._orig = orig;
155     });
156   };
157
158   // Get a reference to the original method from a hooked function.
159   exports.orig = function(obj, prop) {
160     return obj[prop]._orig;
161   };
162
163   // Un-monkey-patch (unhook) a method of an object.
164   exports.unhook = function(obj, props) {
165     return forMethods(obj, props, function(obj, prop) {
166       // Get a reference to the original method, if it exists.
167       var orig = exports.orig(obj, prop);
168       // If there's no original method, it can't be unhooked, so fail.
169       if (!orig) { return false; }
170       // Unhook the method.
171       obj[prop] = orig;
172     });
173   };
174 }(typeof exports === "object" && exports || this));