haraka-config
Advanced tools
Comparing version 1.0.10 to 1.0.11
@@ -5,3 +5,2 @@ { | ||
], | ||
"installedESLint": true, | ||
"extends": ["eslint:recommended", "plugin:haraka/recommended"], | ||
@@ -8,0 +7,0 @@ "rules": { |
# 1.0.10 - 2017-01-27 | ||
# 1.0.11 - 2017-03-04 | ||
- what? node v4 doesn't support let under vm.runInNewContext? | ||
- add config.getDir, loads all files in a directory | ||
# 1.0.10 - 2017-02-05 | ||
- log error vs throw on bad YAML | ||
- fix appveyor badge URL | ||
# 1.0.9 - 2017-01-27 | ||
- config cache fix (see haraka/Haraka#1738) | ||
- config: add overrides handling (sync with Haraka) | ||
- configfile: add win64 watching (sync with Haraka) | ||
- remove grunt | ||
- use haraka-eslint plugin (vs local copy of .eslintrc) | ||
- lint updates | ||
- config cache fix (see haraka/Haraka#1738) | ||
- config: add overrides handling (sync with Haraka) | ||
- configfile: add win64 watching (sync with Haraka) | ||
- remove grunt | ||
- use haraka-eslint plugin (vs local copy of .eslintrc) | ||
- lint updates | ||
# 1.0.8 - 2017-01-02 | ||
* version bump, lint updates & sync | ||
* lint fixes | ||
- version bump, lint updates & sync | ||
- lint fixes | ||
# 1.0.7 - 2016-11-17 | ||
* update tests for appveyor (Windows) compatibility #9 | ||
- update tests for appveyor (Windows) compatibility #9 | ||
# 1.0.6 - 2016-11-10 | ||
* handle invalid .ini lines properly (skip them) | ||
- handle invalid .ini lines properly (skip them) | ||
# 1.0.5 - 2016-10-25 | ||
* do not leave behind a `*` section in config (due to wildcard boolean) | ||
- do not leave behind a `*` section in config (due to wildcard boolean) | ||
# 1.0.3 | ||
* added wildcard boolean support | ||
* reduce node required 4.3 -> 0.10.43 | ||
- added wildcard boolean support | ||
- reduce node required 4.3 -> 0.10.43 |
@@ -48,2 +48,6 @@ 'use strict'; | ||
Config.prototype.getDir = function (name, opts, done) { | ||
cfreader.read_dir(path.resolve(this.root_path, name), opts, done); | ||
}; | ||
function merge_config (defaults, overrides, type) { | ||
@@ -53,12 +57,13 @@ if (type === 'ini' || type === 'json' || type === 'yaml') { | ||
} | ||
else if (Array.isArray(overrides) && Array.isArray(defaults) && | ||
if (Array.isArray(overrides) && Array.isArray(defaults) && | ||
overrides.length > 0) { | ||
return overrides; | ||
} | ||
else if (overrides != null) { | ||
if (overrides != null) { | ||
return overrides; | ||
} | ||
else { | ||
return defaults; | ||
} | ||
return defaults; | ||
} | ||
@@ -65,0 +70,0 @@ |
@@ -56,4 +56,4 @@ 'use strict'; | ||
for (var i=0; i < config_dir_candidates.length; i++) { | ||
var candidate = config_dir_candidates[i]; | ||
for (let i=0; i < config_dir_candidates.length; i++) { | ||
let candidate = config_dir_candidates[i]; | ||
try { | ||
@@ -109,5 +109,7 @@ var stat = fs.statSync(candidate); | ||
cfreader.watch_dir = function () { | ||
// NOTE: This only works on Linux and Windows | ||
// NOTE: Has OS platform limitations: | ||
// https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener | ||
var cp = cfreader.config_path; | ||
if (cfreader._watchers[cp]) return; | ||
var watcher = function (fse, filename) { | ||
@@ -186,5 +188,6 @@ if (!filename) return; | ||
cfreader.read_config = function (name, type, cb, options) { | ||
// Store arguments used so we can re-use them by filename later | ||
// and so we know which files we've attempted to read so that | ||
// we can ignore any other files written to the same directory. | ||
// Store arguments used so we can: | ||
// 1. re-use them by filename later | ||
// 2. to know which files we've read, so we can ignore | ||
// other files written to the same directory. | ||
@@ -227,2 +230,68 @@ cfreader._read_args[name] = { | ||
function isDirectory (filepath) { | ||
return new Promise(function (resolve, reject) { | ||
fs.stat(filepath, function (err, stat) { | ||
if (err) return reject(err); | ||
resolve(stat.isDirectory()); | ||
}) | ||
}) | ||
} | ||
function fsReadDir (filepath) { | ||
return new Promise(function (resolve, reject) { | ||
fs.readdir(filepath, function (err, fileList) { | ||
if (err) return reject(err); | ||
resolve(fileList); | ||
}) | ||
}) | ||
} | ||
function fsWatchDir (dirPath) { | ||
if (cfreader._watchers[dirPath]) return; | ||
cfreader._watchers[dirPath] = fs.watch(dirPath, { persistent: false }, function (fse, filename) { | ||
// console.log('event: ' + fse + ', ' + filename); | ||
if (!filename) return; | ||
var full_path = path.join(dirPath, filename); | ||
var args = cfreader._read_args[dirPath]; | ||
// console.log(args); | ||
if (cfreader._sedation_timers[full_path]) { | ||
clearTimeout(cfreader._sedation_timers[full_path]); | ||
} | ||
cfreader._sedation_timers[full_path] = setTimeout(function () { | ||
delete cfreader._sedation_timers[full_path]; | ||
args.opts.watchCb(); | ||
}, 2 * 1000); | ||
}); | ||
} | ||
cfreader.read_dir = function (name, opts, done) { | ||
cfreader._read_args[name] = { opts: opts } | ||
var type = opts.type || 'binary'; | ||
isDirectory(name) | ||
.then((result) => { | ||
return fsReadDir(name); | ||
}) | ||
.then((result2) => { | ||
var reader = require('./readers/' + type); | ||
var promises = []; | ||
result2.forEach(function (file) { | ||
promises.push(reader.loadPromise(path.resolve(name, file))) | ||
}); | ||
return Promise.all(promises); | ||
}) | ||
.then((fileList) => { | ||
// console.log(fileList); | ||
done(null, fileList); | ||
}) | ||
.catch((error) => { | ||
done(error); | ||
}) | ||
if (opts.watchCb) fsWatchDir(name); | ||
}; | ||
cfreader.ensure_enoent_timer = function () { | ||
@@ -304,6 +373,7 @@ if (cfreader._enoent_timer) return; | ||
catch (err) { | ||
if (err.code !== 'EBADF') throw err; | ||
console.error(err.message); | ||
if (cfreader._config_cache[cache_key]) { | ||
result = cfreader._config_cache[cache_key]; | ||
return cfreader._config_cache[cache_key]; | ||
} | ||
return cfrType.empty(options, type); | ||
} | ||
@@ -310,0 +380,0 @@ return result; |
@@ -6,3 +6,3 @@ { | ||
"description": "Haraka's config file loader", | ||
"version": "1.0.10", | ||
"version": "1.0.11", | ||
"homepage": "http://haraka.github.io", | ||
@@ -9,0 +9,0 @@ "repository": { |
@@ -5,8 +5,17 @@ 'use strict'; | ||
exports.load = function(name) { | ||
exports.load = function (name) { | ||
return fs.readFileSync(name); | ||
}; | ||
exports.loadPromise = function (name) { | ||
return new Promise(function (resolve, reject) { | ||
fs.readFile(name, function (err, content) { | ||
if (err) return reject(err); | ||
resolve({ path: name, data: content }); | ||
}); | ||
}); | ||
}; | ||
exports.empty = function () { | ||
return null; | ||
}; |
@@ -5,7 +5,7 @@ [![Build Status][ci-img]][ci-url] | ||
[![Windows Build status][apv-img]][apv-url] | ||
[![Greenkeeper badge](https://badges.greenkeeper.io/haraka/haraka-config.svg)](https://greenkeeper.io/) | ||
[![Greenkeeper badge][gk-img]][gk-url] | ||
# haraka-config | ||
Haraka config file loader and parser | ||
Haraka config file loader, parser, and watcher. | ||
@@ -27,3 +27,3 @@ # Config Files | ||
See the [File Formats](#file_formats) section below for a more detailed | ||
explaination of each of the formats. | ||
explanation of each of the formats. | ||
@@ -51,5 +51,3 @@ # Usage | ||
`````javascript | ||
var cfg; // variable global to this plugin only | ||
```js | ||
exports.register = function () { | ||
@@ -63,4 +61,4 @@ var plugin = this; | ||
var plugin = this; | ||
plugin.cfg = plugin.config.get('my_plugin.ini', function onIniChange () { | ||
// This closure is run a few milliseconds after my_plugin.ini changes | ||
plugin.cfg = plugin.config.get('my_plugin.ini', function onCfgChange () { | ||
// This closure is run a few seconds after my_plugin.ini changes | ||
// Re-run the outer function again | ||
@@ -73,5 +71,5 @@ plugin.load_my_plugin_ini(); | ||
exports.hook_connect = function (next, connection) { | ||
// plugin.cfg in here will always be up-to-date | ||
// plugin.cfg here will be kept updated | ||
} | ||
````` | ||
``` | ||
@@ -88,9 +86,15 @@ The `options` object can accepts the following keys: | ||
<a name="overrides">Default Config and Overrides</a> | ||
=========== | ||
## <a name="overrides">Default Config and Overrides</a> | ||
The config loader supports dual config files - a file containing default | ||
values, and overridden values installed by a user. This can be useful if | ||
publishing your plugin to npm (and is used by some core plugins). | ||
The config loader supports dual config files - a file containing defaults, | ||
and another user installed file containing overrides. The default configs | ||
reside: | ||
- Haraka: within the config directory in the Haraka install (where `npm i` | ||
installed Haraka) | ||
- NPM plugins - inside the module/config directory | ||
Config files with overrides are **always** installed in the Haraka config | ||
directory, which you specified when you ran `haraka -i`. | ||
Overrides work in the following manner: | ||
@@ -300,3 +304,5 @@ | ||
[clim-url]: https://codeclimate.com/github/haraka/haraka-config | ||
[apv-img]: https://ci.appveyor.com/api/projects/status/lme4otppxe22me0j/branch/master?svg=true | ||
[apv-url]: https://ci.appveyor.com/project/msimerson/haraka-config/branch/master | ||
[apv-img]: https://ci.appveyor.com/api/projects/status/9qh720gq77e2h5x4?svg=true | ||
[apv-url]: https://ci.appveyor.com/project/msimerson/haraka-config | ||
[gk-img]: https://badges.greenkeeper.io/haraka/haraka-config.svg | ||
[gk-url]: https://greenkeeper.io/ |
@@ -5,2 +5,3 @@ 'use strict'; | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
@@ -16,6 +17,6 @@ | ||
delete require.cache[ | ||
path.resolve(__dirname, '../config') + '.js' | ||
path.resolve(__dirname, '..','config') + '.js' | ||
]; | ||
delete require.cache[ | ||
path.resolve(__dirname, '../configfile') + '.js' | ||
path.resolve(__dirname, '..','configfile') + '.js' | ||
]; | ||
@@ -208,3 +209,3 @@ } | ||
function _test_get(test, name, type, callback, options, expected) { | ||
function _test_get (test, name, type, callback, options, expected) { | ||
test.expect(1); | ||
@@ -341,1 +342,62 @@ var config = require('../config'); | ||
} | ||
exports.getDir = { | ||
'setUp' : setUp, | ||
'tearDown' : function (done) { | ||
fs.unlink(path.resolve('test','config','dir', '4.ext'), function () { | ||
done(); | ||
}) | ||
}, | ||
'loads all files in dir' : function (test) { | ||
test.expect(4); | ||
this.config.getDir('dir', { type: 'binary' }, function (err, files) { | ||
// console.log(files); | ||
test.equal(err, null); | ||
test.equal(files.length, 3); | ||
test.equal(files[0].data, 'contents1\n'); | ||
test.equal(files[2].data, 'contents3\n'); | ||
test.done(); | ||
}) | ||
}, | ||
'errs on invalid dir' : function (test) { | ||
test.expect(1); | ||
this.config.getDir('dirInvalid', { type: 'binary' }, function (err, files) { | ||
// console.log(arguments); | ||
test.equal(err.code, 'ENOENT'); | ||
test.done(); | ||
}) | ||
}, | ||
'reloads when file in dir is touched' : function (test) { | ||
test.expect(6); | ||
var self = this; | ||
var tmpFile = path.resolve('test','config','dir', '4.ext'); | ||
var callCount = 0; | ||
var getDirDone = function (err, files) { | ||
// console.log('Loading: test/config/dir'); | ||
if (err) console.error(err); | ||
callCount++; | ||
if (callCount === 1) { | ||
// console.log(files); | ||
test.equal(err, null); | ||
test.equal(files.length, 3); | ||
test.equal(files[0].data, 'contents1\n'); | ||
test.equal(files[2].data, 'contents3\n'); | ||
fs.writeFile(tmpFile, 'contents4\n', function (err, res) { | ||
test.equal(err, null); | ||
// console.log('file touched, waiting for callback'); | ||
// console.log(res); | ||
}); | ||
return; | ||
} | ||
if (callCount === 2) { | ||
test.equal(files[3].data, 'contents4\n'); | ||
test.done(); | ||
} | ||
} | ||
var getDir = function () { | ||
var opts = { type: 'binary', watchCb: getDir }; | ||
self.config.getDir('dir', opts, getDirDone); | ||
}; | ||
getDir(); | ||
} | ||
} |
@@ -375,1 +375,11 @@ 'use strict'; | ||
} | ||
exports.bad_config = { | ||
setUp: _set_up, | ||
'bad.yaml returns empty' : function (test) { | ||
test.expect(1); | ||
var res = this.cfreader.load_config('test/config/bad.yaml'); | ||
test.deepEqual(res, {}); | ||
test.done(); | ||
}, | ||
} |
@@ -22,13 +22,2 @@ 'use strict'; | ||
}, | ||
'throws when file is non-existent': function (test) { | ||
test.expect(2); | ||
try { | ||
this.bin.load('test/config/non-existent.bin'); | ||
} | ||
catch (e) { | ||
test.equal(e.code, 'ENOENT'); | ||
test.ok(/no such file or dir/.test(e.message)); | ||
} | ||
test.done(); | ||
}, | ||
'loads the test binary file': function (test) { | ||
@@ -35,0 +24,0 @@ test.expect(3); |
@@ -22,13 +22,2 @@ 'use strict'; | ||
}, | ||
'throws when file is non-existent': function (test) { | ||
test.expect(2); | ||
try { | ||
this.flat.load('test/config/non-existent.flat'); | ||
} | ||
catch (e) { | ||
test.equal(e.code, 'ENOENT'); | ||
test.ok(/no such file or dir/.test(e.message)); | ||
} | ||
test.done(); | ||
}, | ||
'loads the test flat file, as list': function (test) { | ||
@@ -35,0 +24,0 @@ test.expect(1); |
@@ -20,13 +20,2 @@ 'use strict'; | ||
}, | ||
'throws when file is non-existent': function (test) { | ||
test.expect(2); | ||
try { | ||
this.json.load('test/config/non-existent.json'); | ||
} | ||
catch (e) { | ||
test.equal(e.code, 'ENOENT'); | ||
test.ok(/no such file or dir/.test(e.message)); | ||
} | ||
test.done(); | ||
}, | ||
'loads the test JSON file': function (test) { | ||
@@ -33,0 +22,0 @@ test.expect(3); |
@@ -20,17 +20,5 @@ 'use strict'; | ||
}, | ||
'throws when file is non-existent': function (test) { | ||
test.expect(2); | ||
try { | ||
this.yaml.load('test/config/non-existent.haml'); | ||
} | ||
catch (e) { | ||
test.equal(e.code, 'ENOENT'); | ||
test.ok(/no such file or dir/.test(e.message)); | ||
} | ||
test.done(); | ||
}, | ||
'loads the test yaml file': function (test) { | ||
test.expect(4); | ||
var result = this.yaml.load('test/config/test.yaml'); | ||
// console.log(result); | ||
test.strictEqual(result.main.bool_true, true); | ||
@@ -37,0 +25,0 @@ test.equal(result.matt, 'waz here'); |
74431
45
1713
302
23