From 924e0702a74bf18ee8816ba563c4efa976cae0f8 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Mon, 15 Apr 2024 15:19:00 -0700 Subject: [PATCH] moved configfile -> lib/reader & readers -> lib - moved regex and watch into separate files --- README.md | 105 ++++++++++---------------- config.js | 2 +- configfile.js => lib/reader.js | 116 +++-------------------------- {readers => lib/readers}/binary.js | 0 {readers => lib/readers}/flat.js | 4 +- {readers => lib/readers}/hjson.js | 0 {readers => lib/readers}/ini.js | 4 +- {readers => lib/readers}/js.js | 0 {readers => lib/readers}/json.js | 0 {readers => lib/readers}/yaml.js | 0 lib/regex.js | 12 +++ lib/watch.js | 82 ++++++++++++++++++++ package.json | 7 +- test/config.js | 2 +- test/configfile.js | 64 +--------------- test/readers/binary.js | 2 +- test/readers/flat.js | 11 +-- test/readers/hjson.js | 2 +- test/readers/ini.js | 52 +++++-------- test/readers/json.js | 2 +- test/readers/yaml.js | 2 +- test/regex.js | 59 +++++++++++++++ 22 files changed, 243 insertions(+), 285 deletions(-) rename configfile.js => lib/reader.js (67%) rename {readers => lib/readers}/binary.js (100%) rename {readers => lib/readers}/flat.js (95%) rename {readers => lib/readers}/hjson.js (100%) rename {readers => lib/readers}/ini.js (97%) rename {readers => lib/readers}/js.js (100%) rename {readers => lib/readers}/json.js (100%) rename {readers => lib/readers}/yaml.js (100%) create mode 100644 lib/regex.js create mode 100644 lib/watch.js create mode 100644 test/regex.js diff --git a/README.md b/README.md index a89b7e8..7c6b61b 100644 --- a/README.md +++ b/README.md @@ -8,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, 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. @@ -68,12 +68,8 @@ exports.hook_connect = function (next, connection) { 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_cache` (default: false) - prevents Haraka from caching the file. The file will be re-read on every call to `config.get`. This is not recommended as config files are read syncronously 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 @@ -134,17 +130,14 @@ sub1=something sub2=otherthing ``` -This allows plugins to provide a default config, and allow users to override +This allows plugins to ship a default config and users can override values on a key-by-key basis. # File Formats ## 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 files](https://en.wikipedia.org/wiki/INI_file) are key=value pairs, with optional [sections]. A typical example: ```ini first_name=Matt @@ -180,14 +173,14 @@ That produces the following Javascript object: } ``` -Items before any [section] marker are in the implicit [main] section. +Items before any `[section]` marker are in the implicit `[main]` section. + +Some values on the right hand side of the equals are converted: -There is some auto-conversion of values on the right hand side of -the equals: integers are converted to integers, floats are converted to -floats. +- integers are converted to integers +- floats are converted to floats. -The key=value pairs support continuation lines using the -backslash "\" character. +The key=value pairs support continuation lines using the backslash "\" character. The `options` object allows you to specify which keys are boolean: @@ -198,9 +191,9 @@ The `options` object allows you to specify which keys are boolean: ``` 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]. +If the key name does not specify a section, it is presumed to be `[main]`. -This ensures these values are converted to true Javascript booleans when parsed, and supports the following options for boolean values: +Declaring booleans ensures that values are converted as boolean when parsed, and supports the following options for boolean values: ``` true, yes, ok, enabled, on, 1 @@ -241,20 +234,13 @@ which produces this javascript array: ## 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 -familiar with this format. -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. +Flat files are simply either lists of values separated by \n or a single value in a file on its own. Qmail or qpsmtpd users will be familiar with this format. Lines starting with '#' and blank lines will be ignored unless the type is specified as 'data', however even then line endings will be stripped. ## 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 -because YAML files are far easier for a human to write. +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 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. @@ -264,11 +250,9 @@ You can use JSON, HJSON or YAML files to override any other file by prefixing th } ``` -If the config/smtpgreeting file did not exist, then this value would replace -it. +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. +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. ## Hjson Files @@ -287,24 +271,24 @@ Example syntax ```hjson { - # specify rate in requests/second (because comments are helpful!) - rate: 1000 + # specify rate in requests/second (because comments are helpful!) + rate: 1000 - // prefer c-style comments? - /* feeling old fashioned? */ + // prefer c-style comments? + /* feeling old fashioned? */ - # did you notice that rate does not need quotes? - hey: look ma, no quotes for strings either! + # did you notice that rate does not need quotes? + hey: look ma, no quotes for strings either! - # best of all - notice: [] - anything: ? + # best of all + notice: [] + anything: ? - # yes, commas are optional! + # yes, commas are optional! } ``` -NOTE: Hjson can be also replaced by YAML configuration file. You can find more on this issue under JSON section. +NOTE: Hjson can be also replaced by a YAML configuration file. You can find more on this issue under JSON section. ## YAML Files @@ -312,21 +296,10 @@ As per JSON files above but in YAML format. # 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 -new config. Providing a callback in the config.get() call is the most -efficient method to do this. +Haraka automatically reloads configuration files, but this only works if whatever is looking at that config re-calls config.get() to retrieve the new config. Providing a callback in the config.get() call is the most efficient method to do this. -Configuration files are watched for changes using filesystem events which -are inexpensive. Due to caching, calling config.get() is normally a -lightweight process. +Configuration files are watched for changes using filesystem events which are inexpensive. Due to caching, calling config.get() is normally a lightweight process. -On Linux/Windows, newly created files that Haraka has tried to read in the -past will be noticed immediately and loaded. For other operating systems, -it may take up to 60 seconds to load, due to differences between in the -kernel APIs for watching files/directories. +On Linux/Windows, newly created files that Haraka has tried to read in the past will be noticed immediately and loaded. For other operating systems, 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. -Be sure that Haraka's user/group has permission to read these files else -Haraka will be unable to update them after changes. +Haraka reads a number of configuration files at startup. Any files read 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 19a8601..4f7d9d2 100644 --- a/config.js +++ b/config.js @@ -2,7 +2,7 @@ const path = require('path') -const cfreader = require('./configfile') +const cfreader = require('./lib/reader') class Config { constructor(root_path, no_overrides) { diff --git a/configfile.js b/lib/reader.js similarity index 67% rename from configfile.js rename to lib/reader.js index a063af2..0aaa46c 100644 --- a/configfile.js +++ b/lib/reader.js @@ -4,10 +4,11 @@ const fs = require('node:fs') const fsp = require('node:fs/promises') const path = require('node:path') +const watch = require('./watch') + 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 + path.join(__dirname, '..', 'config'), // Haraka ./config dir + path.join(__dirname, '..'), // npm packaged plugins ] class cfreader { @@ -21,20 +22,6 @@ class cfreader { 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() { @@ -45,8 +32,8 @@ class cfreader { } if (process.env.NODE_ENV === 'test') { - // loaded by haraka-config/test/* - this.config_path = path.join(__dirname, 'test', 'config') + // console.log(`loaded by haraka-config/test/*`) + this.config_path = path.join(__dirname, '..', 'test', 'config') return } @@ -127,64 +114,6 @@ class cfreader { } } - 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() - } - } - } - get_cache_key(name, options) { // Ignore options etc. if this is an overriden value if (this._overrides[name]) return name @@ -233,11 +162,11 @@ class cfreader { case 'win32': case 'win64': case 'linux': - this.watch_dir() + watch.dir(this) break default: // All other operating systems - this.watch_file(name, type, cb, options) + watch.file(this, name, type, cb, options) } return result @@ -264,7 +193,7 @@ class cfreader { .then(resolve) .catch(reject) - if (opts.watchCb) this.fsWatchDir(name) + if (opts.watchCb) watch.dir2(this, name) }) } @@ -326,7 +255,7 @@ class cfreader { try { switch (type) { case 'ini': - result = cfrType.load(name, options, this.regex) + result = cfrType.load(name, options) break case 'hjson': case 'json': @@ -336,7 +265,7 @@ class cfreader { break // case 'binary': default: - result = cfrType.load(name, type, options, this.regex) + result = cfrType.load(name, type, options) } this._config_cache[cache_key] = result } catch (err) { @@ -373,29 +302,6 @@ class cfreader { 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() diff --git a/readers/binary.js b/lib/readers/binary.js similarity index 100% rename from readers/binary.js rename to lib/readers/binary.js diff --git a/readers/flat.js b/lib/readers/flat.js similarity index 95% rename from readers/flat.js rename to lib/readers/flat.js index d8c507f..cc689c3 100644 --- a/readers/flat.js +++ b/lib/readers/flat.js @@ -1,5 +1,7 @@ 'use strict' +const regex = require('../regex') + exports.load = (...args) => { return this.parseValue( ...args, @@ -14,7 +16,7 @@ exports.loadPromise = async (...args) => { ) } -exports.parseValue = (name, type, options, regex, data) => { +exports.parseValue = (name, type, options, data) => { let result = [] if (type === 'data') { diff --git a/readers/hjson.js b/lib/readers/hjson.js similarity index 100% rename from readers/hjson.js rename to lib/readers/hjson.js diff --git a/readers/ini.js b/lib/readers/ini.js similarity index 97% rename from readers/ini.js rename to lib/readers/ini.js index 57e836d..6d8f2a6 100644 --- a/readers/ini.js +++ b/lib/readers/ini.js @@ -1,5 +1,7 @@ 'use strict' +const regex = require('../regex') + exports.load = (...args) => { return this.parseIni( ...args, @@ -14,7 +16,7 @@ exports.loadPromise = async (...args) => { ) } -exports.parseIni = (name, options = {}, regex, data) => { +exports.parseIni = (name, options = {}, data) => { let result = { main: {} } let current_sect = result.main let current_sect_name = 'main' diff --git a/readers/js.js b/lib/readers/js.js similarity index 100% rename from readers/js.js rename to lib/readers/js.js diff --git a/readers/json.js b/lib/readers/json.js similarity index 100% rename from readers/json.js rename to lib/readers/json.js diff --git a/readers/yaml.js b/lib/readers/yaml.js similarity index 100% rename from readers/yaml.js rename to lib/readers/yaml.js diff --git a/lib/regex.js b/lib/regex.js new file mode 100644 index 0000000..4c61fac --- /dev/null +++ b/lib/regex.js @@ -0,0 +1,12 @@ +module.exports = { + 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: /(.+)\[\]$/, +} diff --git a/lib/watch.js b/lib/watch.js new file mode 100644 index 0000000..748a8fb --- /dev/null +++ b/lib/watch.js @@ -0,0 +1,82 @@ +const fs = require('node:fs') +const path = require('node:path') + +module.exports.file = (reader, 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 (reader._watchers[name] || (options && options.no_watch)) return + + try { + reader._watchers[name] = fs.watch( + name, + { persistent: false }, + reader.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 { + reader._enoent.files[name] = true + reader.ensure_enoent_timer() + } + } +} + +module.exports.dir = (reader) => { + // NOTE: Has OS platform limitations: + // https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener + const cp = reader.config_path + if (reader._watchers[cp]) return + + try { + reader._watchers[cp] = fs.watch( + cp, + { persistent: false }, + (fse, filename) => { + if (!filename) return + const full_path = path.join(cp, filename) + const args = reader._read_args[full_path] + if (!args) return + if (args.options?.no_watch) return + if (reader._sedation_timers[filename]) { + clearTimeout(reader._sedation_timers[filename]) + } + reader._sedation_timers[filename] = setTimeout(() => { + console.log(`Reloading file: ${full_path}`) + reader.load_config(full_path, args.type, args.options) + delete reader._sedation_timers[filename] + if (typeof args.cb === 'function') args.cb() + }, 5 * 1000) + }, + ) + } catch (e) { + console.error(`Error watching directory ${cp}(${e})`) + } +} + +module.exports.dir2 = (reader, dirPath) => { + if (reader._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 + + reader._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 = reader._read_args[dirPath] + // console.log(args); + if (reader._sedation_timers[full_path]) { + clearTimeout(reader._sedation_timers[full_path]) + } + reader._sedation_timers[full_path] = setTimeout(() => { + delete reader._sedation_timers[full_path] + args.opts.watchCb() + }, 2 * 1000) + }) +} diff --git a/package.json b/package.json index 749b373..a957203 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,7 @@ }, "main": "config.js", "files": [ - "readers", - "configfile.js", + "lib", "CHANGELOG.md" ], "engines": { @@ -33,8 +32,8 @@ }, "scripts": { "format": "npm run prettier:fix && npm run lint:fix", - "lint": "npx eslint@^8 *.js readers test test/*/*.js", - "lint:fix": "npx eslint@^8 *.js readers test test/*/*.js --fix", + "lint": "npx eslint@^8 *.js lib test test/*/*.js", + "lint:fix": "npx eslint@^8 *.js lib test test/*/*.js --fix", "prettier": "npx prettier . --check", "prettier:fix": "npx prettier . --write --log-level=warn", "test": "npx mocha@10 test test/readers", diff --git a/test/config.js b/test/config.js index daaf9a5..41083cf 100644 --- a/test/config.js +++ b/test/config.js @@ -13,7 +13,7 @@ function clearRequireCache() { // the tests are run in the same process, so process.env changes affect // other tests. Invalidate the require cache between tests delete require.cache[`${path.resolve(__dirname, '..', 'config')}.js`] - delete require.cache[`${path.resolve(__dirname, '..', 'configfile')}.js`] + delete require.cache[`${path.resolve(__dirname, '..', 'lib', 'reader')}.js`] } function testSetup(done) { diff --git a/test/configfile.js b/test/configfile.js index 24b2751..fd7e4dd 100644 --- a/test/configfile.js +++ b/test/configfile.js @@ -3,10 +3,10 @@ const assert = require('node:assert') const path = require('node:path') -describe('configfile', function () { +describe('reader', function () { beforeEach(function (done) { process.env.NODE_ENV === 'test' - this.cfreader = require('../configfile') + this.cfreader = require('../lib/reader') this.opts = { booleans: ['main.bool_true', 'main.bool_false'] } done() }) @@ -307,66 +307,6 @@ describe('configfile', function () { }) }) - 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('param', function () { - assert.equal(this.cfreader.regex.param.exec('foo=true')[1], 'foo') - assert.equal(this.cfreader.regex.param.exec(';foo=true'), undefined) - }) - - it('comment', function () { - assert.equal(this.cfreader.regex.comment.test('; true'), true) - assert.equal(this.cfreader.regex.comment.test('false'), false) - }) - - it('line', function () { - assert.equal(this.cfreader.regex.line.test(' boo '), true) - assert.equal(this.cfreader.regex.line.test('foo'), true) - }) - - it('blank', function () { - assert.equal(this.cfreader.regex.blank.test('foo'), false) - assert.equal(this.cfreader.regex.blank.test(' '), true) - }) - - // 'continuation', function () { - // 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) - }) - - 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) - }) - - 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('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) - }) - }) - describe('bad_config', function () { it('bad.yaml returns empty', function () { assert.deepEqual(this.cfreader.load_config('test/config/bad.yaml'), {}) diff --git a/test/readers/binary.js b/test/readers/binary.js index f336464..aff676e 100644 --- a/test/readers/binary.js +++ b/test/readers/binary.js @@ -3,7 +3,7 @@ const fs = require('fs') const path = require('path') beforeEach(function (done) { - this.bin = require('../../readers/binary') + this.bin = require('../../lib/readers/binary') done() }) diff --git a/test/readers/flat.js b/test/readers/flat.js index 5ecaeba..78f8d2f 100644 --- a/test/readers/flat.js +++ b/test/readers/flat.js @@ -1,9 +1,7 @@ const assert = require('assert') -const regex = require('../../configfile').regex - beforeEach(function (done) { - this.flat = require('../../readers/flat') + this.flat = require('../../lib/readers/flat') done() }) @@ -17,18 +15,17 @@ describe('flat', function () { }) it('loads the test flat file, as list', function () { - const result = this.flat.load('test/config/test.flat', 'list', null, regex) + const result = this.flat.load('test/config/test.flat', 'list', null) assert.deepEqual(result, ['line1', 'line2', 'line3', 'line5']) }) it('loads the test flat file, unspecified type', function () { - const result = this.flat.load('test/config/test.flat', null, null, regex) + const result = this.flat.load('test/config/test.flat', null, null) assert.deepEqual(result, 'line1') }) it('returns hostname for empty "me"', function () { - const result = this.flat.load('test/config/me', null, null, regex) - console.log(result) + const result = this.flat.load('test/config/me', null, null) assert.ok(result) }) }) diff --git a/test/readers/hjson.js b/test/readers/hjson.js index 816e46f..4682428 100644 --- a/test/readers/hjson.js +++ b/test/readers/hjson.js @@ -2,7 +2,7 @@ const assert = require('assert') const path = require('path') beforeEach(function (done) { - this.hjson = require('../../readers/hjson') + this.hjson = require('../../lib/readers/hjson') done() }) diff --git a/test/readers/ini.js b/test/readers/ini.js index c979d24..e31e057 100644 --- a/test/readers/ini.js +++ b/test/readers/ini.js @@ -1,9 +1,7 @@ const assert = require('assert') -const regex = require('../../configfile').regex - beforeEach(function (done) { - this.ini = require('../../readers/ini') + this.ini = require('../../lib/readers/ini') this.opts = { booleans: ['main.bool_true', 'main.bool_false'], } @@ -20,7 +18,7 @@ describe('ini', function () { }) it('loads the test ini file', function () { - const result = this.ini.load('test/config/test.ini', {}, regex) + const result = this.ini.load('test/config/test.ini', {}) // console.log(result); assert.deepEqual(result.main, { bool_true: 'true', @@ -32,7 +30,7 @@ describe('ini', function () { describe('test.ini', function () { it('no opts', function () { - const r = this.ini.load('test/config/test.ini', {}, regex) + const r = this.ini.load('test/config/test.ini', {}) assert.strictEqual(r.main.bool_true, 'true') assert.strictEqual(r.main.bool_false, 'false') assert.strictEqual(r.main.str_true, 'true') @@ -40,7 +38,7 @@ describe('ini', function () { }) it('opts', function () { - const r = this.ini.load('test/config/test.ini', this.opts, regex).main + const r = this.ini.load('test/config/test.ini', this.opts).main assert.strictEqual(r.bool_true, true) assert.strictEqual(r.bool_false, false) assert.strictEqual(r.str_true, 'true') @@ -48,13 +46,9 @@ describe('ini', function () { }) it('sect1, opts', function () { - const r = this.ini.load( - 'test/config/test.ini', - { - booleans: ['sect1.bool_true', 'sect1.bool_false'], - }, - regex, - ) + const r = this.ini.load('test/config/test.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') @@ -62,18 +56,14 @@ describe('ini', function () { }) it('sect1, opts, w/defaults', function () { - 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, - ) + const r = this.ini.load('test/config/test.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') @@ -83,13 +73,9 @@ describe('ini', function () { }) it('wildcard boolean', function () { - const r = this.ini.load( - 'test/config/test.ini', - { - booleans: ['+main.bool_true', '*.is_bool'], - }, - regex, - ) + const r = this.ini.load('test/config/test.ini', { + booleans: ['+main.bool_true', '*.is_bool'], + }) assert.strictEqual(r['*'], undefined) assert.strictEqual(r.main.bool_true, true) assert.strictEqual(r.main.is_bool, undefined) @@ -147,7 +133,7 @@ describe('ini', function () { describe('goobers.ini', function () { it('goobers.ini has invalid entry', function () { - const result = this.ini.load('test/config/goobers.ini', {}, regex) + const result = this.ini.load('test/config/goobers.ini', {}) assert.deepEqual(result, { main: {} }) }) }) diff --git a/test/readers/json.js b/test/readers/json.js index 5378790..988fd1e 100644 --- a/test/readers/json.js +++ b/test/readers/json.js @@ -1,7 +1,7 @@ const assert = require('assert') beforeEach(function (done) { - this.json = require('../../readers/json') + this.json = require('../../lib/readers/json') done() }) diff --git a/test/readers/yaml.js b/test/readers/yaml.js index f296190..f2a2d83 100644 --- a/test/readers/yaml.js +++ b/test/readers/yaml.js @@ -1,7 +1,7 @@ const assert = require('assert') beforeEach(function (done) { - this.yaml = require('../../readers/yaml') + this.yaml = require('../../lib/readers/yaml') done() }) diff --git a/test/regex.js b/test/regex.js new file mode 100644 index 0000000..f095af0 --- /dev/null +++ b/test/regex.js @@ -0,0 +1,59 @@ +const assert = require('node:assert') + +const regex = require('../lib/regex') + +describe('regex', function () { + it('section', function () { + assert.equal(regex.section.test('[foo]'), true) + assert.equal(regex.section.test('bar'), false) + assert.equal(regex.section.test('[bar'), false) + assert.equal(regex.section.test('bar]'), false) + }) + + it('param', function () { + assert.equal(regex.param.exec('foo=true')[1], 'foo') + assert.equal(regex.param.exec(';foo=true'), undefined) + }) + + it('comment', function () { + assert.equal(regex.comment.test('; true'), true) + assert.equal(regex.comment.test('false'), false) + }) + + it('line', function () { + assert.equal(regex.line.test(' boo '), true) + assert.equal(regex.line.test('foo'), true) + }) + + it('blank', function () { + assert.equal(regex.blank.test('foo'), false) + assert.equal(regex.blank.test(' '), true) + }) + + it('is_integer', function () { + assert.equal(regex.is_integer.test(1), true) + assert.equal(regex.is_integer.test(''), false) + assert.equal(regex.is_integer.test('a'), false) + }) + + it('is_float', function () { + assert.equal(regex.is_float.test('1.0'), true) + assert.equal(regex.is_float.test(''), false) + assert.equal(regex.is_float.test('45'), false) + }) + + it('is_truth', function () { + assert.equal(regex.is_truth.test('no'), false) + assert.equal(regex.is_truth.test('nope'), false) + assert.equal(regex.is_truth.test('nuh uh'), false) + assert.equal(regex.is_truth.test('yes'), true) + assert.equal(regex.is_truth.test('true'), true) + assert.equal(regex.is_truth.test(true), true) + }) + + it('is_array', function () { + assert.equal(regex.is_array.test('foo=bar'), false) + assert.equal(regex.is_array.test('foo'), false) + assert.equal(regex.is_array.test('foo[]'), true) + }) +})