Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: rewrite to typescript #20

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
# build
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ node_js:
- '6'
- '8'
script:
- npm build
- npm test
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Config is described with a combination of a functions:
var parser = root(section({
system: section({
parallelLimit: option({
defaultValue: 0,
parseEnv: Number,
parseCli: Number,
validate: function() {...}
Expand Down
22 changes: 22 additions & 0 deletions build/core.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { MapParser } from './types/map';
import type { OptionParser, OptionParserConfig } from './types/option';
import type { RootParser, RootPrefixes, ConfigParser } from './types/root';
import type { SectionParser, SectionProperties } from './types/section';
import type { DeepPartial } from './types/utils';
declare type Parser<T, R = any> = OptionParser<T, R> | SectionParser<T, R> | MapParser<T, R>;
/**
* Single option
*/
export declare function option<T, S = T, R = any>({ defaultValue, parseCli, parseEnv, validate, map: mapFunc }: OptionParserConfig<T, S, R>): OptionParser<S, R>;
/**
* Object with fixed properties.
* Any unknown property will be reported as error.
*/
export declare function section<T, R = any>(properties: SectionProperties<T, R>): SectionParser<T, R>;
/**
* Object with user-specified keys and values,
* parsed by valueParser.
*/
export declare function map<T extends Record<string, any>, V extends T[string] = T[string], R = any>(valueParser: Parser<V, R>, defaultValue: DeepPartial<Record<string, V>>): MapParser<Record<string, V>, R>;
export declare function root<T>(rootParser: RootParser<T>, { envPrefix, cliPrefix }: RootPrefixes): ConfigParser<T>;
export {};
90 changes: 90 additions & 0 deletions build/core.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.root = exports.map = exports.section = exports.option = void 0;
const lodash_1 = __importDefault(require("lodash"));
const errors_1 = require("./errors");
const lazy_1 = require("./lazy");
const locator_1 = __importDefault(require("./locator"));
/**
* Single option
*/
function option({ defaultValue, parseCli = lodash_1.default.identity, parseEnv = lodash_1.default.identity, validate = lodash_1.default.noop, map: mapFunc = lodash_1.default.identity }) {
const validateFunc = validate;
return (locator, parsed) => {
const config = parsed.root;
const currNode = locator.parent ? lodash_1.default.get(parsed, locator.parent) : config;
let value;
if (locator.cliOption !== undefined) {
value = parseCli(locator.cliOption);
}
else if (locator.envVar !== undefined) {
value = parseEnv(locator.envVar);
}
else if (locator.option !== undefined) {
value = locator.option;
}
else if (defaultValue !== undefined) {
value = lodash_1.default.isFunction(defaultValue)
? defaultValue(config, currNode)
: defaultValue;
}
else {
throw new errors_1.MissingOptionError(locator.name);
}
validateFunc(value, config, currNode);
return mapFunc(value, config, currNode);
};
}
exports.option = option;
/**
* Object with fixed properties.
* Any unknown property will be reported as error.
*/
function section(properties) {
const expectedKeys = lodash_1.default.keys(properties);
return (locator, config) => {
const unknownKeys = lodash_1.default.difference(lodash_1.default.keys(locator.option), expectedKeys);
if (unknownKeys.length > 0) {
throw new errors_1.UnknownKeysError(unknownKeys.map((key) => `${locator.name}.${key}`));
}
const lazyResult = (0, lazy_1.buildLazyObject)(expectedKeys, (key) => {
const parser = properties[key];
return () => parser(locator.nested(key), config);
});
lodash_1.default.set(config, locator.name, lazyResult);
return lazyResult;
};
}
exports.section = section;
/**
* Object with user-specified keys and values,
* parsed by valueParser.
*/
function map(valueParser, defaultValue) {
return (locator, config) => {
if (locator.option === undefined) {
if (!defaultValue) {
return {};
}
locator = locator.resetOption(defaultValue);
}
const optionsToParse = Object.keys(locator.option);
const lazyResult = (0, lazy_1.buildLazyObject)(optionsToParse, (key) => {
return () => valueParser(locator.nested(key), config);
});
lodash_1.default.set(config, locator.name, lazyResult);
return lazyResult;
};
}
exports.map = map;
function root(rootParser, { envPrefix, cliPrefix }) {
return ({ options, env, argv }) => {
const rootLocator = (0, locator_1.default)({ options, env, argv, envPrefix, cliPrefix });
const parsed = rootParser(rootLocator, {});
return (0, lazy_1.forceParsing)(parsed);
};
}
exports.root = root;
8 changes: 8 additions & 0 deletions build/errors.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export declare class MissingOptionError extends Error {
optionName: string;
constructor(optionName: string);
}
export declare class UnknownKeysError extends Error {
keys: Array<string>;
constructor(keys: Array<string>);
}
10 changes: 5 additions & 5 deletions lib/errors.js → build/errors.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnknownKeysError = exports.MissingOptionError = void 0;
class MissingOptionError extends Error {
constructor(optionName) {
const message = `${optionName} is required`;
super(message);
this.name = 'MissingOptionError';
this.message = message;
this.optionName = optionName;

Error.captureStackTrace(this, MissingOptionError);
}
}

exports.MissingOptionError = MissingOptionError;
class UnknownKeysError extends Error {
constructor(keys) {
const message = `Unknown options: ${keys.join(', ')}`;
super(message);
this.name = 'UnknownKeysError';
this.message = message;
this.keys = keys;

Error.captureStackTrace(this, UnknownKeysError);
}
}

module.exports = {MissingOptionError, UnknownKeysError};
exports.UnknownKeysError = UnknownKeysError;
2 changes: 2 additions & 0 deletions build/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { root, section, map, option } from './core';
export { MissingOptionError, UnknownKeysError } from './errors';
11 changes: 11 additions & 0 deletions build/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnknownKeysError = exports.MissingOptionError = exports.option = exports.map = exports.section = exports.root = void 0;
var core_1 = require("./core");
Object.defineProperty(exports, "root", { enumerable: true, get: function () { return core_1.root; } });
Object.defineProperty(exports, "section", { enumerable: true, get: function () { return core_1.section; } });
Object.defineProperty(exports, "map", { enumerable: true, get: function () { return core_1.map; } });
Object.defineProperty(exports, "option", { enumerable: true, get: function () { return core_1.option; } });
var errors_1 = require("./errors");
Object.defineProperty(exports, "MissingOptionError", { enumerable: true, get: function () { return errors_1.MissingOptionError; } });
Object.defineProperty(exports, "UnknownKeysError", { enumerable: true, get: function () { return errors_1.UnknownKeysError; } });
4 changes: 4 additions & 0 deletions build/lazy.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { LazyObject } from './types/lazy';
export declare const isLazy: unique symbol;
export declare function buildLazyObject<T>(keys: Array<keyof T>, getKeyGetter: (key: keyof T) => () => (T[keyof T] | LazyObject<T[keyof T]>)): LazyObject<T>;
export declare function forceParsing<T>(lazyObject: LazyObject<T>): T;
48 changes: 48 additions & 0 deletions build/lazy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.forceParsing = exports.buildLazyObject = exports.isLazy = void 0;
const lodash_1 = __importDefault(require("lodash"));
exports.isLazy = Symbol('isLazy');
function buildLazyObject(keys, getKeyGetter) {
const target = {
[exports.isLazy]: true
};
for (const key of keys) {
defineLazy(target, key, getKeyGetter(key));
}
return target;
}
exports.buildLazyObject = buildLazyObject;
function forceParsing(lazyObject) {
return lodash_1.default.cloneDeep(lazyObject);
}
exports.forceParsing = forceParsing;
function defineLazy(object, key, getter) {
let defined = false;
let value;
Object.defineProperty(object, key, {
get() {
if (!defined) {
defined = true;
const val = getter();
if (isLazyObject(val)) {
value = forceParsing(val);
}
else {
value = val;
}
}
return value;
},
enumerable: true
});
}
function isLazyObject(value) {
return lodash_1.default.isObject(value) && hasOwnProperty(value, exports.isLazy) && value[exports.isLazy] === true;
}
function hasOwnProperty(obj, prop) {
return obj.hasOwnProperty(prop);
}
2 changes: 2 additions & 0 deletions build/locator.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import type { LocatorArg, Locator } from './types/locator';
export default function <T>({ options, env, argv, envPrefix, cliPrefix }: LocatorArg<T>): Locator<T>;
61 changes: 61 additions & 0 deletions build/locator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = __importDefault(require("lodash"));
function parseArgv(argv, cliPrefix) {
return argv.reduce(function (argv, arg) {
if (!arg.startsWith(cliPrefix) || !lodash_1.default.includes(arg, '=')) {
return argv.concat(arg);
}
const parts = arg.split('=');
const option = parts[0];
const value = parts.slice(1).join('=');
return argv.concat(option, value);
}, []);
}
function default_1({ options, env, argv, envPrefix = '', cliPrefix = '--' }) {
const parsedArgv = parseArgv(argv, cliPrefix);
function getNested(option, { namePrefix, envPrefix, cliPrefix }) {
return (subKey) => {
const envName = envPrefix + lodash_1.default.snakeCase(subKey.toString());
const cliFlag = cliPrefix + lodash_1.default.kebabCase(subKey.toString());
const argIndex = parsedArgv.lastIndexOf(cliFlag);
const subOption = lodash_1.default.get(option, subKey);
const newName = namePrefix ? `${namePrefix}.${subKey}` : subKey.toString();
return mkLocator({
name: newName,
parent: namePrefix,
option: subOption,
envVar: env[envName],
cliOption: argIndex > -1 ? parsedArgv[argIndex + 1] : undefined
}, {
namePrefix: newName,
envPrefix: `${envName}_`,
cliPrefix: `${cliFlag}-`
});
};
}
function mkLocator(base, prefixes) {
return lodash_1.default.extend(base, {
nested: getNested(base.option, prefixes),
resetOption: function (newOptions) {
return mkLocator(Object.assign(Object.assign({}, base), { option: newOptions }), prefixes);
}
});
}
return mkLocator({
name: 'root',
parent: '',
option: options,
envVar: undefined,
cliOption: undefined
}, {
namePrefix: 'root',
envPrefix,
cliPrefix
});
}
exports.default = default_1;
;
11 changes: 11 additions & 0 deletions build/types/common.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { LazyObject } from './lazy';
import type { MapParser } from './map';
import type { OptionParser } from './option';
import type { SectionParser } from './section';
export declare type ParsedConfig<T> = {
[K in keyof T]: LazyObject<T[K]>;
};
export declare type RootParsedConfig<T> = ParsedConfig<{
root: LazyObject<T>;
}>;
export declare type Parser<T, R = any> = OptionParser<T, R> | SectionParser<T, R> | MapParser<T, R>;
2 changes: 2 additions & 0 deletions build/types/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
4 changes: 4 additions & 0 deletions build/types/lazy.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { isLazy } from "../lazy";
export declare type LazyObject<T> = T & {
[isLazy]: true;
};
2 changes: 2 additions & 0 deletions build/types/lazy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
16 changes: 16 additions & 0 deletions build/types/locator.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { RootPrefixes, ConfigParserArg } from './root';
export declare type LocatorArg<T> = RootPrefixes & ConfigParserArg<T>;
export declare type Prefixes = Required<RootPrefixes> & {
namePrefix: string;
};
export declare type Node<T> = {
name: string;
parent: string;
option?: T;
envVar?: string;
cliOption?: string;
};
export interface Locator<T> extends Node<T> {
nested: (key: keyof T) => Locator<T[keyof T]>;
resetOption: <T>(newOption: T) => Locator<T>;
}
2 changes: 2 additions & 0 deletions build/types/locator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
5 changes: 5 additions & 0 deletions build/types/map.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { RootParsedConfig } from "./common";
import type { LazyObject } from "./lazy";
import type { Locator } from "./locator";
import type { DeepPartial } from "./utils";
export declare type MapParser<T, R = any> = (locator: Locator<DeepPartial<T>>, config: RootParsedConfig<R>) => LazyObject<T>;
2 changes: 2 additions & 0 deletions build/types/map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
12 changes: 12 additions & 0 deletions build/types/option.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { RootParsedConfig } from './common';
import type { LazyObject } from './lazy';
import type { Locator } from './locator';
import type { DeepPartial } from './utils';
export declare type OptionParserConfig<T, S, R = any, P = any> = {
defaultValue?: T | ((config: LazyObject<R>, currNode: LazyObject<R> | LazyObject<P>) => T);
parseCli?: (input: string) => T;
parseEnv?: (input: string) => T;
validate?: (value: unknown, config: LazyObject<R>, currNode: LazyObject<R> | LazyObject<P>) => asserts value is T;
map?(value: T, config: LazyObject<R>, currNode: LazyObject<R> | LazyObject<P>): S;
};
export declare type OptionParser<T, R = any> = (locator: Locator<DeepPartial<T>>, config: RootParsedConfig<R>) => T;
2 changes: 2 additions & 0 deletions build/types/option.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
15 changes: 15 additions & 0 deletions build/types/root.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference types="node" />
import type { MapParser } from './map';
import type { SectionParser } from './section';
import type { DeepPartial } from './utils';
export declare type ConfigParserArg<T> = {
options?: T;
env: NodeJS.ProcessEnv;
argv: NodeJS.Process["argv"];
};
export declare type ConfigParser<T> = (arg: ConfigParserArg<DeepPartial<T>>) => T;
export declare type RootPrefixes = {
envPrefix?: string;
cliPrefix?: string;
};
export declare type RootParser<T> = SectionParser<T, T> | MapParser<T, T>;
2 changes: 2 additions & 0 deletions build/types/root.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
Loading