Compare commits
3 Commits
8e5b9d2bc6
...
257e053f9f
Author | SHA1 | Date |
---|---|---|
Derrick Hammer | 257e053f9f | |
Derrick Hammer | f70348f055 | |
Derrick Hammer | afb5389243 |
|
@ -4,46 +4,22 @@
|
||||||
* https://github.com/bcoin-org/bcoin
|
* https://github.com/bcoin-org/bcoin
|
||||||
*/
|
*/
|
||||||
/// <reference types="node" />
|
/// <reference types="node" />
|
||||||
export interface Options {
|
import arg from "arg";
|
||||||
suffix?: string;
|
|
||||||
fallback?: string;
|
|
||||||
alias?: object;
|
|
||||||
}
|
|
||||||
export interface LoadOptions {
|
|
||||||
hash?: string | boolean;
|
|
||||||
query?: string | boolean;
|
|
||||||
env?: object | boolean;
|
|
||||||
argv?: string[] | boolean;
|
|
||||||
config?: string | boolean;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Config Parser
|
* Config Parser
|
||||||
*/
|
*/
|
||||||
export default class Config {
|
export default class Config {
|
||||||
private module;
|
private module;
|
||||||
private prefix;
|
|
||||||
private suffix?;
|
|
||||||
private fallback?;
|
|
||||||
private options;
|
|
||||||
private alias;
|
|
||||||
private data;
|
private data;
|
||||||
private env;
|
constructor(module: string);
|
||||||
private args;
|
|
||||||
private argv;
|
|
||||||
private pass;
|
|
||||||
private query;
|
|
||||||
private hash;
|
|
||||||
constructor(module: string, options?: Options);
|
|
||||||
private init;
|
|
||||||
inject(options: object): void;
|
inject(options: object): void;
|
||||||
load(options: LoadOptions): void;
|
load(): void;
|
||||||
open(file: string): void;
|
|
||||||
openDir(dir: string): void;
|
openDir(dir: string): void;
|
||||||
openJson(file: string): void;
|
open(file: string): void;
|
||||||
saveConfigJson(file: string, data: object): void;
|
save(file: string, data: object): void;
|
||||||
filter(name: string): Config;
|
|
||||||
set(key: string, value: any): void;
|
set(key: string, value: any): void;
|
||||||
has(key: string): boolean;
|
has(key: string): any;
|
||||||
|
private normalize;
|
||||||
get(key: string, fallback?: any): any;
|
get(key: string, fallback?: any): any;
|
||||||
typeOf(key: string): "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | "null";
|
typeOf(key: string): "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | "null";
|
||||||
str(key: string, fallback?: any): any;
|
str(key: string, fallback?: any): any;
|
||||||
|
@ -58,16 +34,7 @@ export default class Config {
|
||||||
array(key: string, fallback?: any): any;
|
array(key: string, fallback?: any): any;
|
||||||
obj(key: string, fallback?: any): any;
|
obj(key: string, fallback?: any): any;
|
||||||
func(key: string, fallback?: any): any;
|
func(key: string, fallback?: any): any;
|
||||||
path(key: string, fallback?: any): any;
|
|
||||||
mb(key: string, fallback?: any): any;
|
mb(key: string, fallback?: any): any;
|
||||||
getSuffix(): any;
|
parseArg(args: arg.Result<any>): void;
|
||||||
getPrefix(): string;
|
|
||||||
getFile(file: string): any;
|
|
||||||
location(file: string): string;
|
|
||||||
parseConfig(text: string): void;
|
|
||||||
parseArg(argv?: string[]): void;
|
|
||||||
parseEnv(env?: object): void;
|
parseEnv(env?: object): void;
|
||||||
parseQuery(query: string): void | {};
|
|
||||||
parseHash(hash: string): void | {};
|
|
||||||
parseForm(query: string, ch: string, map: object): void;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,60 +10,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const bsert_1 = __importDefault(require("bsert"));
|
const bsert_1 = __importDefault(require("bsert"));
|
||||||
const path_1 = __importDefault(require("path"));
|
const path_1 = __importDefault(require("path"));
|
||||||
const os_1 = __importDefault(require("os"));
|
|
||||||
const fs_1 = __importDefault(require("fs"));
|
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
|
* Config Parser
|
||||||
*/
|
*/
|
||||||
class Config {
|
class Config {
|
||||||
module;
|
module;
|
||||||
prefix;
|
|
||||||
suffix;
|
|
||||||
fallback;
|
|
||||||
options = {};
|
|
||||||
alias = {};
|
|
||||||
data = {};
|
data = {};
|
||||||
env = {};
|
constructor(module) {
|
||||||
args = {};
|
|
||||||
argv = [];
|
|
||||||
pass = [];
|
|
||||||
query = {};
|
|
||||||
hash = {};
|
|
||||||
constructor(module, options) {
|
|
||||||
(0, bsert_1.default)(typeof module === "string");
|
(0, bsert_1.default)(typeof module === "string");
|
||||||
(0, bsert_1.default)(module.length > 0);
|
(0, bsert_1.default)(module.length > 0);
|
||||||
this.module = module;
|
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) {
|
inject(options) {
|
||||||
for (const key of Object.keys(options)) {
|
for (const key of Object.keys(options)) {
|
||||||
const value = options[key];
|
const value = options[key];
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "hash":
|
|
||||||
case "query":
|
|
||||||
case "env":
|
case "env":
|
||||||
case "argv":
|
case "argv":
|
||||||
case "config":
|
case "config":
|
||||||
|
@ -72,41 +37,19 @@ class Config {
|
||||||
this.set(key, value);
|
this.set(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
load(options) {
|
load() {
|
||||||
if (options.hash) {
|
const args = (0, arg_1.default)({}, { permissive: true });
|
||||||
this.parseHash(options.hash);
|
this.parseArg(args);
|
||||||
}
|
|
||||||
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) {
|
openDir(dir) {
|
||||||
(0, bsert_1.default)(fs_1.default.existsSync(dir), `Directory ${dir} does not exist`);
|
(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));
|
let files = fs_1.default
|
||||||
files.forEach(this.openJson.bind(this));
|
.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;
|
let json;
|
||||||
try {
|
try {
|
||||||
json = fs_1.default.readFileSync(file, "utf8");
|
json = fs_1.default.readFileSync(file, "utf8");
|
||||||
|
@ -118,20 +61,22 @@ class Config {
|
||||||
throw new Error(`Error parsing file ${file}: ${e.message}`);
|
throw new Error(`Error parsing file ${file}: ${e.message}`);
|
||||||
}
|
}
|
||||||
(0, bsert_1.default)(typeof json === "object", `Config file ${file} must be an object`);
|
(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];
|
const value = json[key];
|
||||||
switch (true) {
|
let keyPath = key.split(".");
|
||||||
case Array.isArray(value):
|
let isArray = typeof parseInt(keyPath.pop()) === "number";
|
||||||
this.set(key, [...(this.array(key) ?? []), ...value]);
|
if (isArray) {
|
||||||
break;
|
let itemPath = keyPath.join(".");
|
||||||
default:
|
let item = this.get(itemPath, []);
|
||||||
this.set(key, value);
|
item.push(value);
|
||||||
break;
|
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)(typeof data === "object");
|
||||||
(0, bsert_1.default)(!Array.isArray(data));
|
(0, bsert_1.default)(!Array.isArray(data));
|
||||||
const configDir = this.str("configdir");
|
const configDir = this.str("configdir");
|
||||||
|
@ -140,56 +85,34 @@ class Config {
|
||||||
fs_1.default.mkdirSync(configDir, { recursive: true });
|
fs_1.default.mkdirSync(configDir, { recursive: true });
|
||||||
}
|
}
|
||||||
fs_1.default.writeFileSync(fullPath, JSON.stringify(data));
|
fs_1.default.writeFileSync(fullPath, JSON.stringify(data));
|
||||||
this.openJson(fullPath);
|
this.open(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;
|
|
||||||
}
|
}
|
||||||
set(key, value) {
|
set(key, value) {
|
||||||
(0, bsert_1.default)(typeof key === "string", "Key must be a string.");
|
(0, bsert_1.default)(typeof key === "string", "Key must be a string.");
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
key = key.replace(/-/g, "");
|
key = this.normalize(key);
|
||||||
key = key.toLowerCase();
|
object_path_1.default.set(this.data, key, value);
|
||||||
this.options[key] = value;
|
this.data[key] = value;
|
||||||
}
|
}
|
||||||
has(key) {
|
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.");
|
(0, bsert_1.default)(typeof key === "string", "Key must be a string.");
|
||||||
key = key.replace(/-/g, "");
|
key = key.replace(/-/g, "");
|
||||||
key = key.toLowerCase();
|
key = key.toLowerCase();
|
||||||
if (key in this.hash && this.hash[key] !== null) {
|
return object_path_1.default.has(this.data, key);
|
||||||
return true;
|
}
|
||||||
|
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) {
|
else {
|
||||||
return true;
|
key = key.replace(/-/g, "");
|
||||||
}
|
}
|
||||||
if (key in this.args && this.args[key] !== null) {
|
key = key.toLowerCase();
|
||||||
return true;
|
return key;
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
get(key, fallback = null) {
|
get(key, fallback = null) {
|
||||||
if (Array.isArray(key)) {
|
if (Array.isArray(key)) {
|
||||||
|
@ -202,38 +125,9 @@ class Config {
|
||||||
}
|
}
|
||||||
return fallback;
|
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.");
|
(0, bsert_1.default)(typeof key === "string", "Key must be a string.");
|
||||||
key = key.replace(/-/g, "");
|
key = this.normalize(key);
|
||||||
key = key.toLowerCase();
|
return object_path_1.default.get(this.data, key);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
typeOf(key) {
|
typeOf(key) {
|
||||||
const value = this.get(key);
|
const value = this.get(key);
|
||||||
|
@ -429,26 +323,6 @@ class Config {
|
||||||
}
|
}
|
||||||
return value;
|
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) {
|
mb(key, fallback = null) {
|
||||||
const value = this.uint(key);
|
const value = this.uint(key);
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
|
@ -456,176 +330,10 @@ class Config {
|
||||||
}
|
}
|
||||||
return value * 1024 * 1024;
|
return value * 1024 * 1024;
|
||||||
}
|
}
|
||||||
getSuffix() {
|
parseArg(args) {
|
||||||
if (!this.suffix) {
|
for (let key in args) {
|
||||||
throw new Error("No suffix presented.");
|
let newKey = key.replace("-", "");
|
||||||
}
|
object_path_1.default.set(this.data, newKey, args[key]);
|
||||||
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) {
|
parseEnv(env) {
|
||||||
|
@ -640,84 +348,18 @@ class Config {
|
||||||
for (let key of Object.keys(env)) {
|
for (let key of Object.keys(env)) {
|
||||||
const value = env[key];
|
const value = env[key];
|
||||||
(0, bsert_1.default)(typeof value === "string");
|
(0, bsert_1.default)(typeof value === "string");
|
||||||
if (key.indexOf(prefix) !== 0)
|
if (key.indexOf(prefix) !== 0) {
|
||||||
continue;
|
continue;
|
||||||
key = key.substring(prefix.length);
|
}
|
||||||
key = key.replace(/_/g, "");
|
|
||||||
if (!isUpperKey(key)) {
|
if (!isUpperKey(key)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
key = key.substring(prefix.length);
|
||||||
|
key = this.normalize(key, true);
|
||||||
if (value.length === 0) {
|
if (value.length === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
key = key.toLowerCase();
|
object_path_1.default.set(this.data, key);
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -734,41 +376,18 @@ function fmt(key) {
|
||||||
}
|
}
|
||||||
return 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) {
|
function isAlpha(str) {
|
||||||
return /^[a-z0-9_\-]+$/i.test(str);
|
return /^[a-z0-9_\-]+$/i.test(str);
|
||||||
}
|
}
|
||||||
function isKey(key) {
|
function isKey(key) {
|
||||||
return /^[a-zA-Z0-9]+$/.test(key);
|
return /^[a-zA-Z0-9]+$/.test(key);
|
||||||
}
|
}
|
||||||
function isLowerKey(key) {
|
|
||||||
if (!isKey(key)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !/[A-Z]/.test(key);
|
|
||||||
}
|
|
||||||
function isUpperKey(key) {
|
function isUpperKey(key) {
|
||||||
if (!isKey(key)) {
|
if (!isKey(key)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !/[a-z]/.test(key);
|
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) {
|
function fromFloat(num, exp) {
|
||||||
(0, bsert_1.default)(typeof num === "number" && isFinite(num));
|
(0, bsert_1.default)(typeof num === "number" && isFinite(num));
|
||||||
(0, bsert_1.default)(Number.isSafeInteger(exp));
|
(0, bsert_1.default)(Number.isSafeInteger(exp));
|
||||||
|
|
|
@ -12,9 +12,13 @@
|
||||||
"test": "bmocha --reporter spec test/*-test.js"
|
"test": "bmocha --reporter spec test/*-test.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bsert": "~0.0.10"
|
"arg": "^5.0.2",
|
||||||
|
"bsert": "~0.0.10",
|
||||||
|
"deep-to-flat-object": "^1.0.1",
|
||||||
|
"object-path": "^0.11.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.7.18"
|
"@types/node": "^18.7.18",
|
||||||
|
"typescript": "^4.9.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
616
src/index.ts
616
src/index.ts
|
@ -8,24 +8,10 @@
|
||||||
|
|
||||||
import assert from "bsert";
|
import assert from "bsert";
|
||||||
import Path from "path";
|
import Path from "path";
|
||||||
import os from "os";
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import arg from "arg";
|
||||||
const HOME = os.homedir ? os.homedir() : "/";
|
import objectPath from "object-path";
|
||||||
|
import deepToFlatObject from "deep-to-flat-object";
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Config Parser
|
* Config Parser
|
||||||
|
@ -33,52 +19,13 @@ export interface LoadOptions {
|
||||||
|
|
||||||
export default class Config {
|
export default class Config {
|
||||||
private module: string;
|
private module: string;
|
||||||
private prefix: string;
|
|
||||||
private suffix?: string;
|
|
||||||
private fallback?: string;
|
|
||||||
private options: Options = {};
|
|
||||||
private alias = {};
|
|
||||||
private data = {};
|
private data = {};
|
||||||
private env = {};
|
|
||||||
private args = {};
|
|
||||||
private argv: any[] = [];
|
|
||||||
private pass: any[] = [];
|
|
||||||
private query = {};
|
|
||||||
private hash = {};
|
|
||||||
|
|
||||||
constructor(module: string, options?: Options) {
|
constructor(module: string) {
|
||||||
assert(typeof module === "string");
|
assert(typeof module === "string");
|
||||||
assert(module.length > 0);
|
assert(module.length > 0);
|
||||||
|
|
||||||
this.module = module;
|
this.module = module;
|
||||||
this.prefix = Path.join(HOME, `.${module}`);
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
this.init(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private init(options: Options) {
|
|
||||||
assert(options && typeof options === "object");
|
|
||||||
|
|
||||||
if (options.suffix != null) {
|
|
||||||
assert(typeof options.suffix === "string");
|
|
||||||
this.suffix = options.suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.fallback != null) {
|
|
||||||
assert(typeof options.fallback === "string");
|
|
||||||
this.fallback = options.fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.alias) {
|
|
||||||
assert(typeof options.alias === "object");
|
|
||||||
for (const key of Object.keys(options.alias)) {
|
|
||||||
const value = options.alias[key];
|
|
||||||
assert(typeof value === "string");
|
|
||||||
this.alias[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public inject(options: object) {
|
public inject(options: object) {
|
||||||
|
@ -86,8 +33,6 @@ export default class Config {
|
||||||
const value = options[key];
|
const value = options[key];
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "hash":
|
|
||||||
case "query":
|
|
||||||
case "env":
|
case "env":
|
||||||
case "argv":
|
case "argv":
|
||||||
case "config":
|
case "config":
|
||||||
|
@ -98,50 +43,23 @@ export default class Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public load(options: LoadOptions) {
|
public load() {
|
||||||
if (options.hash) {
|
const args = arg({}, { permissive: true });
|
||||||
this.parseHash(options.hash as string);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.query) {
|
this.parseArg(args);
|
||||||
this.parseQuery(options.query as string);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.env) {
|
|
||||||
this.parseEnv(options.env as object);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.argv) {
|
|
||||||
this.parseArg(options.argv as string[]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.prefix = this.getPrefix();
|
|
||||||
}
|
|
||||||
|
|
||||||
public open(file: string) {
|
|
||||||
const path = this.getFile(file);
|
|
||||||
|
|
||||||
let text;
|
|
||||||
try {
|
|
||||||
text = fs.readFileSync(path, "utf8");
|
|
||||||
} catch (e) {
|
|
||||||
if (e.code === "ENOENT") return;
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.parseConfig(text);
|
|
||||||
|
|
||||||
this.prefix = this.getPrefix();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public openDir(dir: string) {
|
public openDir(dir: string) {
|
||||||
assert(fs.existsSync(dir), `Directory ${dir} does not exist`);
|
assert(fs.existsSync(dir), `Directory ${dir} does not exist`);
|
||||||
|
|
||||||
let files = fs.readdirSync(dir).map((item) => Path.join(dir, item));
|
let files = fs
|
||||||
files.forEach(this.openJson.bind(this));
|
.readdirSync(dir)
|
||||||
|
.filter((item) => item.endsWith(".json"))
|
||||||
|
.map((item) => Path.join(dir, item));
|
||||||
|
files.forEach(this.open.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public openJson(file: string) {
|
public open(file: string) {
|
||||||
let json;
|
let json;
|
||||||
try {
|
try {
|
||||||
json = fs.readFileSync(file, "utf8");
|
json = fs.readFileSync(file, "utf8");
|
||||||
|
@ -153,23 +71,27 @@ export default class Config {
|
||||||
|
|
||||||
assert(typeof json === "object", `Config file ${file} must be an object`);
|
assert(typeof json === "object", `Config file ${file} must be an object`);
|
||||||
|
|
||||||
for (const key of Object.keys(json)) {
|
const settings = deepToFlatObject(json);
|
||||||
|
|
||||||
|
for (const key of Object.keys(settings)) {
|
||||||
const value = json[key];
|
const value = json[key];
|
||||||
|
|
||||||
switch (true) {
|
let keyPath = key.split(".");
|
||||||
case Array.isArray(value):
|
let isArray = typeof parseInt(keyPath.pop()) === "number";
|
||||||
this.set(key, [...(this.array(key) ?? []), ...value]);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.set(key, value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.prefix = this.getPrefix();
|
if (isArray) {
|
||||||
|
let itemPath = keyPath.join(".");
|
||||||
|
let item = this.get(itemPath, []);
|
||||||
|
item.push(value);
|
||||||
|
this.set(itemPath, item);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveConfigJson(file: string, data: object) {
|
public save(file: string, data: object) {
|
||||||
assert(typeof data === "object");
|
assert(typeof data === "object");
|
||||||
assert(!Array.isArray(data));
|
assert(!Array.isArray(data));
|
||||||
|
|
||||||
|
@ -181,27 +103,7 @@ export default class Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFileSync(fullPath, JSON.stringify(data));
|
fs.writeFileSync(fullPath, JSON.stringify(data));
|
||||||
this.openJson(fullPath);
|
this.open(fullPath);
|
||||||
}
|
|
||||||
|
|
||||||
public filter(name: string) {
|
|
||||||
assert(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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public set(key: string, value: any) {
|
public set(key: string, value: any) {
|
||||||
|
@ -211,40 +113,35 @@ export default class Config {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
key = key.replace(/-/g, "");
|
key = this.normalize(key);
|
||||||
key = key.toLowerCase();
|
|
||||||
|
|
||||||
this.options[key] = value;
|
objectPath.set(this.data, key, value);
|
||||||
|
|
||||||
|
this.data[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public has(key: string) {
|
public has(key: string) {
|
||||||
if (typeof key === "number") {
|
|
||||||
assert(key >= 0, "Index must be positive.");
|
|
||||||
return key < this.argv.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(typeof key === "string", "Key must be a string.");
|
assert(typeof key === "string", "Key must be a string.");
|
||||||
|
|
||||||
key = key.replace(/-/g, "");
|
key = key.replace(/-/g, "");
|
||||||
key = key.toLowerCase();
|
key = key.toLowerCase();
|
||||||
|
|
||||||
if (key in this.hash && this.hash[key] !== null) {
|
return objectPath.has(this.data, key);
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
if (key in this.query && this.query[key] !== null) {
|
private normalize(key: string, env = false): string {
|
||||||
return true;
|
assert(typeof key === "string", "Key must be a string.");
|
||||||
}
|
|
||||||
if (key in this.args && this.args[key] !== null) {
|
if (env) {
|
||||||
return true;
|
key = key.replace(/__/g, ".");
|
||||||
}
|
key = key.replace(/_/g, "");
|
||||||
if (key in this.env && this.env[key] !== null) {
|
} else {
|
||||||
return true;
|
key = key.replace(/-/g, "");
|
||||||
}
|
|
||||||
if (key in this.data && this.data[key] !== null) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.options[key] !== null;
|
key = key.toLowerCase();
|
||||||
|
|
||||||
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get(key: string, fallback = null) {
|
public get(key: string, fallback = null) {
|
||||||
|
@ -259,45 +156,11 @@ export default class Config {
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof key === "number") {
|
|
||||||
assert(key >= 0, "Index must be positive.");
|
|
||||||
|
|
||||||
if (key >= this.argv.length) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.argv[key] != null) {
|
|
||||||
return this.argv[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(typeof key === "string", "Key must be a string.");
|
assert(typeof key === "string", "Key must be a string.");
|
||||||
|
|
||||||
key = key.replace(/-/g, "");
|
key = this.normalize(key);
|
||||||
key = key.toLowerCase();
|
|
||||||
|
|
||||||
if (key in this.hash && this.hash[key] !== null) {
|
return objectPath.get(this.data, key);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public typeOf(key: string) {
|
public typeOf(key: string) {
|
||||||
|
@ -561,30 +424,6 @@ export default class Config {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public path(key: string, 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.join(HOME, value.substring(1));
|
|
||||||
break;
|
|
||||||
case "@": // prefix
|
|
||||||
value = Path.join(this.prefix, value.substring(1));
|
|
||||||
break;
|
|
||||||
default: // cwd
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Path.normalize(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public mb(key: string, fallback = null) {
|
public mb(key: string, fallback = null) {
|
||||||
const value = this.uint(key);
|
const value = this.uint(key);
|
||||||
|
|
||||||
|
@ -595,233 +434,10 @@ export default class Config {
|
||||||
return value * 1024 * 1024;
|
return value * 1024 * 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSuffix() {
|
public parseArg(args: arg.Result<any>) {
|
||||||
if (!this.suffix) {
|
for (let key in args) {
|
||||||
throw new Error("No suffix presented.");
|
let newKey = key.replace("-", "");
|
||||||
}
|
objectPath.set(this.data, newKey, args[key]);
|
||||||
|
|
||||||
const suffix = this.str(this.suffix, this.fallback);
|
|
||||||
|
|
||||||
assert(isAlpha(suffix), "Bad suffix.");
|
|
||||||
|
|
||||||
return suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getPrefix() {
|
|
||||||
let prefix = this.str("prefix");
|
|
||||||
|
|
||||||
if (prefix) {
|
|
||||||
if (prefix[0] === "~") {
|
|
||||||
prefix = Path.join(HOME, prefix.substring(1));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
prefix = Path.join(HOME, `.${this.module}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.suffix) {
|
|
||||||
const suffix = this.str(this.suffix);
|
|
||||||
|
|
||||||
if (suffix) {
|
|
||||||
assert(isAlpha(suffix), "Bad suffix.");
|
|
||||||
if (this.fallback && suffix !== this.fallback) {
|
|
||||||
prefix = Path.join(prefix, suffix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Path.normalize(prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getFile(file: string) {
|
|
||||||
const name = this.str("config");
|
|
||||||
|
|
||||||
if (name) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Path.join(this.prefix, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
public location(file: string) {
|
|
||||||
return Path.join(this.prefix, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
public parseConfig(text: string) {
|
|
||||||
assert(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public parseArg(argv?: string[]) {
|
|
||||||
if (!argv || typeof argv !== "object") argv = process.argv;
|
|
||||||
|
|
||||||
assert(Array.isArray(argv));
|
|
||||||
|
|
||||||
let last = null;
|
|
||||||
let pass = false;
|
|
||||||
|
|
||||||
for (let i = 2; i < argv.length; i++) {
|
|
||||||
const arg = argv[i];
|
|
||||||
|
|
||||||
assert(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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -843,108 +459,22 @@ export default class Config {
|
||||||
|
|
||||||
assert(typeof value === "string");
|
assert(typeof value === "string");
|
||||||
|
|
||||||
if (key.indexOf(prefix) !== 0) continue;
|
if (key.indexOf(prefix) !== 0) {
|
||||||
|
continue;
|
||||||
key = key.substring(prefix.length);
|
}
|
||||||
key = key.replace(/_/g, "");
|
|
||||||
|
|
||||||
if (!isUpperKey(key)) {
|
if (!isUpperKey(key)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.length === 0) {
|
key = key.substring(prefix.length);
|
||||||
continue;
|
key = this.normalize(key, true);
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public parseQuery(query: string) {
|
|
||||||
if (typeof query !== "string") {
|
|
||||||
if (!global.location) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
query = global.location.search;
|
|
||||||
|
|
||||||
if (typeof query !== "string") {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.parseForm(query, "?", this.query);
|
|
||||||
}
|
|
||||||
|
|
||||||
public parseHash(hash: string) {
|
|
||||||
if (typeof hash !== "string") {
|
|
||||||
if (!global.location) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
hash = global.location.hash;
|
|
||||||
|
|
||||||
if (typeof hash !== "string") {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.parseForm(hash, "#", this.hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
public parseForm(query: string, ch: string, map: object) {
|
|
||||||
assert(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) {
|
if (value.length === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const alias = this.alias[key];
|
objectPath.set(this.data, key);
|
||||||
|
|
||||||
if (alias) {
|
|
||||||
key = alias;
|
|
||||||
}
|
|
||||||
|
|
||||||
map[key] = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -965,15 +495,6 @@ function fmt(key: string[] | string | number) {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
function unescape(str: string) {
|
|
||||||
try {
|
|
||||||
str = decodeURIComponent(str);
|
|
||||||
str = str.replace(/\+/g, " ");
|
|
||||||
} catch (e) {}
|
|
||||||
str = str.replace(/\0/g, "");
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAlpha(str: string) {
|
function isAlpha(str: string) {
|
||||||
return /^[a-z0-9_\-]+$/i.test(str);
|
return /^[a-z0-9_\-]+$/i.test(str);
|
||||||
}
|
}
|
||||||
|
@ -982,14 +503,6 @@ function isKey(key: string) {
|
||||||
return /^[a-zA-Z0-9]+$/.test(key);
|
return /^[a-zA-Z0-9]+$/.test(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLowerKey(key: string) {
|
|
||||||
if (!isKey(key)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !/[A-Z]/.test(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isUpperKey(key: string) {
|
function isUpperKey(key: string) {
|
||||||
if (!isKey(key)) {
|
if (!isKey(key)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -998,15 +511,6 @@ function isUpperKey(key: string) {
|
||||||
return !/[a-z]/.test(key);
|
return !/[a-z]/.test(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _filter(name: string, a: object | any[], b: object | any[]) {
|
|
||||||
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: number, exp: number) {
|
function fromFloat(num: number, exp: number) {
|
||||||
assert(typeof num === "number" && isFinite(num));
|
assert(typeof num === "number" && isFinite(num));
|
||||||
assert(Number.isSafeInteger(exp));
|
assert(Number.isSafeInteger(exp));
|
||||||
|
|
Loading…
Reference in New Issue