diff --git a/.codeclimate.yml b/.codeclimate.yml index dfe3bb6..a8ccd03 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,10 +1,10 @@ engines: eslint: enabled: true - channel: "eslint-6" + channel: 'eslint-6' config: - config: ".eslintrc.yaml" + config: '.eslintrc.yaml' ratings: - paths: - - "**.js" + paths: + - '**.js' diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8abca40..fd04e8f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ version: 2 updates: - - package-ecosystem: "npm" # See documentation for possible values - directory: "/" # Location of package manifests + - package-ecosystem: 'npm' # See documentation for possible values + directory: '/' # Location of package manifests schedule: - interval: "daily" + interval: 'daily' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be9d703..1d07c8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,9 +16,9 @@ jobs: secrets: inherit ubuntu: - needs: [ lint ] + needs: [lint] uses: haraka/.github/.github/workflows/ubuntu.yml@master windows: - needs: [ lint ] - uses: haraka/.github/.github/workflows/windows.yml@master \ No newline at end of file + needs: [lint] + uses: haraka/.github/.github/workflows/windows.yml@master diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3627451..8314a66 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,10 +2,10 @@ name: CodeQL on: push: - branches: [ master ] + branches: [master] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [master] schedule: - cron: '18 7 * * 4' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d489fbd..e81c15f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,4 +13,4 @@ env: jobs: publish: uses: haraka/.github/.github/workflows/publish.yml@master - secrets: inherit \ No newline at end of file + secrets: inherit diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 0000000..8ded5e0 --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,2 @@ +singleQuote: true +semi: false diff --git a/CHANGELOG.md b/CHANGELOG.md index a93cf15..d047ff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,26 +1,23 @@ - ### Unreleased -### 1.2.0 - 2024-04-14 - +### [1.2.0] - 2024-04-14 +- ci: update to shared haraka/.github - dep: eslint-plugin-haraka -> @haraka/eslint-config - lint: remove duplicate / stale rules from .eslintrc - package.json: populate [files] - deps: version bumps -### 1.1.0 - 2022-05-27 +### [1.1.0] - 2022-05-27 - chore(ci): depend on shared GHA workflows - chore(dep): eslint 6 -> 8 - chore(dep): mocha 8 -> 9 - ### 1.0.20 - 2021-09-01 - chore(dep): update YAML 3.13 -> 4.1 (#65) - ### 1.0.19 - 2021-06-10 - configfile: disable watch dir when platform not mac or win @@ -29,12 +26,10 @@ - configfile: use simpler es6 `for..in` and `for..of` - getDir tests, use os.EOL for comparison - ### 1.0.18 - 2019-10-11 - add support for loading `.js` configurations - ### 1.0.17 - 2018-12-19 - refactor ./config.js as an es6 class @@ -43,13 +38,11 @@ - watch: recursive=true - permit retrieval of fully qualified path - ### 1.0.16 - 2018-11-02 - remove trailing ; from function declarations - add config.getInt(filename, default_value) - ### 1.0.15 - 2017-09-21 - additional test for 'missing json loads yaml' @@ -58,35 +51,29 @@ - configs w/o .ext or declared type default to flat - add test for json/yaml !filename overloads - ### 1.0.14 - 2017-09-19 -- add __dirname/../../config to config_dir_candidates for haraka/Haraka/tests/* +- add \_\_dirname/../../config to config_dir_candidates for haraka/Haraka/tests/\* - sync process.env.HARAKA_TEST_DIR from haraka/Haraka/config - eslint no-var updates #25 - ### 1.0.13 - 2017-06-16 - lint updates for eslint 4 - ### 1.0.12 - 2017-05-21 - unref() the setInterval so that Haraka can gracefully exit - ### 1.0.11 - 2017-03-04 - 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) @@ -96,30 +83,27 @@ - use haraka-eslint plugin (vs local copy of .eslintrc) - lint updates - ### 1.0.8 - 2017-01-02 - version bump, lint updates & sync - lint fixes - ### 1.0.7 - 2016-11-17 - update tests for appveyor (Windows) compatibility #9 - ### 1.0.6 - 2016-11-10 - 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) - ### 1.0.3 - added wildcard boolean support - reduce node required 4.3 -> 0.10.43 -[1.2.0]: https://github.com/haraka/haraka-config/releases/tag/1.2.0 + +[1.1.0]: https://github.com/haraka/haraka-config/releases/tag/1.1.0 +[1.2.0]: https://github.com/haraka/haraka-config/releases/tag/v1.2.0 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index eee5ca2..a7ac9c8 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -2,7 +2,7 @@ This handcrafted artisinal software is brought to you by: -|
msimerson (52)|
PSSGCSim (7)|
baudehlo (1)|
Wesitos (1)|
oreoluwa (1)| -| :---: | :---: | :---: | :---: | :---: | +|
msimerson (52) |
PSSGCSim (7) |
baudehlo (1) |
Wesitos (1) |
oreoluwa (1) | +| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | this file is maintained by [.release](https://github.com/msimerson/.release) diff --git a/README.md b/README.md index 47eb135..a89b7e8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # haraka-config Haraka config file loader, parser, and watcher. @@ -9,14 +8,14 @@ Haraka config file loader, parser, and watcher. Haraka's config loader can load several types of configuration files. -* 'value' - load a flat file containing a single value (default) -* 'ini' - load an ini file -* 'json' - load a json file -* 'hjson' - load a hjson file -* 'yaml' - load a yaml file -* 'list' - load a flat file containing a list of values -* 'data' - load a flat file containing a list of values, keeping comments and whitespace. -* 'binary' - load a binary file into a Buffer +- 'value' - load a flat file containing a single value (default) +- 'ini'. - load an ini file +- 'json' - load a json file +- 'hjson' - load a hjson file +- 'yaml' - load a yaml file +- 'list' - load a flat file containing a list of values +- 'data' - load a flat file containing a list, keeping comments and whitespace. +- 'binary' - load a binary file into a Buffer See the [File Formats](#file_formats) section below for a more detailed explanation of each of the formats. @@ -25,14 +24,15 @@ explanation of each of the formats. ```js // From within a plugin: -const cfg = this.config.get(name, [type], [callback], [options]); +const cfg = this.config.get(name, [type], [callback], [options]) ``` + 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 -const cfg = this.config.get('rambling.paths', 'list'); +const cfg = this.config.get('rambling.paths', 'list') ``` `type` can be any of the types listed above. @@ -42,40 +42,38 @@ the `type` parameter can be omitted. `callback` is an optional callback function that will be called when an update is detected on the file after the configuration cache has been -updated by re-reading the file. Use this to refresh configuration +updated by re-reading the file. Use this to refresh configuration variables within your plugin. Example: ```js exports.register = function () { - const plugin = this - plugin.loginfo('register called') - plugin.load_my_plugin_ini() + this.loginfo('register called') + this.load_my_plugin_ini() } exports.load_my_plugin_ini = function () { - 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)}`) + this.cfg = this.config.get('my_plugin.ini', () => { + // This closure is run a few seconds after my_plugin.ini changes + // Re-run the outer function again + this.load_my_plugin_ini() + }) + this.loginfo(`cfg=${JSON.stringify(this.cfg)}`) } exports.hook_connect = function (next, connection) { - // plugin.cfg here will be kept updated + // this.cfg here will be kept updated } ``` The `options` object can accepts the following keys: -* `no_watch` (default: false) - prevents Haraka from watching for updates. -* `no_cache` (default: false) - prevents Haraka from caching the file. This -means that the file will be re-read on every call to `config.get`. This is -not recommended as config files are read syncronously, will block the event -loop, and will slow down Haraka. -* `booleans` (default: none) - for .ini files, this allows specifying -boolean type keys. Default true or false can be specified. +- `no_watch` (default: false) - prevents Haraka from watching for updates. +- `no_cache` (default: false) - prevents Haraka from caching the file. This + means that the file will be re-read on every call to `config.get`. This is + not recommended as config files are read syncronously, will block the event + loop, and will slow down Haraka. +- `booleans` (default: none) - for .ini files, this allows specifying + boolean type keys. Default true or false can be specified. ## Default Config and Overrides @@ -84,7 +82,7 @@ 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) + installed Haraka) - NPM plugins - inside the module/config directory Config files with overrides are **always** installed in the Haraka config @@ -92,21 +90,21 @@ directory, which you specified when you ran `haraka -i`. Overrides work in the following manner: -* For `json`, `ini` and `yaml` config, values are overridden on a deep -key by key basis. -* For every other config format, an override file replaces the entire -config. +- For `json`, `ini` and `yaml` config, values are overridden on a deep + key by key basis. +- For every other config format, an override file replaces the entire + config. ## Examples 1. a plugin installed as a module (or a core Haraka plugin) -loads a `list` config from their own `config/plugin_name` file. That list -can be completely overridden by a file called `config/plugin_name` in the -Haraka local install directory. + loads a `list` config from their own `config/plugin_name` file. That list + can be completely overridden by a file called `config/plugin_name` in the + Haraka local install directory. 2. a plugin using default config from `config/plugin_name.ini` -can be overridden on a key-by-key basis. A default -`plugin_name.ini` might contain: + can be overridden on a key-by-key basis. A default + `plugin_name.ini` might contain: ```ini toplevel1=foo @@ -139,16 +137,15 @@ sub2=otherthing This allows plugins to provide a default config, and allow users to override values on a key-by-key basis. -File Formats -============ +# File Formats -Ini Files ---------- +## Ini Files INI files have their heritage in early versions of Microsoft Windows. Entries are a simple format of key=value pairs, with optional [sections]. Here is a typical example: + ```ini first_name=Matt last_name=Sergeant @@ -162,6 +159,7 @@ haraka qpsmtpd spamassassin ``` + That produces the following Javascript object: ```js @@ -192,9 +190,13 @@ The key=value pairs support continuation lines using the backslash "\" character. The `options` object allows you to specify which keys are boolean: + ```js -{ booleans: ['reject','some_true_value'] } +{ + booleans: ['reject', 'some_true_value'] +} ``` + On the options declarations, key names are formatted as section.key. If the key name does not specify a section, it is presumed to be [main]. @@ -210,13 +212,19 @@ To default a boolean as true (when the key is undefined or the config file is 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 @@ -231,8 +239,7 @@ which produces this javascript array: ['first_host', 'second_host', 'third_host'] ``` -Flat Files ----------- +## Flat Files Flat files are simply either lists of values separated by \n or a single value in a file on its own. Those who have used qmail or qpsmtpd will be @@ -241,13 +248,12 @@ Lines starting with '#' and blank lines will be ignored unless the type is specified as 'data', however even then line endings will be stripped. See plugins/dnsbl.js for an example. -JSON Files ----------- +## JSON Files These are as you would expect, and returns an object as given in the file. If a requested .json or .hjson file does not exist then the same file will be checked -for with a .yaml extension and that will be loaded instead. This is done +for with a .yaml extension and that will be loaded instead. This is done because YAML files are far easier for a human to write. You can use JSON, HJSON or YAML files to override any other file by prefixing the outer variable name with a `!` e.g. @@ -262,16 +268,16 @@ If the config/smtpgreeting file did not exist, then this value would replace it. NOTE: You must ensure that the data type (e.g. Object, Array or String) for -the replaced value is correct. This cannot be done automatically. +the replaced value is correct. This cannot be done automatically. -Hjson Files ----------- +## Hjson Files Hjson is a syntax extension to JSON. It is intended to be used like a user interface for humans, to read and edit before passing the JSON data to the machine. That means you can use it to parse JSON files but it is not intended as a replacement. You can check [Hjson's homepage](https://hjson.org) to get familiar with it and you can [try out its syntax](https://hjson.org/try.html). Main features: + - Comments - Optional quotes - Optional commas @@ -300,15 +306,11 @@ Example syntax NOTE: Hjson can be also replaced by YAML configuration file. You can find more on this issue under JSON section. - -YAML Files ----------- +## YAML Files As per JSON files above but in YAML format. - -Reloading/Caching -======== +# Reloading/Caching Haraka automatically reloads configuration files, but this only works if whatever is looking at that config re-calls config.get() to retrieve the @@ -325,7 +327,6 @@ it may take up to 60 seconds to load, due to differences between in the kernel APIs for watching files/directories. Haraka reads a number of configuration files at startup. Any files read -in a plugins register() function are read *before* Haraka drops privileges. +in a plugins register() function are read _before_ Haraka drops privileges. Be sure that Haraka's user/group has permission to read these files else Haraka will be unable to update them after changes. - diff --git a/config.js b/config.js index 41d1dbd..b9109ed 100644 --- a/config.js +++ b/config.js @@ -1,65 +1,68 @@ -'use strict'; +'use strict' -const path = require('path'); +const path = require('path') -const cfreader = require('./configfile'); +const cfreader = require('./configfile') class Config { - constructor (root_path, no_overrides) { - this.root_path = root_path || cfreader.config_path; - - 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'); - } - } - - get (name, type, cb, options) { - const a = this.arrange_args([name, type, cb, options]); - if (!a[1]) a[1] = 'value'; + constructor(root_path, no_overrides) { + this.root_path = root_path || cfreader.config_path - const full_path = path.isAbsolute(name) ? name : path.resolve(this.root_path, a[0]); + 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') + } + } - let results = cfreader.read_config(full_path, a[1], a[2], a[3]); + get(name, type, cb, options) { + const a = this.arrange_args([name, type, cb, options]) + if (!a[1]) a[1] = 'value' - if (this.overrides_path) { - const overrides_path = path.resolve(this.overrides_path, a[0]); + const full_path = path.isAbsolute(name) + ? name + : path.resolve(this.root_path, a[0]) - const overrides = cfreader.read_config(overrides_path, a[1], a[2], a[3]); + let results = cfreader.read_config(full_path, a[1], a[2], a[3]) - results = merge_config(results, overrides, a[1]); - } + if (this.overrides_path) { + const overrides_path = path.resolve(this.overrides_path, a[0]) - // Pass arrays by value to prevent config being modified accidentally. - if (Array.isArray(results)) return results.slice(); + const overrides = cfreader.read_config(overrides_path, a[1], a[2], a[3]) - return results; + results = merge_config(results, overrides, a[1]) } - getInt (filename, default_value) { + // Pass arrays by value to prevent config being modified accidentally. + if (Array.isArray(results)) return results.slice() - if (!filename) return NaN; + return results + } - const full_path = path.resolve(this.root_path, filename); - const r = parseInt(cfreader.read_config(full_path, 'value', null, null), 10); + getInt(filename, default_value) { + if (!filename) return NaN - if (!isNaN(r)) return r; - return parseInt(default_value, 10); - } + const full_path = path.resolve(this.root_path, filename) + const r = parseInt(cfreader.read_config(full_path, 'value', null, null), 10) - getDir (name, opts, done) { - cfreader.read_dir(path.resolve(this.root_path, name), opts).then((files) => { - done(null, files) // keep the API consistent - }).catch(done) - } + if (!isNaN(r)) return r + return parseInt(default_value, 10) + } - arrange_args (args) { + getDir(name, opts, done) { + cfreader + .read_dir(path.resolve(this.root_path, name), opts) + .then((files) => { + done(null, files) // keep the API consistent + }) + .catch(done) + } - /* ways get() can be called: + arrange_args(args) { + /* ways get() can be called: config.get('thing'); config.get('thing', type); config.get('thing', cb); @@ -69,96 +72,99 @@ class Config { config.get('thing', type, options); config.get('thing', type, cb, options); */ - 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|js|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 'js': - 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; + 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|js|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]}`); } -} -module.exports = new Config(); + if (!fs_type) { + const fs_ext = path.extname(fs_name).substring(1) -function merge_config (defaults, overrides, type) { - switch (type) { - case 'ini': + switch (fs_ext) { case 'hjson': case 'json': - case 'js': case 'yaml': - return merge_struct(JSON.parse(JSON.stringify(defaults)), overrides); - } + case 'js': + case 'ini': + fs_type = fs_ext + break - if (Array.isArray(overrides) && Array.isArray(defaults) && - overrides.length > 0) { - return overrides; + default: + fs_type = 'value' + break + } } - if (overrides != null) return overrides; + 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 + } +} - return defaults; +module.exports = new Config() + +function merge_config(defaults, overrides, type) { + switch (type) { + case 'ini': + case 'hjson': + case 'json': + case 'js': + case 'yaml': + return merge_struct(JSON.parse(JSON.stringify(defaults)), overrides) + } + + if ( + Array.isArray(overrides) && + Array.isArray(defaults) && + overrides.length > 0 + ) { + return overrides + } + + if (overrides != null) return overrides + + return defaults } -function merge_struct (defaults, overrides) { - for (const k in overrides) { - if (k in defaults) { - if (typeof overrides[k] === 'object' && typeof defaults[k] === 'object') { - defaults[k] = merge_struct(defaults[k], overrides[k]); - } - else { - defaults[k] = overrides[k]; - } - } - else { - defaults[k] = overrides[k]; - } +function merge_struct(defaults, overrides) { + for (const k in overrides) { + if (k in defaults) { + if (typeof overrides[k] === 'object' && typeof defaults[k] === 'object') { + defaults[k] = merge_struct(defaults[k], overrides[k]) + } else { + defaults[k] = overrides[k] + } + } else { + defaults[k] = overrides[k] } - return defaults; + } + return defaults } diff --git a/configfile.js b/configfile.js index 5d756c4..853f182 100644 --- a/configfile.js +++ b/configfile.js @@ -1,397 +1,404 @@ -'use strict'; +'use strict' -const fs = require('fs'); -const path = require('path'); +const fs = require('fs') +const path = require('path') let config_dir_candidates = [ - // these work when this file is loaded as require('./config.js') - path.join(__dirname, 'config'), // Haraka ./config dir - __dirname, // npm packaged plugins -]; + // these work when this file is loaded as require('./config.js') + path.join(__dirname, 'config'), // Haraka ./config dir + __dirname, // npm packaged plugins +] class cfreader { - constructor () { - this.watch_files = true; - this._config_cache = {}; - this._read_args = {}; - this._watchers = {}; - this._enoent_timer = false; - this._enoent_files = {}; - this._sedation_timers = {}; - this._overrides = {}; - - this.get_path_to_config_dir() - - // for "ini" type files - this.regex = { - section: /^\s*\[\s*([^\]]*?)\s*\]\s*$/, - param: /^\s*([\w@:._\-/[\]]+)\s*(?:=\s*(.*?)\s*)?$/, - comment: /^\s*[;#].*$/, - line: /^\s*(.*?)\s*$/, - blank: /^\s*$/, - continuation: /\\[ \t]*$/, - is_integer: /^-?\d+$/, - is_float: /^-?\d+\.\d+$/, - is_truth: /^(?:true|yes|ok|enabled|on|1)$/i, - is_array: /(.+)\[\]$/, - } + constructor() { + this.watch_files = true + this._config_cache = {} + this._read_args = {} + this._watchers = {} + this._enoent_timer = false + this._enoent_files = {} + this._sedation_timers = {} + this._overrides = {} + + this.get_path_to_config_dir() + + // for "ini" type files + this.regex = { + section: /^\s*\[\s*([^\]]*?)\s*\]\s*$/, + param: /^\s*([\w@:._\-/[\]]+)\s*(?:=\s*(.*?)\s*)?$/, + comment: /^\s*[;#].*$/, + line: /^\s*(.*?)\s*$/, + blank: /^\s*$/, + continuation: /\\[ \t]*$/, + is_integer: /^-?\d+$/, + is_float: /^-?\d+\.\d+$/, + is_truth: /^(?:true|yes|ok|enabled|on|1)$/i, + is_array: /(.+)\[\]$/, } + } - get_path_to_config_dir () { - if (process.env.HARAKA) { - // console.log(`process.env.HARAKA: ${process.env.HARAKA}`); - this.config_path = path.join(process.env.HARAKA, 'config'); - return; - } - - if (process.env.NODE_ENV === 'test') { - // loaded by haraka-config/test/* - this.config_path = path.join(__dirname, 'test', 'config'); - return; - } - - // these work when this is loaded with require('haraka-config') - if (/node_modules[\\/]haraka-config$/.test(__dirname)) { - config_dir_candidates = [ - path.join(__dirname, '..', '..', 'config'), // haraka/Haraka/* - path.join(__dirname, '..', '..'), // npm packaged modules - ] - } + get_path_to_config_dir() { + if (process.env.HARAKA) { + // console.log(`process.env.HARAKA: ${process.env.HARAKA}`); + this.config_path = path.join(process.env.HARAKA, 'config') + return + } - for (const candidate of config_dir_candidates) { - try { - const stat = fs.statSync(candidate); - if (stat && stat.isDirectory()) { - this.config_path = candidate; - return; - } - } - catch (ignore) { - console.error(ignore.message); - } - } + if (process.env.NODE_ENV === 'test') { + // loaded by haraka-config/test/* + this.config_path = path.join(__dirname, 'test', 'config') + return } - on_watch_event (name, type, options, cb) { - return (fse) => { - if (this._sedation_timers[name]) { - clearTimeout(this._sedation_timers[name]); - } - this._sedation_timers[name] = setTimeout(() => { - console.log(`Reloading file: ${name}`); - this.load_config(name, type, options); - delete this._sedation_timers[name]; - if (typeof cb === 'function') cb(); - }, 5 * 1000); - - if (fse !== 'rename') return; - // https://github.com/joyent/node/issues/2062 - // After a rename event, re-watch the file - this._watchers[name].close(); - try { - this._watchers[name] = fs.watch(name, { persistent: false }, this.on_watch_event(...arguments)); - } - catch (e) { - if (e.code === 'ENOENT') { - this._enoent_files[name] = true; - this.ensure_enoent_timer(); - } - else { - console.error(`Error watching file: ${name} : ${e}`); - } - } - } + // these work when this is loaded with require('haraka-config') + if (/node_modules[\\/]haraka-config$/.test(__dirname)) { + config_dir_candidates = [ + path.join(__dirname, '..', '..', 'config'), // haraka/Haraka/* + path.join(__dirname, '..', '..'), // npm packaged modules + ] } - watch_dir () { - // NOTE: Has OS platform limitations: - // https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener - const cp = this.config_path; - if (this._watchers[cp]) return; - - try { - this._watchers[cp] = fs.watch(cp, { persistent: false }, (fse, filename) => { - if (!filename) return; - const full_path = path.join(cp, filename); - if (!this._read_args[full_path]) return; - const args = this._read_args[full_path]; - if (args.options && args.options.no_watch) return; - if (this._sedation_timers[filename]) { - clearTimeout(this._sedation_timers[filename]); - } - this._sedation_timers[filename] = setTimeout(() => { - console.log(`Reloading file: ${full_path}`); - this.load_config(full_path, args.type, args.options); - delete this._sedation_timers[filename]; - if (typeof args.cb === 'function') args.cb(); - }, 5 * 1000); - }); + for (const candidate of config_dir_candidates) { + try { + const stat = fs.statSync(candidate) + if (stat && stat.isDirectory()) { + this.config_path = candidate + return } - catch (e) { - console.error(`Error watching directory ${cp}(${e})`); + } catch (ignore) { + console.error(ignore.message) + } + } + } + + on_watch_event(name, type, options, cb) { + return (fse) => { + if (this._sedation_timers[name]) { + clearTimeout(this._sedation_timers[name]) + } + this._sedation_timers[name] = setTimeout(() => { + console.log(`Reloading file: ${name}`) + this.load_config(name, type, options) + delete this._sedation_timers[name] + if (typeof cb === 'function') cb() + }, 5 * 1000) + + if (fse !== 'rename') return + // https://github.com/joyent/node/issues/2062 + // After a rename event, re-watch the file + this._watchers[name].close() + try { + this._watchers[name] = fs.watch( + name, + { persistent: false }, + this.on_watch_event(...arguments), + ) + } catch (e) { + if (e.code === 'ENOENT') { + this._enoent_files[name] = true + this.ensure_enoent_timer() + } else { + console.error(`Error watching file: ${name} : ${e}`) } - return; + } + } + } + + watch_dir() { + // NOTE: Has OS platform limitations: + // https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener + const cp = this.config_path + if (this._watchers[cp]) return + + try { + this._watchers[cp] = fs.watch( + cp, + { persistent: false }, + (fse, filename) => { + if (!filename) return + const full_path = path.join(cp, filename) + if (!this._read_args[full_path]) return + const args = this._read_args[full_path] + if (args.options && args.options.no_watch) return + if (this._sedation_timers[filename]) { + clearTimeout(this._sedation_timers[filename]) + } + this._sedation_timers[filename] = setTimeout(() => { + console.log(`Reloading file: ${full_path}`) + this.load_config(full_path, args.type, args.options) + delete this._sedation_timers[filename] + if (typeof args.cb === 'function') args.cb() + }, 5 * 1000) + }, + ) + } catch (e) { + console.error(`Error watching directory ${cp}(${e})`) + } + return + } + + watch_file(name, type, cb, options) { + // This works on all OS's, but watch_dir() above is preferred for Linux and + // Windows as it is far more efficient. + // NOTE: we need a fs.watch per file. It's impossible to watch non-existent + // files. Instead, note which files we attempted + // to watch that returned ENOENT and fs.stat each periodically + if (this._watchers[name] || (options && options.no_watch)) return + + try { + this._watchers[name] = fs.watch( + name, + { persistent: false }, + this.on_watch_event(name, type, options, cb), + ) + } catch (e) { + if (e.code !== 'ENOENT') { + // ignore error when ENOENT + console.error(`Error watching config file: ${name} : ${e}`) + } else { + this._enoent_files[name] = true + this.ensure_enoent_timer() + } } + } - watch_file (name, type, cb, options) { - // This works on all OS's, but watch_dir() above is preferred for Linux and - // Windows as it is far more efficient. - // NOTE: we need a fs.watch per file. It's impossible to watch non-existent - // files. Instead, note which files we attempted - // to watch that returned ENOENT and fs.stat each periodically - if (this._watchers[name] || (options && options.no_watch)) return; + get_cache_key(name, options) { + // Ignore options etc. if this is an overriden value + if (this._overrides[name]) return name - try { - this._watchers[name] = fs.watch(name, {persistent: false}, this.on_watch_event(name, type, options, cb)); - } - catch (e) { - if (e.code !== 'ENOENT') { // ignore error when ENOENT - console.error(`Error watching config file: ${name} : ${e}`); - } - else { - this._enoent_files[name] = true; - this.ensure_enoent_timer(); - } - } + if (options) { + // ordering of objects isn't guaranteed to be consistent, but typically is. + return name + JSON.stringify(options) } - get_cache_key (name, options) { + if (this._read_args[name] && this._read_args[name].options) { + return name + JSON.stringify(this._read_args[name].options) + } - // Ignore options etc. if this is an overriden value - if (this._overrides[name]) return name; + return name + } - if (options) { - // ordering of objects isn't guaranteed to be consistent, but typically is. - return name + JSON.stringify(options); - } + read_config(name, type, cb, options) { + // 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. - if (this._read_args[name] && this._read_args[name].options) { - return name + JSON.stringify(this._read_args[name].options); - } + this._read_args[name] = { + type, + cb, + options, + } - return name; + // Check cache first + if (!process.env.WITHOUT_CONFIG_CACHE) { + const cache_key = this.get_cache_key(name, options) + // console.log(`\tcache_key: ${cache_key}`); + if (this._config_cache[cache_key] !== undefined) { + // console.log(`\t${name} is cached`); + return this._config_cache[cache_key] + } } - read_config (name, type, cb, options) { - // 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. - - this._read_args[name] = { - type, - cb, - options - }; - - // Check cache first - if (!process.env.WITHOUT_CONFIG_CACHE) { - const cache_key = this.get_cache_key(name, options); - // console.log(`\tcache_key: ${cache_key}`); - if (this._config_cache[cache_key] !== undefined) { - // console.log(`\t${name} is cached`); - return this._config_cache[cache_key]; - } - } + // load config file + const result = this.load_config(name, type, options) + if (!this.watch_files) return result + + // We can watch the directory on these platforms which + // allows us to notice when files are newly created. + switch (process.platform) { + case 'win32': + case 'win64': + case 'linux': + this.watch_dir() + break + default: + // All other operating systems + this.watch_file(name, type, cb, options) + } - // load config file - const result = this.load_config(name, type, options); - if (!this.watch_files) return result; - - // We can watch the directory on these platforms which - // allows us to notice when files are newly created. - switch (process.platform) { - case 'win32': - case 'win64': - case 'linux': - this.watch_dir(); - break; - default: - // All other operating systems - this.watch_file(name, type, cb, options); - } + return result + } - return result; - } + read_dir(name, opts) { + return new Promise((resolve, reject) => { + this._read_args[name] = { opts } + const type = opts.type || 'binary' - read_dir (name, opts) { - return new Promise((resolve, reject) => { - - this._read_args[name] = { opts } - const type = opts.type || 'binary'; - - isDirectory(name) - .then(() => { - return fsReadDir(name); - }) - .then((fileList) => { - const reader = require(path.resolve(__dirname, 'readers', type)); - const promises = []; - for (const file of fileList) { - promises.push(reader.loadPromise(path.resolve(name, file))) - } - return Promise.all(promises); - }) - .then((fileList) => { - // console.log(fileList); - resolve(fileList); - }) - .catch(reject) - - if (opts.watchCb) this.fsWatchDir(name); + isDirectory(name) + .then(() => { + return fsReadDir(name) }) - } - - ensure_enoent_timer () { - if (this._enoent_timer) return; - // Create timer - this._enoent_timer = setInterval(() => { - const files = Object.keys(this._enoent_files); - for (const fileOuter of files) { - /* BLOCK SCOPE */ - ((file) => { - fs.stat(file, (err) => { - if (err) return; - // File now exists - delete(this._enoent_files[file]); - const args = this._read_args[file]; - this.load_config(file, args.type, args.options, args.cb); - this._watchers[file] = fs.watch(file, {persistent: false}, - this.on_watch_event(file, args.type, args.options, args.cb)); - }); - })(fileOuter); // END BLOCK SCOPE - } - }, 60 * 1000); - this._enoent_timer.unref(); // This shouldn't block exit - } + .then((fileList) => { + const reader = require(path.resolve(__dirname, 'readers', type)) + const promises = [] + for (const file of fileList) { + promises.push(reader.loadPromise(path.resolve(name, file))) + } + return Promise.all(promises) + }) + .then((fileList) => { + // console.log(fileList); + resolve(fileList) + }) + .catch(reject) - get_filetype_reader (type) { - switch (type) { - case 'list': - case 'value': - case 'data': - case '': - return require(path.resolve(__dirname, 'readers', 'flat')); - } - return require(path.resolve(__dirname, 'readers', type)); + if (opts.watchCb) this.fsWatchDir(name) + }) + } + + ensure_enoent_timer() { + if (this._enoent_timer) return + // Create timer + this._enoent_timer = setInterval(() => { + const files = Object.keys(this._enoent_files) + for (const fileOuter of files) { + /* BLOCK SCOPE */ + ;((file) => { + fs.stat(file, (err) => { + if (err) return + // File now exists + delete this._enoent_files[file] + const args = this._read_args[file] + this.load_config(file, args.type, args.options, args.cb) + this._watchers[file] = fs.watch( + file, + { persistent: false }, + this.on_watch_event(file, args.type, args.options, args.cb), + ) + }) + })(fileOuter) // END BLOCK SCOPE + } + }, 60 * 1000) + this._enoent_timer.unref() // This shouldn't block exit + } + + get_filetype_reader(type) { + switch (type) { + case 'list': + case 'value': + case 'data': + case '': + return require(path.resolve(__dirname, 'readers', 'flat')) } + return require(path.resolve(__dirname, 'readers', type)) + } - load_config (name, type, options) { - let result; - - if (!type) { - type = path.extname(name).toLowerCase().substring(1); - } + load_config(name, type, options) { + let result - let cfrType = this.get_filetype_reader(type); + if (!type) { + type = path.extname(name).toLowerCase().substring(1) + } - if (!fs.existsSync(name)) { - if (!/\.h?json$/.test(name)) { - return cfrType.empty(options, type); - } + let cfrType = this.get_filetype_reader(type) - const yaml_name = name.replace(/\.h?json$/, '.yaml'); - if (!fs.existsSync(yaml_name)) return cfrType.empty(options, type); + if (!fs.existsSync(name)) { + if (!/\.h?json$/.test(name)) { + return cfrType.empty(options, type) + } - name = yaml_name; - type = 'yaml'; + const yaml_name = name.replace(/\.h?json$/, '.yaml') + if (!fs.existsSync(yaml_name)) return cfrType.empty(options, type) - cfrType = this.get_filetype_reader(type); - } + name = yaml_name + type = 'yaml' - const cache_key = this.get_cache_key(name, options); - try { - switch (type) { - case 'ini': - result = cfrType.load(name, options, this.regex); - break; - case 'hjson': - case 'json': - case 'yaml': - result = cfrType.load(name); - this.process_file_overrides(name, options, result); - break; - // case 'binary': - default: - result = cfrType.load(name, type, options, this.regex); - } - this._config_cache[cache_key] = result; - } - catch (err) { - console.error(err.message); - if (this._config_cache[cache_key]) { - return this._config_cache[cache_key]; - } - return cfrType.empty(options, type); - } - return result; + cfrType = this.get_filetype_reader(type) } - process_file_overrides (name, options, result) { - // We might be re-loading this file: - // * build a list of cached overrides - // * remove them and add them back - const cp = this.config_path; - const cache_key = this.get_cache_key(name, options); - - if (this._config_cache[cache_key]) { - for (const ck in this._config_cache[cache_key]) { - if (ck.substr(0,1) === '!') delete this._config_cache[path.join(cp, ck.substr(1))]; - } - } - - // Allow JSON files to create or overwrite other config file data - // by prefixing the outer variable name with ! e.g. !smtp.ini - for (const key in result) { - if (key.substr(0,1) !== '!') continue; - const fn = key.substr(1); - // Overwrite the config cache for this filename - console.log(`Overriding file ${fn} with config from ${name}`); - this._config_cache[path.join(cp, fn)] = result[key]; - } + const cache_key = this.get_cache_key(name, options) + try { + switch (type) { + case 'ini': + result = cfrType.load(name, options, this.regex) + break + case 'hjson': + case 'json': + case 'yaml': + result = cfrType.load(name) + this.process_file_overrides(name, options, result) + break + // case 'binary': + default: + result = cfrType.load(name, type, options, this.regex) + } + this._config_cache[cache_key] = result + } catch (err) { + console.error(err.message) + if (this._config_cache[cache_key]) { + return this._config_cache[cache_key] + } + return cfrType.empty(options, type) + } + return result + } + + process_file_overrides(name, options, result) { + // We might be re-loading this file: + // * build a list of cached overrides + // * remove them and add them back + const cp = this.config_path + const cache_key = this.get_cache_key(name, options) + + if (this._config_cache[cache_key]) { + for (const ck in this._config_cache[cache_key]) { + if (ck.substr(0, 1) === '!') + delete this._config_cache[path.join(cp, ck.substr(1))] + } } - fsWatchDir (dirPath) { - - if (this._watchers[dirPath]) return; - const watchOpts = { persistent: false, recursive: true } - - // recursive is only supported on Windows (win32, win64) and macOS (darwin) - if (!/win/.test(process.platform)) watchOpts.recursive = false - - this._watchers[dirPath] = fs.watch(dirPath, watchOpts, (fse, filename) => { - // console.log(`event: ${fse}, ${filename}`); - if (!filename) return; - const full_path = path.join(dirPath, filename); - const args = this._read_args[dirPath]; - // console.log(args); - if (this._sedation_timers[full_path]) { - clearTimeout(this._sedation_timers[full_path]); - } - this._sedation_timers[full_path] = setTimeout(() => { - delete this._sedation_timers[full_path]; - args.opts.watchCb(); - }, 2 * 1000); - }); + // Allow JSON files to create or overwrite other config file data + // by prefixing the outer variable name with ! e.g. !smtp.ini + for (const key in result) { + if (key.substr(0, 1) !== '!') continue + const fn = key.substr(1) + // Overwrite the config cache for this filename + console.log(`Overriding file ${fn} with config from ${name}`) + this._config_cache[path.join(cp, fn)] = result[key] } + } + + fsWatchDir(dirPath) { + if (this._watchers[dirPath]) return + const watchOpts = { persistent: false, recursive: true } + + // recursive is only supported on Windows (win32, win64) and macOS (darwin) + if (!/win/.test(process.platform)) watchOpts.recursive = false + + this._watchers[dirPath] = fs.watch(dirPath, watchOpts, (fse, filename) => { + // console.log(`event: ${fse}, ${filename}`); + if (!filename) return + const full_path = path.join(dirPath, filename) + const args = this._read_args[dirPath] + // console.log(args); + if (this._sedation_timers[full_path]) { + clearTimeout(this._sedation_timers[full_path]) + } + this._sedation_timers[full_path] = setTimeout(() => { + delete this._sedation_timers[full_path] + args.opts.watchCb() + }, 2 * 1000) + }) + } } -module.exports = new cfreader(); +module.exports = new cfreader() -function isDirectory (filepath) { - return new Promise((resolve, reject) => { - fs.stat(filepath, (err, stat) => { - if (err) return reject(err); - resolve(stat.isDirectory()); - }) +function isDirectory(filepath) { + return new Promise((resolve, reject) => { + fs.stat(filepath, (err, stat) => { + if (err) return reject(err) + resolve(stat.isDirectory()) }) + }) } -function fsReadDir (filepath) { - return new Promise((resolve, reject) => { - fs.readdir(filepath, (err, fileList) => { - if (err) return reject(err); - resolve(fileList); - }) +function fsReadDir(filepath) { + return new Promise((resolve, reject) => { + fs.readdir(filepath, (err, fileList) => { + if (err) return reject(err) + resolve(fileList) }) + }) } diff --git a/package.json b/package.json index ca92bab..544fdac 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,11 @@ "url": "git@github.com:haraka/haraka-config.git" }, "main": "config.js", - "files": ["readers","configfile.js","CHANGELOG.md"], + "files": [ + "readers", + "configfile.js", + "CHANGELOG.md" + ], "engines": { "node": ">=16" }, diff --git a/readers/binary.js b/readers/binary.js index 1c0296b..6acc3bd 100644 --- a/readers/binary.js +++ b/readers/binary.js @@ -1,20 +1,20 @@ -'use strict'; +'use strict' -const fs = require('fs'); +const fs = require('fs') exports.load = (name) => { - return fs.readFileSync(name); + return fs.readFileSync(name) } exports.loadPromise = (name) => { - return new Promise((resolve, reject) => { - fs.readFile(name, (err, content) => { - if (err) return reject(err); - resolve({ path: name, data: content }); - }); - }); + return new Promise((resolve, reject) => { + fs.readFile(name, (err, content) => { + if (err) return reject(err) + resolve({ path: name, data: content }) + }) + }) } exports.empty = () => { - return null; + return null } diff --git a/readers/flat.js b/readers/flat.js index 2ff5c05..829eeda 100644 --- a/readers/flat.js +++ b/readers/flat.js @@ -1,69 +1,69 @@ -'use strict'; +'use strict' -const fs = require('fs'); +const fs = require('fs') exports.load = (name, type, options, regex) => { - let result = []; + let result = [] - let data = fs.readFileSync(name, 'UTF-8'); - if (type === 'data') { - while (data.length > 0) { - const match = data.match(/^([^\r\n]*)\r?\n?/); - result.push(match[1]); - data = data.slice(match[0].length); - } - return result; + let data = fs.readFileSync(name, 'UTF-8') + if (type === 'data') { + while (data.length > 0) { + const match = data.match(/^([^\r\n]*)\r?\n?/) + result.push(match[1]) + data = data.slice(match[0].length) } + return result + } - data.split(/\r\n|\r|\n/).forEach( (line) => { - if (regex.comment.test(line)) return; - if (regex.blank.test(line)) return; + data.split(/\r\n|\r|\n/).forEach((line) => { + if (regex.comment.test(line)) return + if (regex.blank.test(line)) return - const line_data = regex.line.exec(line); - if (!line_data) return; + const line_data = regex.line.exec(line) + if (!line_data) return - result.push(line_data[1].trim()); - }) + result.push(line_data[1].trim()) + }) - if (result.length && type !== 'list' && type !== 'data') { - result = result[0]; - if (options && in_array(result, options.booleans)) { - return regex.is_truth.test(result); - } - if (regex.is_integer.test(result)) { - return parseInt(result, 10); - } - if (regex.is_float.test(result)) { - return parseFloat(result); - } - return result; + if (result.length && type !== 'list' && type !== 'data') { + result = result[0] + if (options && in_array(result, options.booleans)) { + return regex.is_truth.test(result) } - - // Return hostname for 'me' if no result - if (/\/me$/.test(name) && !(result && result.length)) { - return [ require('os').hostname() ]; + if (regex.is_integer.test(result)) { + return parseInt(result, 10) } - - // For value types with no result - if (!(type && (type === 'list' || type === 'data'))) { - if (!(result && result.length)) return null; + if (regex.is_float.test(result)) { + return parseFloat(result) } + return result + } + + // Return hostname for 'me' if no result + if (/\/me$/.test(name) && !(result && result.length)) { + return [require('os').hostname()] + } - return result; + // For value types with no result + if (!(type && (type === 'list' || type === 'data'))) { + if (!(result && result.length)) return null + } + + return result } exports.empty = (options, type) => { - switch (type) { - case 'flat': - case 'value': - return null; - default: - return []; - } + switch (type) { + case 'flat': + case 'value': + return null + default: + return [] + } } -function in_array (item, array) { - if (!array) return false; - if (!Array.isArray(array)) return false; - return (array.indexOf(item) !== -1); +function in_array(item, array) { + if (!array) return false + if (!Array.isArray(array)) return false + return array.indexOf(item) !== -1 } diff --git a/readers/hjson.js b/readers/hjson.js index e7e8a58..b3d71f5 100644 --- a/readers/hjson.js +++ b/readers/hjson.js @@ -1,12 +1,12 @@ -'use strict'; +'use strict' -const fs = require('fs'); -const hjson = require('hjson'); +const fs = require('fs') +const hjson = require('hjson') exports.load = (name) => { - return hjson.parse(fs.readFileSync(name, "utf8")); + return hjson.parse(fs.readFileSync(name, 'utf8')) } exports.empty = () => { - return {}; + return {} } diff --git a/readers/ini.js b/readers/ini.js index 8c4d171..b5098f1 100644 --- a/readers/ini.js +++ b/readers/ini.js @@ -1,122 +1,122 @@ -'use strict'; +'use strict' -const fs = require('fs'); +const fs = require('fs') exports.load = (name, options, regex) => { - let result = { main: {} }; - let current_sect = result.main; - let current_sect_name = 'main'; - this.bool_matches = []; - if (options && options.booleans) { - this.bool_matches = options.booleans.slice(); - } - - // Initialize any booleans - result = this.init_booleans(options, result); - - let match; - let setter; - let pre = ''; - - fs.readFileSync(name, 'UTF-8') - .split(/\r\n|\r|\n/) - .forEach( (line) => { - if (regex.comment.test(line)) return; - if (regex.blank.test(line) ) return; - - match = regex.section.exec(line); - if (match) { - if (!result[match[1]]) result[match[1]] = {}; - current_sect = result[match[1]]; - current_sect_name = match[1]; - return; - } - - if (regex.continuation.test(line)) { - pre += line.replace(regex.continuation, ''); - return; - } - - line = `${pre}${line}`; - pre = ''; - - match = regex.param.exec(line); - if (!match) { - exports.logger(`Invalid line in config file '${name}': ${line}`); - return; - } - - const is_array_match = regex.is_array.exec(match[1]); - if (is_array_match) { - setter = function (key, value) { - key = key.replace('[]', ''); - if (! current_sect[key]) current_sect[key] = []; - current_sect[key].push(value); - }; - } - else { - setter = function (key, value) { current_sect[key] = value; }; - } - - if (options && Array.isArray(options.booleans) && - ( - exports.bool_matches.indexOf(`${current_sect_name}.${match[1]}`) !== -1 - || - exports.bool_matches.indexOf(`*.${match[1]}`) !== -1 - )) { - current_sect[match[1]] = regex.is_truth.test(match[2]); - // exports.logger(`Using boolean ${current_sect[match[1]]} for ${current_sect_name}.${match[1]}=${match[2]}`, 'logdebug'); - } - else if (regex.is_integer.test(match[2])) { - setter(match[1], parseInt(match[2], 10)); - } - else if (regex.is_float.test(match[2])) { - setter(match[1], parseFloat(match[2])); - } - else { - setter(match[1], match[2]); - } - }); - - return result; + let result = { main: {} } + let current_sect = result.main + let current_sect_name = 'main' + this.bool_matches = [] + if (options && options.booleans) { + this.bool_matches = options.booleans.slice() + } + + // Initialize any booleans + result = this.init_booleans(options, result) + + let match + let setter + let pre = '' + + fs.readFileSync(name, 'UTF-8') + .split(/\r\n|\r|\n/) + .forEach((line) => { + if (regex.comment.test(line)) return + if (regex.blank.test(line)) return + + match = regex.section.exec(line) + if (match) { + if (!result[match[1]]) result[match[1]] = {} + current_sect = result[match[1]] + current_sect_name = match[1] + return + } + + if (regex.continuation.test(line)) { + pre += line.replace(regex.continuation, '') + return + } + + line = `${pre}${line}` + pre = '' + + match = regex.param.exec(line) + if (!match) { + exports.logger(`Invalid line in config file '${name}': ${line}`) + return + } + + const is_array_match = regex.is_array.exec(match[1]) + if (is_array_match) { + setter = function (key, value) { + key = key.replace('[]', '') + if (!current_sect[key]) current_sect[key] = [] + current_sect[key].push(value) + } + } else { + setter = function (key, value) { + current_sect[key] = value + } + } + + if ( + options && + Array.isArray(options.booleans) && + (exports.bool_matches.indexOf(`${current_sect_name}.${match[1]}`) !== + -1 || + exports.bool_matches.indexOf(`*.${match[1]}`) !== -1) + ) { + current_sect[match[1]] = regex.is_truth.test(match[2]) + // exports.logger(`Using boolean ${current_sect[match[1]]} for ${current_sect_name}.${match[1]}=${match[2]}`, 'logdebug'); + } else if (regex.is_integer.test(match[2])) { + setter(match[1], parseInt(match[2], 10)) + } else if (regex.is_float.test(match[2])) { + setter(match[1], parseFloat(match[2])) + } else { + setter(match[1], match[2]) + } + }) + + return result } exports.empty = (options) => { - this.bool_matches = []; - return this.init_booleans(options, { main: {} }); + this.bool_matches = [] + return this.init_booleans(options, { main: {} }) } exports.init_booleans = (options, result) => { - if (!options) return result; - if (!Array.isArray(options.booleans)) return result; + if (!options) return result + if (!Array.isArray(options.booleans)) return result - // console.log(options.booleans); - for (let i=0; i { - console.log(msg); + console.log(msg) } diff --git a/readers/js.js b/readers/js.js index c289ac6..af6f9c1 100644 --- a/readers/js.js +++ b/readers/js.js @@ -1,9 +1,9 @@ -'use strict'; +'use strict' exports.load = (name) => { - return require(name); + return require(name) } exports.empty = () => { - return {}; + return {} } diff --git a/readers/json.js b/readers/json.js index a4d51c7..daa1c18 100644 --- a/readers/json.js +++ b/readers/json.js @@ -1,11 +1,11 @@ -'use strict'; +'use strict' -const fs = require('fs'); +const fs = require('fs') exports.load = (name) => { - return JSON.parse(fs.readFileSync(name)); + return JSON.parse(fs.readFileSync(name)) } exports.empty = () => { - return {}; -} \ No newline at end of file + return {} +} diff --git a/readers/yaml.js b/readers/yaml.js index 657bf30..e4b7d94 100644 --- a/readers/yaml.js +++ b/readers/yaml.js @@ -1,12 +1,12 @@ -'use strict'; +'use strict' -const fs = require('fs'); -const yaml = require('js-yaml'); +const fs = require('fs') +const yaml = require('js-yaml') exports.load = (name) => { - return yaml.load(fs.readFileSync(name, 'utf8')); + return yaml.load(fs.readFileSync(name, 'utf8')) } exports.empty = () => { - return {}; + return {} } diff --git a/test/config.js b/test/config.js index 29360a3..ac3929d 100644 --- a/test/config.js +++ b/test/config.js @@ -1,567 +1,574 @@ - const assert = require('assert') -const fs = require('fs') -const os = require('os') -const path = require('path') - -function cb () { return false; } -const opts = { booleans: ['arg1'] }; - -function clearRequireCache () { - // node_unit runs all the tests in the same process, so the process.env - // changes affect other tests. Icky. Work around by invalidating - // the require cache, so config and configfile re-initialize - delete require.cache[ - `${path.resolve(__dirname, '..','config')}.js` - ]; - delete require.cache[ - `${path.resolve(__dirname, '..','configfile')}.js` - ]; +// const { beforeEach, describe, it } = require('node:test') +const fs = require('fs') +const os = require('os') +const path = require('path') + +function cb() { + return false +} +const opts = { booleans: ['arg1'] } + +function clearRequireCache() { + // node_unit runs all the tests in the same process, so the process.env + // changes affect other tests. Icky. Work around by invalidating + // the require cache, so config and configfile re-initialize + delete require.cache[`${path.resolve(__dirname, '..', 'config')}.js`] + delete require.cache[`${path.resolve(__dirname, '..', 'configfile')}.js`] } -function testSetup (done) { - process.env.NODE_ENV = 'test' - process.env.HARAKA = ''; - process.env.WITHOUT_CONFIG_CACHE = '1'; - clearRequireCache(); - this.config = require('../config'); - done(); +function testSetup(done) { + process.env.NODE_ENV = 'test' + process.env.HARAKA = '' + process.env.WITHOUT_CONFIG_CACHE = '1' + clearRequireCache() + this.config = require('../config') + done() } describe('config', function () { + beforeEach(testSetup) - beforeEach(testSetup) + it('new', function () { + assert.equal(path.resolve('test', 'config'), this.config.root_path) + }) - it('new', function (done) { - assert.equal(path.resolve('test','config'), this.config.root_path); - done(); - }) + it('module_config', function () { + const c = this.config.module_config('foo', 'bar') + assert.equal(c.root_path, path.join('foo', 'config')) + assert.equal(c.overrides_path, path.join('bar', 'config')) + }) - it('module_config', function (done) { - const c = this.config.module_config('foo', 'bar'); - assert.equal(c.root_path, path.join('foo','config')); - assert.equal(c.overrides_path, path.join('bar','config')); - done(); + describe('config_path', function () { + it('config_path process.env.HARAKA', function () { + process.env.HARAKA = '/tmp' + clearRequireCache() + const config = require('../config') + assert.equal(config.root_path, path.join('/tmp', 'config')) }) - describe('config_path', function () { - it('config_path process.env.HARAKA', function (done) { - process.env.HARAKA = '/tmp'; - clearRequireCache(); - const config = require('../config'); - // console.log(config); - assert.equal(config.root_path, path.join('/tmp','config')); - done(); - }) - - it('config_path process.env.NODE_ENV', function (done) { - process.env.HARAKA = ''; - process.env.NODE_ENV = 'not-test'; - clearRequireCache(); - const config = require('../config'); - // ./config doesn't exist so path will be resolved ./ - assert.ok(/haraka-config$/.test(config.root_path)); - process.env.NODE_ENV = 'test'; - done(); - }) + it('config_path process.env.NODE_ENV', function () { + process.env.HARAKA = '' + process.env.NODE_ENV = 'not-test' + clearRequireCache() + const config = require('../config') + assert.ok(/haraka-config$/.test(config.root_path)) }) + }) - describe('arrange_args', function () { - beforeEach(testSetup) - - // config.get('name'); - it('name', function (done) { - assert.deepEqual( - this.config.arrange_args(['test.ini']), - ['test.ini', 'ini', undefined, undefined]); - done(); - }) - // config.get('name', type); - it('name, type', function (done) { - assert.deepEqual( - this.config.arrange_args(['test.ini','ini']), - ['test.ini', 'ini', undefined, undefined]); - done(); - }) - - // config.get('name', cb); - it('name, callback', function (done) { - assert.deepEqual( - this.config.arrange_args(['test.ini',cb]), - ['test.ini', 'ini', cb, undefined]); - done(); - }) - - // config.get('name', cb, options); - it('name, callback, options', function (done) { - assert.deepEqual( - this.config.arrange_args(['test.ini',cb,opts]), - ['test.ini', 'ini', cb, opts]); - done(); - }) - - // config.get('name', options); - it('name, options', function (done) { - assert.deepEqual( - this.config.arrange_args(['test.ini',opts]), - ['test.ini', 'ini', undefined, opts]); - done(); - }) - - // config.get('name', type, cb); - it('name, type, callback', function (done) { - assert.deepEqual( - this.config.arrange_args(['test.ini','ini',cb]), - ['test.ini', 'ini', cb, undefined]); - done(); - }) - - // config.get('name', type, options); - it('name, type, options', function (done) { - assert.deepEqual( - this.config.arrange_args(['test.ini','ini',opts]), - ['test.ini', 'ini', undefined, opts]); - done(); - }) - - // config.get('name', type, cb, options); - it('name, type, callback, options', function (done) { - assert.deepEqual( - this.config.arrange_args(['test.ini','ini',cb, opts]), - ['test.ini', 'ini', cb, opts]); - done(); - }) - - // config.get('name', list, cb, options); - it('name, list type, callback, options', function (done) { - assert.deepEqual( - this.config.arrange_args(['test.ini','list',cb, opts]), - ['test.ini', 'list', cb, opts]); - done(); - }) - - // config.get('name', binary, cb, options); - it('name, binary type, callback, options', function (done) { - assert.deepEqual( - this.config.arrange_args(['test.ini','binary',cb, opts]), - ['test.ini', 'binary', cb, opts]); - done(); - }) - - // config.get('name', type, cb, options); - it('name, value type, callback, options', function (done) { - assert.deepEqual( - this.config.arrange_args(['test.ini','value',cb, opts]), - ['test.ini', 'value', cb, opts]); - done(); - }) - - // config.get('name', type, cb, options); - it('name, hjson type, callback, options', function (done) { - assert.deepEqual( - this.config.arrange_args(['test.ini','hjson',cb, opts]), - ['test.ini', 'hjson', cb, opts]); - done(); - }) - - // config.get('name', type, cb, options); - it('name, json type, callback, options', function (done) { - assert.deepEqual( - this.config.arrange_args(['test.ini','json',cb, opts]), - ['test.ini', 'json', cb, opts]); - done(); - }) - - // config.get('name', type, cb, options); - it('name, data type, callback, options', function (done) { - assert.deepEqual( - this.config.arrange_args(['test.ini','data',cb, opts]), - ['test.ini', 'data', cb, opts]); - done(); - }) - }) -}) - -const hjsonRes = { - matt: 'waz here and also made comments', - differentArray: [ 'has element #1', 'has element #2' ], - object: { - 'has a property one': 'with a value A', - 'has a property two': 'with a value B' - } -} - -const jsonRes = { - matt: 'waz here', - array: [ 'has an element' ], - objecty: { 'has a property': 'with a value' } -} - -const yamlRes = { - main: { - bool_true: true, - bool_false: false, - str_true: true, - str_false: false - }, - sect1: { - bool_true: true, - bool_false: false, - str_true: true, - str_false: false - }, - whitespace: { - str_no_trail: true, - str_trail: true - }, - matt: 'waz here', - array: ['has an element'], - objecty: { - 'has a property': 'with a value' - } -} - -function _test_get (done, name, type, callback, options, expected) { - const config = require('../config'); - const cfg = config.get(name, type, callback, options); - assert.deepEqual(cfg, expected); - done(); -} - -function _test_int (done, name, default_value, expected) { - const config = require('../config'); - const result = config.getInt(name, default_value); - if (result) { - assert.equal(typeof result, 'number'); - } - assert.deepEqual(result, expected); - done(); -} - -describe('get', function () { + describe('arrange_args', function () { beforeEach(testSetup) - // config.get('name'); - it('test (non-existing)', function (done) { - _test_get(done, 'test', null, null, null, null); + it('name', function () { + assert.deepEqual(this.config.arrange_args(['test.ini']), [ + 'test.ini', + 'ini', + undefined, + undefined, + ]) }) - it('test (non-existing, cached)', function (done) { - process.env.WITHOUT_CONFIG_CACHE= ''; - const cfg = this.config.get('test', null, null); - assert.deepEqual(cfg, null); - done(); + it('name, type', function () { + assert.deepEqual(this.config.arrange_args(['test.ini', 'ini']), [ + 'test.ini', + 'ini', + undefined, + undefined, + ]) }) - // config.get('test.ini'); - it('test.ini, no opts', function (done) { - _test_get(done, 'test.ini', null, null, null, { - main: { bool_true: 'true', bool_false: 'false', str_true: 'true', str_false: 'false' }, - sect1: { bool_true: 'true', bool_false: 'false', str_true: 'true', str_false: 'false' }, - whitespace: { str_no_trail: 'true', str_trail: 'true' }, - funnychars: { 'results.auth/auth_base.fail': 'fun' }, - empty_values: { first: undefined, second: undefined }, - has_ipv6: { '2605:ae00:329::2': undefined }, - array_test: { - hostlist: [ 'first_host', 'second_host', 'third_host' ], - intlist: [ '123', '456', '789' ], - }, - 'foo.com': { is_bool: 'true' }, - 'bar.com': { is_bool: 'false' }, - has_nums: { integer: 454, float: 10.5 }, - }) + it('name, callback', function () { + assert.deepEqual(this.config.arrange_args(['test.ini', cb]), [ + 'test.ini', + 'ini', + cb, + undefined, + ]) }) - it('test.ini, opts', function (done) { - _test_get(done, 'test.ini', 'ini', null, { - booleans: [ - '*.bool_true', - '*.bool_false', - ] - }, { - main: { bool_true: true, bool_false: false, str_true: 'true', str_false: 'false' }, - sect1: { bool_true: true, bool_false: false, str_true: 'true', str_false: 'false' }, - whitespace: { str_no_trail: 'true', str_trail: 'true' }, - funnychars: { 'results.auth/auth_base.fail': 'fun' }, - empty_values: { first: undefined, second: undefined }, - has_ipv6: { '2605:ae00:329::2': undefined }, - array_test: { - hostlist: [ 'first_host', 'second_host', 'third_host' ], - intlist: [ '123', '456', '789' ], - }, - 'foo.com': { is_bool: 'true' }, - 'bar.com': { is_bool: 'false' }, - has_nums: { integer: 454, float: 10.5 }, - }) + it('name, callback, options', function () { + assert.deepEqual(this.config.arrange_args(['test.ini', cb, opts]), [ + 'test.ini', + 'ini', + cb, + opts, + ]) }) - // config.get('test.txt'); - it('test.txt', function (done) { - _test_get(done, 'test.txt', null, null, null, null); + it('name, options', function () { + assert.deepEqual(this.config.arrange_args(['test.ini', opts]), [ + 'test.ini', + 'ini', + undefined, + opts, + ]) }) - // config.get('test.flat'); - it('test.flat, type=', function (done) { - _test_get(done, 'test.flat', null, null, null, 'line1'); + it('name, type, callback', function () { + assert.deepEqual(this.config.arrange_args(['test.ini', 'ini', cb]), [ + 'test.ini', + 'ini', + cb, + undefined, + ]) }) - // NOTE: the test.flat file had to be duplicated for these tests, to avoid - // the config cache from returning invalid results. - - // config.get('test.flat', 'value'); - it('test.flat, type=value', function (done) { - _test_get(done, 'test.value', 'value', null, null, 'line1'); + it('name, type, options', function () { + assert.deepEqual(this.config.arrange_args(['test.ini', 'ini', opts]), [ + 'test.ini', + 'ini', + undefined, + opts, + ]) }) - // config.get('test.flat', 'list'); - it('test.flat, type=list', function (done) { - _test_get(done, 'test.list', 'list', null, null, - ['line1', 'line2','line3', 'line5'] ); + it('name, type, callback, options', function () { + assert.deepEqual( + this.config.arrange_args(['test.ini', 'ini', cb, opts]), + ['test.ini', 'ini', cb, opts], + ) }) - // config.get('test.flat', 'data'); - it('test.flat, type=data', function (done) { - _test_get(done, 'test.data', 'data', null, null, - ['line1', 'line2','line3', '', 'line5'] ); + it('name, list type, callback, options', function () { + assert.deepEqual( + this.config.arrange_args(['test.ini', 'list', cb, opts]), + ['test.ini', 'list', cb, opts], + ) }) - // config.get('test.hjson'); - it('test.hjson, type=', function (done) { - _test_get(done, 'test.hjson', null, null, null, hjsonRes); + it('name, binary type, callback, options', function () { + assert.deepEqual( + this.config.arrange_args(['test.ini', 'binary', cb, opts]), + ['test.ini', 'binary', cb, opts], + ) }) - // config.get('test.hjson', 'hjson'); - it('test.hjson, type=hjson', function (done) { - _test_get(done, 'test.hjson', 'hjson', null, null, hjsonRes); + it('name, value type, callback, options', function () { + assert.deepEqual( + this.config.arrange_args(['test.ini', 'value', cb, opts]), + ['test.ini', 'value', cb, opts], + ) }) - // config.get('test.json'); - it('test.json, type=', function (done) { - _test_get(done, 'test.json', null, null, null, jsonRes); + it('name, hjson type, callback, options', function () { + assert.deepEqual( + this.config.arrange_args(['test.ini', 'hjson', cb, opts]), + ['test.ini', 'hjson', cb, opts], + ) }) - // config.get('test.json', 'json'); - it('test.json, type=json', function (done) { - _test_get(done, 'test.json', 'json', null, null, jsonRes); + // config.get('name', type, cb, options); + it('name, json type, callback, options', function () { + assert.deepEqual( + this.config.arrange_args(['test.ini', 'json', cb, opts]), + ['test.ini', 'json', cb, opts], + ) }) - // config.get('test.yaml'); - it('test.yaml, type=', function (done) { - _test_get(done, 'test.yaml', null, null, null, yamlRes); - }) - // config.get('test.yaml', 'yaml'); - it('test.yaml, type=yaml', function (done) { - _test_get(done, 'test.yaml', 'yaml', null, null, yamlRes); - }) - // config.get('missing2.hjson'); - it('missing2.yaml, asked for hjson', function (done) { - _test_get(done, 'missing2.hjson', 'hjson', null, null, {"matt": "waz here - hjson type"}); - }) - // config.get('missing.json'); - it('missing.yaml, asked for json', function (done) { - _test_get(done, 'missing.json', 'json', null, null, {"matt": "waz here"}); - }) - - it('test.bin, type=binary', function (done) { - const res = this.config.get('test.binary', 'binary'); - assert.equal(res.length, 120); - assert.ok(Buffer.isBuffer(res)); - done(); - }) - - it('fully qualified path: /etc/services', function (done) { - let res; - if (/^win/.test(process.platform)) { - res = this.config.get('c:\\windows\\win.ini', 'list'); - } - else { - res = this.config.get('/etc/services', 'list'); - } - assert.ok(res.length); - done(); + // config.get('name', type, cb, options); + it('name, data type, callback, options', function () { + assert.deepEqual( + this.config.arrange_args(['test.ini', 'data', cb, opts]), + ['test.ini', 'data', cb, opts], + ) }) + }) }) -describe('merged', function () { - beforeEach(testSetup) - - it('before_merge', function (done) { - const lc = this.config.module_config( - path.join('test','default') - ); - assert.deepEqual(lc.get('test.ini'), - { main: {}, defaults: { one: 'one', two: 'two' } } - ); - done(); - }) - - it('after_merge', function (done) { - const lc = this.config.module_config( - path.join('test','default'), - path.join('test','override') - ); - assert.deepEqual(lc.get('test.ini'), - { main: {}, defaults: { one: 'three', two: 'four' } } - ); - done(); - }) +const hjsonRes = { + matt: 'waz here and also made comments', + differentArray: ['has element #1', 'has element #2'], + object: { + 'has a property one': 'with a value A', + 'has a property two': 'with a value B', + }, +} - it('flat overridden', function (done) { - const lc = this.config.module_config( - path.join('test','default'), - path.join('test','override') - ); - assert.equal(lc.get('test.flat'), 'flatoverrode'); - done(); - }) -}) +const jsonRes = { + matt: 'waz here', + array: ['has an element'], + objecty: { 'has a property': 'with a value' }, +} -describe('getInt', function () { - beforeEach(testSetup) +const yamlRes = { + main: { + bool_true: true, + bool_false: false, + str_true: true, + str_false: false, + }, + sect1: { + bool_true: true, + bool_false: false, + str_true: true, + str_false: false, + }, + whitespace: { + str_no_trail: true, + str_trail: true, + }, + matt: 'waz here', + array: ['has an element'], + objecty: { + 'has a property': 'with a value', + }, +} - // config.get('name'); - it('empty filename is NaN', function (done) { - const result = this.config.getInt(); - assert.equal(typeof result, 'number'); - assert.ok(isNaN(result)); - done(); - }) +function _test_get(name, type, callback, options, expected) { + const config = require('../config') + const cfg = config.get(name, type, callback, options) + assert.deepEqual(cfg, expected) +} - it('empty/missing file contents is NaN', function (done) { - const result = this.config.getInt('test-non-exist'); - assert.equal(typeof result, 'number'); - assert.ok(isNaN(result)); - done(); - }) +function _test_int(name, default_value, expected) { + const config = require('../config') + const result = config.getInt(name, default_value) + if (result) assert.equal(typeof result, 'number') + assert.deepEqual(result, expected) +} - it('non-existing file returns default', function (done) { - _test_int(done, 'test-non-exist', 5, 5); - }) +describe('get', function () { + beforeEach(testSetup) + + // config.get('name'); + it('test (non-existing)', function () { + _test_get('test', null, null, null, null) + }) + + it('test (non-existing, cached)', function (done) { + process.env.WITHOUT_CONFIG_CACHE = '' + const cfg = this.config.get('test', null, null) + assert.deepEqual(cfg, null) + done() + }) + + // config.get('test.ini'); + it('test.ini, no opts', function () { + _test_get('test.ini', null, null, null, { + main: { + bool_true: 'true', + bool_false: 'false', + str_true: 'true', + str_false: 'false', + }, + sect1: { + bool_true: 'true', + bool_false: 'false', + str_true: 'true', + str_false: 'false', + }, + whitespace: { str_no_trail: 'true', str_trail: 'true' }, + funnychars: { 'results.auth/auth_base.fail': 'fun' }, + empty_values: { first: undefined, second: undefined }, + has_ipv6: { '2605:ae00:329::2': undefined }, + array_test: { + hostlist: ['first_host', 'second_host', 'third_host'], + intlist: ['123', '456', '789'], + }, + 'foo.com': { is_bool: 'true' }, + 'bar.com': { is_bool: 'false' }, + has_nums: { integer: 454, float: 10.5 }, + }) + }) + + it('test.ini, opts', function () { + _test_get( + 'test.ini', + 'ini', + null, + { + booleans: ['*.bool_true', '*.bool_false'], + }, + { + main: { + bool_true: true, + bool_false: false, + str_true: 'true', + str_false: 'false', + }, + sect1: { + bool_true: true, + bool_false: false, + str_true: 'true', + str_false: 'false', + }, + whitespace: { str_no_trail: 'true', str_trail: 'true' }, + funnychars: { 'results.auth/auth_base.fail': 'fun' }, + empty_values: { first: undefined, second: undefined }, + has_ipv6: { '2605:ae00:329::2': undefined }, + array_test: { + hostlist: ['first_host', 'second_host', 'third_host'], + intlist: ['123', '456', '789'], + }, + 'foo.com': { is_bool: 'true' }, + 'bar.com': { is_bool: 'false' }, + has_nums: { integer: 454, float: 10.5 }, + }, + ) + }) + + // config.get('test.txt'); + it('test.txt', function () { + _test_get('test.txt', null, null, null, null) + }) + + // config.get('test.flat'); + it('test.flat, type=', function () { + _test_get('test.flat', null, null, null, 'line1') + }) + + // NOTE: the test.flat file had to be duplicated for these tests, to avoid + // the config cache from returning invalid results. + + // config.get('test.flat', 'value'); + it('test.flat, type=value', function () { + _test_get('test.value', 'value', null, null, 'line1') + }) + + // config.get('test.flat', 'list'); + it('test.flat, type=list', function () { + _test_get('test.list', 'list', null, null, [ + 'line1', + 'line2', + 'line3', + 'line5', + ]) + }) + + // config.get('test.flat', 'data'); + it('test.flat, type=data', function () { + _test_get('test.data', 'data', null, null, [ + 'line1', + 'line2', + 'line3', + '', + 'line5', + ]) + }) + + // config.get('test.hjson'); + it('test.hjson, type=', function () { + _test_get('test.hjson', null, null, null, hjsonRes) + }) + + // config.get('test.hjson', 'hjson'); + it('test.hjson, type=hjson', function () { + _test_get('test.hjson', 'hjson', null, null, hjsonRes) + }) + + // config.get('test.json'); + it('test.json, type=', function () { + _test_get('test.json', null, null, null, jsonRes) + }) + + // config.get('test.json', 'json'); + it('test.json, type=json', function () { + _test_get('test.json', 'json', null, null, jsonRes) + }) + + // config.get('test.yaml'); + it('test.yaml, type=', function () { + _test_get('test.yaml', null, null, null, yamlRes) + }) + // config.get('test.yaml', 'yaml'); + it('test.yaml, type=yaml', function () { + _test_get('test.yaml', 'yaml', null, null, yamlRes) + }) + // config.get('missing2.hjson'); + it('missing2.yaml, asked for hjson', function () { + _test_get('missing2.hjson', 'hjson', null, null, { + matt: 'waz here - hjson type', + }) + }) + // config.get('missing.json'); + it('missing.yaml, asked for json', function () { + _test_get('missing.json', 'json', null, null, { matt: 'waz here' }) + }) + + it('test.bin, type=binary', function (done) { + const res = this.config.get('test.binary', 'binary') + assert.equal(res.length, 120) + assert.ok(Buffer.isBuffer(res)) + done() + }) + + it('fully qualified path: /etc/services', function (done) { + let res + if (/^win/.test(process.platform)) { + res = this.config.get('c:\\windows\\win.ini', 'list') + } else { + res = this.config.get('/etc/services', 'list') + } + assert.ok(res.length) + done() + }) +}) - it('test.int equals 6', function (done) { - _test_int(done, 'test.int', undefined, 6); - }) +describe('merged', function () { + beforeEach(testSetup) + + it('before_merge', function (done) { + const lc = this.config.module_config(path.join('test', 'default')) + assert.deepEqual(lc.get('test.ini'), { + main: {}, + defaults: { one: 'one', two: 'two' }, + }) + done() + }) + + it('after_merge', function (done) { + const lc = this.config.module_config( + path.join('test', 'default'), + path.join('test', 'override'), + ) + assert.deepEqual(lc.get('test.ini'), { + main: {}, + defaults: { one: 'three', two: 'four' }, + }) + done() + }) + + it('flat overridden', function (done) { + const lc = this.config.module_config( + path.join('test', 'default'), + path.join('test', 'override'), + ) + assert.equal(lc.get('test.flat'), 'flatoverrode') + done() + }) +}) - it('test.int equals 6 (with default 7)', function (done) { - _test_int(done, 'test.int', 7, 6); - }) +describe('getInt', function () { + beforeEach(testSetup) + + // config.get('name'); + it('empty filename is NaN', function (done) { + const result = this.config.getInt() + assert.equal(typeof result, 'number') + assert.ok(isNaN(result)) + done() + }) + + it('empty/missing file contents is NaN', function (done) { + const result = this.config.getInt('test-non-exist') + assert.equal(typeof result, 'number') + assert.ok(isNaN(result)) + done() + }) + + it('non-existing file returns default', function () { + _test_int('test-non-exist', 5, 5) + }) + + it('test.int equals 6', function () { + _test_int('test.int', undefined, 6) + }) + + it('test.int equals 6 (with default 7)', function () { + _test_int('test.int', 7, 6) + }) }) -const tmpFile = path.resolve('test', 'config', 'dir', '4.ext'); +const tmpFile = path.resolve('test', 'config', 'dir', '4.ext') -function cleanup (done) { - fs.unlink(tmpFile, () => { - done(); - }) +function cleanup(done) { + fs.unlink(tmpFile, () => { + done() + }) } describe('getDir', function () { - beforeEach(function (done) { - process.env.HARAKA = ''; - clearRequireCache(); - this.config = require('../config'); - cleanup(done); - }) - - it('loads all files in dir', function (done) { - this.config.getDir('dir', { type: 'binary' }, (err, files) => { - assert.ifError(err); - assert.equal(err, null); - assert.equal(files.length, 3); - assert.equal(files[0].data, `contents1${os.EOL}`); - assert.equal(files[2].data, `contents3${os.EOL}`); - done(); - }) - }) - - it('errs on invalid dir', function (done) { - this.config.getDir('dirInvalid', { type: 'binary' }, (err) => { - assert.equal(err.code, 'ENOENT'); - done(); - }) - }) - - it('reloads when file in dir is touched', function (done) { - this.timeout(3500); - if (/darwin/.test(process.platform)) { - // due to differences in fs.watch, this test is not reliable on Mac OS X - done(); - return; + beforeEach(function (done) { + process.env.HARAKA = '' + this.config = require('../config') + cleanup(done) + }) + + it('loads all files in dir', function (done) { + this.config.getDir('dir', { type: 'binary' }, (err, files) => { + assert.ifError(err) + assert.equal(err, null) + assert.equal(files.length, 3) + assert.equal(files[0].data, `contents1${os.EOL}`) + assert.equal(files[2].data, `contents3${os.EOL}`) + done() + }) + }) + + it('errs on invalid dir', function (done) { + this.config.getDir('dirInvalid', { type: 'binary' }, (err) => { + assert.equal(err.code, 'ENOENT') + done() + }) + }) + + it('reloads when file in dir is touched', function (done) { + this.timeout(3500) + if (/darwin/.test(process.platform)) { + // due to differences in fs.watch, this test is not reliable on Mac OS X + done() + return + } + const self = this + let callCount = 0 + + function getDir() { + const opts2 = { type: 'binary', watchCb: getDir } + self.config.getDir('dir', opts2, (err, files) => { + // console.log('Loading: test/config/dir'); + if (err) console.error(err) + callCount++ + if (callCount === 1) { + // console.log(files); + assert.equal(err, null) + assert.equal(files.length, 3) + assert.equal(files[0].data, `contents1${os.EOL}`) + assert.equal(files[2].data, `contents3${os.EOL}`) + fs.writeFile(tmpFile, 'contents4\n', (err2) => { + assert.equal(err2, null) + // console.log('file touched, waiting for callback'); + }) } - const self = this; - let callCount = 0; - - function getDir () { - const opts2 = { type: 'binary', watchCb: getDir }; - self.config.getDir('dir', opts2, (err, files) => { - // console.log('Loading: test/config/dir'); - if (err) console.error(err); - callCount++; - if (callCount === 1) { - // console.log(files); - assert.equal(err, null); - assert.equal(files.length, 3); - assert.equal(files[0].data, `contents1${os.EOL}`); - assert.equal(files[2].data, `contents3${os.EOL}`); - fs.writeFile(tmpFile, 'contents4\n', (err2) => { - assert.equal(err2, null); - // console.log('file touched, waiting for callback'); - }); - } - if (callCount === 2) { - assert.equal(files[3].data, 'contents4\n'); - done(); - } - }); + if (callCount === 2) { + assert.equal(files[3].data, 'contents4\n') + done() } - getDir(); - }) + }) + } + getDir() + }) }) describe('hjsonOverrides', function () { - beforeEach(testSetup) - - it('no override for smtpgreeting', function (done) { - // console.log(this.config); - assert.deepEqual( - this.config.get('smtpgreeting', 'list'), - [] - ); - done(); - }) - - it('with smtpgreeting override', function (done) { - process.env.WITHOUT_CONFIG_CACHE=''; - const main = this.config.get('main.hjson'); - console.log(main); - assert.deepEqual( - this.config.get('smtpgreeting', 'list'), - [ 'this is line one for hjson', 'this is line two for hjson' ] - ); - done(); - }) + beforeEach(testSetup) + + it('no override for smtpgreeting', function () { + assert.deepEqual(this.config.get('smtpgreeting', 'list'), []) + }) + + it('with smtpgreeting override', function () { + process.env.WITHOUT_CONFIG_CACHE = '' + const main = this.config.get('main.hjson') + assert.deepEqual(this.config.get('smtpgreeting', 'list'), [ + 'this is line one for hjson', + 'this is line two for hjson', + ]) + }) }) describe('jsonOverrides', function () { - beforeEach(testSetup) - - it('no override for smtpgreeting', function (done) { - // console.log(this.config); - assert.deepEqual( - this.config.get('smtpgreeting', 'list'), - [] - ); - done(); - }) - - it('with smtpgreeting override', function (done) { - process.env.WITHOUT_CONFIG_CACHE=''; - const main = this.config.get('main.json'); - console.log(main); - assert.deepEqual( - this.config.get('smtpgreeting', 'list'), - [ 'this is line one', 'this is line two' ] - ); - done(); - }) + beforeEach(testSetup) + + it('no override for smtpgreeting', function () { + assert.deepEqual(this.config.get('smtpgreeting', 'list'), []) + }) + + it('with smtpgreeting override', function () { + process.env.WITHOUT_CONFIG_CACHE = '' + const main = this.config.get('main.json') + assert.deepEqual(this.config.get('smtpgreeting', 'list'), [ + 'this is line one', + 'this is line two', + ]) + }) }) diff --git a/test/config/main.json b/test/config/main.json index c990f72..c787dee 100644 --- a/test/config/main.json +++ b/test/config/main.json @@ -1,3 +1,3 @@ { - "!smtpgreeting": [ "this is line one", "this is line two" ] -} \ No newline at end of file + "!smtpgreeting": ["this is line one", "this is line two"] +} diff --git a/test/config/missing.yaml b/test/config/missing.yaml index d5392a0..ab2d2dd 100644 --- a/test/config/missing.yaml +++ b/test/config/missing.yaml @@ -1,3 +1,2 @@ --- -matt: "waz here" -... \ No newline at end of file +matt: 'waz here' diff --git a/test/config/missing2.yaml b/test/config/missing2.yaml index a088f48..a5044cf 100644 --- a/test/config/missing2.yaml +++ b/test/config/missing2.yaml @@ -1,3 +1,2 @@ --- -matt: "waz here - hjson type" -... +matt: 'waz here - hjson type' diff --git a/test/config/override.yaml b/test/config/override.yaml index 50f4ef3..63163fa 100644 --- a/test/config/override.yaml +++ b/test/config/override.yaml @@ -1,2 +1,2 @@ has: - value: true \ No newline at end of file + value: true diff --git a/test/config/test.js b/test/config/test.js index 620db27..d86fc9a 100644 --- a/test/config/test.js +++ b/test/config/test.js @@ -1,9 +1,7 @@ module.exports = { - matthrew: "waz here", - marray: [ - "has an element" - ], - objective: { - "has a property": "with a value" - } + matthrew: 'waz here', + marray: ['has an element'], + objective: { + 'has a property': 'with a value', + }, } diff --git a/test/config/test.json b/test/config/test.json index e8e5edc..2526ff7 100644 --- a/test/config/test.json +++ b/test/config/test.json @@ -1,9 +1,7 @@ { - "matt": "waz here", - "array": [ - "has an element" - ], - "objecty": { - "has a property": "with a value" - } -} \ No newline at end of file + "matt": "waz here", + "array": ["has an element"], + "objecty": { + "has a property": "with a value" + } +} diff --git a/test/config/test.yaml b/test/config/test.yaml index e7da16d..d9a5f0a 100644 --- a/test/config/test.yaml +++ b/test/config/test.yaml @@ -1,23 +1,22 @@ --- main: - bool_true: true - bool_false: false - str_true: true - str_false: false + bool_true: true + bool_false: false + str_true: true + str_false: false sect1: - bool_true: true - bool_false: false - str_true: true - str_false: false + bool_true: true + bool_false: false + str_true: true + str_false: false whitespace: - str_no_trail: true - str_trail: true + str_no_trail: true + str_trail: true -matt: "waz here" +matt: 'waz here' array: - - "has an element" + - 'has an element' objecty: - "has a property": "with a value" -... \ No newline at end of file + 'has a property': 'with a value' diff --git a/test/configfile.js b/test/configfile.js index 1b648b2..024cbeb 100644 --- a/test/configfile.js +++ b/test/configfile.js @@ -1,445 +1,405 @@ -'use strict'; +'use strict' const assert = require('assert') -// const path = require('path'); -function _setUp (done) { - process.env.NODE_ENV === 'test'; - this.cfreader = require('../configfile'); - this.opts = { booleans: ['main.bool_true','main.bool_false'] }; - done(); +function _setUp(done) { + process.env.NODE_ENV === 'test' + this.cfreader = require('../configfile') + this.opts = { booleans: ['main.bool_true', 'main.bool_false'] } + done() } describe('configfile', function () { - beforeEach(_setUp) - - describe('load_config', function () { - - describe('non-exist.ini', function () { - - it('empty', function (done) { - assert.deepEqual( - this.cfreader.load_config('non-exist.ini','ini'), - { main: { } } - ); - done() - }) - - it('boolean', function (done) { - assert.deepEqual( - this.cfreader.load_config('non-exist.ini', 'ini', - { booleans: ['reject']}), - { main: { reject: false } } - ); - done() - }) - - it('boolean true default', function (done) { - assert.deepEqual( - this.cfreader.load_config('non-exist.ini', 'ini', - { booleans: ['+reject']}), - { main: { reject: true } } - ); - assert.deepEqual( - this.cfreader.load_config('non-exist.ini', 'ini', - { booleans: ['+main.reject']}), - { main: { reject: true } } - ); - assert.deepEqual( - this.cfreader.load_config('non-exist.ini', 'ini', - { booleans: ['main.+reject']}), - { main: { reject: true } } - ); - done() - }) - - it('boolean false default', function (done) { - assert.deepEqual( - this.cfreader.load_config('non-exist.ini', 'ini', - { booleans: ['-reject']}), - { main: { reject: false } } - ); - assert.deepEqual( - this.cfreader.load_config('non-exist.ini', 'ini', - { booleans: ['-main.reject']}), - { main: { reject: false } } - ); - assert.deepEqual( - this.cfreader.load_config('non-exist.ini', 'ini', - { booleans: ['main.-reject']}), - { main: { reject: false } } - ); - done() - }) - - it('boolean false default, section', function (done) { - assert.deepEqual( - this.cfreader.load_config('non-exist.ini', 'ini', - { booleans: ['-reject.boolf']}), - { main: { }, reject: {boolf: false} } - ); - assert.deepEqual( - this.cfreader.load_config('non-exist.ini', 'ini', - { booleans: ['+reject.boolt']}), - { main: { }, reject: {boolt: true} } - ); - done() - }) - }) - - describe('test.ini', function () { - - it('no opts', function (done) { - const r = this.cfreader.load_config('test/config/test.ini','ini'); - assert.strictEqual(r.main.bool_true, 'true'); - assert.strictEqual(r.main.bool_false, 'false'); - assert.strictEqual(r.main.str_true, 'true'); - assert.strictEqual(r.main.str_false, 'false'); - done() - }) - - it('opts', function (done) { - const r = this.cfreader.load_config('test/config/test.ini', 'ini', this.opts); - assert.strictEqual(r.main.bool_true, true); - assert.strictEqual(r.main.bool_false, false); - assert.strictEqual(r.main.str_true, 'true'); - assert.strictEqual(r.main.str_false, 'false'); - done() - }) - - it('sect1, opts', function (done) { - const r = this.cfreader.load_config('test/config/test.ini', 'ini', { - booleans: ['sect1.bool_true','sect1.bool_false'] - }); - assert.strictEqual(r.sect1.bool_true, true); - assert.strictEqual(r.sect1.bool_false, false); - assert.strictEqual(r.sect1.str_true, 'true'); - assert.strictEqual(r.sect1.str_false, 'false'); - done() - }) - - it('sect1, opts, w/defaults', function (done) { - const r = this.cfreader.load_config('test/config/test.ini', 'ini', { - booleans: [ - '+sect1.bool_true','-sect1.bool_false', - '+sect1.bool_true_default', 'sect1.-bool_false_default' - ] - }); - assert.strictEqual(r.sect1.bool_true, true); - assert.strictEqual(r.sect1.bool_false, false); - assert.strictEqual(r.sect1.str_true, 'true'); - assert.strictEqual(r.sect1.str_false, 'false'); - assert.strictEqual(r.sect1.bool_true_default, true); - assert.strictEqual(r.sect1.bool_false_default, false); - done() - }) - - it('funnychars, /', function (done) { - const r = this.cfreader.load_config('test/config/test.ini'); - assert.strictEqual(r.funnychars['results.auth/auth_base.fail'], 'fun'); - done() - }) - - it('funnychars, _', function (done) { - const r = this.cfreader.load_config('test/config/test.ini'); - assert.strictEqual(r.funnychars['results.auth/auth_base.fail'], 'fun'); - done() - }) - - it('ipv6 addr, :', function (done) { - const r = this.cfreader.load_config('test/config/test.ini'); - assert.ok( '2605:ae00:329::2' in r.has_ipv6 ); - done() - }) - - it('empty value', function (done) { - const r = this.cfreader.load_config('test/config/test.ini'); - assert.deepEqual({ first: undefined, second: undefined}, r.empty_values); - done() - }) - - it('array', function (done) { - const r = this.cfreader.load_config('test/config/test.ini'); - assert.deepEqual(['first_host', 'second_host', 'third_host'], r.array_test.hostlist); - assert.deepEqual([123, 456, 789], r.array_test.intlist); - done() - }) - }) + beforeEach(_setUp) + + describe('load_config', function () { + describe('non-exist.ini', function () { + it('empty', function () { + assert.deepEqual(this.cfreader.load_config('non-exist.ini', 'ini'), { + main: {}, + }) + }) + + it('boolean', function () { + assert.deepEqual( + this.cfreader.load_config('non-exist.ini', 'ini', { + booleans: ['reject'], + }), + { main: { reject: false } }, + ) + }) + + it('boolean true default', function () { + assert.deepEqual( + this.cfreader.load_config('non-exist.ini', 'ini', { + booleans: ['+reject'], + }), + { main: { reject: true } }, + ) + assert.deepEqual( + this.cfreader.load_config('non-exist.ini', 'ini', { + booleans: ['+main.reject'], + }), + { main: { reject: true } }, + ) + assert.deepEqual( + this.cfreader.load_config('non-exist.ini', 'ini', { + booleans: ['main.+reject'], + }), + { main: { reject: true } }, + ) + }) + + it('boolean false default', function () { + assert.deepEqual( + this.cfreader.load_config('non-exist.ini', 'ini', { + booleans: ['-reject'], + }), + { main: { reject: false } }, + ) + assert.deepEqual( + this.cfreader.load_config('non-exist.ini', 'ini', { + booleans: ['-main.reject'], + }), + { main: { reject: false } }, + ) + assert.deepEqual( + this.cfreader.load_config('non-exist.ini', 'ini', { + booleans: ['main.-reject'], + }), + { main: { reject: false } }, + ) + }) + + it('boolean false default, section', function () { + assert.deepEqual( + this.cfreader.load_config('non-exist.ini', 'ini', { + booleans: ['-reject.boolf'], + }), + { main: {}, reject: { boolf: false } }, + ) + assert.deepEqual( + this.cfreader.load_config('non-exist.ini', 'ini', { + booleans: ['+reject.boolt'], + }), + { main: {}, reject: { boolt: true } }, + ) + }) }) - describe('get_filetype_reader', function () { - - it('binary', function (done) { - const reader = this.cfreader.get_filetype_reader('binary'); - assert.equal(typeof reader.load, 'function'); - assert.equal(typeof reader.empty, 'function'); - done() - }) - - it('flat', function (done) { - const reader = this.cfreader.get_filetype_reader('flat'); - assert.equal(typeof reader.load, 'function'); - assert.equal(typeof reader.empty, 'function'); - done() - }) - - it('hjson', function (done) { - const reader = this.cfreader.get_filetype_reader('hjson'); - assert.equal(typeof reader.load, 'function'); - assert.equal(typeof reader.empty, 'function'); - done() - }) - - it('json', function (done) { - const reader = this.cfreader.get_filetype_reader('json'); - assert.equal(typeof reader.load, 'function'); - assert.equal(typeof reader.empty, 'function'); - done() - }) + describe('test.ini', function () { + it('no opts', function () { + const r = this.cfreader.load_config('test/config/test.ini', 'ini') + assert.strictEqual(r.main.bool_true, 'true') + assert.strictEqual(r.main.bool_false, 'false') + assert.strictEqual(r.main.str_true, 'true') + assert.strictEqual(r.main.str_false, 'false') + }) + + it('opts', function () { + const r = this.cfreader.load_config( + 'test/config/test.ini', + 'ini', + this.opts, + ) + assert.strictEqual(r.main.bool_true, true) + assert.strictEqual(r.main.bool_false, false) + assert.strictEqual(r.main.str_true, 'true') + assert.strictEqual(r.main.str_false, 'false') + }) + + it('sect1, opts', function () { + const r = this.cfreader.load_config('test/config/test.ini', 'ini', { + booleans: ['sect1.bool_true', 'sect1.bool_false'], + }) + assert.strictEqual(r.sect1.bool_true, true) + assert.strictEqual(r.sect1.bool_false, false) + assert.strictEqual(r.sect1.str_true, 'true') + assert.strictEqual(r.sect1.str_false, 'false') + }) + + it('sect1, opts, w/defaults', function () { + const r = this.cfreader.load_config('test/config/test.ini', 'ini', { + booleans: [ + '+sect1.bool_true', + '-sect1.bool_false', + '+sect1.bool_true_default', + 'sect1.-bool_false_default', + ], + }) + assert.strictEqual(r.sect1.bool_true, true) + assert.strictEqual(r.sect1.bool_false, false) + assert.strictEqual(r.sect1.str_true, 'true') + assert.strictEqual(r.sect1.str_false, 'false') + assert.strictEqual(r.sect1.bool_true_default, true) + assert.strictEqual(r.sect1.bool_false_default, false) + }) + + it('funnychars, /', function () { + const r = this.cfreader.load_config('test/config/test.ini') + assert.strictEqual(r.funnychars['results.auth/auth_base.fail'], 'fun') + }) + + it('funnychars, _', function () { + const r = this.cfreader.load_config('test/config/test.ini') + assert.strictEqual(r.funnychars['results.auth/auth_base.fail'], 'fun') + }) + + it('ipv6 addr, :', function () { + const r = this.cfreader.load_config('test/config/test.ini') + assert.ok('2605:ae00:329::2' in r.has_ipv6) + }) + + it('empty value', function () { + const r = this.cfreader.load_config('test/config/test.ini') + assert.deepEqual( + { first: undefined, second: undefined }, + r.empty_values, + ) + }) + + it('array', function () { + const r = this.cfreader.load_config('test/config/test.ini') + assert.deepEqual( + ['first_host', 'second_host', 'third_host'], + r.array_test.hostlist, + ) + assert.deepEqual([123, 456, 789], r.array_test.intlist) + }) + }) + }) - it('ini', function (done) { - const reader = this.cfreader.get_filetype_reader('ini'); - assert.equal(typeof reader.load, 'function'); - assert.equal(typeof reader.empty, 'function'); - done() - }) + describe('get_filetype_reader', function () { + it('binary', function () { + const reader = this.cfreader.get_filetype_reader('binary') + assert.equal(typeof reader.load, 'function') + assert.equal(typeof reader.empty, 'function') + }) - it('yaml', function (done) { - const reader = this.cfreader.get_filetype_reader('yaml'); - assert.equal(typeof reader.load, 'function'); - assert.equal(typeof reader.empty, 'function'); - done() - }) + it('flat', function () { + const reader = this.cfreader.get_filetype_reader('flat') + assert.equal(typeof reader.load, 'function') + assert.equal(typeof reader.empty, 'function') + }) - it('value', function (done) { - const reader = this.cfreader.get_filetype_reader('value'); - assert.equal(typeof reader.load, 'function'); - assert.equal(typeof reader.empty, 'function'); - done() - }) + it('hjson', function () { + const reader = this.cfreader.get_filetype_reader('hjson') + assert.equal(typeof reader.load, 'function') + assert.equal(typeof reader.empty, 'function') + }) - it('list', function (done) { - const reader = this.cfreader.get_filetype_reader('list'); - assert.equal(typeof reader.load, 'function'); - assert.equal(typeof reader.empty, 'function'); - done() - }) + it('json', function () { + const reader = this.cfreader.get_filetype_reader('json') + assert.equal(typeof reader.load, 'function') + assert.equal(typeof reader.empty, 'function') + }) - it('data', function (done) { - const reader = this.cfreader.get_filetype_reader('data'); - assert.equal(typeof reader.load, 'function'); - assert.equal(typeof reader.empty, 'function'); - done() - }) + it('ini', function () { + const reader = this.cfreader.get_filetype_reader('ini') + assert.equal(typeof reader.load, 'function') + assert.equal(typeof reader.empty, 'function') }) - describe('empty', function () { - it('empty object for HJSON files', function (done) { - const result = this.cfreader.load_config( - 'test/config/non-existent.hjson' - ); - assert.deepEqual(result, {}); - done() - }) + it('yaml', function () { + const reader = this.cfreader.get_filetype_reader('yaml') + assert.equal(typeof reader.load, 'function') + assert.equal(typeof reader.empty, 'function') + }) - it('empty object for JSON files', function (done) { - const result = this.cfreader.load_config( - 'test/config/non-existent.json' - ); - assert.deepEqual(result, {}); - done() - }) + it('value', function () { + const reader = this.cfreader.get_filetype_reader('value') + assert.equal(typeof reader.load, 'function') + assert.equal(typeof reader.empty, 'function') + }) - it('empty object for YAML files', function (done) { - const result = this.cfreader.load_config( - 'test/config/non-existent.yaml' - ); - assert.deepEqual(result, {}); - done() - }) + it('list', function () { + const reader = this.cfreader.get_filetype_reader('list') + assert.equal(typeof reader.load, 'function') + assert.equal(typeof reader.empty, 'function') + }) - it('null for binary file', function (done) { - const result = this.cfreader.load_config( - 'test/config/non-existent.bin', - 'binary' - ); - assert.equal(result, null); - done() - }) + it('data', function () { + const reader = this.cfreader.get_filetype_reader('data') + assert.equal(typeof reader.load, 'function') + assert.equal(typeof reader.empty, 'function') + }) + }) - it('null for flat file', function (done) { - const result = this.cfreader.load_config( - 'test/config/non-existent.flat' - ); - assert.deepEqual(result, null); - done() - }) + describe('empty', function () { + it('empty object for HJSON files', function () { + const result = this.cfreader.load_config('test/config/non-existent.hjson') + assert.deepEqual(result, {}) + }) - it('null for value file', function (done) { - const result = this.cfreader.load_config( - 'test/config/non-existent.value' - ); - assert.deepEqual(result, null); - done() - }) + it('empty object for JSON files', function () { + const result = this.cfreader.load_config('test/config/non-existent.json') + assert.deepEqual(result, {}) + }) - it('empty array for list file', function (done) { - const result = this.cfreader.load_config( - 'test/config/non-existent.list' - ); - assert.deepEqual(result, []); - done() - }) + it('empty object for YAML files', function () { + const result = this.cfreader.load_config('test/config/non-existent.yaml') + assert.deepEqual(result, {}) + }) - it('template ini for INI file', function (done) { - const result = this.cfreader.load_config( - 'test/config/non-existent.ini' - ); - assert.deepEqual(result, { main: {} }); - done() - }) + it('null for binary file', function () { + const result = this.cfreader.load_config( + 'test/config/non-existent.bin', + 'binary', + ) + assert.equal(result, null) }) - describe('get_cache_key', function () { - it('no options is the name', function (done) { - assert.equal(this.cfreader.get_cache_key('test'), - 'test'); - done() - }) + it('null for flat file', function () { + const result = this.cfreader.load_config('test/config/non-existent.flat') + assert.deepEqual(result, null) + }) - it('one option is name + serialized opts', function (done) { - assert.equal(this.cfreader.get_cache_key('test', {foo: 'bar'}), - 'test{"foo":"bar"}'); - done() - }) + it('null for value file', function () { + const result = this.cfreader.load_config('test/config/non-existent.value') + assert.deepEqual(result, null) + }) - it('two options are returned predictably', function (done) { - assert.equal( - this.cfreader.get_cache_key('test', {opt1: 'foo', opt2: 'bar'}), - 'test{"opt1":"foo","opt2":"bar"}'); - done() - }) + it('empty array for list file', function () { + const result = this.cfreader.load_config('test/config/non-existent.list') + assert.deepEqual(result, []) }) - describe('regex', function () { - it('section', function (done) { - assert.equal(this.cfreader.regex.section.test('[foo]'), true); - assert.equal(this.cfreader.regex.section.test('bar'), false); - assert.equal(this.cfreader.regex.section.test('[bar'), false); - assert.equal(this.cfreader.regex.section.test('bar]'), false); - done() - }) + it('template ini for INI file', function () { + const result = this.cfreader.load_config('test/config/non-existent.ini') + assert.deepEqual(result, { main: {} }) + }) + }) - it('param', function (done) { - assert.equal(this.cfreader.regex.param.exec('foo=true')[1], 'foo'); - assert.equal(this.cfreader.regex.param.exec(';foo=true'), undefined); - done() - }) + describe('get_cache_key', function () { + it('no options is the name', function () { + assert.equal(this.cfreader.get_cache_key('test'), 'test') + }) - it('comment', function (done) { - assert.equal(this.cfreader.regex.comment.test('; true'), true); - assert.equal(this.cfreader.regex.comment.test('false'), false); - done() - }) + it('one option is name + serialized opts', function () { + assert.equal( + this.cfreader.get_cache_key('test', { foo: 'bar' }), + 'test{"foo":"bar"}', + ) + }) - it('line', function (done) { - assert.equal(this.cfreader.regex.line.test(' boo '), true); - assert.equal(this.cfreader.regex.line.test('foo'), true); - done() - }) + it('two options are returned predictably', function () { + assert.equal( + this.cfreader.get_cache_key('test', { opt1: 'foo', opt2: 'bar' }), + 'test{"opt1":"foo","opt2":"bar"}', + ) + }) + }) + + describe('regex', function () { + it('section', function () { + assert.equal(this.cfreader.regex.section.test('[foo]'), true) + assert.equal(this.cfreader.regex.section.test('bar'), false) + assert.equal(this.cfreader.regex.section.test('[bar'), false) + assert.equal(this.cfreader.regex.section.test('bar]'), false) + }) - it('blank', function (done) { - assert.equal(this.cfreader.regex.blank.test('foo'), false); - assert.equal(this.cfreader.regex.blank.test(' '), true); - done() - }) + it('param', function () { + assert.equal(this.cfreader.regex.param.exec('foo=true')[1], 'foo') + assert.equal(this.cfreader.regex.param.exec(';foo=true'), undefined) + }) - // 'continuation', function (done) { - // done() - // }) + it('comment', function () { + assert.equal(this.cfreader.regex.comment.test('; true'), true) + assert.equal(this.cfreader.regex.comment.test('false'), false) + }) - it('is_integer', function (done) { - assert.equal(this.cfreader.regex.is_integer.test(1), true); - assert.equal(this.cfreader.regex.is_integer.test(''), false); - assert.equal(this.cfreader.regex.is_integer.test('a'), false); - done() - }) + it('line', function () { + assert.equal(this.cfreader.regex.line.test(' boo '), true) + assert.equal(this.cfreader.regex.line.test('foo'), true) + }) - it('is_float', function (done) { - assert.equal(this.cfreader.regex.is_float.test('1.0'), true); - assert.equal(this.cfreader.regex.is_float.test(''), false); - assert.equal(this.cfreader.regex.is_float.test('45'), false); - done() - }) + it('blank', function () { + assert.equal(this.cfreader.regex.blank.test('foo'), false) + assert.equal(this.cfreader.regex.blank.test(' '), true) + }) - it('is_truth', function (done) { - assert.equal(this.cfreader.regex.is_truth.test('no'), false); - assert.equal(this.cfreader.regex.is_truth.test('nope'), false); - assert.equal(this.cfreader.regex.is_truth.test('nuh uh'), false); - assert.equal(this.cfreader.regex.is_truth.test('yes'), true); - assert.equal(this.cfreader.regex.is_truth.test('true'), true); - assert.equal(this.cfreader.regex.is_truth.test(true), true); - done() - }) + // 'continuation', function () { + // done() + // }) - it('is_array', function (done) { - assert.equal(this.cfreader.regex.is_array.test('foo=bar'), false); - assert.equal(this.cfreader.regex.is_array.test('foo'), false); - assert.equal(this.cfreader.regex.is_array.test('foo[]'), true); - done() - }) + it('is_integer', function () { + assert.equal(this.cfreader.regex.is_integer.test(1), true) + assert.equal(this.cfreader.regex.is_integer.test(''), false) + assert.equal(this.cfreader.regex.is_integer.test('a'), false) }) - describe('bad_config', function () { - it('bad.yaml returns empty', function (done) { - assert.deepEqual( - this.cfreader.load_config('test/config/bad.yaml'), - {} - ); - done() - }) + it('is_float', function () { + assert.equal(this.cfreader.regex.is_float.test('1.0'), true) + assert.equal(this.cfreader.regex.is_float.test(''), false) + assert.equal(this.cfreader.regex.is_float.test('45'), false) }) - describe('overrides', function () { + it('is_truth', function () { + assert.equal(this.cfreader.regex.is_truth.test('no'), false) + assert.equal(this.cfreader.regex.is_truth.test('nope'), false) + assert.equal(this.cfreader.regex.is_truth.test('nuh uh'), false) + assert.equal(this.cfreader.regex.is_truth.test('yes'), true) + assert.equal(this.cfreader.regex.is_truth.test('true'), true) + assert.equal(this.cfreader.regex.is_truth.test(true), true) + }) - it('missing hjson loads yaml instead', function (done) { - assert.deepEqual( - this.cfreader.load_config('test/config/override2.hjson'), - { hasDifferent: { value: false } }); - done() - }) + it('is_array', function () { + assert.equal(this.cfreader.regex.is_array.test('foo=bar'), false) + assert.equal(this.cfreader.regex.is_array.test('foo'), false) + assert.equal(this.cfreader.regex.is_array.test('foo[]'), true) + }) + }) - it('missing json loads yaml instead', function (done) { - assert.deepEqual( - this.cfreader.load_config('test/config/override.json'), - { has: { value: true } }); - done() - }) + describe('bad_config', function () { + it('bad.yaml returns empty', function () { + assert.deepEqual(this.cfreader.load_config('test/config/bad.yaml'), {}) + }) + }) + + describe('overrides', function () { + it('missing hjson loads yaml instead', function () { + assert.deepEqual( + this.cfreader.load_config('test/config/override2.hjson'), + { hasDifferent: { value: false } }, + ) }) - describe('get_path_to_config_dir', function () { - it('Haraka runtime (env.HARAKA=*)', function (done) { - process.env.HARAKA = '/etc/'; - this.cfreader.get_path_to_config_dir(); - assert.ok(/etc.config$/.test(this.cfreader.config_path), this.cfreader.config_path); - delete process.env.HARAKA; - done() - }) + it('missing json loads yaml instead', function () { + assert.deepEqual(this.cfreader.load_config('test/config/override.json'), { + has: { value: true }, + }) + }) + }) + + describe('get_path_to_config_dir', function () { + it('Haraka runtime (env.HARAKA=*)', function () { + process.env.HARAKA = '/etc/' + this.cfreader.get_path_to_config_dir() + assert.ok( + /etc.config$/.test(this.cfreader.config_path), + this.cfreader.config_path, + ) + delete process.env.HARAKA + }) - it('NODE_ENV=test', function (done) { - process.env.NODE_ENV = 'test'; - this.cfreader.get_path_to_config_dir(); - assert.ok(/haraka-config.test.config$/.test(this.cfreader.config_path), this.cfreader.config_path); - delete process.env.NODE_ENV; - done() - }) + it('NODE_ENV=test', function () { + process.env.NODE_ENV = 'test' + this.cfreader.get_path_to_config_dir() + assert.ok( + /haraka-config.test.config$/.test(this.cfreader.config_path), + this.cfreader.config_path, + ) + delete process.env.NODE_ENV + }) - it('no $ENV defaults to ./config (if present) or ./', function (done) { - delete process.env.HARAKA; - delete process.env.NODE_ENV; - this.cfreader.get_path_to_config_dir(); - assert.ok(/haraka-config$/.test(this.cfreader.config_path), this.cfreader.config_path); - done() - }) + it('no $ENV defaults to ./config (if present) or ./', function () { + delete process.env.HARAKA + delete process.env.NODE_ENV + this.cfreader.get_path_to_config_dir() + assert.ok( + /haraka-config$/.test(this.cfreader.config_path), + this.cfreader.config_path, + ) }) + }) }) diff --git a/test/readers/binary.js b/test/readers/binary.js index 9f4d8db..27b6bcb 100644 --- a/test/readers/binary.js +++ b/test/readers/binary.js @@ -1,30 +1,29 @@ - const assert = require('assert') -const fs = require('fs') -const path = require('path') +const fs = require('fs') +const path = require('path') beforeEach(function (done) { - this.bin = require('../../readers/binary') - done() + this.bin = require('../../readers/binary') + done() }) describe('binary', function () { - it('module is required', function (done) { - assert.ok(this.bin) - done() - }) + it('module is required', function (done) { + assert.ok(this.bin) + done() + }) - it('has a load function', function (done) { - assert.ok(typeof this.bin.load === 'function') - done() - }) + it('has a load function', function (done) { + assert.ok(typeof this.bin.load === 'function') + done() + }) - it('loads the test binary file', function (done) { - const testBin = path.join('test','config','test.binary') - const result = this.bin.load(testBin) - assert.ok(Buffer.isBuffer(result)) - assert.equal(result.length, 120) - assert.deepEqual(result, fs.readFileSync(testBin)) - done() - }) -}) \ No newline at end of file + it('loads the test binary file', function (done) { + const testBin = path.join('test', 'config', 'test.binary') + const result = this.bin.load(testBin) + assert.ok(Buffer.isBuffer(result)) + assert.equal(result.length, 120) + assert.deepEqual(result, fs.readFileSync(testBin)) + done() + }) +}) diff --git a/test/readers/flat.js b/test/readers/flat.js index 384dd3f..34852b5 100644 --- a/test/readers/flat.js +++ b/test/readers/flat.js @@ -1,41 +1,39 @@ - const assert = require('assert') const regex = require('../../configfile').regex beforeEach(function (done) { - this.flat = require('../../readers/flat') - done() + this.flat = require('../../readers/flat') + done() }) describe('flat', function () { + it('module is required', function (done) { + assert.ok(this.flat) + done() + }) - it('module is required', function (done) { - assert.ok(this.flat); - done(); - }) - - it('has a load function', function (done) { - assert.ok(typeof this.flat.load === 'function'); - done(); - }) + it('has a load function', function (done) { + assert.ok(typeof this.flat.load === 'function') + done() + }) - it('loads the test flat file, as list', function (done) { - const result = this.flat.load('test/config/test.flat', 'list', null, regex); - assert.deepEqual(result, [ 'line1', 'line2', 'line3', 'line5' ]); - done(); - }) + it('loads the test flat file, as list', function (done) { + const result = this.flat.load('test/config/test.flat', 'list', null, regex) + assert.deepEqual(result, ['line1', 'line2', 'line3', 'line5']) + done() + }) - it('loads the test flat file, unspecified type', function (done) { - const result = this.flat.load('test/config/test.flat', null, null, regex); - assert.deepEqual(result, 'line1'); - done(); - }) + it('loads the test flat file, unspecified type', function (done) { + const result = this.flat.load('test/config/test.flat', null, null, regex) + assert.deepEqual(result, 'line1') + done() + }) - it('returns hostname for empty "me"', function (done) { - const result = this.flat.load( 'test/config/me', null, null, regex); - console.log(result); - assert.ok(result); - done(); - }) + it('returns hostname for empty "me"', function (done) { + const result = this.flat.load('test/config/me', null, null, regex) + console.log(result) + assert.ok(result) + done() + }) }) diff --git a/test/readers/hjson.js b/test/readers/hjson.js index 5f9ffc1..9efbccd 100644 --- a/test/readers/hjson.js +++ b/test/readers/hjson.js @@ -1,30 +1,29 @@ - const assert = require('assert') -const path = require('path') +const path = require('path') beforeEach(function (done) { - this.hjson = require('../../readers/hjson') - done() + this.hjson = require('../../readers/hjson') + done() }) describe('hjson', function () { - it('module is required', function (done) { - assert.ok(this.hjson) - done() - }) + it('module is required', function (done) { + assert.ok(this.hjson) + done() + }) - it('has a load function', function (done) { - assert.ok(typeof this.hjson.load === 'function') - done() - }) + it('has a load function', function (done) { + assert.ok(typeof this.hjson.load === 'function') + done() + }) - it('loads the test HJSON file', function (done) { - const result = this.hjson.load(path.join('test','config','test.hjson')) - // console.log(result) - assert.equal(result.matt, 'waz here and also made comments') - assert.ok(result.differentArray.length) - assert.ok(result.object['has a property one']) - assert.ok(result.object['has a property two']) - done() - }) + it('loads the test HJSON file', function (done) { + const result = this.hjson.load(path.join('test', 'config', 'test.hjson')) + // console.log(result) + assert.equal(result.matt, 'waz here and also made comments') + assert.ok(result.differentArray.length) + assert.ok(result.object['has a property one']) + assert.ok(result.object['has a property two']) + done() + }) }) diff --git a/test/readers/ini.js b/test/readers/ini.js index ed6e331..e9d96c0 100644 --- a/test/readers/ini.js +++ b/test/readers/ini.js @@ -1,164 +1,168 @@ - const assert = require('assert') -const regex = require('../../configfile').regex +const regex = require('../../configfile').regex beforeEach(function (done) { - this.ini = require('../../readers/ini'); - this.opts = { - booleans: [ 'main.bool_true', 'main.bool_false' ] - }; - done(); + this.ini = require('../../readers/ini') + this.opts = { + booleans: ['main.bool_true', 'main.bool_false'], + } + done() }) describe('ini', function () { + it('requires', function (done) { + assert.ok(this.ini) + done() + }) + + it('has a load function', function (done) { + assert.ok(typeof this.ini.load === 'function') + done() + }) + + it('loads the test ini file', function (done) { + const result = this.ini.load('test/config/test.ini', {}, regex) + // console.log(result); + assert.deepEqual(result.main, { + bool_true: 'true', + bool_false: 'false', + str_true: 'true', + str_false: 'false', + }) + done() + }) + + describe('test.ini', function () { + it('no opts', function (done) { + const r = this.ini.load('test/config/test.ini', {}, regex) + assert.strictEqual(r.main.bool_true, 'true') + assert.strictEqual(r.main.bool_false, 'false') + assert.strictEqual(r.main.str_true, 'true') + assert.strictEqual(r.main.str_false, 'false') + done() + }) + + it('opts', function (done) { + const r = this.ini.load('test/config/test.ini', this.opts, regex).main + assert.strictEqual(r.bool_true, true) + assert.strictEqual(r.bool_false, false) + assert.strictEqual(r.str_true, 'true') + assert.strictEqual(r.str_false, 'false') + done() + }) + + it('sect1, opts', function (done) { + const r = this.ini.load( + 'test/config/test.ini', + { + booleans: ['sect1.bool_true', 'sect1.bool_false'], + }, + regex, + ) + assert.strictEqual(r.sect1.bool_true, true) + assert.strictEqual(r.sect1.bool_false, false) + assert.strictEqual(r.sect1.str_true, 'true') + assert.strictEqual(r.sect1.str_false, 'false') + done() + }) + + it('sect1, opts, w/defaults', function (done) { + const r = this.ini.load( + 'test/config/test.ini', + { + booleans: [ + '+sect1.bool_true', + '-sect1.bool_false', + '+sect1.bool_true_default', + 'sect1.-bool_false_default', + ], + }, + regex, + ) + assert.strictEqual(r.sect1.bool_true, true) + assert.strictEqual(r.sect1.bool_false, false) + assert.strictEqual(r.sect1.str_true, 'true') + assert.strictEqual(r.sect1.str_false, 'false') + assert.strictEqual(r.sect1.bool_true_default, true) + assert.strictEqual(r.sect1.bool_false_default, false) + done() + }) + + it('wildcard boolean', function (done) { + const r = this.ini.load( + 'test/config/test.ini', + { + booleans: ['+main.bool_true', '*.is_bool'], + }, + regex, + ) + assert.strictEqual(r['*'], undefined) + assert.strictEqual(r.main.bool_true, true) + assert.strictEqual(r.main.is_bool, undefined) + assert.strictEqual(r['foo.com'].is_bool, true) + assert.strictEqual(r['bar.com'].is_bool, false) + done() + }) + }) - it('requires', function (done) { - assert.ok(this.ini); - done() + describe('non-exist.ini (empty)', function () { + it('is template', function (done) { + assert.deepEqual(this.ini.empty(), { main: {} }) + done() }) - it('has a load function', function (done) { - assert.ok(typeof this.ini.load === 'function'); - done() + it('boolean', function (done) { + assert.deepEqual(this.ini.empty({ booleans: ['reject'] }), { + main: { reject: false }, + }) + done() }) - it('loads the test ini file', function (done) { - const result = this.ini.load('test/config/test.ini', {}, regex); - // console.log(result); - assert.deepEqual(result.main, { - bool_true: 'true', bool_false: 'false', - str_true: 'true', str_false: 'false' - }); - done() + it('boolean true default', function (done) { + assert.deepEqual(this.ini.empty({ booleans: ['+reject'] }), { + main: { reject: true }, + }) + assert.deepEqual(this.ini.empty({ booleans: ['+main.reject'] }), { + main: { reject: true }, + }) + assert.deepEqual(this.ini.empty({ booleans: ['main.+reject'] }), { + main: { reject: true }, + }) + done() }) - describe('test.ini', function () { - it('no opts', function (done) { - const r = this.ini.load('test/config/test.ini', {}, regex); - assert.strictEqual(r.main.bool_true, 'true'); - assert.strictEqual(r.main.bool_false, 'false'); - assert.strictEqual(r.main.str_true, 'true'); - assert.strictEqual(r.main.str_false, 'false'); - done() - }) - - it('opts', function (done) { - const r = this.ini.load('test/config/test.ini', this.opts, regex).main; - assert.strictEqual(r.bool_true, true); - assert.strictEqual(r.bool_false, false); - assert.strictEqual(r.str_true, 'true'); - assert.strictEqual(r.str_false, 'false'); - done() - }) - - it('sect1, opts', function (done) { - const r = this.ini.load('test/config/test.ini', { - booleans: ['sect1.bool_true','sect1.bool_false'] - }, regex); - assert.strictEqual(r.sect1.bool_true, true); - assert.strictEqual(r.sect1.bool_false, false); - assert.strictEqual(r.sect1.str_true, 'true'); - assert.strictEqual(r.sect1.str_false, 'false'); - done() - }) - - it('sect1, opts, w/defaults', function (done) { - const r = this.ini.load('test/config/test.ini', { - booleans: [ - '+sect1.bool_true', - '-sect1.bool_false', - '+sect1.bool_true_default', - 'sect1.-bool_false_default' - ] - }, regex); - assert.strictEqual(r.sect1.bool_true, true); - assert.strictEqual(r.sect1.bool_false, false); - assert.strictEqual(r.sect1.str_true, 'true'); - assert.strictEqual(r.sect1.str_false, 'false'); - assert.strictEqual(r.sect1.bool_true_default, true); - assert.strictEqual(r.sect1.bool_false_default, false); - done() - }) - - it('wildcard boolean', function (done) { - const r = this.ini.load('test/config/test.ini', { - booleans: [ '+main.bool_true', '*.is_bool' ] - }, regex); - assert.strictEqual(r['*'], undefined); - assert.strictEqual(r.main.bool_true, true); - assert.strictEqual(r.main.is_bool, undefined); - assert.strictEqual(r['foo.com'].is_bool, true); - assert.strictEqual(r['bar.com'].is_bool, false); - done() - }) + it('boolean false default', function (done) { + assert.deepEqual(this.ini.empty({ booleans: ['-reject'] }), { + main: { reject: false }, + }) + assert.deepEqual(this.ini.empty({ booleans: ['-main.reject'] }), { + main: { reject: false }, + }) + assert.deepEqual(this.ini.empty({ booleans: ['main.-reject'] }), { + main: { reject: false }, + }) + done() }) - describe('non-exist.ini (empty)', function () { - - it('is template', function (done) { - assert.deepEqual(this.ini.empty(), { main: { } } ); - done() - }) - - it('boolean', function (done) { - assert.deepEqual( - this.ini.empty({ booleans: ['reject']}), - { main: { reject: false } } - ); - done() - }) - - it('boolean true default', function (done) { - assert.deepEqual( - this.ini.empty({ booleans: ['+reject']}), - { main: { reject: true } } - ); - assert.deepEqual( - this.ini.empty({ booleans: ['+main.reject']}), - { main: { reject: true } } - ); - assert.deepEqual( - this.ini.empty({ booleans: ['main.+reject']}), - { main: { reject: true } } - ); - done() - }) - - it('boolean false default', function (done) { - assert.deepEqual( - this.ini.empty({ booleans: ['-reject']}), - { main: { reject: false } } - ); - assert.deepEqual( - this.ini.empty({ booleans: ['-main.reject']}), - { main: { reject: false } } - ); - assert.deepEqual( - this.ini.empty({ booleans: ['main.-reject']}), - { main: { reject: false } } - ); - done() - }) - - it('boolean false default, section', function (done) { - assert.deepEqual( - this.ini.empty({ booleans: ['-reject.boolf']}), - { main: { }, reject: {boolf: false} } - ); - assert.deepEqual( - this.ini.empty({ booleans: ['+reject.boolt']}), - { main: { }, reject: {boolt: true} } - ); - done() - }) + it('boolean false default, section', function (done) { + assert.deepEqual(this.ini.empty({ booleans: ['-reject.boolf'] }), { + main: {}, + reject: { boolf: false }, + }) + assert.deepEqual(this.ini.empty({ booleans: ['+reject.boolt'] }), { + main: {}, + reject: { boolt: true }, + }) + done() }) + }) - describe('goobers.ini', function () { - it('goobers.ini has invalid entry', function (done) { - const result = this.ini.load('test/config/goobers.ini', {}, regex); - assert.deepEqual(result, { main: { } } ); - done() - }) + describe('goobers.ini', function () { + it('goobers.ini has invalid entry', function (done) { + const result = this.ini.load('test/config/goobers.ini', {}, regex) + assert.deepEqual(result, { main: {} }) + done() }) + }) }) diff --git a/test/readers/json.js b/test/readers/json.js index 6eea5d4..5370b6d 100644 --- a/test/readers/json.js +++ b/test/readers/json.js @@ -1,29 +1,27 @@ - const assert = require('assert') beforeEach(function (done) { - this.json = require('../../readers/json'); - done(); + this.json = require('../../readers/json') + done() }) describe('json', function () { + it('module is required', function (done) { + assert.ok(this.json) + done() + }) - it('module is required', function (done) { - assert.ok(this.json); - done(); - }) - - it('has a load function', function (done) { - assert.ok(typeof this.json.load === 'function'); - done(); - }) + it('has a load function', function (done) { + assert.ok(typeof this.json.load === 'function') + done() + }) - it('loads the test JSON file', function (done) { - const result = this.json.load('test/config/test.json'); - // console.log(result); - assert.equal(result.matt, 'waz here'); - assert.ok(result.array.length); - assert.ok(result.objecty['has a property']); - done(); - }) + it('loads the test JSON file', function (done) { + const result = this.json.load('test/config/test.json') + // console.log(result); + assert.equal(result.matt, 'waz here') + assert.ok(result.array.length) + assert.ok(result.objecty['has a property']) + done() + }) }) diff --git a/test/readers/yaml.js b/test/readers/yaml.js index d77e6b8..5997ed7 100644 --- a/test/readers/yaml.js +++ b/test/readers/yaml.js @@ -1,29 +1,27 @@ - const assert = require('assert') beforeEach(function (done) { - this.yaml = require('../../readers/yaml'); - done(); + this.yaml = require('../../readers/yaml') + done() }) describe('yaml', function () { + it('module is required', function (done) { + assert.ok(this.yaml) + done() + }) - it('module is required', function (done) { - assert.ok(this.yaml); - done(); - }) - - it('has a load function', function (done) { - assert.ok(typeof this.yaml.load === 'function'); - done(); - }) + it('has a load function', function (done) { + assert.ok(typeof this.yaml.load === 'function') + done() + }) - it('loads the test yaml file', function (done) { - const result = this.yaml.load('test/config/test.yaml'); - assert.strictEqual(result.main.bool_true, true); - assert.equal(result.matt, 'waz here'); - assert.ok(result.array.length); - assert.ok(result.objecty['has a property']); - done(); - }) + it('loads the test yaml file', function (done) { + const result = this.yaml.load('test/config/test.yaml') + assert.strictEqual(result.main.bool_true, true) + assert.equal(result.matt, 'waz here') + assert.ok(result.array.length) + assert.ok(result.objecty['has a property']) + done() + }) })