Comparing version 0.0.1 to 1.0.0
67
index.js
@@ -1,4 +0,3 @@ | ||
/***@@@ BEGIN LICENSE @@@***/ | ||
/*───────────────────────────────────────────────────────────────────────────*\ | ||
│ Copyright (C) 2013 eBay Software Foundation │ | ||
│ Copyright (C) 2014 eBay Software Foundation │ | ||
│ │ | ||
@@ -19,32 +18,27 @@ │hh ,'""`. │ | ||
\*───────────────────────────────────────────────────────────────────────────*/ | ||
/***@@@ END LICENSE @@@***/ | ||
'use strict'; | ||
var fs = require('fs'), | ||
path = require('path'), | ||
resolver = require('./lib/resolver'); | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var resolver = require('./lib/resolver'); | ||
exports.create = function (parent) { | ||
function isModule(file) { | ||
// require.resolve will locate a file without a known extension (e.g. txt) | ||
// and try to load it as javascript. That won't work for this case. | ||
var ext = path.extname(file); | ||
return ext === '' || require.extensions.hasOwnProperty(ext); | ||
} | ||
exports.create = function create(parent) { | ||
return Object.create(resolver.create(parent), { | ||
resolveFile: { | ||
value: function (file, callback) { | ||
var self, ext; | ||
value: function resolveFile(file, callback) { | ||
var resolve = this.resolve.bind(this); | ||
self = this; | ||
function done(err, data) { | ||
if (err) { | ||
callback(err); | ||
return; | ||
} | ||
callback(null, self.resolve(data)); | ||
} | ||
// Short circuit file types node can handle natively. | ||
ext = path.extname(file); | ||
if (ext === '' || require.extensions.hasOwnProperty(ext)) { | ||
process.nextTick(done.bind(undefined, null, require(file))); | ||
if (isModule(file)) { | ||
resolve(require(file), callback); | ||
return; | ||
@@ -54,6 +48,4 @@ } | ||
fs.readFile(file, 'utf8', function (err, data) { | ||
var json, error; | ||
if (err) { | ||
done(err); | ||
callback(err); | ||
return; | ||
@@ -63,28 +55,9 @@ } | ||
try { | ||
json = JSON.parse(data); | ||
error = null; | ||
data = JSON.parse(data); | ||
resolve(data, callback); | ||
} catch (err) { | ||
json = undefined; | ||
error = err; | ||
} finally { | ||
done(error, json); | ||
callback(err); | ||
} | ||
}); | ||
} | ||
}, | ||
resolveFileSync: { | ||
value: function (file) { | ||
var data, ext; | ||
ext = path.extname(file); | ||
if (ext === '' || require.extensions.hasOwnProperty(ext)) { | ||
return this.resolve(require(file)); | ||
} | ||
data = fs.readFileSync(file, 'utf8'); | ||
data = JSON.parse(data); | ||
return this.resolve(data); | ||
} | ||
} | ||
@@ -91,0 +64,0 @@ |
@@ -1,4 +0,3 @@ | ||
/***@@@ BEGIN LICENSE @@@***/ | ||
/*───────────────────────────────────────────────────────────────────────────*\ | ||
│ Copyright (C) 2013 eBay Software Foundation │ | ||
│ Copyright (C) 2014 eBay Software Foundation │ | ||
│ │ | ||
@@ -19,22 +18,88 @@ │hh ,'""`. │ | ||
\*───────────────────────────────────────────────────────────────────────────*/ | ||
/***@@@ END LICENSE @@@***/ | ||
'use strict'; | ||
var async = require('async'); | ||
var thing = require('core-util-is'); | ||
exports.create = function (parent) { | ||
exports.create = function create(parent) { | ||
return { | ||
_parent: parent, | ||
parent: parent, | ||
_handlers: Object.create(null), | ||
use: function (protocol, impl) { | ||
var handlers, handler, index, removed; | ||
/** | ||
* Locates a handler for the provided value, searching the parent, if necessary | ||
* @param value the value to match | ||
* @returns {Object} the handler, if found, otherwise undefined. | ||
*/ | ||
getHandler: function getHandler(value) { | ||
var resolver, handler; | ||
resolver = this; | ||
handler = undefined; | ||
while (!handler && resolver && resolver._handlers) { | ||
Object.keys(resolver._handlers).some(function (protocol) { | ||
var current = resolver._handlers[protocol]; | ||
// Test the value to see if this is the appropriate handler. | ||
if (current.predicate(value)) { | ||
handler = current; | ||
return true; | ||
} | ||
return false; | ||
}); | ||
// Move to the parent | ||
resolver = resolver.parent; | ||
} | ||
return handler; | ||
}, | ||
/** | ||
* Returns the handler stack for a given protocol, including parent handlers | ||
* @param protocol | ||
* @returns [] | ||
*/ | ||
getStack: function getStack(protocol) { | ||
var currentStack, parentStack, hasParent; | ||
currentStack = this._handlers[protocol] && this._handlers[protocol].stack; | ||
parentStack = this.parent && this.parent.getStack(protocol); | ||
hasParent = parentStack && parentStack.length; | ||
if (currentStack && hasParent) { | ||
return currentStack.concat(parentStack); | ||
} | ||
if (hasParent) { | ||
return parentStack; | ||
} | ||
return currentStack; | ||
}, | ||
/** | ||
* Register a given handler for the provided protocol. | ||
* @param protocol the protocol for which the handler should be registered. | ||
* @param impl the handler function with the signature `function (input, [fn])` | ||
* @returns {Function} invoke to remove the registered handler from the stack | ||
*/ | ||
use: function use(protocol, impl) { | ||
var handlers, handler, removed; | ||
handlers = this._handlers; | ||
handler = handlers[protocol]; | ||
if(!handler) { | ||
if (!handler) { | ||
handler = handlers[protocol] = { | ||
@@ -55,66 +120,93 @@ | ||
index = handler.stack.push(impl); | ||
handler.stack.push(impl); | ||
removed = false; | ||
// Unuse | ||
return function () { | ||
return function unuse() { | ||
var idx; | ||
if (!removed) { | ||
removed = true; | ||
return handler.stack.splice(index - 1, 1)[0]; | ||
idx = handler.stack.indexOf(impl); | ||
return handler.stack.splice(idx, 1)[0]; | ||
} | ||
return undefined; | ||
} | ||
}; | ||
}, | ||
getStack: function (protocol) { | ||
var current, parent, hasParent; | ||
current = this._handlers[protocol] && this._handlers[protocol].stack; | ||
parent = this._parent && this._parent.getStack(protocol); | ||
hasParent = parent && parent.length; | ||
/** | ||
* Resolves all the protocols contained in the provided object. | ||
* @param data The data structure to scan | ||
* @param callback the callback to invoke when processing is complete with the signature `function (err, data)` | ||
*/ | ||
resolve: function resolve(data, callback) { | ||
var self, tasks, handler; | ||
if (current && hasParent) { | ||
return current.concat(parent); | ||
} | ||
self = this; | ||
if (hasParent) { | ||
return parent; | ||
} | ||
if (thing.isObject(data)) { | ||
return current; | ||
}, | ||
if (thing.isArray(data)) { | ||
tasks = data.map(function (val) { | ||
return resolve.bind(self, val); | ||
}); | ||
} else { | ||
tasks = {}; | ||
Object.keys(data).forEach(function (key) { | ||
tasks[key] = resolve.bind(self, data[key]); | ||
}); | ||
} | ||
resolve: function resolve(src) { | ||
var dest, handlers; | ||
async.parallel(tasks, function (err, data) { | ||
err ? callback(err) : callback(null, data); | ||
}); | ||
dest = src; | ||
} else if (thing.isString(data)) { | ||
if (typeof src === 'object' && src !== null) { | ||
tasks = []; | ||
dest = (Array.isArray(src) ? [] : Object.create(Object.getPrototypeOf(src))); | ||
Object.keys(src).forEach(function (key) { | ||
dest[key] = this.resolve(src[key]); | ||
}, this); | ||
handler = this.getHandler(data); | ||
if (!handler) { | ||
setImmediate(callback.bind(null, null, data)); | ||
return; | ||
} | ||
} else if (typeof src === 'string') { | ||
// Remove protocol prefix | ||
data = data.slice(handler.protocol.length + 1); | ||
handlers = this._handlers; | ||
Object.keys(handlers).forEach(function (protocol) { | ||
var handler = handlers[protocol]; | ||
tasks = self.getStack(handler.protocol).map(function (handler) { | ||
if (handler.length < 2) { | ||
return function wrapper(input, done) { | ||
var data, error; | ||
if (handler.predicate(src)) { | ||
// run through stack and mutate | ||
dest = src.slice(protocol.length + 1); | ||
this.getStack(protocol).forEach(function (handler) { | ||
dest = handler(dest); | ||
}); | ||
try { | ||
data = handler(input); | ||
} catch (err) { | ||
error = err; | ||
} | ||
done(error, data); | ||
}; | ||
} | ||
}, this); | ||
return handler; | ||
}); | ||
tasks.unshift(function init(done) { | ||
done(null, data); | ||
}); | ||
// Waterfall will *always* resolve asynchronously | ||
async.waterfall(tasks, callback); | ||
} else { | ||
// Non-protocol-able value | ||
callback(null, data); | ||
} | ||
return dest; | ||
} | ||
}; | ||
}; | ||
}; |
@@ -1,4 +0,3 @@ | ||
/***@@@ BEGIN LICENSE @@@***/ | ||
/*───────────────────────────────────────────────────────────────────────────*\ | ||
│ Copyright (C) 2013 eBay Software Foundation │ | ||
│ Copyright (C) 2014 eBay Software Foundation │ | ||
│ │ | ||
@@ -18,3 +17,2 @@ │hh ,'""`. │ | ||
│ limitations under the License. │ | ||
\*───────────────────────────────────────────────────────────────────────────*/ | ||
/***@@@ END LICENSE @@@***/ | ||
\*───────────────────────────────────────────────────────────────────────────*/ |
{ | ||
"name": "shortstop", | ||
"version": "0.0.1", | ||
"version": "1.0.0", | ||
"description": "Enable use of protocols (such as file:, buffer:, or method:) in configuration files.", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "make test" | ||
"test": "tape test/*.js", | ||
"cover": "istanbul cover tape -- test/*.js", | ||
"lint": "jshint -c .jshintrc index.js" | ||
}, | ||
@@ -29,5 +31,10 @@ "repository": { | ||
"devDependencies": { | ||
"mocha": "~1.13.0", | ||
"chai": "~1.8.0" | ||
"tape": "~2.5.0", | ||
"istanbul": "~0.2.4", | ||
"jshint": "~2.4.4" | ||
}, | ||
"dependencies": { | ||
"core-util-is": "~1.0.1", | ||
"async": "~0.2.10" | ||
} | ||
} |
178
README.md
@@ -1,5 +0,3 @@ | ||
shortstop | ||
========= | ||
# shortstop | ||
Sometimes JSON just isn't enough for configuration needs. Occasionally it would be nice to use arbitrary types as values, | ||
@@ -9,6 +7,19 @@ but JSON is necessarily a subset of all available JS types. `shortstop` enables the use of protocols and handlers to | ||
#### The Basics | ||
[![Build Status](https://travis-ci.org/paypal/shortstop-handlers.png?branch=master)](https://travis-ci.org/paypal/shortstop-handlers) | ||
```json | ||
{ | ||
```javascript | ||
var fs = require('fs'); | ||
var shortstop = require('shortstop'); | ||
function buffer(value) { | ||
return new Buffer(value); | ||
} | ||
var resolver, json; | ||
resolver = shortstop.create(); | ||
resolver.use('buffer', buffer); | ||
resolver.use('file', fs.readFile); | ||
json = { | ||
"secret": "buffer:SGVsbG8sIHdvcmxkIQ==", | ||
@@ -19,56 +30,52 @@ "ssl": { | ||
} | ||
} | ||
}; | ||
resolver.resolve(json, function (err, data) { | ||
console.log(data); | ||
// { | ||
// "secret": <Buffer ... >, | ||
// "ssl" { | ||
// "pfx": <Buffer ... >, | ||
// "key": <Buffer ... > | ||
// } | ||
// } | ||
}); | ||
``` | ||
```javascript | ||
var fs = require('fs'), | ||
shortstop = require('shortstop'); | ||
## API | ||
### shortstop.create([parent]); | ||
* `parent` (*Object*, optional) - An optional shortstop resolver. Returns a resolver instance. | ||
function buffer(value) { | ||
return new Buffer(value); | ||
} | ||
### resolver.use(protocol, handler); | ||
function file(value) { | ||
return fs.readFileSync(value); | ||
} | ||
* `protocol` (*String*) - The protocol used to identify a property to be processed, e.g. "file" | ||
* `handler` (*Function*) - The implementation of the given protocol with signature `function (value, [callback])` | ||
This method returns a function when invoked will remove the handler from the stack for this protocol. | ||
var resolver, data; | ||
resolver = shortstop.create(); | ||
resolver.use('buffer', buffer); | ||
resolver.use('file', file); | ||
data = resolver.resolve(json); | ||
### resolver.resolve(data, callback); | ||
// { | ||
// "secret": <Buffer ... >, | ||
// "ssl" { | ||
// "pfx": <Buffer ... >, | ||
// "key": <Buffer ... > | ||
// } | ||
// } | ||
* `data` (*Object*) - The object, containing protocols in values, to be processed. | ||
* `callback` (*Function*) - The callback invoked when the processing is complete with signature `function (err, result)`. | ||
``` | ||
### resolver.resolveFile(path, callback); | ||
#### Multiple handlers | ||
* `path` (*String*) - The path to a file which is, or exports, JSON or a javascript object. | ||
* `callback` (*Function*) - The callback invoked when the processing is complete with signature `function (err, result)`. | ||
## Multiple handlers | ||
Multiple handlers can be registered for a given protocol. They will be executed in the order registered and the output | ||
of one handler will be the input of the next handler in the chain. | ||
```json | ||
{ | ||
"key": "file:foo/baz.key", | ||
"certs": "path:certs/myapp" | ||
} | ||
``` | ||
```javascript | ||
var fs = require('fs'), | ||
path = require('path'), | ||
shortstop = require('shortstop'); | ||
var path = require('path'), | ||
var shortstop = require('shortstop'); | ||
function path(value) { | ||
function resolve(value) { | ||
if (path.resolve(value) === value) { | ||
@@ -78,54 +85,37 @@ // Is absolute path already | ||
} | ||
return path.join(process.cwd(), value; | ||
return path.join(process.cwd(), value); | ||
} | ||
function file(value) { | ||
return fs.readFileSync(value); | ||
} | ||
var resolver, data; | ||
var resolver, json; | ||
resolver = shortstop.create(); | ||
resolver.use('path', path); | ||
resolver.use('path', resolve); | ||
resolver.use('file', resolve); | ||
resolver.use('file', fs.readFile); | ||
resolver.use('file', path); | ||
resolver.use('file', file); | ||
json = { | ||
"key": "file:foo/baz.key", | ||
"certs": "path:certs/myapp" | ||
}; | ||
data = resolver.resolve(json); | ||
// { | ||
// "key": <Buffer ... >, | ||
// "certs": "/path/to/my/certs/myapp" | ||
// } | ||
resolver.resolve(json, function (err, data) { | ||
console.log(data); | ||
// { | ||
// "key": <Buffer ... >, | ||
// "certs": "/path/to/my/certs/myapp" | ||
// } | ||
}); | ||
``` | ||
#### Removing Handlers | ||
## Removing Handlers | ||
When registered, handlers return an `unregister` function you can call when you no longer want a handler in the chain. | ||
```js | ||
// json1 | ||
{ | ||
"key": "path:foo/baz.key" | ||
} | ||
``` | ||
```js | ||
// json2 | ||
{ | ||
"key": "path:foo/bar.key" | ||
} | ||
``` | ||
```javascript | ||
var fs = require('fs'), | ||
path = require('path'), | ||
shortstop = require('shortstop'); | ||
var path = require('path'); | ||
var shortstop = require('shortstop'); | ||
function path(value) { | ||
function resolve(value) { | ||
if (path.resolve(value) === value) { | ||
@@ -135,23 +125,25 @@ // Is absolute path already | ||
} | ||
return path.join(process.cwd(), value; | ||
return path.join(process.cwd(), value); | ||
} | ||
var resolver, unuse, data; | ||
var resolver, unuse, json; | ||
resolver = shortstop.create(); | ||
unuse = resolver.use('path', path); | ||
data = resolver.resolve(json1); | ||
unuse = resolver.use('path', resolve); | ||
json = { "key": "path:foo/baz.key" }; | ||
// { | ||
// "key": "/path/to/my/foo/baz.key" | ||
// } | ||
resolver.resolve(json, function (err, data) { | ||
console.log(data); | ||
// { | ||
// "key": "/path/to/my/foo/baz.key" | ||
// } | ||
unuse(); | ||
unuse(); | ||
data = resolver.resolve(json2); | ||
// { | ||
// "key": "path:foo/bar.key" | ||
// } | ||
resolver.resolve(json, function (err, data) { | ||
console.log(data); | ||
// { | ||
// "key": "path:foo/baz.key" | ||
// } | ||
}); | ||
}); | ||
``` |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
18870
10
206
1
2
2
3
145
1
+ Addedasync@~0.2.10
+ Addedcore-util-is@~1.0.1
+ Addedasync@0.2.10(transitive)
+ Addedcore-util-is@1.0.3(transitive)