{"_id":"externr","_rev":"236854","name":"externr","description":"Provide a plug-in mechanism for your objects, exposing their inmost secrets","dist-tags":{"latest":"1.0.0"},"maintainers":[{"name":"rvagg","email":""}],"time":{"modified":"2023-03-24T16:31:36.000Z","created":"2013-03-01T05:04:22.549Z","1.0.0":"2014-08-19T12:50:28.389Z","0.0.1":"2013-03-01T05:23:10.249Z","0.0.0":"2013-03-01T05:04:22.549Z"},"users":{},"repository":{"type":"git","url":"https://github.com/rvagg/externr.git"},"versions":{"1.0.0":{"name":"externr","description":"Provide a plug-in mechanism for your objects, exposing their inmost secrets","version":"1.0.0","homepage":"https://github.com/rvagg/externr","authors":["Rod Vagg <rod@vagg.org> (https://github.com/rvagg)"],"keywords":["plugin"],"main":"./externr.js","repository":{"type":"git","url":"https://github.com/rvagg/externr.git"},"dependencies":{},"devDependencies":{"colors":"~0.6.2","tape":"~2.14.0"},"scripts":{"test":"node ./test.js"},"license":"MIT","gitHead":"e4afaa5357fc7d11bcf21a58f1b9d9043d45a970","bugs":{"url":"https://github.com/rvagg/externr/issues"},"_id":"externr@1.0.0","_shasum":"e2a741f7cf9a37fe9919a02a560b0a0afe7fa0ba","_from":".","_npmVersion":"1.4.14","_npmUser":{"name":"rvagg","email":"rod@vagg.org"},"maintainers":[{"name":"rvagg","email":""}],"dist":{"shasum":"e2a741f7cf9a37fe9919a02a560b0a0afe7fa0ba","size":5963,"noattachment":false,"key":"/externr/-/externr-1.0.0.tgz","tarball":"http://name.csiicloud.com:7001/externr/download/externr-1.0.0.tgz"},"directories":{},"publish_time":1408452628389,"_hasShrinkwrap":false,"deprecated":"no longer maintained","_cnpm_publish_time":1408452628389,"_cnpmcore_publish_time":"2021-12-18T15:52:36.279Z"},"0.0.1":{"name":"externr","description":"Provide a plug-in mechanism for your objects, exposing their inmost secrets","version":"0.0.1","homepage":"https://github.com/rvagg/externr","authors":["Rod Vagg <rod@vagg.org> (https://github.com/rvagg)"],"keywords":["plugin"],"main":"./externr.js","repository":{"type":"git","url":"https://github.com/rvagg/externr.git"},"dependencies":{},"devDependencies":{"tape":"*","colors":"*"},"scripts":{"test":"node ./test.js"},"license":"MIT","readmeFilename":"README.md","_id":"externr@0.0.1","dist":{"shasum":"ec5e61aea79214b21bfdbeecef615a5ff06556c4","size":6163,"noattachment":false,"key":"/externr/-/externr-0.0.1.tgz","tarball":"http://name.csiicloud.com:7001/externr/download/externr-0.0.1.tgz"},"_from":".","_npmVersion":"1.2.11","_npmUser":{"name":"rvagg","email":"rod@vagg.org"},"maintainers":[{"name":"rvagg","email":""}],"directories":{},"publish_time":1362115390249,"_hasShrinkwrap":false,"deprecated":"no longer maintained","_cnpm_publish_time":1362115390249,"_cnpmcore_publish_time":"2021-12-18T15:52:36.510Z"},"0.0.0":{"name":"externr","description":"Provide a plug-in mechanism for your objects, exposing their inmost secrets","version":"0.0.0","homepage":"https://github.com/rvagg/externr","authors":["Rod Vagg <rod@vagg.org> (https://github.com/rvagg)"],"keywords":["plugin"],"main":"./externr.js","repository":{"type":"git","url":"https://github.com/rvagg/externr.git"},"dependencies":{},"devDependencies":{"tape":"*","colors":"*"},"scripts":{"test":"node ./test.js"},"license":"MIT","readmeFilename":"README.md","_id":"externr@0.0.0","dist":{"shasum":"a602f562c5af3e4d65b3a07f1ece6f072631c577","size":5209,"noattachment":false,"key":"/externr/-/externr-0.0.0.tgz","tarball":"http://name.csiicloud.com:7001/externr/download/externr-0.0.0.tgz"},"_from":".","_npmVersion":"1.2.11","_npmUser":{"name":"rvagg","email":"rod@vagg.org"},"maintainers":[{"name":"rvagg","email":""}],"directories":{},"publish_time":1362114262549,"_hasShrinkwrap":false,"deprecated":"no longer maintained","_cnpm_publish_time":1362114262549,"_cnpmcore_publish_time":"2021-12-18T15:52:36.835Z"}},"readme":"# Externr [![Build Status](https://secure.travis-ci.org/rvagg/externr.png)](http://travis-ci.org/rvagg/externr)\n\n**Provide a plug-in mechanism for JavaScript objects, exposing their inmost secrets.**\n\n[![NPM](https://nodei.co/npm/externr.png?stars&downloads&downloadRank)](https://nodei.co/npm/externr/) [![NPM](https://nodei.co/npm-dl/externr.png?months=6&height=3)](https://nodei.co/npm/externr/)\n\n\n## Example\n\nLet's imagine a super-useful module that gives you an object, with a name, that can take a file path and return a humanised string as if the object is speaking the size. (See what I mean by super-useful??)\n\n```js\nvar fs = require('fs')\n\nfunction SizeSpeaker (name) {\n  this.name = name\n}\n\nSizeSpeaker.prototype.getName = function () {\n  return this.name\n}\n\nSizeSpeaker.prototype.fsize = function (path, callback) {\n  fs.stat(path, function (err, stat) {\n    if (err) return callback(err)\n\n    var say = this.getName() + ' says that the size of ' + path + ' is ' + stat.size\n    callback(null, say)\n  }.bind(this))\n}\n\nmodule.exports = function (name) { return new SizeSpeaker(name) }\n```\n\nThen we might use it like this:\n\n```js\nvar SizeSpeaker = require('size-speaker')\n  , bruce = SizeSpeaker('Bruce')\n\nbruce.fsize('/usr/share/dict/british-english', function (err, said) {\n  if (err) throw err\n  console.log(said)\n})\n```\n\nRunning our little program we get:\n\n```\nBruce says that the size of /usr/share/dict/british-english is 938969\n```\n\n\nSo, that's great, I really should put that in npm. Anyway, say you want to make it extendible so that users of your neat little module can easily *plug in* objects that alter the functionality without having to explicitly monkey-patch and get all dirty.\n\nIn comes **Externr**. Identify potential extension points and then set up an internal `Externr` object which is aware of the kinds of extensions you want.\n\nIn our example, lets let plugins modify the `getName()` result and also inject themselves into the async file-size call:\n\n```js\nvar fs = require('fs')\n  , Externr = require('externr')\n\nfunction SizeSpeaker (name) {\n  this.name = name\n  this._externs = Externr({\n      extend: [ 'getName' ] // `extend` is a simple wrapped function\n    , wrap: [ 'fsize' ] // `wrap` is for more complicated async calls\n  })\n  // expose a `.use()` method that can inject plugins into our own Externr instance\n  this.use = this._externs.$register.bind(this._externs)\n}\n\nSizeSpeaker.prototype.getName = function() {\n  // pass our default return value through Externr, which is a noop by default\n  return this._externs.getName(this.name)\n}\n\nSizeSpeaker.prototype.fsize = function (path, callback) {\n  fs.stat(path, function (err, stat) {\n    if (err) return callback(err)\n    // allow async injections *after* the `stat()` but before our returning callback\n    this._externs.fsize(this, [ path, stat, callback ], function (path, stat, callback) {\n      var say = this.getName() + ' says that the size of ' + path + ' is ' + stat.size\n      callback(null, say)\n    })\n  }.bind(this))\n}\n\nmodule.exports = function (name) { return new SizeSpeaker(name) }\n```\n\nBy default, anything passed through your `Externr` instance is a noop and will just pass back what it was given as if it wasn't there. Thankfully VMs can optimise most of the overhead away so it's as if it didn't exist.\n\nBut, we can now inject plugins! Plugins are simply objects whose keys match the functions we registered with our `Externr` instance:\n\n```js\n// give our object a bit of personality by injecting a nickname along with the name\nvar personalityPlugin = {\n  getName: function (name) { return name + ' the Spruce' }\n}\n```\n\n```js\n// byte sizes aren't so nice when the file is large, so we'll do it in *kB*\n// by adjusting the `stat()` data\nvar niceSizePlugin = {\n  fsize: function (path, stat, callback, next) {\n    stat.size = (Math.round(stat.size / 1024 * 10) / 10) + ' kB'\n    next(path, stat, callback)\n  }\n}\n```\n\nNow we just need to inject the plugins with the `.use()` method that our `SizeSpeaker` exposes. First we'll register the `personalityPlugin`:\n\n```js\n// ...\nvar bruce = SizeSpeaker('Bruce')\n// ...\n\nbruce.use(personalityPlugin)\n\nbruce.fsize('/usr/share/dict/british-english', function (err, said) {\n  if (err) throw err\n  console.log(said)\n})\n```\n\nGives us the output:\n\n```\nBruce the Spruce says that the size of /usr/share/dict/british-english is 938969\n```\n\nNow let's register both plugins:\n\n```js\n// ...\nvar bruce = SizeSpeaker('Bruce')\n// ...\n\nbruce.use(personalityPlugin)\nbruce.use(niceSizePlugin)\n\nbruce.fsize('/usr/share/dict/british-english', function (err, said) {\n  if (err) throw err\n  console.log(said)\n})\n```\n\nAnd we get the output:\n\n```\nBruce the Spruce says that the size of /usr/share/dict/british-english is 917 kB\n```\n\nWe can take it further by adding multiple plugins for the same extension point:\n\n```js\nrequire('colors')\n// for use on the console only, 'colors' provides ANSI colour codes\nvar boldNamePlugin = {\n  getName: function (name) { return name.bold }\n}\n```\n\nAnd we just register it as well, this time we'll bundle them all into an array (optional):\n\n```js\nbruce.use([ personalityPlugin, boldNamePlugin, niceSizePlugin ])\n```\n\n## API\n\n### Externr(extensionPoints)\nCreates a new `Externr` object that can deal with the specified extension points. The extension points are defined in a plain object with arrays of strings on any, or all, of the following types:\n\n**<code>'extend'</code>**: a simple single-property extension. Our `getName()` method above is an example of this; it returns a single property (the `name`) but passing it through our `Externr` instance lets plugins mutate the value, or even replace it completely. Plugin functions take the form of `function (arg) { return arg /* or something else */ }`.\n\nYour internal code simply passes the default property through its `Externr` instance, as in our example: `return this._externs.getName(this.name)`.\n\nPlugin functions for this type simply take an argument and return a value.\n\n**<code>'extendReverse'</code>**: the same as `'extend'` but if multiple plugins are registered then they are processed in reverse order. Handy if you have properties that come *in* to your API and properties that go *out* so you want plugins to be applied in the opposite order for both.\n\n**<code>'wrap'</code>**: more complex multi-argument and/or async calls. When you pass a call through an `Externr` instance you have to provide it with a context to bind function calls to (usually `this`), an array of arguments (can be an empty array if that makes sense for your API) and a final callback function that provides the default behaviour.\n\nAs in our example:\n\n```js\nthis._externs.fsize(\n    this                              // the `this` property for function calls\n  , [ path, stat, callback ]          // array of initial arguments\n  , function (path, stat, callback) { /* default behaviour callback */ }\n)\n```\n\nPlugin calls look just like the default behaviour callback *except* they have an additional `next()` function:\n\n```js\nfunction (path, stat, callback, next) {\n  stat.size = (Math.round(stat.size / 1024 * 10) / 10) + ' kB' // do something\n  next(path, stat, callback) // defer to the next plugin or default callback\n}\n```\n\nThe `next()` function *must* be called with the correct number of arguments, although they need not be the same objects as it were passed (for example you could replace the `callback` function if you wanted to be clever).\n\nAdditionally, you don't have to even call `next()` if your plugin decides that the default behaviour is not desirable. Since plugins have access to the `this` that you provide it (usually the actual parent object being operated on), your plugins can divert calls from one part of your API to another. For example a `put()` call may be diverted to a `batch()` call along with additional entries for your database.\n\n<code>'wrapReverse'</code> is the same as `'wrap'` but the plugins are applied in reverse order.\n\n### externr.$register(plugin)\nOnce you have an `Externr` object, it will have a `.$register()` method that will allow plugins to be injected into it. The `plugin` argument can be a single plugin object or an array of plugin objects.\n\nYou an expose this directly to your API users (like we have done with a `use()` method in our example) but you need to make sure it's bound to the `Externr` instance, i.e. usually something like this `this.use = this._externs.$register.bind(this._externs)`.\n\n**Note that plugins can implement any number of the available extension points**, they just need to provide the right keys on the object passed in to `Externr`.\n\n## Licence\n\nExternr is Copyright (c) 2014 Rod Vagg [@rvagg](https://twitter.com/rvagg) and licensed under the MIT licence. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE.md file for more details.\n","_attachments":{},"homepage":"https://github.com/rvagg/externr","bugs":{"url":"https://github.com/rvagg/externr/issues"},"license":"MIT"}