pax_global_header00006660000000000000000000000064151731722750014524gustar00rootroot0000000000000052 comment=3f30712abe82233a456355c19859d73466c329bf pixl-config-1.0.15/000077500000000000000000000000001517317227500140275ustar00rootroot00000000000000pixl-config-1.0.15/.npmignore000066400000000000000000000000311517317227500160200ustar00rootroot00000000000000.gitignore node_modules/ pixl-config-1.0.15/README.md000066400000000000000000000101271517317227500153070ustar00rootroot00000000000000# Overview This module provides a simple interface to your application's JSON configuration file on disk. It will load and parse the file, and can automatically reload it when it changes (and emit an event when this happens). Command-line arguments are automatically merged in with your config (uses [pixl-args](https://www.npmjs.com/package/pixl-args)), and treated as overrides. # Usage Use [npm](https://www.npmjs.com/) to install the module: ```sh npm install pixl-config ``` Then use `require()` to load it in your code: ```js const Config = require('pixl-config'); ``` To use the module, instantiate an object: ```js let config = new Config('conf/config.json'); ``` If there is an error loading or parsing the file, an exception will be thrown, so you might want to wrap it in a try/catch. Upon success, you can access your configuration keys/values using the `get()` method: ```js let verbose = config.get('verbose'); let debug = config.get('debug'); ``` If you just want a hash of the entire config file, call `get()` without passing a key: ```js let opts = config.get(); if (opts.verbose) console.log("Verbose option is set."); if (opts.debug) console.log("Debug option is set."); ``` You can also set values by calling `set()` and passing both a key and a value. Example: ```js config.set('verbose', true); ``` Values you set manually are persisted as long as the Node process is alive, even if the configuration file is reloaded on disk. ## Command-line Arguments Command-line arguments (from [process.argv](http://nodejs.org/docs/latest/api/process.html#process_process_argv)) are automatically parsed and merged in with your configuration, and treated as overrides. Only `--key value --key value` style arguments are processed. Please see the [pixl-args](https://www.npmjs.com/package/pixl-args) module for more details. ## Live File Reloading If you want the library to monitor your configuration file for live changes and automatically reload it, pass `true` as a second argument to the constructor: ```js let config = new Config('conf/config.json', true); ``` This uses polling, and by default it checks the file for changes every 10 seconds. The library then reloads the configuration and fires off an event you can listen for: ```js let config = new Config('conf/config.json', true); config.on('reload', function() { console.log("My app's config was reloaded!"); console.log("Verbose = " + config.get('verbose')); } ); ``` If there is an error reloading the configuration, an `error` event is emitted (no exception is thrown). To set the polling frequency, pass in the desired number of milliseconds instead of `true` as the second argument. For example, to check every five seconds pass in `5000`: ```js let config = new Config('conf/config.json', 5000); ``` To stop monitoring (for example during your app's shutdown sequence) call the `stop()` method. If you would prefer to control exactly when to check for changes, do not enable live monitoring, and instead simply call the `check()` method at any frequency you want. # License **The MIT License** *Copyright (c) 2014 - 2019 by Joseph Huckaby* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pixl-config-1.0.15/config.js000066400000000000000000000204041517317227500156320ustar00rootroot00000000000000// JSON Server Configuration System // Loads config file and command-line arguments // Copyright (c) 2014 - 2019 Joseph Huckaby // Released under the MIT License var fs = require("fs"); var cp = require("child_process"); var os = require('os'); var Class = require("pixl-class"); var Args = require("pixl-args"); var Tools = require("pixl-tools"); var Config = module.exports = Class.create({ configFile: "", config: null, args: null, subs: null, mod: 0, timer: null, freq: 10 * 1000, hostname: '', ip: '', __construct: function(thingy, watch, isa_sub) { // class constructor this.subs = {}; if (thingy) { if (typeof(thingy) == 'string') this.configFile = thingy; else { this.config = thingy; this.configFile = ""; } } else return; // manual setup if (!isa_sub) { this.args = new Args(); } if (this.configFile) this.load(); else if (!isa_sub) this.loadArgs(); if (this.configFile && watch && !isa_sub) { if (typeof(watch) == 'number') this.freq = watch; if (this.config.check_config_freq_ms) this.freq = this.config.check_config_freq_ms; this.monitor(); } }, parse: function(text) { // default JSON parser (client can override) return JSON.parse(text); }, load: function() { // load config and merge in cmdline var self = this; this.config = {}; var stats = fs.statSync( this.configFile ); this.mod = (stats && stats.mtime) ? stats.mtime.getTime() : 0; var config = this.parse( fs.readFileSync( this.configFile, { encoding: 'utf8' } ) ); for (var key in config) { this.config[key] = config[key]; } // cmdline args (--key value) this.loadArgs(); }, loadArgs: function() { // merge in cmdline args (--key value) if (!this.args) return; for (var key in this.args.get()) { this.setPath(key, this.args.get(key)); } }, monitor: function() { // start monitoring file for changes this.timer = setInterval( this.check.bind(this), this.freq ); }, stop: function() { // stop monitoring file clearTimeout( this.timer ); }, check: function() { // check file for changes, reload if necessary var self = this; fs.stat( this.configFile, function(err, stats) { // ignore errors here due to possible race conditions var mod = (stats && stats.mtime) ? stats.mtime.getTime() : 0; if (mod && (mod != self.mod)) { // file has changed on disk, reload it async self.mod = mod; fs.readFile( self.configFile, { encoding: 'utf8' }, function(err, data) { // fs read complete if (err) { self.emit('error', "Failed to reload config file: " + self.configFile + ": " + err); return; } // now parse the JSON var config = null; try { config = self.parse( data ); } catch (err) { self.emit('error', "Failed to parse config file: " + self.configFile + ": " + err); return; } // replace master copy self.config = config; // re-merge in cli args if (self.args) { for (var key in self.args.get()) { self.setPath(key, self.args.get(key)); } } // emit event for listeners self.emit('reload'); // refresh subs self.refreshSubs(); // reinitialize monitor if frequency has changed if (self.timer && config.check_config_freq_ms && (config.check_config_freq_ms != self.freq)) { self.freq = config.check_config_freq_ms; self.stop(); self.monitor(); } } ); // fs.readFile } // mod changed } ); // fs.stat }, get: function(key) { // get single key or entire config hash return key ? this.config[key] : this.config; }, set: function(key, value) { // set config value this.config[key] = value; // also set it in this.args so a file reload won't clobber it if (this.args) this.args.set(key, value); }, delete: function(key) { // delete config key delete this.config[key]; }, import: function(hash) { // import all keys/values from specified hash (shallow copy) Tools.mergeHashInto( this.config, hash ); }, getSub: function(key) { // get cloned Config object pointed at sub-key var sub = new Config( this.get(key) || {}, null, true ); // keep track so we can refresh on reload this.subs[key] = sub; return sub; }, refreshSubs: function() { // refresh sub key objects on a reload for (var key in this.subs) { var sub = this.subs[key]; sub.config = this.get(key) || {}; sub.emit('reload'); sub.refreshSubs(); } }, getEnv: function(callback) { // determine environment (hostname and ip) async var self = this; // get hostname and ip (async ops) self.getHostname( function(err) { if (err) callback(err); else { self.getIPAddress( callback ); } } ); }, getHostname: function(callback) { // determine server hostname this.hostname = this.get('hostname'); if (this.hostname) { // well that was easy callback(); return; } // try ENV vars next this.hostname = (process.env['HOSTNAME'] || process.env['HOST'] || '').toLowerCase(); if (this.hostname) { // well that was easy callback(); return; } // try the OS module this.hostname = os.hostname().toLowerCase(); if (this.hostname) { // well that was easy callback(); return; } // sigh, the hard way (exec hostname binary) var self = this; child = cp.execFile('/bin/hostname', function (error, stdout, stderr) { self.hostname = stdout.toString().trim().toLowerCase(); if (!self.hostname) { callback( new Error("Failed to determine server hostname via /bin/hostname") ); } else callback(); } ); }, getIPAddress: function(callback) { // determine server ip address // allow the config to override this this.ip = this.get('ip'); if (this.ip) { // well that was easy callback(); return; } // try OS networkInterfaces() // find the first external IPv4 address that doesn't match 127.* or 169.254.* var ifaces = os.networkInterfaces(); var addrs = []; for (var key in ifaces) { if (ifaces[key] && ifaces[key].length) { Array.from(ifaces[key]).forEach( function(item) { addrs.push(item); } ); } } var iaddrs = Tools.findObjects( addrs, { family: 'IPv4', internal: false } ); for (var idx = 0, len = iaddrs.length; idx < len; idx++) { var addr = iaddrs[idx]; if (addr && addr.address && addr.address.match(/^\d+\.\d+\.\d+\.\d+$/) && !addr.address.match(/^127\./) && !addr.address.match(/^169\.254\./)) { // found an interface that is not 127.* or 169.254.* so go with that one this.ip = addr.address; callback(); return; } } var addr = iaddrs[0]; if (addr && addr.address && addr.address.match(/^\d+\.\d+\.\d+\.\d+$/) && !addr.address.match(/^127\./)) { // this will allow 169.254. to be chosen only after all other non-internal IPv4s are considered this.ip = addr.address; callback(); return; } this.ip = '127.0.0.1'; callback(); }, setPath: function(path, value) { // set path using dir/slash/syntax or dot.path.syntax // preserve dots and slashes if escaped var parts = path.replace(/\\\./g, '__PXDOT__').replace(/\\\//g, '__PXSLASH__').split(/[\.\/]/).map( function(elem) { return elem.replace(/__PXDOT__/g, '.').replace(/__PXSLASH__/g, '/'); } ); var key = parts.pop(); var target = this.config; // traverse path while (parts.length) { var part = parts.shift(); if (part) { if (!(part in target)) { // auto-create nodes target[part] = {}; } if (typeof(target[part]) != 'object') { // path runs into non-object return false; } target = target[part]; } } target[key] = value; return true; }, getPath: function(path) { // get path using dir/slash/syntax or dot.path.syntax // preserve dots and slashes if escaped var parts = path.replace(/\\\./g, '__PXDOT__').replace(/\\\//g, '__PXSLASH__').split(/[\.\/]/).map( function(elem) { return elem.replace(/__PXDOT__/g, '.').replace(/__PXSLASH__/g, '/'); } ); var key = parts.pop(); var target = this.config; // traverse path while (parts.length) { var part = parts.shift(); if (part) { if (typeof(target[part]) != 'object') { // path runs into non-object return undefined; } target = target[part]; } } return target[key]; } }); pixl-config-1.0.15/package.json000066400000000000000000000011001517317227500163050ustar00rootroot00000000000000{ "name": "pixl-config", "version": "1.0.15", "description": "A simple JSON configuration loader.", "author": "Joseph Huckaby ", "homepage": "https://github.com/jhuckaby/pixl-config", "license": "MIT", "main": "config.js", "repository": { "type": "git", "url": "https://github.com/jhuckaby/pixl-config" }, "bugs": { "url": "https://github.com/jhuckaby/pixl-config/issues" }, "keywords": [ "json", "config" ], "dependencies": { "pixl-class": "^1.0.3", "pixl-args": "^1.0.0", "pixl-tools": "^2.0.2" }, "devDependencies": {} }