Skip to content

Commit

Permalink
Merge pull request #13 from paypal/async
Browse files Browse the repository at this point in the history
Modify API to be asynchronous
  • Loading branch information
grawk committed Mar 14, 2014
2 parents f3bc8c9 + 6a26563 commit 3bb5528
Show file tree
Hide file tree
Showing 8 changed files with 430 additions and 416 deletions.
206 changes: 90 additions & 116 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,171 +1,145 @@
shortstop
=========

# shortstop

Sometimes JSON just isn't enough for configuration needs. Occasionally it would be nice to use arbitrary types as values,
but JSON is necessarily a subset of all available JS types. `shortstop` enables the use of protocols and handlers to
enable identification and special handling of json values.

#### The Basics
[![Build Status](https://travis-ci.org/paypal/shortstop-handlers.png?branch=master)](https://travis-ci.org/paypal/shortstop-handlers)

```javascript
var fs = require('fs');
var shortstop = require('shortstop');

function buffer(value) {
return new Buffer(value);
}


```json
{
var resolver, json;
resolver = shortstop.create();
resolver.use('buffer', buffer);
resolver.use('file', fs.readFile);

json = {
"secret": "buffer:SGVsbG8sIHdvcmxkIQ==",
"ssl": {
"pfx": "file:foo/bar",
"key": "file:foo/baz.key",
}
}
};

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);

* `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
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) {
// Is absolute path already
return value;
}

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('file', path);
resolver.use('file', file);
resolver.use('path', resolve);
resolver.use('file', resolve);
resolver.use('file', fs.readFile);

data = resolver.resolve(json);

// {
// "key": <Buffer ... >,
// "certs": "/path/to/my/certs/myapp"
// }
json = {
"key": "file:foo/baz.key",
"certs": "path: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) {
// Is absolute path already
return value;
}

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);

// {
// "key": "/path/to/my/foo/baz.key"
// }

unuse();

data = resolver.resolve(json2);

// {
// "key": "path:foo/bar.key"
// }
```
#### Protocols
Protocols can be chained, using the following format.
`<protocol>:<value>|<protocol>:<value>`
If available the previous protocol's value is passed along to the next protocol's handlers.
```javascript
function mycustomhandler(value, previousValue) {

// do something with previous value

return previousValue + ' ' + value;

}
```
unuse = resolver.use('path', resolve);
json = { "key": "path:foo/baz.key" };

resolver.resolve(json, function (err, data) {
console.log(data);
// {
// "key": "/path/to/my/foo/baz.key"
// }

unuse();

resolver.resolve(json, function (err, data) {
console.log(data);
// {
// "key": "path:foo/baz.key"
// }
});
});
```
63 changes: 19 additions & 44 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,71 +17,46 @@
\*───────────────────────────────────────────────────────────────────────────*/
'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;

self = this;
function done(err, data) {
if (err) {
callback(err);
return;
}
callback(null, self.resolve(data));
}
value: function resolveFile(file, callback) {
var resolve = this.resolve.bind(this);

// 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;
}

fs.readFile(file, 'utf8', function (err, data) {
var json, error;

if (err) {
done(err);
callback(err);
return;
}

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);
}
}

});
Expand Down
Loading

0 comments on commit 3bb5528

Please sign in to comment.