diff --git a/lib/nconf/provider.js b/lib/nconf/provider.js index 56246e3..75480e3 100644 --- a/lib/nconf/provider.js +++ b/lib/nconf/provider.js @@ -5,7 +5,8 @@ * */ -var stores = require('./stores'); +var optimist = require('optimist'), + stores = require('./stores'); // // ### function Provider (options) @@ -14,7 +15,10 @@ var stores = require('./stores'); // for exposing the pluggable storage features of `nconf`. // var Provider = exports.Provider = function (options) { - options = options || {}; + options = options || {}; + this.overrides = options.overrides || null + this.useArgv = options.useArgv || false; + this.store = stores.create(options.type || 'memory', options); }; @@ -50,6 +54,14 @@ Provider.prototype.use = function (type, options) { // Retrieves the value for the specified key (if any). // Provider.prototype.get = function (key, callback) { + if (this.overrides && Object.prototype.hasOwnProperty.call(this.overrides, key)) { + if (callback) { + callback(null, this.overrides[key]); + } + + return this.overrides[key]; + } + return this.store.get(key, callback); }; @@ -154,4 +166,23 @@ Provider.prototype.save = function (value, callback) { // Provider.prototype.reset = function (callback) { return this.store.reset(callback); -}; \ No newline at end of file +}; + +// +// ### getter @useArgv {boolean} +// Gets a property indicating if we should wrap calls to `.get` +// by checking `optimist.argv`. +// +Provider.prototype.__defineGetter__('useArgv', function () { + return this._useArgv; +}); + +// +// ### setter @useArgv {boolean} +// Sets a property indicating if we should wrap calls to `.get` +// by checking `optimist.argv`. +// +Provider.prototype.__defineSetter__('useArgv', function (val) { + this._useArgv = val || false; + this.overrides = this.overrides || optimist.argv; +}); \ No newline at end of file diff --git a/lib/nconf/stores/file.js b/lib/nconf/stores/file.js index d8b5615..913a26e 100644 --- a/lib/nconf/stores/file.js +++ b/lib/nconf/stores/file.js @@ -25,7 +25,7 @@ var File = exports.File = function (options) { this.type = 'file'; this.file = options.file; - this.search = options.search || false; + this.dir = options.dir || process.cwd(); this.format = options.format || { stringify: function (obj) { return JSON.stringify(obj, null, 2) @@ -144,21 +144,26 @@ File.prototype.loadSync = function () { }; // -// ### function resolve (base) +// ### function search (base) // #### @base {string} Base directory (or file) to begin searching for the target file. +// Attempts to find `this.file` by iteratively searching up the +// directory structure // -File.prototype.resolve = function (base) { - var looking = this.search, +File.prototype.search = function (base) { + var looking = true, fullpath, + previous, stats; + base = base || process.cwd(); + if (this.file[0] === '/') { // // If filename for this instance is a fully qualified path // (i.e. it starts with a `'/'`) then check if it exists // try { - stats = fs.statSync(fs.realpathSync(fullpath)); + stats = fs.statSync(fs.realpathSync(this.file)); if (stats.isFile()) { fullpath = this.file; looking = false; @@ -186,26 +191,32 @@ File.prototype.resolve = function (base) { } while (looking) { + // + // Iteratively look up the directory structure from `base` + // try { stats = fs.statSync(fs.realpathSync(fullpath = path.join(base, this.file))); looking = stats.isDirectory(); } catch (ex) { - var olddir = dir; - dir = path.dirname(dir); + previous = base; + base = path.dirname(base); - if (olddir === dir) { + if (previous === base) { + // + // If we've reached the top of the directory structure then simply use + // the default file path. + // try { - var stat = fs.statSync(fs.realpathSync(configPath = path.join(process.env.HOME, filename))); - if(stat.isDirectory()) { - configPath = undefined; + stats = fs.statSync(fs.realpathSync(fullpath = path.join(this.dir, this.file))); + if (stats.isDirectory()) { + fullpath = undefined; } } catch (ex) { // // Ignore errors // - configPath = undefined; } looking = false; @@ -213,5 +224,12 @@ File.prototype.resolve = function (base) { } } + // + // Set the file for this instance to the fullpath + // that we have found during the search. In the event that + // the search was unsuccessful use the original value for `this.file`. + // + this.file = fullpath || this.file; + return fullpath; }; \ No newline at end of file diff --git a/package.json b/package.json index 3a1061b..7a7c523 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "keywords": ["configuration", "key value store", "plugabble"], "dependencies": { "async": "0.1.x", + "optimist": "0.2.x", "pkginfo": "0.2.x" }, "devDependencies": { diff --git a/test/file-store-test.js b/test/file-store-test.js index 6f7d1b5..f4a675c 100644 --- a/test/file-store-test.js +++ b/test/file-store-test.js @@ -101,4 +101,36 @@ vows.describe('nconf/stores/file').addBatch({ } } } +}).addBatch({ + "When using the nconf file store": { + "the search() method": { + "when the target file exists higher in the directory tree": { + topic: function () { + var filePath = this.filePath = path.join(process.env.HOME, '.nconf'); + fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); + return new (nconf.stores.File)({ + file: '.nconf' + }) + }, + "should update the file appropriately": function (store) { + store.search(); + assert.equal(store.file, this.filePath); + fs.unlinkSync(this.filePath); + } + }, + "when the target file doesn't exist higher in the directory tree": { + topic: function () { + var filePath = this.filePath = path.join(__dirname, 'fixtures', 'search-store.json'); + return new (nconf.stores.File)({ + dir: path.dirname(filePath), + file: 'search-store.json' + }) + }, + "should update the file appropriately": function (store) { + store.search(); + assert.equal(store.file, this.filePath); + } + } + } + } }).export(module); \ No newline at end of file diff --git a/test/fixtures/scripts/default-override.js b/test/fixtures/scripts/default-override.js new file mode 100644 index 0000000..e3e793c --- /dev/null +++ b/test/fixtures/scripts/default-override.js @@ -0,0 +1,11 @@ +/* + * nconf-override.js: Test fixture for using optimist defaults with nconf. + * + * (C) 2011, Charlie Robbins + * + */ + +var nconf = require('../../../lib/nconf'); + +nconf.useArgv = true; +process.stdout.write(nconf.get('something')); \ No newline at end of file diff --git a/test/fixtures/scripts/nconf-override.js b/test/fixtures/scripts/nconf-override.js new file mode 100644 index 0000000..185046a --- /dev/null +++ b/test/fixtures/scripts/nconf-override.js @@ -0,0 +1,12 @@ +/* + * nconf-override.js: Test fixture for using optimist defaults with nconf. + * + * (C) 2011, Charlie Robbins + * + */ + +var nconf = require('../../../lib/nconf'); + +var provider = new (nconf.Provider)({ useArgv: true }); + +process.stdout.write(provider.get('something')); \ No newline at end of file diff --git a/test/provider-test.js b/test/provider-test.js index 356945b..4c32c51 100644 --- a/test/provider-test.js +++ b/test/provider-test.js @@ -5,28 +5,47 @@ * */ -var fs = require('fs'), +var assert = require('assert'), + fs = require('fs'), path = require('path'), + spawn = require('child_process').spawn, vows = require('vows'), - assert = require('assert'), nconf = require('../lib/nconf') var first = '/path/to/file1', second = '/path/to/file2'; +function assertDefaults (script) { + return { + topic: function () { + spawn('node', [script, '--something', 'foobar']) + .stdout.once('data', this.callback.bind(this, null)); + }, + "should respond with the value passed into the script": function (_, data) { + assert.equal(data.toString(), 'foobar'); + } + } +} + vows.describe('nconf/provider').addBatch({ - "When using an instance of nconf.Provier": { - "calling the use() method with the same store type and different options": { - topic: new nconf.Provider().use('file', { file: first }), - "should use a new instance of the store type": function (provider) { - var old = provider.store; + "When using nconf": { + "an instance of 'nconf.Provider'": { + "calling the use() method with the same store type and different options": { + topic: new nconf.Provider().use('file', { file: first }), + "should use a new instance of the store type": function (provider) { + var old = provider.store; - assert.equal(provider.store.file, first); - provider.use('file', { file: second }); + assert.equal(provider.store.file, first); + provider.use('file', { file: second }); - assert.notStrictEqual(old, provider.store); - assert.equal(provider.store.file, second); - } + assert.notStrictEqual(old, provider.store); + assert.equal(provider.store.file, second); + } + }, + "when 'useArgv' is true": assertDefaults(path.join(__dirname, 'fixtures', 'scripts', 'nconf-override.js')) + }, + "the default nconf provider": { + "when 'useArgv' is true": assertDefaults(path.join(__dirname, 'fixtures', 'scripts', 'default-override.js')) } } }).export(module); \ No newline at end of file