gestalt
Advanced tools
Comparing version 0.0.8 to 0.0.9
@@ -6,3 +6,4 @@ var gestalt = require('../lib/gestalt'); | ||
var cf = new ConfigFile({ source: config_yaml, format: 'yaml' } ); | ||
var cf = new ConfigFile({ source: config_yaml } ); | ||
cf.on('state', function(state_change ) { | ||
@@ -12,5 +13,5 @@ if( state_change.state == 'invalid' ) { | ||
} else if ( state_change.state == 'ready' ) { | ||
console.log( cf.get('new:foo') ); | ||
cf.report() | ||
} | ||
}); | ||
var EventEmitter = require('events').EventEmitter, | ||
util = require('util'), | ||
_ = require('underscore'); | ||
_ = require('underscore'), | ||
fs = require('fs'); | ||
@@ -454,6 +455,8 @@ | ||
p.report = function () { | ||
p.report = function ( stream ) { | ||
stream = stream || process.stdout; | ||
var self = this; | ||
keys = []; | ||
console.log(); | ||
stream.write("\n"); | ||
_.each( this.keys().sort(), function(key) { | ||
@@ -466,3 +469,3 @@ var path = self._get_path_(key); | ||
for(; i<path.length-1;++i) { | ||
console.log( indent + path[i] + ":" ); | ||
stream.write( indent + path[i] + ":\n" ); | ||
indent += " "; | ||
@@ -484,3 +487,3 @@ } | ||
} | ||
console.log(out.join(''), source); | ||
stream.write( out.join('') + source + "\n"); | ||
keys = path; | ||
@@ -534,1 +537,21 @@ }); | ||
}; | ||
p.writeFile = function( filename, format ) { | ||
var ws = fs.createWriteStream( filename ); | ||
var contents; | ||
// TODO - default format from filename | ||
if( ! format ) { | ||
format = 'json'; | ||
} | ||
if( _.isFunction(format) ) { | ||
ws.write( format( this.toObject ) ); | ||
} else if( format == 'yaml' ) { | ||
this.report(ws); | ||
} else if( format == 'json' ) { | ||
ws.write( JSON.stringify( this.toObject() ) ); | ||
} | ||
ws.end(); | ||
} |
@@ -18,3 +18,17 @@ var util = require('util'), | ||
this._contents_ = []; | ||
this._cache_ = {}; | ||
this.state( config.state() ); | ||
var self = this; | ||
this.on('change', function(change) { | ||
if( ! _.isUndefined( change.value ) ) { | ||
self._cache_[change.name] = { value: change.value, source: change.source } | ||
} else { | ||
delete self._cache_[change.name]; | ||
} | ||
}); | ||
this.addOverride( config ); | ||
@@ -44,10 +58,23 @@ | ||
p.get = function(name) { | ||
var results; | ||
// Find the first config on the stack with a defined value | ||
// for the name, setting the value as a side effect. | ||
_.find( this._contents_, function(c) { | ||
results = c.get(name); | ||
return( ! _.isUndefined( results ) ); | ||
}); | ||
return results; | ||
// if name is an array, reassemble it | ||
if(_.isArray(name) ) { | ||
name = name.join(':'); | ||
} | ||
var vs = this._cache_[name]; | ||
if( vs ) { | ||
return vs.value; | ||
} else { | ||
return undefined; | ||
} | ||
//// Find the first config on the stack with a defined value | ||
//// for the name, setting the value as a side effect. | ||
//_.find( this._contents_, function(c) { | ||
// results = c.get(name); | ||
// return( ! _.isUndefined( results ) ); | ||
//}); | ||
//return results; | ||
}; | ||
@@ -91,18 +118,24 @@ | ||
p.getValSource = function(name) { | ||
var results; | ||
// same pattern as get | ||
_.find( this._contents_, function(c) { | ||
results = c.getValSource(name); | ||
return( ! _.isUndefined( results ) ); | ||
}); | ||
return results; | ||
if( _.isArray(name) ) { | ||
name = name.join(':'); | ||
} | ||
return this._cache_[name]; | ||
// var results; | ||
// // same pattern as get | ||
// _.find( this._contents_, function(c) { | ||
// results = c.getValSource(name); | ||
// return( ! _.isUndefined( results ) ); | ||
// }); | ||
// return results; | ||
}; | ||
p.keys = function() { | ||
var keys = []; | ||
//var keys = []; | ||
// Take the union of all of the keys | ||
_.each( this._contents_, function(c) { | ||
keys = _.union( keys, c.keys() ); | ||
}); | ||
return keys; | ||
//_.each( this._contents_, function(c) { | ||
// keys = _.union( keys, c.keys() ); | ||
//}); | ||
//return keys; | ||
return _.keys( this._cache_ ); | ||
}; | ||
@@ -109,0 +142,0 @@ |
@@ -6,2 +6,3 @@ var fs = require('fs'), | ||
util = require('util'), | ||
watchr = require('watchr'), | ||
_ = require('underscore'); //jslint ignore | ||
@@ -18,2 +19,16 @@ /* | ||
function detectFormat(filename) { | ||
var m = filename.match(/\.([^\.]+)$/); | ||
if( ! m ) { | ||
return undefined; | ||
} | ||
return { | ||
ini: 'ini', | ||
yaml: 'yaml', | ||
js: 'json', | ||
json: 'json' | ||
}[ m[1].toLowerCase() ]; | ||
} | ||
function readFile() { | ||
@@ -66,30 +81,39 @@ var self = this; | ||
options = _.extend( { initial_state: 'not ready' }, options ); | ||
options = _.extend( { format: 'auto', initial_state: 'not ready' }, options ); | ||
ConfigFile.super_.call(this,options); | ||
format = this._options_.format; | ||
if( format == 'auto' ) { | ||
format = detectFormat( this._source_ ); | ||
} | ||
if( format ) { | ||
this._options_.parser = this._options_.parser || parsers[ format ]; | ||
if( format != 'raw' ) { | ||
this._options_.parser = this._options_.parser || parsers[ format ]; | ||
} else { | ||
this._options_parser = parsers[ 'rawFile' ]; | ||
} | ||
} | ||
//console.log( "options: ", this._options_ ); | ||
this.readFile(); | ||
if( this._options_.watch ) { | ||
if( _.isFunction(fs.watch) ) { // modern node behavior | ||
fs.watch( this._source_, {persistent: false}, function(event,f) { | ||
if( event == 'change' ) { | ||
self.readFile(); | ||
} | ||
// TODO: deal with "rename" | ||
if( this._options_.watch ) { | ||
this._watchr_ = watchr.watch( | ||
{ path: this._source_, | ||
listener: function(event, file, stat, ostat) { | ||
if( event == 'change' ) { | ||
self.readFile(); | ||
} else if (event == 'unlink' ) { | ||
self.state('invalid', 'backing file deleted;'); | ||
} | ||
} | ||
}); | ||
} else if( _.isFunction(fs.watchFile) ) { // older behavior | ||
fs.watchFile( this._source_, {persistent: false, interval: 100}, function(curr,prev) { | ||
self.readFile(); | ||
}); | ||
} | ||
} | ||
} | ||
util.inherits(ConfigFile, config.Configuration); | ||
ConfigFile.prototype.readFile = readFile; | ||
exports.ConfigFile = ConfigFile; |
@@ -0,1 +1,3 @@ | ||
var _=require('underscore'); | ||
function parseYaml(data) { | ||
@@ -19,2 +21,22 @@ return require('js-yaml').load(data.toString()); | ||
function rawFile(data) { | ||
return { contents: data.toString() } | ||
} | ||
// this one works for many standard, unstructures unix config files: | ||
function config(data) { | ||
var object = {}; | ||
var lines = data.toString().replace(/#.*\n/g, '\n').replace(/(\n)+/g,'\n').split(/\n/); | ||
_.each( lines, function( string ) { | ||
var match = string.match(/\s*(\w+)\s*=\s*(.*?)\s*$/); | ||
if(match) { | ||
object[ match[1] ] = match[2]; | ||
} | ||
}); | ||
return object; | ||
} | ||
var parsers = { | ||
@@ -25,5 +47,6 @@ 'json': JSON.parse, | ||
'null': nullParse, | ||
'raw': raw | ||
'raw': raw, | ||
'config': config | ||
} | ||
exports.parsers = parsers; |
@@ -12,10 +12,22 @@ var parsers = require('./format').parsers, | ||
this._reverse_ = undefined; | ||
if( _.isFunction(this._options_.mapper) ) { | ||
this._mapper_ = this._options_.mapper; | ||
this._mapper_ = this._options_.mapper; | ||
} else { | ||
// Build a function to dereference an object | ||
var m = this._options_.mapper; | ||
this._mapper_ = function(old) { return m[old]; }; | ||
var rev = {}; | ||
_.each(m, function(value, index) { | ||
rev[value] = index; | ||
}); | ||
this._mapper_ = function(old) { return m[old]; }; | ||
this._reverse_ = function(nuw) { return rev[nuw]; }; | ||
} | ||
if( _.isFunction( this._options_.reverse) ) { | ||
this._reverse_ = this._options_.reverse; | ||
} | ||
var org = this._org_ = this._options_.config; | ||
@@ -59,2 +71,5 @@ this._cache_ = {forward: {}, reverse: {}}; | ||
p.map_old_name = function(old_name) { | ||
if( _.isArray( old_name ) ) | ||
old_name = old_name.join(':'); | ||
var forward = this._cache_.forward; | ||
@@ -76,2 +91,6 @@ var reverse = this._cache_.reverse; | ||
p.map_new_name = function(new_name) { | ||
if( _.isArray( new_name ) ) | ||
new_name = new_name.join(':'); | ||
var forward = this._cache_.forward; | ||
var reverse = this._cache_.reverse; | ||
@@ -81,5 +100,11 @@ | ||
return reverse[new_name]; | ||
} else { | ||
return undefined; | ||
} | ||
if( this._reverse_ ) { | ||
var old_name = this._reverse_(new_name); | ||
reverse[new_name] = old_name; | ||
if( old_name ) { | ||
forward[old_name] = new_name; | ||
} | ||
} | ||
return undefined; | ||
} | ||
@@ -103,14 +128,11 @@ | ||
p.remove = function(name) { | ||
// remapped configs are read only | ||
// this._org_.remove( this.map_new_name(name)); | ||
this._org_.remove( this.map_new_name(name)); | ||
}; | ||
p.update = function(name,value,source) { | ||
// remapped objects are read only | ||
// this._org_.set( this.map_new_name(name), value, source); | ||
this._org_.set( this.map_new_name(name), value, source); | ||
}; | ||
p.set = function(name,value,source) { | ||
// remapped objects are read only | ||
// this._org_.set( this.map_new_name(name), value, source); | ||
this._org_.set( this.map_new_name(name), value, source); | ||
}; | ||
@@ -117,0 +139,0 @@ |
@@ -17,3 +17,5 @@ | ||
options = _.extend( { initial_state: 'not ready' }, options ); | ||
options = _.extend( { initial_state: 'not ready', | ||
include_stat: false, | ||
}, options ); | ||
ZookeeperConfig.super_.call(this, options); | ||
@@ -78,2 +80,5 @@ | ||
self.set('data', object, self._source_ + "&version=" + stat.version ); | ||
if( self._options_.include_stat ) { | ||
self.set('stat', stat, self._source_ ); | ||
} | ||
// Tell the world we are ready | ||
@@ -80,0 +85,0 @@ self.state('ready'); |
{ | ||
"name": "gestalt", | ||
"version": "0.0.8", | ||
"version": "0.0.9", | ||
"author": "Chris Howe <chris@howeville.com>", | ||
@@ -21,3 +21,4 @@ "description": "Event driven configuration management.", | ||
"iniparser": ">= 1.0.0", | ||
"optimist": ">= 0.3.4" | ||
"optimist": ">= 0.3.4", | ||
"watchr": ">= 2.1.0" | ||
}, | ||
@@ -24,0 +25,0 @@ "devDependencies": { |
131
README.md
@@ -350,6 +350,15 @@ # gestalt | ||
- report( ) | ||
- report( stream ) | ||
Generates a detailed report (on console.log) of all of the names. | ||
Generates a detailed report on the given stream (or stdout) of all of | ||
the names and values in YAML format. | ||
- writeFile( filename, format ) | ||
Attempts to open a writeStream on the filename and writes the contents | ||
of the configuration object to the file in the specified format. If format | ||
is left out, 'json' is assumed. Valid options are 'json', 'yaml' and a | ||
function that takes a plain javascript object (the results of toObject() ) | ||
and returns a string or a buffer object. | ||
- toObject() | ||
@@ -448,5 +457,17 @@ | ||
Returns the value associated with the name of the highest priority object | ||
containing a defined value for the name. | ||
Returns the value associated with the name of the highest priority | ||
object containing a defined value for the name. Actually, the real | ||
contract is that the results of get will agree with the latest change | ||
event for the object, which is effectively the highest priority | ||
contained object's values, with the following caveat: unlike regular | ||
Configuration objects, it is not well defined what will happen if you | ||
get an intermediate Configuration value. | ||
```javascript | ||
var container = new gestalt.ConfigContainer(); | ||
container.set("a:b:c",7); | ||
container.get("a:b:c") // returns 7 | ||
container.get("a:b") // unspecified | ||
``` | ||
- set(name, value, source) | ||
@@ -525,2 +546,13 @@ | ||
-reverse (optional) | ||
Specifies how to map new names back into old names. The reverse | ||
function is calculated automatically if a structure mapper was | ||
used. Reverse and Mapper should be inverses of each other over the | ||
range of kkeys that are not mapped out of the model. If the reverse | ||
function is not given, set and update functions will only be able to | ||
modify existing values in the structure. With the reverse function, | ||
they will also be able to set new values and have them map back to | ||
the original structure. | ||
### Methods | ||
@@ -590,4 +622,14 @@ | ||
Tells what format the file is in. Current options are 'json', 'yaml' | ||
and 'ini'. | ||
and 'ini'. By default, the format will be 'auto', which will try to guess | ||
the format of the file based on its file extension. If you specify 'raw' | ||
as a format, the contents of the file will be added as a string to the | ||
'contents' name of the configuration object. | ||
- parser | ||
Normally, the format of the file determines what to use as a parser. This option | ||
can be used to override exactly how to turn the contents of a file into | ||
configuration. It should be a function that can accept the data from a file read | ||
and convert it into a raw javascript object. | ||
- source | ||
@@ -603,1 +645,80 @@ | ||
### ZookeeperConfig | ||
ZooKeeper (http://zookeeper.apache.org/) is "a centralized service for | ||
maintaining configuration information, naming, providing distributed | ||
synchronization, and providing group services". The ZookeeperConfig | ||
object assists in integrating zookeeper services into an overall configuration | ||
package. | ||
ZookeeperConfig does not really assist with writing information to zookeeper, | ||
only to reacting to information as it changes on the zookeeper servers. | ||
Much like the ConfigFile object, the ZookeeperConfig object is just a standard | ||
Configuration object with a couple of extra options and methods. | ||
This configuration object relies on the 'zookeeper' npm package. This dependency | ||
is not listed in the gestalt package dependencies, and is only required when | ||
a ZookeeperConfig object is first instantiated. | ||
Zookeeper has a slightly different idea about hierarchy from other | ||
configuration systems: in general every node can have both a value and | ||
children. ZookeeperConfig manages this by adding two sub-configuration | ||
objects to a configuration representing a a zookeeper node - one | ||
called 'data' and the other called 'children'. 'data' will contain | ||
whatever comes back from parsing the data in the zookeeper | ||
node. 'children' contains names corresponding to the relative names of the | ||
zookeeper nodes children and values of more ZookeeperConfig objects | ||
corresponding to the zookeeper child nodes. | ||
- constructor ZookeeperConfig( options ) | ||
The options are the same as for Configuration with the following additions: | ||
- source | ||
The source should be a string of the form 'zk://host1:port1,host2:port2/path/to/config'. | ||
- format | ||
What format is the data stored in on zookeeper nodes. 'raw' means that | ||
the data for a given node will be placed in a javascript string object and | ||
stored under the name "data" for that node. Other options are 'json', 'ini', | ||
and 'yaml'. | ||
- parser | ||
The same as for ConfigFile - you can provide your own parser. | ||
- zookeeper | ||
Use this option to hand off an existing zookeeper connection | ||
- zookeeper_options | ||
If the ZookeeperConfig object is to create its own zookeeper | ||
connection, these options will be passed to the constructor for | ||
ZooKeeper (from the npm package). | ||
- create_paths | ||
Boolean. If true, once connected to zookeeper, ZookeeperConfig will | ||
create the path of the zookeeper node it is trying to listen to, if | ||
it is not already there. | ||
- include_stat | ||
Boolean. If true, zookeeper nodes will include a 'stat' name in addition | ||
to 'data' and 'children'. The stat object will contain the stats reported | ||
by the node's data callback. | ||
#### Methods | ||
- zookeeper( function(zk) ) | ||
The callback will be called when a zookeeper connection becomes | ||
available, or immediately if it is already available. This method can | ||
be used to make zookeeper calls on the same connection that is being | ||
used by the ZookeeperConfig object. | ||
@@ -62,11 +62,17 @@ var vows = require('vows'), | ||
assert.equal( config.get("structured:d:e") , "f" ); | ||
}, | ||
'can be converted to a plain object': | ||
function(config) { | ||
var objVal = { a: "a", b: "b", c: [1,2,3], d: {e: "f"} }; | ||
config.set("object", { a: "a", b: "b", c: [1,2,3], d: {e: "f"} }); | ||
var objConfig = config.get("object"); | ||
var obj = objConfig.toObject(); | ||
assert.deepEqual( obj, objVal ); | ||
} | ||
}//, | ||
// 'can be converted to a plain object': | ||
// function(config) { | ||
// var objVal = { a: "a", b: "b", c: [1,2,3], d: {e: "f"} }; | ||
// config.set('a','a');/ | ||
// config.set('b','b'); | ||
// config.set('c',[1,2,3]); | ||
// config.set('d', {e:'f'} ); | ||
// | ||
// // config.set( { a: "a", b: "b", c: [1,2,3], d: {e: "f"} }); | ||
// // var objConfig = config.get("object"); | ||
// // var obj = objConfig.toObject(); | ||
// var obj = config.toObject(); | ||
// assert.deepEqual( obj, objVal ); | ||
// } | ||
}, | ||
@@ -73,0 +79,0 @@ 'configuration keys and each': { |
@@ -5,4 +5,4 @@ var vows = require('vows'), | ||
_ = require('underscore'), | ||
util = require('util'); | ||
util = require('util'), | ||
fs = require('fs'); | ||
var gestalt = require('../lib/gestalt'), | ||
@@ -22,9 +22,9 @@ Configuration = gestalt.Configuration, | ||
var config = new ConfigFile( {source: config_json, format: 'json'} ); | ||
config.on('state', function(state_change) { | ||
if( state_change.state == 'invalid' ) { | ||
promise.emit('failure', config); | ||
} else if ( state_change.state == 'ready' ) { | ||
promise.emit('success', config); | ||
} | ||
}); | ||
config.on('state', function(state_change) { | ||
if( state_change.state == 'invalid' ) { | ||
promise.emit('failure', config); | ||
} else if ( state_change.state == 'ready' ) { | ||
promise.emit('success', config); | ||
} | ||
}); | ||
return promise; | ||
@@ -38,2 +38,23 @@ }, | ||
}, | ||
"json file, auto": { | ||
topic: function() { | ||
var promise = new EventEmitter(); | ||
var config_json = require.resolve('./files/config.json'); | ||
var config = new ConfigFile( {source: config_json } ); | ||
config.on('state', function(state_change) { | ||
if( state_change.state == 'invalid' ) { | ||
promise.emit('failure', config); | ||
} else if ( state_change.state == 'ready' ) { | ||
promise.emit('success', config); | ||
} | ||
}); | ||
return promise; | ||
}, | ||
'loads properly': function(config) { | ||
assert.instanceOf( config, Configuration); | ||
assert.instanceOf( config, ConfigFile ); | ||
assert.deepEqual( config.toObject(), { "test1": "a", "test2": "b", "test3": {"c": "d", "e": ["f","g","h"] } , "test4": "i" } ); | ||
} | ||
}, | ||
"yaml file": { | ||
@@ -44,9 +65,9 @@ topic: function() { | ||
var config = new ConfigFile( {source: config_yaml, format: 'yaml'} ); | ||
config.on('state', function(state_change) { | ||
if( state_change.state == 'invalid' ) { | ||
promise.emit('failure', config); | ||
} else if ( state_change.state == 'ready' ) { | ||
promise.emit('success', config); | ||
} | ||
}); | ||
config.on('state', function(state_change) { | ||
if( state_change.state == 'invalid' ) { | ||
promise.emit('failure', config); | ||
} else if ( state_change.state == 'ready' ) { | ||
promise.emit('success', config); | ||
} | ||
}); | ||
return promise; | ||
@@ -62,2 +83,25 @@ }, | ||
}, | ||
"yaml file, auto": { | ||
topic: function() { | ||
var promise = new EventEmitter(); | ||
var config_yaml = require.resolve('./files/config.yaml'); | ||
var config = new ConfigFile( {source: config_yaml } ); | ||
config.on('state', function(state_change) { | ||
if( state_change.state == 'invalid' ) { | ||
promise.emit('failure', config); | ||
} else if ( state_change.state == 'ready' ) { | ||
promise.emit('success', config); | ||
} | ||
}); | ||
return promise; | ||
}, | ||
'loads properly': function(config) { | ||
assert.instanceOf( config, Configuration); | ||
assert.instanceOf( config, ConfigFile ); | ||
var contents = { test: [ {one:1, two:2, three:3}, {four:2, five:5,six:6}, {a: 'b', c: 'asdfasd' } ], | ||
aaa: { bbb: { ccc: 'a' } } }; | ||
assert.deepEqual( config.toObject(), contents ); | ||
} | ||
}, | ||
"ini file": { | ||
@@ -68,9 +112,9 @@ topic: function() { | ||
var config = new ConfigFile( { source: config_ini, format: 'ini'} ); | ||
config.on('state', function(state_change) { | ||
if( state_change.state == 'invalid' ) { | ||
promise.emit('failure', config); | ||
} else if ( state_change.state == 'ready' ) { | ||
promise.emit('success', config); | ||
} | ||
}); | ||
config.on('state', function(state_change) { | ||
if( state_change.state == 'invalid' ) { | ||
promise.emit('failure', config); | ||
} else if ( state_change.state == 'ready' ) { | ||
promise.emit('success', config); | ||
} | ||
}); | ||
@@ -86,4 +130,197 @@ //config.emit('loaded'); | ||
} | ||
} //, | ||
}, | ||
"ini file, auto": { | ||
topic: function() { | ||
var promise = new EventEmitter(); | ||
var config_ini = require.resolve('./files/config.ini'); | ||
var config = new ConfigFile( { source: config_ini } ); | ||
config.on('state', function(state_change) { | ||
if( state_change.state == 'invalid' ) { | ||
promise.emit('failure', config); | ||
} else if ( state_change.state == 'ready' ) { | ||
promise.emit('success', config); | ||
} | ||
}); | ||
//config.emit('loaded'); | ||
return promise; | ||
}, | ||
'loads properly': function(config) { | ||
assert.instanceOf( config, Configuration); | ||
assert.instanceOf( config, ConfigFile ); | ||
var contents = { top: 'level', one: {stuff: 'more stuff'}, two: {x: 'y'} }; | ||
assert.deepEqual( config.toObject(), contents ); | ||
} | ||
}, | ||
"json file, custom parser": { | ||
topic: function() { | ||
var promise = new EventEmitter(); | ||
var config_json = require.resolve('./files/config.json'); | ||
var config = new ConfigFile( {source: config_json, parser: function(data) { | ||
var value = JSON.parse(data); | ||
value.extra = "extra"; | ||
delete value.test1; | ||
return value; | ||
} } ); | ||
config.on('state', function(state_change) { | ||
if( state_change.state == 'invalid' ) { | ||
promise.emit('failure', config); | ||
} else if ( state_change.state == 'ready' ) { | ||
promise.emit('success', config); | ||
} | ||
}); | ||
return promise; | ||
}, | ||
'loads properly': function(config) { | ||
assert.instanceOf( config, Configuration); | ||
assert.instanceOf( config, ConfigFile ); | ||
assert.deepEqual( config.toObject(), { "extra":"extra", "test2": "b", "test3": {"c": "d", "e": ["f","g","h"] } , "test4": "i" } ); | ||
} | ||
}, | ||
}, | ||
"JSON File Watch": { | ||
teardown: function(topic) { | ||
topic.config._watchr_.close(); | ||
delete topic.config._watchr_; | ||
}, | ||
topic: function() { | ||
// In this scenario, we are going to | ||
// 1) write a file containing some json data. | ||
// 2) set up a config file watching it. | ||
// 3) Overwrite the file with different data. | ||
// 4) Delete the file | ||
// 5) wait for fallout | ||
// We will check that the changes emitted are correct | ||
// We will check that the state changes are correct | ||
var promise = new EventEmitter(); | ||
var object1 = {a:1, b:2, c: [1,2,3], d: {e: 55, f: 66 } }; | ||
var object2 = {a:2, b:1, c: [3,2,1], d: {g: 55, h: 66 } }; | ||
var filename = "./test_watch_file.json"; | ||
var writeStream = fs.createWriteStream(filename); | ||
var changes = []; | ||
var del = false; | ||
var results = { changes: [], states: [], invalids: 0 }; | ||
var result_function = function(change, state) { | ||
if(change) { | ||
results.changes.push(change); | ||
} | ||
if(state) { | ||
results.states.push(state); | ||
if( state.state == 'invalid') { | ||
results.invalids++ | ||
} | ||
} | ||
} | ||
writeStream.end( JSON.stringify( object1 ) ); | ||
writeStream.on('close', function() { | ||
var config = new ConfigFile( {source: filename, format: 'json', watch: true} ); | ||
var ready = false | ||
config.on('change', function(change) { | ||
if( ready ) | ||
result_function( change ); | ||
}); | ||
results.config = config; | ||
config.on('state', function(state) { | ||
result_function(null, state ); | ||
if( state.state == 'ready' && ! ready) { | ||
ready = true; | ||
setTimeout( function() { | ||
var ws2 = fs.createWriteStream(filename); | ||
ws2.end( JSON.stringify(object2) ); | ||
}, 1000 ); | ||
} else if (state.state == 'ready' && ready ) { | ||
// second ready state | ||
setTimeout( function() { | ||
fs.unlink(filename, function() { } ); | ||
}, 1000 ); | ||
} else if ( state.state == 'invalid' && ready ) { | ||
// invalid after ready | ||
promise.emit('success', results ); | ||
} | ||
}); | ||
}); | ||
return promise; | ||
}, | ||
"test changes" : function(results) { | ||
var change_obj = {}; | ||
_.each( results.changes, function(change) { | ||
change_obj[ change.name + "_" + change.value ] = change; | ||
}); | ||
expected_results = { | ||
"a_2": { | ||
"name": "a", | ||
"value": 2, | ||
"source": "./test_watch_file.json", | ||
"old_value": 1 | ||
}, | ||
"b_1": { | ||
"name": "b", | ||
"value": 1, | ||
"source": "./test_watch_file.json", | ||
"old_value": 2 | ||
}, | ||
"c:0_3": { | ||
"name": "c:0", | ||
"value": 3, | ||
"source": "./test_watch_file.json", | ||
"old_value": 1 | ||
}, | ||
"c:2_1": { | ||
"name": "c:2", | ||
"value": 1, | ||
"source": "./test_watch_file.json", | ||
"old_value": 3 | ||
}, | ||
"d:e_undefined": { | ||
"name": "d:e", | ||
"source": "./test_watch_file.json", | ||
"old_value": 55, | ||
"value": undefined | ||
}, | ||
"d:f_undefined": { | ||
"name": "d:f", | ||
"source": "./test_watch_file.json", | ||
"old_value": 66, | ||
"value": undefined | ||
}, | ||
"d:g_55": { | ||
"name": "d:g", | ||
"value": 55, | ||
"source": "./test_watch_file.json", | ||
"old_value": undefined | ||
}, | ||
"d:h_66": { | ||
"name": "d:h", | ||
"value": 66, | ||
"source": "./test_watch_file.json", | ||
"old_value": undefined | ||
} | ||
}; | ||
_.each( expected_results, function(r,name) { | ||
assert.deepEqual( change_obj[name], r ); | ||
}); | ||
}, | ||
"test states": function(results) { | ||
var expected_state = "not ready"; | ||
assert.equal( results.states.length, 5 ); | ||
_.each( results.states, function( state ) { | ||
assert.equal( state.old_state, expected_state ); | ||
expected_state = state.state; | ||
}); | ||
assert.equal( expected_state, "invalid" ); | ||
} | ||
} | ||
}).export(module); |
@@ -65,3 +65,3 @@ var vows = require('vows'), | ||
}, | ||
'mapped config read only': { | ||
'mapped config not read only': { | ||
topic: function() { | ||
@@ -79,8 +79,10 @@ var config = new Configuration({source: "basic config object"}); | ||
remap.set(["e","g"],0); | ||
remap.remove("a"); | ||
return remap; | ||
}, | ||
'writes were not effective': function(remap) { | ||
assert.equal( remap.get("d"), 3 ); | ||
assert.equal( remap.get("e:f"), 1); | ||
assert.equal( remap.get("e:g"), 2); | ||
assert.equal( remap.get("d"), 0 ); | ||
assert.equal( remap.get("e:f"), 0); | ||
assert.equal( remap.get("e:g"), 0); | ||
assert.isUndefined( remap.get("a") ); | ||
}, | ||
@@ -87,0 +89,0 @@ }, |
149025
2885
720
5
36
5
+ Addedwatchr@>= 2.1.0
+ Addedambi@3.2.0(transitive)
+ Addedeachr@4.5.0(transitive)
+ Addededitions@2.3.1(transitive)
+ Addederrlop@2.2.0(transitive)
+ Addedextendr@5.20.0(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedignorefs@3.18.0(transitive)
+ Addedignorepatterns@4.18.0(transitive)
+ Addedreaddir-cluster@3.18.0(transitive)
+ Addedsafefs@6.16.0(transitive)
+ Addedscandirectory@6.18.0(transitive)
+ Addedsemver@6.3.1(transitive)
+ Addedtaskgroup@7.20.0(transitive)
+ Addedtypechecker@4.11.06.4.07.18.0(transitive)
+ Addedunbounded@3.16.0(transitive)
+ Addedwatchr@6.11.0(transitive)