[Pkg-javascript-commits] [node-hooker] 01/01: Initial import of node-hooker version 0.2.3

matthew pideil mpideil-guest at moszumanska.debian.org
Sun Mar 30 23:58:41 UTC 2014


This is an automated email from the git hooks/post-receive script.

mpideil-guest pushed a commit to branch master
in repository node-hooker.

commit 3474c44b7d590af7422c445c7b5a2ff91d97ed64
Author: Matthew Pideil <matthewp_debian at teledetection.fr>
Date:   Sun Mar 30 23:47:55 2014 +0000

    Initial import of node-hooker version 0.2.3
---
 LICENSE-MIT                 |  22 +++
 README.md                   | 186 +++++++++++++++++++
 debian/changelog            |   5 +
 debian/compat               |   1 +
 debian/control              |  35 ++++
 debian/copyright            |  30 +++
 debian/docs                 |   1 +
 debian/examples             |   1 +
 debian/libjs-hooker.install |   1 +
 debian/node-hooker.install  |   1 +
 debian/rules                |  22 +++
 debian/source/format        |   1 +
 debian/watch                |   2 +
 dist/ba-hooker.js           | 169 +++++++++++++++++
 dist/ba-hooker.min.js       |   4 +
 grunt.js                    |  47 +++++
 lib/hooker.js               | 174 ++++++++++++++++++
 package.json                |  41 +++++
 test/hooker_test.js         | 435 ++++++++++++++++++++++++++++++++++++++++++++
 19 files changed, 1178 insertions(+)

diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 0000000..90c336c
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,22 @@
+Copyright (c) 2012 "Cowboy" Ben Alman
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..138943a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,186 @@
+# JavaScript Hooker
+
+Monkey-patch (hook) functions for debugging and stuff.
+
+## Getting Started
+
+This code should work just fine in Node.js:
+
+First, install the module with: `npm install hooker`
+
+```javascript
+var hooker = require('hooker');
+hooker.hook(Math, "max", function() {
+  console.log(arguments.length + " arguments passed");
+});
+Math.max(5, 6, 7) // logs: "3 arguments passed", returns 7
+```
+
+Or in the browser:
+
+```html
+<script src="dist/ba-hooker.min.js"></script>
+<script>
+hook(Math, "max", function() {
+  console.log(arguments.length + " arguments passed");
+});
+Math.max(5, 6, 7) // logs: "3 arguments passed", returns 7
+</script>
+```
+
+In the browser, you can attach Hooker's methods to any object.
+
+```html
+<script>
+this.exports = Bocoup.utils;
+</script>
+<script src="dist/ba-hooker.min.js"></script>
+<script>
+Bocoup.utils.hook(Math, "max", function() {
+  console.log(arguments.length + " arguments passed");
+});
+Math.max(5, 6, 7) // logs: "3 arguments passed", returns 7
+</script>
+```
+
+## Documentation
+
+### hooker.hook
+Monkey-patch (hook) one or more methods of an object.
+#### Signature:
+`hooker.hook(object, [ props, ] [options | prehookFunction])`
+#### `props`
+The optional `props` argument can be a method name, array of method names or null. If null (or omitted), all enumerable methods of `object` will be hooked.
+#### `options`
+* `pre` - (Function) a pre-hook function to be executed before the original function. Arguments passed into the method will be passed into the pre-hook function as well.
+* `post` - (Function) a post-hook function to be executed after the original function. The original function's result is passed into the post-hook function as its first argument, followed by the method arguments.
+* `once` - (Boolean) if true, auto-unhook the function after the first execution.
+* `passName` - (Boolean) if true, pass the name of the method into the pre-hook function as its first arg (preceding all other arguments), and into the post-hook function as the second arg (after result but preceding all other arguments).
+
+#### Returns:
+An array of hooked method names.
+
+### hooker.unhook
+Un-monkey-patch (unhook) one or more methods of an object.
+#### Signature:
+`hooker.unhook(object [, props ])`
+#### `props`
+The optional `props` argument can be a method name, array of method names or null. If null (or omitted), all methods of `object` will be unhooked.
+#### Returns:
+An array of unhooked method names.
+
+### hooker.orig
+Get a reference to the original method from a hooked function.
+#### Signature:
+`hooker.orig(object, props)`
+
+### hooker.override
+When a pre- or post-hook returns the result of this function, the value
+passed will be used in place of the original function's return value. Any
+post-hook override value will take precedence over a pre-hook override value.
+#### Signature:
+`hooker.override(value)`
+
+### hooker.preempt
+When a pre-hook returns the result of this function, the value passed will
+be used in place of the original function's return value, and the original
+function will NOT be executed.
+#### Signature:
+`hooker.preempt(value)`
+
+### hooker.filter
+When a pre-hook returns the result of this function, the context and
+arguments passed will be applied into the original function.
+#### Signature:
+`hooker.filter(context, arguments)`
+
+
+## Examples
+See the unit tests for more examples.
+
+```javascript
+var hooker = require('hooker');
+// Simple logging.
+hooker.hook(Math, "max", function() {
+  console.log(arguments.length + " arguments passed");
+});
+Math.max(5, 6, 7) // logs: "3 arguments passed", returns 7
+
+hooker.unhook(Math, "max"); // (This is assumed between all further examples)
+Math.max(5, 6, 7) // 7
+
+// Returning hooker.override(value) overrides the original value.
+hooker.hook(Math, "max", function() {
+  if (arguments.length === 0) {
+    return hooker.override(9000);
+  }
+});
+Math.max(5, 6, 7) // 7
+Math.max() // 9000
+
+// Auto-unhook after one execution.
+hooker.hook(Math, "max", {
+  once: true,
+  pre: function() {
+    console.log("Init something here");
+  }
+});
+Math.max(5, 6, 7) // logs: "Init something here", returns 7
+Math.max(5, 6, 7) // 7
+
+// Filter `this` and arguments through a pre-hook function.
+hooker.hook(Math, "max", {
+  pre: function() {
+    var args = [].map.call(arguments, function(num) {
+      return num * 2;
+    });
+    return hooker.filter(this, args); // thisValue, arguments
+  }
+});
+Math.max(5, 6, 7) // 14
+
+// Modify the original function's result with a post-hook function.
+hooker.hook(Math, "max", {
+  post: function(result) {
+    return hooker.override(result * 100);
+  }
+});
+Math.max(5, 6, 7) // 700
+
+// Hook every Math method. Note: if Math's methods were enumerable, the second
+// argument could be omitted. Since they aren't, an array of properties to hook
+// must be explicitly passed. Non-method properties will be skipped.
+// See a more generic example here: http://bit.ly/vvJlrS
+hooker.hook(Math, Object.getOwnPropertyNames(Math), {
+  passName: true,
+  pre: function(name) {
+    console.log("=> Math." + name, [].slice.call(arguments, 1));
+  },
+  post: function(result, name) {
+    console.log("<= Math." + name, result);
+  }
+});
+
+var result = Math.max(5, 6, 7);
+// => Math.max [ 5, 6, 7 ]
+// <= Math.max 7
+result // 7
+
+result = Math.ceil(3.456);
+// => Math.ceil [ 3.456 ]
+// <= Math.ceil 4
+result // 4
+```
+
+## Contributing
+In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt](https://github.com/cowboy/grunt).
+
+_Also, please don't edit files in the "dist" subdirectory as they are generated via grunt. You'll find source code in the "lib" subdirectory!_
+
+## Release History
+2012/01/09 - v0.2.3 - First official release.
+
+## License
+Copyright (c) 2012 "Cowboy" Ben Alman  
+Licensed under the MIT license.  
+<http://benalman.com/about/license/>
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..c674949
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+node-hooker (0.2.3-1) unstable; urgency=low
+
+  * Initial release (Closes: #nnnn)  <nnnn is the bug number of your ITP>
+
+ -- Matthew Pideil <matthewp_debian at teledetection.fr>  Sun, 30 Mar 2014 23:16:09 +0000
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..591850e
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,35 @@
+Source: node-hooker
+Section: web
+Priority: optional
+Maintainer: Debian Javascript Maintainers <pkg-javascript-devel at lists.alioth.debian.org>
+Uploaders: Matthew Pideil <matthewp_debian at teledetection.fr>
+Build-Depends: debhelper (>= 9.0.0), node-uglify | yui-compressor
+Standards-Version: 3.9.5
+Homepage: http://github.com/cowboy/javascript-hooker
+Vcs-Git: git://git.debian.org/pkg-javascript/node-hooker.git
+Vcs-Browser: http://git.debian.org/?p=pkg-javascript/node-hooker.git;a=summary
+
+Package: node-hooker
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, nodejs
+Description: Usefull debugging library for Node.js
+ Node is an event-based server-side JavaScript engine.
+ .
+ Hooker is a monkey-patch (hook) functions for debugging and stuff.
+ .
+ Hooker provides 4 functions that include 'hook' and 'unhook', usefull
+ for debugging an object call, as well as a 'override' function if you
+ want to replace the result of a function.
+
+Package: libjs-hooker
+Architecture: all
+Depends: ${misc:Depends},
+ nodejs
+Description: Usefull javascript debugging library usable in a browser
+ Node is an event-based server-side JavaScript engine.
+ .
+ Hooker is a monkey-patch (hook) functions for debugging and stuff.
+ .
+ Hooker provides 4 functions that include 'hook' and 'unhook', usefull
+ for debugging an object call, as well as a 'override' function if you
+ want to replace the result of a function.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..f13a50a
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,30 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: node-hooker
+Source: https://github.com/cowboy/javascript-hooker
+
+Files: *
+Copyright: 2012 "Cowboy" Ben Alman
+License: MIT
+
+Files: debian/*
+Copyright: 2014 Matthew Pideil <matthewp_debian at teledetection.fr>
+License: MIT
+
+License: MIT
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+ .
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..b43bf86
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1 @@
+README.md
diff --git a/debian/examples b/debian/examples
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/debian/examples
@@ -0,0 +1 @@
+test
diff --git a/debian/libjs-hooker.install b/debian/libjs-hooker.install
new file mode 100644
index 0000000..3e50c5a
--- /dev/null
+++ b/debian/libjs-hooker.install
@@ -0,0 +1 @@
+lib/*	/usr/share/javascript/hooker/
diff --git a/debian/node-hooker.install b/debian/node-hooker.install
new file mode 100644
index 0000000..f90c7c5
--- /dev/null
+++ b/debian/node-hooker.install
@@ -0,0 +1 @@
+lib/*	/usr/lib/nodejs/
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..5e703cc
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,22 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+js-compressor := $(or $(notdir $(shell which uglifyjs)),yui-compressor)
+
+# This has to be exported to make some magic below work.
+export DH_OPTIONS
+
+
+%:
+	dh $@ 
+
+override_dh_auto_build:
+	dh_auto_build
+	for _FILE in lib/*.js ; \
+	do \
+#		$(js-compressor)  -o $$(dirname $${_FILE})/$$(basename $${_FILE} .js).min.js $${_FILE}; \
+		$(js-compressor)  -o debin/build/$$(basename $${_FILE} .js).min.js $${_FILE}; \
+	done
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/debian/watch b/debian/watch
new file mode 100644
index 0000000..334bca7
--- /dev/null
+++ b/debian/watch
@@ -0,0 +1,2 @@
+version=3
+https://github.com/cowboy/javascript-hooker/tags .*/archive/v?(\d[\d\.]+).tar.gz
diff --git a/dist/ba-hooker.js b/dist/ba-hooker.js
new file mode 100644
index 0000000..07cfc18
--- /dev/null
+++ b/dist/ba-hooker.js
@@ -0,0 +1,169 @@
+/*! JavaScript Hooker - v0.2.3 - 1/22/2012
+* http://github.com/cowboy/javascript-hooker
+* Copyright (c) 2012 "Cowboy" Ben Alman; Licensed MIT */
+
+(function(exports) {
+  // Get an array from an array-like object with slice.call(arrayLikeObject).
+  var slice = [].slice;
+  // Get an "[object [[Class]]]" string with toString.call(value).
+  var toString = {}.toString;
+
+  // I can't think of a better way to ensure a value is a specific type other
+  // than to create instances and use the `instanceof` operator.
+  function HookerOverride(v) { this.value = v; }
+  function HookerPreempt(v) { this.value = v; }
+  function HookerFilter(c, a) { this.context = c; this.args = a; }
+
+  // When a pre- or post-hook returns the result of this function, the value
+  // passed will be used in place of the original function's return value. Any
+  // post-hook override value will take precedence over a pre-hook override
+  // value.
+  exports.override = function(value) {
+    return new HookerOverride(value);
+  };
+
+  // When a pre-hook returns the result of this function, the value passed will
+  // be used in place of the original function's return value, and the original
+  // function will NOT be executed.
+  exports.preempt = function(value) {
+    return new HookerPreempt(value);
+  };
+
+  // When a pre-hook returns the result of this function, the context and
+  // arguments passed will be applied into the original function.
+  exports.filter = function(context, args) {
+    return new HookerFilter(context, args);
+  };
+
+  // Execute callback(s) for properties of the specified object.
+  function forMethods(obj, props, callback) {
+    var prop;
+    if (typeof props === "string") {
+      // A single prop string was passed. Create an array.
+      props = [props];
+    } else if (props == null) {
+      // No props were passed, so iterate over all properties, building an
+      // array. Unfortunately, Object.keys(obj) doesn't work everywhere yet, so
+      // this has to be done manually.
+      props = [];
+      for (prop in obj) {
+        if (obj.hasOwnProperty(prop)) {
+          props.push(prop);
+        }
+      }
+    }
+    // Execute callback for every method in the props array.
+    var i = props.length;
+    while (i--) {
+      // If the property isn't a function...
+      if (toString.call(obj[props[i]]) !== "[object Function]" ||
+        // ...or the callback returns false...
+        callback(obj, props[i]) === false) {
+        // ...remove it from the props array to be returned.
+        props.splice(i, 1);
+      }
+    }
+    // Return an array of method names for which the callback didn't fail.
+    return props;
+  }
+
+  // Monkey-patch (hook) a method of an object.
+  exports.hook = function(obj, props, options) {
+    // If the props argument was omitted, shuffle the arguments.
+    if (options == null) {
+      options = props;
+      props = null;
+    }
+    // If just a function is passed instead of an options hash, use that as a
+    // pre-hook function.
+    if (typeof options === "function") {
+      options = {pre: options};
+    }
+
+    // Hook the specified method of the object.
+    return forMethods(obj, props, function(obj, prop) {
+      // The original (current) method.
+      var orig = obj[prop];
+      // The new hooked function.
+      function hooked() {
+        var result, origResult, tmp;
+
+        // Get an array of arguments.
+        var args = slice.call(arguments);
+
+        // If passName option is specified, prepend prop to the args array,
+        // passing it as the first argument to any specified hook functions.
+        if (options.passName) {
+          args.unshift(prop);
+        }
+
+        // If a pre-hook function was specified, invoke it in the current
+        // context with the passed-in arguments, and store its result.
+        if (options.pre) {
+          result = options.pre.apply(this, args);
+        }
+
+        if (result instanceof HookerFilter) {
+          // If the pre-hook returned hooker.filter(context, args), invoke the
+          // original function with that context and arguments, and store its
+          // result.
+          origResult = result = orig.apply(result.context, result.args);
+        } else if (result instanceof HookerPreempt) {
+          // If the pre-hook returned hooker.preempt(value) just use the passed
+          // value and don't execute the original function.
+          origResult = result = result.value;
+        } else {
+          // Invoke the original function in the current context with the
+          // passed-in arguments, and store its result.
+          origResult = orig.apply(this, arguments);
+          // If the pre-hook returned hooker.override(value), use the passed
+          // value, otherwise use the original function's result.
+          result = result instanceof HookerOverride ? result.value : origResult;
+        }
+
+        if (options.post) {
+          // If a post-hook function was specified, invoke it in the current
+          // context, passing in the result of the original function as the
+          // first argument, followed by any passed-in arguments.
+          tmp = options.post.apply(this, [origResult].concat(args));
+          if (tmp instanceof HookerOverride) {
+            // If the post-hook returned hooker.override(value), use the passed
+            // value, otherwise use the previously computed result.
+            result = tmp.value;
+          }
+        }
+
+        // Unhook if the "once" option was specified.
+        if (options.once) {
+          exports.unhook(obj, prop);
+        }
+
+        // Return the result!
+        return result;
+      }
+      // Re-define the method.
+      obj[prop] = hooked;
+      // Fail if the function couldn't be hooked.
+      if (obj[prop] !== hooked) { return false; }
+      // Store a reference to the original method as a property on the new one.
+      obj[prop]._orig = orig;
+    });
+  };
+
+  // Get a reference to the original method from a hooked function.
+  exports.orig = function(obj, prop) {
+    return obj[prop]._orig;
+  };
+
+  // Un-monkey-patch (unhook) a method of an object.
+  exports.unhook = function(obj, props) {
+    return forMethods(obj, props, function(obj, prop) {
+      // Get a reference to the original method, if it exists.
+      var orig = exports.orig(obj, prop);
+      // If there's no original method, it can't be unhooked, so fail.
+      if (!orig) { return false; }
+      // Unhook the method.
+      obj[prop] = orig;
+    });
+  };
+}(typeof exports === "object" && exports || this));
diff --git a/dist/ba-hooker.min.js b/dist/ba-hooker.min.js
new file mode 100644
index 0000000..eed6e8d
--- /dev/null
+++ b/dist/ba-hooker.min.js
@@ -0,0 +1,4 @@
+/*! JavaScript Hooker - v0.2.3 - 1/22/2012
+* http://github.com/cowboy/javascript-hooker
+* Copyright (c) 2012 "Cowboy" Ben Alman; Licensed MIT */
+(function(a){function d(a){this.value=a}function e(a){this.value=a}function f(a,b){this.context=a,this.args=b}function g(a,b,d){var e;if(typeof b=="string")b=[b];else if(b==null){b=[];for(e in a)a.hasOwnProperty(e)&&b.push(e)}var f=b.length;while(f--)(c.call(a[b[f]])!=="[object Function]"||d(a,b[f])===!1)&&b.splice(f,1);return b}var b=[].slice,c={}.toString;a.override=function(a){return new d(a)},a.preempt=function(a){return new e(a)},a.filter=function(a,b){return new f(a,b)},a.hook=func [...]
\ No newline at end of file
diff --git a/grunt.js b/grunt.js
new file mode 100644
index 0000000..c695148
--- /dev/null
+++ b/grunt.js
@@ -0,0 +1,47 @@
+/*global config:true, task:true*/
+config.init({
+  pkg: '<json:package.json>',
+  meta: {
+    name: 'JavaScript Hooker',
+    banner: '/*! <%= meta.name %> - v<%= pkg.version %> - <%= template.today("m/d/yyyy") %>\n' +
+            '* <%= pkg.homepage %>\n' +
+            '* Copyright (c) <%= template.today("yyyy") %> <%= pkg.author.name %>;' +
+            ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
+  },
+  concat: {
+    'dist/ba-hooker.js': ['<banner>', '<file_strip_banner:lib/hooker.js>']
+  },
+  min: {
+    'dist/ba-hooker.min.js': ['<banner>', 'dist/ba-hooker.js']
+  },
+  test: {
+    files: ['test/**/*.js']
+  },
+  lint: {
+    files: ['grunt.js', 'lib/**/*.js', 'test/**/*.js']
+  },
+  watch: {
+    files: '<config:lint.files>',
+    tasks: 'lint:files test:files'
+  },
+  jshint: {
+    options: {
+      curly: true,
+      eqeqeq: true,
+      immed: true,
+      latedef: true,
+      newcap: true,
+      noarg: true,
+      sub: true,
+      undef: true,
+      eqnull: true
+    },
+    globals: {
+      exports: true
+    }
+  },
+  uglify: {}
+});
+
+// Default task.
+task.registerTask('default', 'lint:files test:files concat min');
diff --git a/lib/hooker.js b/lib/hooker.js
new file mode 100644
index 0000000..1ff9764
--- /dev/null
+++ b/lib/hooker.js
@@ -0,0 +1,174 @@
+/*
+ * JavaScript Hooker
+ * http://github.com/cowboy/javascript-hooker
+ *
+ * Copyright (c) 2012 "Cowboy" Ben Alman
+ * Licensed under the MIT license.
+ * http://benalman.com/about/license/
+ */
+
+(function(exports) {
+  // Get an array from an array-like object with slice.call(arrayLikeObject).
+  var slice = [].slice;
+  // Get an "[object [[Class]]]" string with toString.call(value).
+  var toString = {}.toString;
+
+  // I can't think of a better way to ensure a value is a specific type other
+  // than to create instances and use the `instanceof` operator.
+  function HookerOverride(v) { this.value = v; }
+  function HookerPreempt(v) { this.value = v; }
+  function HookerFilter(c, a) { this.context = c; this.args = a; }
+
+  // When a pre- or post-hook returns the result of this function, the value
+  // passed will be used in place of the original function's return value. Any
+  // post-hook override value will take precedence over a pre-hook override
+  // value.
+  exports.override = function(value) {
+    return new HookerOverride(value);
+  };
+
+  // When a pre-hook returns the result of this function, the value passed will
+  // be used in place of the original function's return value, and the original
+  // function will NOT be executed.
+  exports.preempt = function(value) {
+    return new HookerPreempt(value);
+  };
+
+  // When a pre-hook returns the result of this function, the context and
+  // arguments passed will be applied into the original function.
+  exports.filter = function(context, args) {
+    return new HookerFilter(context, args);
+  };
+
+  // Execute callback(s) for properties of the specified object.
+  function forMethods(obj, props, callback) {
+    var prop;
+    if (typeof props === "string") {
+      // A single prop string was passed. Create an array.
+      props = [props];
+    } else if (props == null) {
+      // No props were passed, so iterate over all properties, building an
+      // array. Unfortunately, Object.keys(obj) doesn't work everywhere yet, so
+      // this has to be done manually.
+      props = [];
+      for (prop in obj) {
+        if (obj.hasOwnProperty(prop)) {
+          props.push(prop);
+        }
+      }
+    }
+    // Execute callback for every method in the props array.
+    var i = props.length;
+    while (i--) {
+      // If the property isn't a function...
+      if (toString.call(obj[props[i]]) !== "[object Function]" ||
+        // ...or the callback returns false...
+        callback(obj, props[i]) === false) {
+        // ...remove it from the props array to be returned.
+        props.splice(i, 1);
+      }
+    }
+    // Return an array of method names for which the callback didn't fail.
+    return props;
+  }
+
+  // Monkey-patch (hook) a method of an object.
+  exports.hook = function(obj, props, options) {
+    // If the props argument was omitted, shuffle the arguments.
+    if (options == null) {
+      options = props;
+      props = null;
+    }
+    // If just a function is passed instead of an options hash, use that as a
+    // pre-hook function.
+    if (typeof options === "function") {
+      options = {pre: options};
+    }
+
+    // Hook the specified method of the object.
+    return forMethods(obj, props, function(obj, prop) {
+      // The original (current) method.
+      var orig = obj[prop];
+      // The new hooked function.
+      function hooked() {
+        var result, origResult, tmp;
+
+        // Get an array of arguments.
+        var args = slice.call(arguments);
+
+        // If passName option is specified, prepend prop to the args array,
+        // passing it as the first argument to any specified hook functions.
+        if (options.passName) {
+          args.unshift(prop);
+        }
+
+        // If a pre-hook function was specified, invoke it in the current
+        // context with the passed-in arguments, and store its result.
+        if (options.pre) {
+          result = options.pre.apply(this, args);
+        }
+
+        if (result instanceof HookerFilter) {
+          // If the pre-hook returned hooker.filter(context, args), invoke the
+          // original function with that context and arguments, and store its
+          // result.
+          origResult = result = orig.apply(result.context, result.args);
+        } else if (result instanceof HookerPreempt) {
+          // If the pre-hook returned hooker.preempt(value) just use the passed
+          // value and don't execute the original function.
+          origResult = result = result.value;
+        } else {
+          // Invoke the original function in the current context with the
+          // passed-in arguments, and store its result.
+          origResult = orig.apply(this, arguments);
+          // If the pre-hook returned hooker.override(value), use the passed
+          // value, otherwise use the original function's result.
+          result = result instanceof HookerOverride ? result.value : origResult;
+        }
+
+        if (options.post) {
+          // If a post-hook function was specified, invoke it in the current
+          // context, passing in the result of the original function as the
+          // first argument, followed by any passed-in arguments.
+          tmp = options.post.apply(this, [origResult].concat(args));
+          if (tmp instanceof HookerOverride) {
+            // If the post-hook returned hooker.override(value), use the passed
+            // value, otherwise use the previously computed result.
+            result = tmp.value;
+          }
+        }
+
+        // Unhook if the "once" option was specified.
+        if (options.once) {
+          exports.unhook(obj, prop);
+        }
+
+        // Return the result!
+        return result;
+      }
+      // Re-define the method.
+      obj[prop] = hooked;
+      // Fail if the function couldn't be hooked.
+      if (obj[prop] !== hooked) { return false; }
+      // Store a reference to the original method as a property on the new one.
+      obj[prop]._orig = orig;
+    });
+  };
+
+  // Get a reference to the original method from a hooked function.
+  exports.orig = function(obj, prop) {
+    return obj[prop]._orig;
+  };
+
+  // Un-monkey-patch (unhook) a method of an object.
+  exports.unhook = function(obj, props) {
+    return forMethods(obj, props, function(obj, prop) {
+      // Get a reference to the original method, if it exists.
+      var orig = exports.orig(obj, prop);
+      // If there's no original method, it can't be unhooked, so fail.
+      if (!orig) { return false; }
+      // Unhook the method.
+      obj[prop] = orig;
+    });
+  };
+}(typeof exports === "object" && exports || this));
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6b976a6
--- /dev/null
+++ b/package.json
@@ -0,0 +1,41 @@
+{
+  "name": "hooker",
+  "description": "Monkey-patch (hook) functions for debugging and stuff.",
+  "version": "0.2.3",
+  "homepage": "http://github.com/cowboy/javascript-hooker",
+  "author": {
+    "name": "\"Cowboy\" Ben Alman",
+    "url": "http://benalman.com/"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/cowboy/javascript-hooker.git"
+  },
+  "bugs": {
+    "url": "https://github.com/cowboy/javascript-hooker/issues"
+  },
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://github.com/cowboy/javascript-hooker/blob/master/LICENSE-MIT"
+    }
+  ],
+  "dependencies": {},
+  "devDependencies": {
+    "grunt": "~0.2.1"
+  },
+  "keywords": [
+    "patch",
+    "hook",
+    "function",
+    "debug",
+    "aop"
+  ],
+  "engines": {
+    "node": ">= 0.6.7"
+  },
+  "main": "lib/hooker",
+  "scripts": {
+    "test": "grunt test"
+  }
+}
diff --git a/test/hooker_test.js b/test/hooker_test.js
new file mode 100644
index 0000000..dc5910a
--- /dev/null
+++ b/test/hooker_test.js
@@ -0,0 +1,435 @@
+/*global require:true */
+var hooker = require('../lib/hooker');
+
+exports['hook'] = {
+  setUp: function(done) {
+    this.order = [];
+    this.track = function() {
+      [].push.apply(this.order, arguments);
+    };
+
+    this.prop = 1;
+    this.add = function(a, b) {
+      this.track("add", this.prop, a, b);
+      return this.prop + a + b;
+    };
+
+    this.obj = {
+      that: this,
+      prop: 1,
+      add1: function(a, b) {
+        this.that.track("add1", this.prop, a, b);
+        return this.prop + a + b;
+      },
+      add2: function(a, b) {
+        this.that.track("add2", this.prop, a, b);
+        return this.prop + a + b;
+      },
+      add3: function(a, b) {
+        this.that.track("add3", this.prop, a, b);
+        return this.prop + a + b;
+      }
+    };
+
+    done();
+  },
+  'orig': function(test) {
+    test.expect(1);
+    var orig = this.add;
+    hooker.hook(this, "add", function() {});
+    test.strictEqual(hooker.orig(this, "add"), orig, "should return a refernce to the original function.");
+    test.done();
+  },
+  'once': function(test) {
+    test.expect(5);
+    var orig = this.add;
+    hooker.hook(this, "add", {
+      once: true,
+      pre: function(a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.track("before", this.prop, a, b);
+      }
+    });
+    test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3], "functions should execute in-order.");
+    test.strictEqual(this.add, orig, "should automatically unhook when once is specified.");
+    this.order = [];
+    test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["add", 1, 2, 3], "only the original function should execute.");
+    test.done();
+  },
+  'pre-hook (simple syntax)': function(test) {
+    test.expect(3);
+    // Pre-hook.
+    var result = hooker.hook(this, "add", function(a, b) {
+      // Arguments are passed into pre-hook as specified.
+      this.track("before", this.prop, a, b);
+    });
+    test.deepEqual(result, ["add"], "add should have been hooked.");
+    test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3], "functions should execute in-order.");
+    test.done();
+  },
+  'pre-hook': function(test) {
+    test.expect(3);
+    // Pre-hook.
+    var result = hooker.hook(this, "add", {
+      pre: function(a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.track("before", this.prop, a, b);
+      }
+    });
+    test.deepEqual(result, ["add"], "add should have been hooked.");
+    test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3], "functions should execute in-order.");
+    test.done();
+  },
+  'post-hook': function(test) {
+    test.expect(3);
+    // Post-hook.
+    var result = hooker.hook(this, "add", {
+      post: function(result, a, b) {
+        // Arguments to post-hook are the original function's return value,
+        // followed by the specified function arguments.
+        this.track("after", this.prop, a, b, result);
+      }
+    });
+    test.deepEqual(result, ["add"], "add should have been hooked.");
+    test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["add", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
+    test.done();
+  },
+  'pre- & post-hook': function(test) {
+    test.expect(2);
+    // Pre- & post-hook.
+    hooker.hook(this, "add", {
+      pre: function(a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.track("before", this.prop, a, b);
+      },
+      post: function(result, a, b) {
+        // Arguments to post-hook are the original function's return value,
+        // followed by the specified function arguments.
+        this.track("after", this.prop, a, b, result);
+      }
+    });
+    test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
+    test.done();
+  },
+
+  'pre-hook, return value override': function(test) {
+    test.expect(2);
+    // Pre-hook.
+    hooker.hook(this, "add", {
+      pre: function(a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.track("before", this.prop, a, b);
+        // This return value will override the original function's return value.
+        return hooker.override("b" + this.prop + a + b);
+      }
+    });
+    test.strictEqual(this.add(2, 3), "b123", "should return the overridden result.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3], "functions should execute in-order.");
+    test.done();
+  },
+  'post-hook, return value override': function(test) {
+    test.expect(2);
+    // Post-hook.
+    hooker.hook(this, "add", {
+      post: function(result, a, b) {
+        // Arguments to post-hook are the original function's return value,
+        // followed by the specified function arguments.
+        this.track("after", this.prop, a, b, result);
+        // This return value will override the original function's return value.
+        return hooker.override("a" + this.prop + a + b + result);
+      }
+    });
+    test.strictEqual(this.add(2, 3), "a1236", "should return the post-hook overridden result.");
+    test.deepEqual(this.order, ["add", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
+    test.done();
+  },
+  'pre- & post-hook, return value override': function(test) {
+    test.expect(2);
+    // Pre- & post-hook.
+    hooker.hook(this, "add", {
+      pre: function(a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.track("before", this.prop, a, b);
+        // This return value will override the original function's return value.
+        return hooker.override("b" + this.prop + a + b);
+      },
+      post: function(result, a, b) {
+        // Arguments to post-hook are the original function's return value,
+        // followed by the specified function arguments.
+        this.track("after", this.prop, a, b, result);
+        // This return value will override the original function's return value
+        // AND the pre-hook's return value.
+        return hooker.override("a" + this.prop + a + b + result);
+      }
+    });
+    test.strictEqual(this.add(2, 3), "a1236", "should return the overridden result, and post-hook result should take precedence over pre-hook result.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
+    test.done();
+  },
+
+  'pre-hook, filtering arguments': function(test) {
+    test.expect(2);
+    // Pre-hook.
+    hooker.hook(this, "add", {
+      pre: function(a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.track("before", this.prop, a, b);
+        // Return hooker.filter(context, arguments) and they will be passed into
+        // the original function. The "track" and "order" propterites are just
+        // set here for the same of this unit test.
+        return hooker.filter({prop: "x", track: this.track, order: this.order}, ["y", "z"]);
+      }
+    });
+    test.strictEqual(this.add(2, 3), "xyz", "should return the original function's result, given filtered context and arguments.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "add", "x", "y", "z"], "functions should execute in-order.");
+    test.done();
+  },
+  'pre- & post-hook, filtering arguments': function(test) {
+    test.expect(2);
+    // Pre- & post-hook.
+    hooker.hook(this, "add", {
+      pre: function(a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.track("before", this.prop, a, b);
+        // Return hooker.filter(context, arguments) and they will be passed into
+        // the original function. The "track" and "order" propterites are just
+        // set here for the same of this unit test.
+        return hooker.filter({prop: "x", track: this.track, order: this.order}, ["y", "z"]);
+      },
+      post: function(result, a, b) {
+        // Arguments to post-hook are the original function's return value,
+        // followed by the specified function arguments.
+        this.track("after", this.prop, a, b, result);
+      }
+    });
+    test.strictEqual(this.add(2, 3), "xyz", "should return the original function's result, given filtered context and arguments.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "add", "x", "y", "z", "after", 1, 2, 3, "xyz"], "functions should execute in-order.");
+    test.done();
+  },
+  'pre- & post-hook, filtering arguments, return value override': function(test) {
+    test.expect(2);
+    // Pre- & post-hook.
+    hooker.hook(this, "add", {
+      pre: function(a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.track("before", this.prop, a, b);
+        // Return hooker.filter(context, arguments) and they will be passed into
+        // the original function. The "track" and "order" propterites are just
+        // set here for the same of this unit test.
+        return hooker.filter({prop: "x", track: this.track, order: this.order}, ["y", "z"]);
+      },
+      post: function(result, a, b) {
+        // Arguments to post-hook are the original function's return value,
+        // followed by the specified function arguments.
+        this.track("after", this.prop, a, b, result);
+        // This return value will override the original function's return value
+        // AND the pre-hook's return value.
+        return hooker.override("a" + this.prop + a + b + result);
+      }
+    });
+    test.strictEqual(this.add(2, 3), "a123xyz", "should return the post-hook overridden result.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "add", "x", "y", "z", "after", 1, 2, 3, "xyz"], "functions should execute in-order.");
+    test.done();
+  },
+
+  'pre-hook, preempt original function': function(test) {
+    test.expect(2);
+    // Pre-hook.
+    hooker.hook(this, "add", {
+      pre: function(a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.track("before", this.prop, a, b);
+        // Returning hooker.preempt will prevent the original function from being
+        // invoked and optionally set a return value.
+        return hooker.preempt();
+      }
+    });
+    test.strictEqual(this.add(2, 3), undefined, "should return the value passed to preempt.");
+    test.deepEqual(this.order, ["before", 1, 2, 3], "functions should execute in-order.");
+    test.done();
+  },
+  'pre-hook, preempt original function with value': function(test) {
+    test.expect(2);
+    // Pre-hook.
+    hooker.hook(this, "add", {
+      pre: function(a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.track("before", this.prop, a, b);
+        // Returning hooker.preempt will prevent the original function from being
+        // invoked and optionally set a return value.
+        return hooker.preempt(9000);
+      }
+    });
+    test.strictEqual(this.add(2, 3), 9000, "should return the value passed to preempt.");
+    test.deepEqual(this.order, ["before", 1, 2, 3], "functions should execute in-order.");
+    test.done();
+  },
+  'pre- & post-hook, preempt original function with value': function(test) {
+    test.expect(2);
+    // Pre- & post-hook.
+    hooker.hook(this, "add", {
+      pre: function(a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.track("before", this.prop, a, b);
+        // Returning hooker.preempt will prevent the original function from being
+        // invoked and optionally set a return value.
+        return hooker.preempt(9000);
+      },
+      post: function(result, a, b) {
+        // Arguments to post-hook are the original function's return value,
+        // followed by the specified function arguments.
+        this.track("after", this.prop, a, b, result);
+      }
+    });
+    test.strictEqual(this.add(2, 3), 9000, "should return the value passed to preempt.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "after", 1, 2, 3, 9000], "functions should execute in-order.");
+    test.done();
+  },
+  'pre- & post-hook, preempt original function with value, return value override': function(test) {
+    test.expect(2);
+    // Pre- & post-hook.
+    hooker.hook(this, "add", {
+      pre: function(a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.track("before", this.prop, a, b);
+        // Returning hooker.preempt will prevent the original function from being
+        // invoked and optionally set a return value.
+        return hooker.preempt(9000);
+      },
+      post: function(result, a, b) {
+        // Arguments to post-hook are the original function's return value,
+        // followed by the specified function arguments.
+        this.track("after", this.prop, a, b, result);
+        // This return value will override any preempt value set in pre-hook.
+        return hooker.override("a" + this.prop + a + b + result);
+      }
+    });
+    test.strictEqual(this.add(2, 3), "a1239000", "should return the overridden result, and post-hook result should take precedence over preempt value.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "after", 1, 2, 3, 9000], "functions should execute in-order.");
+    test.done();
+  },
+  'pre- & post-hook, some properties': function(test) {
+    test.expect(7);
+    // Pre- & post-hook.
+    var result = hooker.hook(this.obj, ["add1", "add2"], {
+      pre: function(a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.that.track("before", this.prop, a, b);
+      },
+      post: function(result, a, b) {
+        // Arguments to post-hook are the original function's return value,
+        // followed by the specified function arguments.
+        this.that.track("after", this.prop, a, b, result);
+      }
+    });
+    test.deepEqual(result.sort(), ["add1", "add2"], "both functions should have been hooked.");
+    test.strictEqual(this.obj.add1(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "add1", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
+    this.order = [];
+    test.strictEqual(this.obj.add2(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "add2", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
+    this.order = [];
+    test.strictEqual(this.obj.add3(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["add3", 1, 2, 3], "functions should execute in-order.");
+    test.done();
+  },
+  'pre- & post-hook, all properties': function(test) {
+    test.expect(7);
+    // Pre- & post-hook.
+    var result = hooker.hook(this.obj, {
+      pre: function(a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.that.track("before", this.prop, a, b);
+      },
+      post: function(result, a, b) {
+        // Arguments to post-hook are the original function's return value,
+        // followed by the specified function arguments.
+        this.that.track("after", this.prop, a, b, result);
+      }
+    });
+    test.deepEqual(result.sort(), ["add1", "add2", "add3"], "all functions should have been hooked.");
+    test.strictEqual(this.obj.add1(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "add1", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
+    this.order = [];
+    test.strictEqual(this.obj.add2(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "add2", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
+    this.order = [];
+    test.strictEqual(this.obj.add3(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["before", 1, 2, 3, "add3", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
+    test.done();
+  },
+  'pre- & post-hook, all properties, passName': function(test) {
+    test.expect(6);
+    // Pre- & post-hook.
+    hooker.hook(this.obj, {
+      passName: true,
+      pre: function(name, a, b) {
+        // Arguments are passed into pre-hook as specified.
+        this.that.track("before", this.prop, name, a, b);
+      },
+      post: function(result, name, a, b) {
+        // Arguments to post-hook are the original function's return value,
+        // followed by the specified function arguments.
+        this.that.track("after", this.prop, name, a, b, result);
+      }
+    });
+    test.strictEqual(this.obj.add1(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["before", 1, "add1", 2, 3, "add1", 1, 2, 3, "after", 1, "add1", 2, 3, 6], "functions should execute in-order.");
+    this.order = [];
+    test.strictEqual(this.obj.add2(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["before", 1, "add2", 2, 3, "add2", 1, 2, 3, "after", 1, "add2", 2, 3, 6], "functions should execute in-order.");
+    this.order = [];
+    test.strictEqual(this.obj.add3(2, 3), 6, "should return the original function's result.");
+    test.deepEqual(this.order, ["before", 1, "add3", 2, 3, "add3", 1, 2, 3, "after", 1, "add3", 2, 3, 6], "functions should execute in-order.");
+    test.done();
+  },
+  'unhook one property': function(test) {
+    test.expect(5);
+    var orig = this.add;
+    hooker.hook(this, "add", function() {});
+    var result = hooker.unhook(this, "add");
+    test.deepEqual(result, ["add"], "one function should have been unhooked.");
+    test.strictEqual(this.add, orig, "should have unhooked, restoring the original function");
+    result = hooker.unhook(this, "add");
+    test.deepEqual(result, [], "nothing should have been unhooked.");
+    test.strictEqual(this.add, orig, "shouldn't explode if already unhooked");
+    test.strictEqual(this.add.orig, undefined, "original function shouldn't have an orig property");
+    test.done();
+  },
+  'unhook some properties': function(test) {
+    test.expect(6);
+    var add1 = this.obj.add1;
+    var add2 = this.obj.add2;
+    hooker.hook(this.obj, ["add1", "add2"], function() {});
+    test.strictEqual(hooker.orig(this.obj, "add1"), add1, "should return a refernce to the original function");
+    test.strictEqual(hooker.orig(this.obj, "add2"), add2, "should return a refernce to the original function");
+    test.strictEqual(hooker.orig(this.obj, "add3"), undefined, "should not have been hooked, so should not have an original function");
+    var result = hooker.unhook(this.obj, ["add1", "add2"]);
+    test.deepEqual(result.sort(), ["add1", "add2"], "both functions should have been unhooked.");
+    test.strictEqual(this.obj.add1, add1, "should have unhooked, restoring the original function");
+    test.strictEqual(this.obj.add2, add2, "should have unhooked, restoring the original function");
+    test.done();
+  },
+  'unhook all properties': function(test) {
+    test.expect(7);
+    var add1 = this.obj.add1;
+    var add2 = this.obj.add2;
+    var add3 = this.obj.add3;
+    hooker.hook(this.obj, function() {});
+    test.strictEqual(hooker.orig(this.obj, "add1"), add1, "should return a refernce to the original function");
+    test.strictEqual(hooker.orig(this.obj, "add2"), add2, "should return a refernce to the original function");
+    test.strictEqual(hooker.orig(this.obj, "add3"), add3, "should return a refernce to the original function");
+    var result = hooker.unhook(this.obj);
+    test.deepEqual(result.sort(), ["add1", "add2", "add3"], "all functions should have been unhooked.");
+    test.strictEqual(this.obj.add1, add1, "should have unhooked, restoring the original function");
+    test.strictEqual(this.obj.add2, add2, "should have unhooked, restoring the original function");
+    test.strictEqual(this.obj.add3, add3, "should have unhooked, restoring the original function");
+    test.done();
+  }
+};

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-hooker.git



More information about the Pkg-javascript-commits mailing list