diff --git a/dist/index.d.ts b/dist/index.d.ts
index 70b7737..a101a14 100644
--- a/dist/index.d.ts
+++ b/dist/index.d.ts
@@ -4,46 +4,22 @@
* https://github.com/bcoin-org/bcoin
*/
///
-export interface Options {
- suffix?: string;
- fallback?: string;
- alias?: object;
-}
-export interface LoadOptions {
- hash?: string | boolean;
- query?: string | boolean;
- env?: object | boolean;
- argv?: string[] | boolean;
- config?: string | boolean;
-}
+import arg from "arg";
/**
* Config Parser
*/
export default class Config {
private module;
- private prefix;
- private suffix?;
- private fallback?;
- private options;
- private alias;
private data;
- private env;
- private args;
- private argv;
- private pass;
- private query;
- private hash;
- constructor(module: string, options?: Options);
- private init;
+ constructor(module: string);
inject(options: object): void;
- load(options: LoadOptions): void;
- open(file: string): void;
+ load(): void;
openDir(dir: string): void;
- openJson(file: string): void;
- saveConfigJson(file: string, data: object): void;
- filter(name: string): Config;
+ open(file: string): void;
+ save(file: string, data: object): void;
set(key: string, value: any): void;
- has(key: string): boolean;
+ has(key: string): any;
+ private normalize;
get(key: string, fallback?: any): any;
typeOf(key: string): "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | "null";
str(key: string, fallback?: any): any;
@@ -58,16 +34,7 @@ export default class Config {
array(key: string, fallback?: any): any;
obj(key: string, fallback?: any): any;
func(key: string, fallback?: any): any;
- path(key: string, fallback?: any): any;
mb(key: string, fallback?: any): any;
- getSuffix(): any;
- getPrefix(): string;
- getFile(file: string): any;
- location(file: string): string;
- parseConfig(text: string): void;
- parseArg(argv?: string[]): void;
+ parseArg(args: arg.Result): void;
parseEnv(env?: object): void;
- parseQuery(query: string): void | {};
- parseHash(hash: string): void | {};
- parseForm(query: string, ch: string, map: object): void;
}
diff --git a/dist/index.js b/dist/index.js
index b0f33db..6abb613 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -10,60 +10,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true });
const bsert_1 = __importDefault(require("bsert"));
const path_1 = __importDefault(require("path"));
-const os_1 = __importDefault(require("os"));
const fs_1 = __importDefault(require("fs"));
-const HOME = os_1.default.homedir ? os_1.default.homedir() : "/";
+const arg_1 = __importDefault(require("arg"));
+const object_path_1 = __importDefault(require("object-path"));
+const deep_to_flat_object_1 = __importDefault(require("deep-to-flat-object"));
/**
* Config Parser
*/
class Config {
module;
- prefix;
- suffix;
- fallback;
- options = {};
- alias = {};
data = {};
- env = {};
- args = {};
- argv = [];
- pass = [];
- query = {};
- hash = {};
- constructor(module, options) {
+ constructor(module) {
(0, bsert_1.default)(typeof module === "string");
(0, bsert_1.default)(module.length > 0);
this.module = module;
- this.prefix = path_1.default.join(HOME, `.${module}`);
- if (options) {
- this.init(options);
- }
- }
- init(options) {
- (0, bsert_1.default)(options && typeof options === "object");
- if (options.suffix != null) {
- (0, bsert_1.default)(typeof options.suffix === "string");
- this.suffix = options.suffix;
- }
- if (options.fallback != null) {
- (0, bsert_1.default)(typeof options.fallback === "string");
- this.fallback = options.fallback;
- }
- if (options.alias) {
- (0, bsert_1.default)(typeof options.alias === "object");
- for (const key of Object.keys(options.alias)) {
- const value = options.alias[key];
- (0, bsert_1.default)(typeof value === "string");
- this.alias[key] = value;
- }
- }
}
inject(options) {
for (const key of Object.keys(options)) {
const value = options[key];
switch (key) {
- case "hash":
- case "query":
case "env":
case "argv":
case "config":
@@ -72,41 +37,19 @@ class Config {
this.set(key, value);
}
}
- load(options) {
- if (options.hash) {
- this.parseHash(options.hash);
- }
- if (options.query) {
- this.parseQuery(options.query);
- }
- if (options.env) {
- this.parseEnv(options.env);
- }
- if (options.argv) {
- this.parseArg(options.argv);
- }
- this.prefix = this.getPrefix();
- }
- open(file) {
- const path = this.getFile(file);
- let text;
- try {
- text = fs_1.default.readFileSync(path, "utf8");
- }
- catch (e) {
- if (e.code === "ENOENT")
- return;
- throw e;
- }
- this.parseConfig(text);
- this.prefix = this.getPrefix();
+ load() {
+ const args = (0, arg_1.default)({}, { permissive: true });
+ this.parseArg(args);
}
openDir(dir) {
(0, bsert_1.default)(fs_1.default.existsSync(dir), `Directory ${dir} does not exist`);
- let files = fs_1.default.readdirSync(dir).map((item) => path_1.default.join(dir, item));
- files.forEach(this.openJson.bind(this));
+ let files = fs_1.default
+ .readdirSync(dir)
+ .filter((item) => item.endsWith(".json"))
+ .map((item) => path_1.default.join(dir, item));
+ files.forEach(this.open.bind(this));
}
- openJson(file) {
+ open(file) {
let json;
try {
json = fs_1.default.readFileSync(file, "utf8");
@@ -118,20 +61,22 @@ class Config {
throw new Error(`Error parsing file ${file}: ${e.message}`);
}
(0, bsert_1.default)(typeof json === "object", `Config file ${file} must be an object`);
- for (const key of Object.keys(json)) {
+ const settings = (0, deep_to_flat_object_1.default)(json);
+ for (const key of Object.keys(settings)) {
const value = json[key];
- switch (true) {
- case Array.isArray(value):
- this.set(key, [...(this.array(key) ?? []), ...value]);
- break;
- default:
- this.set(key, value);
- break;
+ let keyPath = key.split(".");
+ let isArray = typeof parseInt(keyPath.pop()) === "number";
+ if (isArray) {
+ let itemPath = keyPath.join(".");
+ let item = this.get(itemPath, []);
+ item.push(value);
+ this.set(itemPath, item);
+ continue;
}
+ this.set(key, value);
}
- this.prefix = this.getPrefix();
}
- saveConfigJson(file, data) {
+ save(file, data) {
(0, bsert_1.default)(typeof data === "object");
(0, bsert_1.default)(!Array.isArray(data));
const configDir = this.str("configdir");
@@ -140,56 +85,34 @@ class Config {
fs_1.default.mkdirSync(configDir, { recursive: true });
}
fs_1.default.writeFileSync(fullPath, JSON.stringify(data));
- this.openJson(fullPath);
- }
- filter(name) {
- (0, bsert_1.default)(typeof name === "string");
- const child = new Config(this.module);
- child.prefix = this.prefix;
- child.suffix = this.suffix;
- child.fallback = this.fallback;
- child.argv = this.argv;
- child.pass = this.pass;
- _filter(name, this.env, child.env);
- _filter(name, this.args, child.args);
- _filter(name, this.query, child.query);
- _filter(name, this.hash, child.hash);
- _filter(name, this.options, child.options);
- return child;
+ this.open(fullPath);
}
set(key, value) {
(0, bsert_1.default)(typeof key === "string", "Key must be a string.");
if (value == null) {
return;
}
- key = key.replace(/-/g, "");
- key = key.toLowerCase();
- this.options[key] = value;
+ key = this.normalize(key);
+ object_path_1.default.set(this.data, key, value);
+ this.data[key] = value;
}
has(key) {
- if (typeof key === "number") {
- (0, bsert_1.default)(key >= 0, "Index must be positive.");
- return key < this.argv.length;
- }
(0, bsert_1.default)(typeof key === "string", "Key must be a string.");
key = key.replace(/-/g, "");
key = key.toLowerCase();
- if (key in this.hash && this.hash[key] !== null) {
- return true;
+ return object_path_1.default.has(this.data, key);
+ }
+ normalize(key, env = false) {
+ (0, bsert_1.default)(typeof key === "string", "Key must be a string.");
+ if (env) {
+ key = key.replace(/__/g, ".");
+ key = key.replace(/_/g, "");
}
- if (key in this.query && this.query[key] !== null) {
- return true;
+ else {
+ key = key.replace(/-/g, "");
}
- if (key in this.args && this.args[key] !== null) {
- return true;
- }
- if (key in this.env && this.env[key] !== null) {
- return true;
- }
- if (key in this.data && this.data[key] !== null) {
- return true;
- }
- return this.options[key] !== null;
+ key = key.toLowerCase();
+ return key;
}
get(key, fallback = null) {
if (Array.isArray(key)) {
@@ -202,38 +125,9 @@ class Config {
}
return fallback;
}
- if (typeof key === "number") {
- (0, bsert_1.default)(key >= 0, "Index must be positive.");
- if (key >= this.argv.length) {
- return fallback;
- }
- if (this.argv[key] != null) {
- return this.argv[key];
- }
- return fallback;
- }
(0, bsert_1.default)(typeof key === "string", "Key must be a string.");
- key = key.replace(/-/g, "");
- key = key.toLowerCase();
- if (key in this.hash && this.hash[key] !== null) {
- return this.hash[key];
- }
- if (key in this.query && this.query[key] !== null) {
- return this.query[key];
- }
- if (key in this.args && this.args[key] !== null) {
- return this.args[key];
- }
- if (key in this.env && this.env[key] !== null) {
- return this.env[key];
- }
- if (key in this.data && this.data[key] !== null) {
- return this.data[key];
- }
- if (key in this.options && this.options[key] !== null) {
- return this.options[key];
- }
- return fallback;
+ key = this.normalize(key);
+ return object_path_1.default.get(this.data, key);
}
typeOf(key) {
const value = this.get(key);
@@ -429,26 +323,6 @@ class Config {
}
return value;
}
- path(key, fallback = null) {
- let value = this.str(key);
- if (value === null) {
- return fallback;
- }
- if (value.length === 0) {
- return fallback;
- }
- switch (value[0]) {
- case "~": // home dir
- value = path_1.default.join(HOME, value.substring(1));
- break;
- case "@": // prefix
- value = path_1.default.join(this.prefix, value.substring(1));
- break;
- default: // cwd
- break;
- }
- return path_1.default.normalize(value);
- }
mb(key, fallback = null) {
const value = this.uint(key);
if (value === null) {
@@ -456,176 +330,10 @@ class Config {
}
return value * 1024 * 1024;
}
- getSuffix() {
- if (!this.suffix) {
- throw new Error("No suffix presented.");
- }
- const suffix = this.str(this.suffix, this.fallback);
- (0, bsert_1.default)(isAlpha(suffix), "Bad suffix.");
- return suffix;
- }
- getPrefix() {
- let prefix = this.str("prefix");
- if (prefix) {
- if (prefix[0] === "~") {
- prefix = path_1.default.join(HOME, prefix.substring(1));
- }
- }
- else {
- prefix = path_1.default.join(HOME, `.${this.module}`);
- }
- if (this.suffix) {
- const suffix = this.str(this.suffix);
- if (suffix) {
- (0, bsert_1.default)(isAlpha(suffix), "Bad suffix.");
- if (this.fallback && suffix !== this.fallback) {
- prefix = path_1.default.join(prefix, suffix);
- }
- }
- }
- return path_1.default.normalize(prefix);
- }
- getFile(file) {
- const name = this.str("config");
- if (name) {
- return name;
- }
- return path_1.default.join(this.prefix, file);
- }
- location(file) {
- return path_1.default.join(this.prefix, file);
- }
- parseConfig(text) {
- (0, bsert_1.default)(typeof text === "string", "Config must be text.");
- if (text.charCodeAt(0) === 0xfeff) {
- text = text.substring(1);
- }
- text = text.replace(/\r\n/g, "\n");
- text = text.replace(/\r/g, "\n");
- text = text.replace(/\\\n/g, "");
- let num = 0;
- for (const chunk of text.split("\n")) {
- const line = chunk.trim();
- num += 1;
- if (line.length === 0) {
- continue;
- }
- if (line[0] === "#") {
- continue;
- }
- const index = line.indexOf(":");
- if (index === -1) {
- throw new Error(`Expected ':' on line ${num}: "${line}".`);
- }
- let key = line.substring(0, index).trim();
- key = key.replace(/\-/g, "");
- if (!isLowerKey(key)) {
- throw new Error(`Invalid option on line ${num}: ${key}.`);
- }
- const value = line.substring(index + 1).trim();
- if (value.length === 0) {
- continue;
- }
- const alias = this.alias[key];
- if (alias) {
- key = alias;
- }
- this.data[key] = value;
- }
- }
- parseArg(argv) {
- if (!argv || typeof argv !== "object")
- argv = process.argv;
- (0, bsert_1.default)(Array.isArray(argv));
- let last = null;
- let pass = false;
- for (let i = 2; i < argv.length; i++) {
- const arg = argv[i];
- (0, bsert_1.default)(typeof arg === "string");
- if (arg === "--") {
- pass = true;
- continue;
- }
- if (pass) {
- this.pass.push(arg);
- continue;
- }
- if (arg.length === 0) {
- last = null;
- continue;
- }
- if (arg.indexOf("--") === 0) {
- const index = arg.indexOf("=");
- let key = null;
- let value = null;
- let empty = false;
- if (index !== -1) {
- // e.g. --opt=val
- key = arg.substring(2, index);
- value = arg.substring(index + 1);
- last = null;
- empty = false;
- }
- else {
- // e.g. --opt
- key = arg.substring(2);
- value = "true";
- last = null;
- empty = true;
- }
- key = key.replace(/\-/g, "");
- if (!isLowerKey(key)) {
- throw new Error(`Invalid argument: --${key}.`);
- }
- if (value.length === 0) {
- continue;
- }
- // Do not allow one-letter aliases.
- if (key.length > 1) {
- const alias = this.alias[key];
- if (alias) {
- key = alias;
- }
- }
- this.args[key] = value;
- if (empty) {
- last = key;
- }
- continue;
- }
- if (arg[0] === "-") {
- // e.g. -abc
- last = null;
- for (let j = 1; j < arg.length; j++) {
- let key = arg[j];
- if ((key < "a" || key > "z") &&
- (key < "A" || key > "Z") &&
- (key < "0" || key > "9") &&
- key !== "?") {
- throw new Error(`Invalid argument: -${key}.`);
- }
- const alias = this.alias[key];
- if (alias) {
- key = alias;
- }
- this.args[key] = "true";
- last = key;
- }
- continue;
- }
- // e.g. foo
- const value = arg;
- if (value.length === 0) {
- last = null;
- continue;
- }
- if (last) {
- this.args[last] = value;
- last = null;
- }
- else {
- this.argv.push(value);
- }
+ parseArg(args) {
+ for (let key in args) {
+ let newKey = key.replace("-", "");
+ object_path_1.default.set(this.data, newKey, args[key]);
}
}
parseEnv(env) {
@@ -640,84 +348,18 @@ class Config {
for (let key of Object.keys(env)) {
const value = env[key];
(0, bsert_1.default)(typeof value === "string");
- if (key.indexOf(prefix) !== 0)
+ if (key.indexOf(prefix) !== 0) {
continue;
- key = key.substring(prefix.length);
- key = key.replace(/_/g, "");
+ }
if (!isUpperKey(key)) {
continue;
}
+ key = key.substring(prefix.length);
+ key = this.normalize(key, true);
if (value.length === 0) {
continue;
}
- key = key.toLowerCase();
- // Do not allow one-letter aliases.
- if (key.length > 1) {
- const alias = this.alias[key];
- if (alias) {
- key = alias;
- }
- }
- this.env[key] = value;
- }
- }
- parseQuery(query) {
- if (typeof query !== "string") {
- if (!global.location) {
- return {};
- }
- query = global.location.search;
- if (typeof query !== "string") {
- return {};
- }
- }
- return this.parseForm(query, "?", this.query);
- }
- parseHash(hash) {
- if (typeof hash !== "string") {
- if (!global.location) {
- return {};
- }
- hash = global.location.hash;
- if (typeof hash !== "string") {
- return {};
- }
- }
- return this.parseForm(hash, "#", this.hash);
- }
- parseForm(query, ch, map) {
- (0, bsert_1.default)(typeof query === "string");
- if (query.length === 0) {
- return;
- }
- if (query[0] === ch) {
- query = query.substring(1);
- }
- for (const pair of query.split("&")) {
- const index = pair.indexOf("=");
- let key, value;
- if (index !== -1) {
- key = pair.substring(0, index);
- value = pair.substring(index + 1);
- }
- else {
- key = pair;
- value = "true";
- }
- key = unescape(key);
- key = key.replace(/\-/g, "");
- if (!isLowerKey(key)) {
- continue;
- }
- value = unescape(value);
- if (value.length === 0) {
- continue;
- }
- const alias = this.alias[key];
- if (alias) {
- key = alias;
- }
- map[key] = value;
+ object_path_1.default.set(this.data, key);
}
}
}
@@ -734,41 +376,18 @@ function fmt(key) {
}
return key;
}
-function unescape(str) {
- try {
- str = decodeURIComponent(str);
- str = str.replace(/\+/g, " ");
- }
- catch (e) { }
- str = str.replace(/\0/g, "");
- return str;
-}
function isAlpha(str) {
return /^[a-z0-9_\-]+$/i.test(str);
}
function isKey(key) {
return /^[a-zA-Z0-9]+$/.test(key);
}
-function isLowerKey(key) {
- if (!isKey(key)) {
- return false;
- }
- return !/[A-Z]/.test(key);
-}
function isUpperKey(key) {
if (!isKey(key)) {
return false;
}
return !/[a-z]/.test(key);
}
-function _filter(name, a, b) {
- for (const key of Object.keys(a)) {
- if (key.length > name.length && key.indexOf(name) === 0) {
- const sub = key.substring(name.length);
- b[sub] = a[key];
- }
- }
-}
function fromFloat(num, exp) {
(0, bsert_1.default)(typeof num === "number" && isFinite(num));
(0, bsert_1.default)(Number.isSafeInteger(exp));