diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..7c42a43 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,802 @@ +/*! + * config.js - configuration parsing for bcoin + * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": 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() : "/"; +/** + * Config Parser + */ +class Config { + module; + prefix; + suffix; + fallback; + options; + alias = {}; + data = {}; + env = {}; + args = {}; + argv = []; + pass = []; + query = {}; + hash = {}; + constructor(module, options) { + (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": + continue; + } + 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(); + } + 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); + } + openJson(file) { + const path = this.getFile(file); + let json; + try { + json = fs_1.default.readFileSync(path, "utf8"); + json = JSON.parse(json); + } + catch (e) { + if (e.code === "ENOENT") + return; + throw e; + } + (0, bsert_1.default)(typeof json === "object", `Config file ${file} must be an object`); + for (const key of Object.keys(json)) { + const value = json[key]; + switch (value) { + case Array.isArray(value): + let newVal = this.array(key) ?? []; + newVal.push(value); + this.set(key, newVal); + break; + default: + this.set(key, value); + break; + } + } + this.prefix = this.getPrefix(); + } + 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; + } + 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; + } + 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 (this.hash[key] !== null) { + return true; + } + if (this.query[key] !== null) { + return true; + } + if (this.args[key] !== null) { + return true; + } + if (this.env[key] !== null) { + return true; + } + if (this.data[key] !== null) { + return true; + } + return this.options[key] !== null; + } + get(key, fallback = null) { + if (Array.isArray(key)) { + const keys = key; + for (const key of keys) { + const value = this.get(key); + if (value !== null) { + return value; + } + } + 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 (this.hash[key] !== null) { + return this.hash[key]; + } + if (this.query[key] !== null) { + return this.query[key]; + } + if (this.args[key] !== null) { + return this.args[key]; + } + if (this.env[key] !== null) { + return this.env[key]; + } + if (this.data[key] !== null) { + return this.data[key]; + } + if (this.options[key] !== null) { + return this.options[key]; + } + return fallback; + } + typeOf(key) { + const value = this.get(key); + if (value === null) { + return "null"; + } + return typeof value; + } + str(key, fallback = null) { + const value = this.get(key); + if (value === null) { + return fallback; + } + if (typeof value !== "string") { + throw new Error(`${fmt(key)} must be a string.`); + } + return value; + } + int(key, fallback = null) { + const value = this.get(key); + if (value === null) { + return fallback; + } + if (typeof value !== "string") { + if (typeof value !== "number") { + throw new Error(`${fmt(key)} must be an int.`); + } + if (!Number.isSafeInteger(value)) { + throw new Error(`${fmt(key)} must be an int.`); + } + return value; + } + if (!/^\-?\d+$/.test(value)) { + throw new Error(`${fmt(key)} must be an int.`); + } + const num = parseInt(value, 10); + if (!Number.isSafeInteger(num)) { + throw new Error(`${fmt(key)} must be an int.`); + } + return num; + } + uint(key, fallback = null) { + const value = this.int(key); + if (value === null) { + return fallback; + } + if (value < 0) { + throw new Error(`${fmt(key)} must be a uint.`); + } + return value; + } + float(key, fallback = null) { + const value = this.get(key); + if (value === null) { + return fallback; + } + if (typeof value !== "string") { + if (typeof value !== "number") { + throw new Error(`${fmt(key)} must be a float.`); + } + if (!isFinite(value)) { + throw new Error(`${fmt(key)} must be a float.`); + } + return value; + } + if (!/^\-?\d*(?:\.\d*)?$/.test(value)) { + throw new Error(`${fmt(key)} must be a float.`); + } + if (!/\d/.test(value)) { + throw new Error(`${fmt(key)} must be a float.`); + } + const num = parseFloat(value); + if (!isFinite(num)) { + throw new Error(`${fmt(key)} must be a float.`); + } + return num; + } + ufloat(key, fallback = null) { + const value = this.float(key); + if (value === null) { + return fallback; + } + if (value < 0) { + throw new Error(`${fmt(key)} must be a positive float.`); + } + return value; + } + fixed(key, exp, fallback = null) { + const value = this.float(key); + if (value === null) { + return fallback; + } + try { + return fromFloat(value, exp || 0); + } + catch (e) { + throw new Error(`${fmt(key)} must be a fixed number.`); + } + } + ufixed(key, exp, fallback = null) { + const value = this.fixed(key, exp); + if (value === null) { + return fallback; + } + if (value < 0) { + throw new Error(`${fmt(key)} must be a positive fixed number.`); + } + return value; + } + bool(key, fallback = null) { + const value = this.get(key); + if (value === null) { + return fallback; + } + // Bitcoin Core compat. + if (typeof value === "number") { + if (value === 1) { + return true; + } + if (value === 0) { + return false; + } + } + if (typeof value !== "string") { + if (typeof value !== "boolean") { + throw new Error(`${fmt(key)} must be a boolean.`); + } + return value; + } + if (value === "true" || value === "1") { + return true; + } + if (value === "false" || value === "0") { + return false; + } + throw new Error(`${fmt(key)} must be a boolean.`); + } + buf(key, fallback = null, enc = "hex") { + const value = this.get(key); + if (value === null) { + return fallback; + } + if (typeof value !== "string") { + if (!Buffer.isBuffer(value)) { + throw new Error(`${fmt(key)} must be a buffer.`); + } + return value; + } + const data = Buffer.from(value, enc); + if (data.length !== Buffer.byteLength(value, enc)) { + throw new Error(`${fmt(key)} must be a ${enc} string.`); + } + return data; + } + array(key, fallback = null) { + const value = this.get(key); + if (value === null) { + return fallback; + } + if (typeof value !== "string") { + if (!Array.isArray(value)) { + throw new Error(`${fmt(key)} must be an array.`); + } + return value; + } + const parts = value.trim().split(/\s*,\s*/); + const result = []; + for (const part of parts) { + if (part.length === 0) { + continue; + } + result.push(part); + } + return result; + } + obj(key, fallback = null) { + const value = this.get(key); + if (value === null) { + return fallback; + } + if (typeof value !== "object" || Array.isArray(value)) { + throw new Error(`${fmt(key)} must be an object.`); + } + return value; + } + func(key, fallback = null) { + const value = this.get(key); + if (value === null) { + return fallback; + } + if (typeof value !== "function") { + throw new Error(`${fmt(key)} must be a function.`); + } + 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) { + return fallback; + } + 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); + } + } + } + parseEnv(env) { + let prefix = this.module; + prefix = prefix.toUpperCase(); + prefix = prefix.replace(/-/g, "_"); + prefix += "_"; + if (!env || typeof env !== "object") { + env = process.env; + } + (0, bsert_1.default)(env && typeof env === "object"); + for (let key of Object.keys(env)) { + const value = env[key]; + (0, bsert_1.default)(typeof value === "string"); + if (key.indexOf(prefix) !== 0) + continue; + key = key.substring(prefix.length); + key = key.replace(/_/g, ""); + if (!isUpperKey(key)) { + continue; + } + 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; + } + } +} +exports.default = Config; +/* + * Helpers + */ +function fmt(key) { + if (Array.isArray(key)) { + key = key[0]; + } + if (typeof key === "number") { + return `Argument #${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)); + let str = num.toFixed(exp); + let sign = 1; + if (str.length > 0 && str[0] === "-") { + str = str.substring(1); + sign = -1; + } + let hi = str; + let lo = "0"; + const index = str.indexOf("."); + if (index !== -1) { + hi = str.substring(0, index); + lo = str.substring(index + 1); + } + hi = hi.replace(/^0+/, ""); + lo = lo.replace(/0+$/, ""); + (0, bsert_1.default)(hi.length <= 16 - exp, "Fixed number string exceeds 2^53-1."); + (0, bsert_1.default)(lo.length <= exp, "Too many decimal places in fixed number string."); + if (hi.length === 0) { + hi = "0"; + } + while (lo.length < exp) { + lo += "0"; + } + if (lo.length === 0) { + lo = "0"; + } + (0, bsert_1.default)(/^\d+$/.test(hi) && /^\d+$/.test(lo), "Non-numeric characters in fixed number string."); + hi = parseInt(hi, 10); + lo = parseInt(lo, 10); + const mult = Math.pow(10, exp); + const maxLo = Number.MAX_SAFE_INTEGER % mult; + const maxHi = (Number.MAX_SAFE_INTEGER - maxLo) / mult; + (0, bsert_1.default)(hi < maxHi || (hi === maxHi && lo <= maxLo), "Fixed number string exceeds 2^53-1."); + return sign * (hi * mult + lo); +} +//# sourceMappingURL=index.js.map \ No newline at end of file