haraka-config
Advanced tools
Comparing version 1.0.16 to 1.0.17
@@ -0,2 +1,9 @@ | ||
## 1.0.17 - 2018-12-19 | ||
- refactor ./config.js as an es6 class | ||
- update README syntax and improve formatting | ||
- use path.resolve instead of ./dir/file (2x) | ||
- watch: recursive=true | ||
- permit retrieval of fully qualified path | ||
## 1.0.16 - 2018-11-02 | ||
@@ -3,0 +10,0 @@ |
199
config.js
@@ -7,62 +7,121 @@ 'use strict'; | ||
module.exports = new Config(); | ||
class Config { | ||
constructor (root_path, no_overrides) { | ||
this.root_path = root_path || cfreader.config_path; | ||
function Config (root_path, no_overrides) { | ||
this.root_path = root_path || cfreader.config_path; | ||
this.module_config = function (defaults_path, overrides_path) { | ||
const cfg = new Config(path.join(defaults_path, 'config'), true); | ||
if (overrides_path) { | ||
cfg.overrides_path = path.join(overrides_path, 'config'); | ||
if (process.env.HARAKA_TEST_DIR) { | ||
this.root_path = path.join(process.env.HARAKA_TEST_DIR, 'config'); | ||
return; | ||
} | ||
return cfg; | ||
if (process.env.HARAKA && !no_overrides) { | ||
this.overrides_path = root_path || cfreader.config_path; | ||
this.root_path = path.join(process.env.HARAKA, 'config'); | ||
} | ||
} | ||
if (process.env.HARAKA_TEST_DIR) { | ||
this.root_path = path.join(process.env.HARAKA_TEST_DIR, 'config'); | ||
return; | ||
} | ||
if (process.env.HARAKA && !no_overrides) { | ||
this.overrides_path = root_path || cfreader.config_path; | ||
this.root_path = path.join(process.env.HARAKA, 'config'); | ||
} | ||
} | ||
Config.prototype.get = function (name, type, cb, options) { | ||
const a = this.arrange_args([name, type, cb, options]); | ||
if (!a[1]) a[1] = 'value'; | ||
get (name, type, cb, options) { | ||
const a = this.arrange_args([name, type, cb, options]); | ||
if (!a[1]) a[1] = 'value'; | ||
const full_path = path.resolve(this.root_path, a[0]); | ||
const full_path = path.isAbsolute(name) ? name : path.resolve(this.root_path, a[0]); | ||
let results = cfreader.read_config(full_path, a[1], a[2], a[3]); | ||
let results = cfreader.read_config(full_path, a[1], a[2], a[3]); | ||
if (this.overrides_path) { | ||
const overrides_path = path.resolve(this.overrides_path, a[0]); | ||
if (this.overrides_path) { | ||
const overrides_path = path.resolve(this.overrides_path, a[0]); | ||
const overrides = cfreader.read_config(overrides_path, a[1], a[2], a[3]); | ||
const overrides = cfreader.read_config(overrides_path, a[1], a[2], a[3]); | ||
results = merge_config(results, overrides, a[1]); | ||
results = merge_config(results, overrides, a[1]); | ||
} | ||
// Pass arrays by value to prevent config being modified accidentally. | ||
if (Array.isArray(results)) return results.slice(); | ||
return results; | ||
} | ||
// Pass arrays by value to prevent config being modified accidentally. | ||
if (Array.isArray(results)) { | ||
return results.slice(); | ||
getInt (filename, default_value) { | ||
if (!filename) return NaN; | ||
const full_path = path.resolve(this.root_path, filename); | ||
const r = parseInt(cfreader.read_config(full_path, 'value', null, null), 10); | ||
if (!isNaN(r)) return r; | ||
return parseInt(default_value, 10); | ||
} | ||
return results; | ||
} | ||
getDir (name, opts, done) { | ||
cfreader.read_dir(path.resolve(this.root_path, name), opts, done); | ||
} | ||
Config.prototype.getInt = function (filename, default_value) { | ||
arrange_args (args) { | ||
if (!filename) return NaN; | ||
/* ways get() can be called: | ||
config.get('thing'); | ||
config.get('thing', type); | ||
config.get('thing', cb); | ||
config.get('thing', cb, options); | ||
config.get('thing', options); | ||
config.get('thing', type, cb); | ||
config.get('thing', type, options); | ||
config.get('thing', type, cb, options); | ||
*/ | ||
const fs_name = args.shift(); | ||
let fs_type = null; | ||
let cb; | ||
let options; | ||
const full_path = path.resolve(this.root_path, filename); | ||
const r = parseInt(cfreader.read_config(full_path, 'value', null, null), 10); | ||
for (let i=0; i < args.length; i++) { | ||
if (args[i] === undefined) continue; | ||
switch (typeof args[i]) { // what is it? | ||
case 'function': | ||
cb = args[i]; | ||
break; | ||
case 'object': | ||
options = args[i]; | ||
break; | ||
case 'string': | ||
if (/^(ini|value|list|data|h?json|yaml|binary)$/.test(args[i])) { | ||
fs_type = args[i]; | ||
break; | ||
} | ||
console.log(`unknown string: ${args[i]}`); | ||
break; | ||
} | ||
// console.log(`unknown arg: ${args[i]}, typeof: ${typeof args[i]}`); | ||
} | ||
if (!isNaN(r)) return r; | ||
return parseInt(default_value, 10); | ||
} | ||
if (!fs_type) { | ||
const fs_ext = path.extname(fs_name).substring(1); | ||
Config.prototype.getDir = function (name, opts, done) { | ||
cfreader.read_dir(path.resolve(this.root_path, name), opts, done); | ||
switch (fs_ext) { | ||
case 'hjson': | ||
case 'json': | ||
case 'yaml': | ||
case 'ini': | ||
fs_type = fs_ext; | ||
break; | ||
default: | ||
fs_type = 'value'; | ||
break; | ||
} | ||
} | ||
return [fs_name, fs_type, cb, options]; | ||
} | ||
module_config (defaults_path, overrides_path) { | ||
const cfg = new Config(path.join(defaults_path, 'config'), true); | ||
if (overrides_path) { | ||
cfg.overrides_path = path.join(overrides_path, 'config'); | ||
} | ||
return cfg; | ||
} | ||
} | ||
module.exports = new Config(); | ||
function merge_config (defaults, overrides, type) { | ||
@@ -90,4 +149,3 @@ switch (type) { | ||
if (k in defaults) { | ||
if (typeof overrides[k] === 'object' && | ||
typeof defaults[k] === 'object') { | ||
if (typeof overrides[k] === 'object' && typeof defaults[k] === 'object') { | ||
defaults[k] = merge_struct(defaults[k], overrides[k]); | ||
@@ -105,58 +163,1 @@ } | ||
} | ||
/* ways get() can be called: | ||
config.get('thing'); | ||
config.get('thing', type); | ||
config.get('thing', cb); | ||
config.get('thing', cb, options); | ||
config.get('thing', options); | ||
config.get('thing', type, cb); | ||
config.get('thing', type, options); | ||
config.get('thing', type, cb, options); | ||
*/ | ||
Config.prototype.arrange_args = function (args) { | ||
const fs_name = args.shift(); | ||
let fs_type = null; | ||
let cb; | ||
let options; | ||
for (let i=0; i < args.length; i++) { | ||
if (args[i] === undefined) continue; | ||
switch (typeof args[i]) { // what is it? | ||
case 'function': | ||
cb = args[i]; | ||
break; | ||
case 'object': | ||
options = args[i]; | ||
break; | ||
case 'string': | ||
if (/^(ini|value|list|data|h?json|yaml|binary)$/.test(args[i])) { | ||
fs_type = args[i]; | ||
break; | ||
} | ||
console.log(`unknown string: ${args[i]}`); | ||
break; | ||
} | ||
// console.log(`unknown arg: ${args[i]}, typeof: ${typeof args[i]}`); | ||
} | ||
if (!fs_type) { | ||
const fs_ext = path.extname(fs_name).substring(1); | ||
switch (fs_ext) { | ||
case 'hjson': | ||
case 'json': | ||
case 'yaml': | ||
case 'ini': | ||
fs_type = fs_ext; | ||
break; | ||
default: | ||
fs_type = 'value'; | ||
break; | ||
} | ||
} | ||
return [fs_name, fs_type, cb, options]; | ||
}; |
@@ -248,3 +248,3 @@ 'use strict'; | ||
cfreader._watchers[dirPath] = fs.watch(dirPath, { persistent: false }, function (fse, filename) { | ||
cfreader._watchers[dirPath] = fs.watch(dirPath, { persistent: false, recursive: true }, function (fse, filename) { | ||
// console.log(`event: ${fse}, ${filename}`); | ||
@@ -275,3 +275,3 @@ if (!filename) return; | ||
.then((fileList) => { | ||
const reader = require(`./readers/${type}`); | ||
const reader = require(path.resolve(__dirname, 'readers', type)); | ||
const promises = []; | ||
@@ -327,5 +327,5 @@ fileList.forEach((file) => { | ||
case '': | ||
return require('./readers/flat'); | ||
return require(path.resolve(__dirname, 'readers', 'flat')); | ||
} | ||
return require(`./readers/${type}`); | ||
return require(path.resolve(__dirname, 'readers', type)); | ||
} | ||
@@ -332,0 +332,0 @@ |
@@ -6,3 +6,3 @@ { | ||
"description": "Haraka's config file loader", | ||
"version": "1.0.16", | ||
"version": "1.0.17", | ||
"homepage": "http://haraka.github.io", | ||
@@ -9,0 +9,0 @@ "repository": { |
@@ -30,5 +30,6 @@ [![Build Status][ci-img]][ci-url] | ||
# Usage | ||
```js | ||
// From within a plugin: | ||
var cfg = this.config.get(name, [type], [callback], [options]); | ||
// From within a plugin: | ||
const cfg = this.config.get(name, [type], [callback], [options]); | ||
``` | ||
@@ -38,5 +39,7 @@ This will load the file config/rambling.paths in the Haraka directory. | ||
`name` is not a full path, but a filename in the config/ directory. For example: | ||
```js | ||
var cfg = this.config.get('rambling.paths', 'list'); | ||
const cfg = this.config.get('rambling.paths', 'list'); | ||
``` | ||
`type` can be any of the types listed above. | ||
@@ -54,15 +57,15 @@ | ||
exports.register = function () { | ||
var plugin = this; | ||
plugin.loginfo('register function called'); | ||
plugin.load_my_plugin_ini(); | ||
const plugin = this | ||
plugin.loginfo('register called') | ||
plugin.load_my_plugin_ini() | ||
} | ||
exports.load_my_plugin_ini = function () { | ||
var plugin = this; | ||
const plugin = this | ||
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 | ||
plugin.load_my_plugin_ini(); | ||
}); | ||
plugin.loginfo('cfg=' + JSON.stringify(plugin.cfg)); | ||
plugin.load_my_plugin_ini() | ||
}) | ||
plugin.loginfo(`cfg=${JSON.stringify(plugin.cfg)}`) | ||
} | ||
@@ -158,13 +161,13 @@ | ||
```ini | ||
first_name=Matt | ||
last_name=Sergeant | ||
first_name=Matt | ||
last_name=Sergeant | ||
[job] | ||
title=Senior Principal Software Engineer | ||
role=Architect | ||
[job] | ||
title=Senior Principal Software Engineer | ||
role=Architect | ||
[projects] | ||
haraka | ||
qpsmtpd | ||
spamassassin | ||
[projects] | ||
haraka | ||
qpsmtpd | ||
spamassassin | ||
``` | ||
@@ -202,3 +205,3 @@ That produces the following Javascript object: | ||
```js | ||
{ booleans: ['reject','some_true_value'] } | ||
{ booleans: ['reject','some_true_value'] } | ||
``` | ||
@@ -208,7 +211,8 @@ On the options declarations, key names are formatted as section.key. | ||
This ensures these values are converted to true Javascript booleans when parsed, | ||
and supports the following options for boolean values: | ||
This ensures these values are converted to true Javascript booleans when parsed, and supports the following options for boolean values: | ||
``` | ||
true, yes, ok, enabled, on, 1 | ||
true, yes, ok, enabled, on, 1 | ||
``` | ||
Anything else is treated as false. | ||
@@ -218,18 +222,23 @@ | ||
missing), prefix the key with +: | ||
```js | ||
{ booleans: [ '+reject' ] } | ||
{ booleans: [ '+reject' ] } | ||
``` | ||
For completeness the inverse is also allowed: | ||
```js | ||
{ booleans: [ '-reject' ] } | ||
{ booleans: [ '-reject' ] } | ||
``` | ||
Lists are supported using this syntax: | ||
```ini | ||
hosts[] = first_host | ||
hosts[] = second_host | ||
hosts[] = third_host | ||
hosts[] = first_host | ||
hosts[] = second_host | ||
hosts[] = third_host | ||
``` | ||
which produces this javascript array: | ||
```js | ||
['first_host', 'second_host', 'third_host'] | ||
['first_host', 'second_host', 'third_host'] | ||
``` | ||
@@ -256,4 +265,3 @@ | ||
You can use JSON, HJSON or YAML files to override any other file by prefixing the | ||
outer variable name with a `!` e.g. | ||
You can use JSON, HJSON or YAML files to override any other file by prefixing the outer variable name with a `!` e.g. | ||
@@ -287,3 +295,3 @@ ```js | ||
```js | ||
```hjson | ||
{ | ||
@@ -290,0 +298,0 @@ # specify rate in requests/second (because comments are helpful!) |
@@ -6,3 +6,3 @@ 'use strict'; | ||
const cb = function () { return false; }; | ||
function cb () { return false; } | ||
const opts = { booleans: ['arg1'] }; | ||
@@ -363,3 +363,2 @@ | ||
// config.get('test.bin', 'binary'); | ||
'test.bin, type=binary' : function (test) { | ||
@@ -372,2 +371,15 @@ test.expect(2); | ||
}, | ||
'fully qualified path: /etc/services' : function (test) { | ||
test.expect(1); | ||
let res; | ||
if (/^win/.test(process.platform)) { | ||
res = this.config.get('c:\\windows\\win.ini', 'list'); | ||
} | ||
else { | ||
res = this.config.get('/etc/services', 'list'); | ||
} | ||
test.ok(res.length); | ||
test.done(); | ||
} | ||
} | ||
@@ -457,3 +469,3 @@ | ||
test.expect(4); | ||
this.config.getDir('dir', { type: 'binary' }, function (err, files) { | ||
this.config.getDir('dir', { type: 'binary' }, (err, files) => { | ||
// console.log(files); | ||
@@ -469,3 +481,3 @@ test.equal(err, null); | ||
test.expect(1); | ||
this.config.getDir('dirInvalid', { type: 'binary' }, function (err, files) { | ||
this.config.getDir('dirInvalid', { type: 'binary' }, (err, files) => { | ||
// console.log(arguments); | ||
@@ -478,4 +490,3 @@ test.equal(err.code, 'ENOENT'); | ||
if (/darwin/.test(process.platform)) { | ||
// due to differences in fs.watch, this test is not reliable on | ||
// Mac OS X | ||
// due to differences in fs.watch, this test is not reliable on Mac OS X | ||
test.done(); | ||
@@ -482,0 +493,0 @@ return; |
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
86095
1983
348
38