Skip to content

Commit 79db1c7

Browse files
author
oldskytree
committed
chore: rewrite package on typescript
1 parent a114a9d commit 79db1c7

26 files changed

+450
-317
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
node_modules
2+
build

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ node_js:
33
- '6'
44
- '8'
55
script:
6+
- npm build
67
- npm test

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Config is described with a combination of a functions:
88
var parser = root(section({
99
system: section({
1010
parallelLimit: option({
11+
defaultValue: 0,
1112
parseEnv: Number,
1213
parseCli: Number,
1314
validate: function() {...}

lib/core.ts

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,36 @@
1-
const _ = require('lodash');
2-
const {buildLazyObject, forceParsing} = require('./lazy');
3-
const {MissingOptionError, UnknownKeysError} = require('./errors');
4-
const initLocator = require('./locator');
1+
import _ from 'lodash';
2+
3+
import {MissingOptionError, UnknownKeysError} from './errors';
4+
import {buildLazyObject, forceParsing} from './lazy';
5+
import initLocator from './locator';
6+
7+
import type {Rooted, Parser} from './types/common';
8+
import type {LazyObject} from './types/lazy';
9+
import type {Locator} from './types/locator';
10+
import type {MapParser} from './types/map';
11+
import type {OptionParser, OptionParserConfig} from './types/option';
12+
import type {RootParser, RootPrefixes, ConfigParser} from './types/root';
13+
import type {SectionParser, SectionProperties} from './types/section';
14+
import type {Map} from './types/utils';
515

616
/**
717
* Single option
818
*/
9-
function option({
19+
export function option<Value, MappedValue = Value, Result = any>({
1020
defaultValue,
1121
parseCli = _.identity,
1222
parseEnv = _.identity,
1323
validate = _.noop,
1424
map: mapFunc = _.identity,
1525
isDeprecated = false
16-
}) {
26+
}: OptionParserConfig<Value, MappedValue, Result> = {}): OptionParser<MappedValue, Result> {
27+
const validateFunc: typeof validate = validate;
28+
1729
return (locator, parsed) => {
1830
const config = parsed.root;
1931
const currNode = locator.parent ? _.get(config, locator.parent) : config;
2032

21-
let value, isSetByUser = true;
33+
let value: unknown, isSetByUser = true;
2234
if (locator.cliOption !== undefined) {
2335
value = parseCli(locator.cliOption);
2436
} else if (locator.envVar !== undefined) {
@@ -38,7 +50,7 @@ function option({
3850
console.warn(`Using "${locator.name}" option is deprecated`);
3951
}
4052

41-
validate(value, config, currNode, {isSetByUser});
53+
validateFunc(value, config, currNode, {isSetByUser});
4254

4355
return mapFunc(value, config, currNode, {isSetByUser});
4456
};
@@ -48,13 +60,15 @@ function option({
4860
* Object with fixed properties.
4961
* Any unknown property will be reported as error.
5062
*/
51-
function section(properties) {
52-
const expectedKeys = _.keys(properties);
63+
export function section<Config, Result>(properties: SectionProperties<Config, Result>): SectionParser<Config, Result> {
64+
const expectedKeys = _.keys(properties) as Array<keyof Config>;
65+
5366
return (locator, config) => {
5467
const unknownKeys = _.difference(
5568
_.keys(locator.option),
56-
expectedKeys
69+
expectedKeys as Array<string>
5770
);
71+
5872
if (unknownKeys.length > 0) {
5973
throw new UnknownKeysError(
6074
unknownKeys.map((key) => `${locator.name}.${key}`)
@@ -63,6 +77,7 @@ function section(properties) {
6377

6478
const lazyResult = buildLazyObject(expectedKeys, (key) => {
6579
const parser = properties[key];
80+
6681
return () => parser(locator.nested(key), config);
6782
});
6883

@@ -76,32 +91,34 @@ function section(properties) {
7691
* Object with user-specified keys and values,
7792
* parsed by valueParser.
7893
*/
79-
function map(valueParser, defaultValue) {
94+
export function map<SubConfig, Result>(
95+
valueParser: Parser<SubConfig, Result>,
96+
defaultValue: Map<SubConfig>
97+
): MapParser<Map<SubConfig>, Result> {
8098
return (locator, config) => {
8199
if (locator.option === undefined) {
82100
if (!defaultValue) {
83-
return {};
101+
return {} as LazyObject<Map<SubConfig>>;
84102
}
85103
locator = locator.resetOption(defaultValue);
86104
}
87105

88-
const optionsToParse = Object.keys(locator.option);
89-
const lazyResult = buildLazyObject(optionsToParse, (key) => {
106+
const optionsToParse = Object.keys(locator.option as Map<SubConfig>);
107+
const lazyResult = buildLazyObject<Map<SubConfig>>(optionsToParse, (key) => {
90108
return () => valueParser(locator.nested(key), config);
91109
});
110+
92111
_.set(config, locator.name, lazyResult);
93112

94113
return lazyResult;
95114
};
96115
}
97116

98-
function root(rootParser, {envPrefix, cliPrefix}) {
117+
export function root<Config, Result = Config>(rootParser: RootParser<Config, Result>, {envPrefix, cliPrefix}: RootPrefixes = {}): ConfigParser<Config> {
99118
return ({options, env, argv}) => {
100119
const rootLocator = initLocator({options, env, argv, envPrefix, cliPrefix});
101-
const parsed = {};
102-
rootParser(rootLocator, parsed);
103-
return forceParsing(parsed.root);
120+
const parsed = rootParser(rootLocator as Locator<Config>, {} as Rooted<Result>);
121+
122+
return forceParsing(parsed);
104123
};
105124
}
106-
107-
module.exports = {option, section, map, root};

lib/errors.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
class MissingOptionError extends Error {
2-
constructor(optionName) {
1+
export class MissingOptionError extends Error {
2+
optionName: string;
3+
4+
constructor(optionName: string) {
35
const message = `${optionName} is required`;
46
super(message);
57
this.name = 'MissingOptionError';
@@ -10,8 +12,10 @@ class MissingOptionError extends Error {
1012
}
1113
}
1214

13-
class UnknownKeysError extends Error {
14-
constructor(keys) {
15+
export class UnknownKeysError extends Error {
16+
keys: Array<string>;
17+
18+
constructor(keys: Array<string>) {
1519
const message = `Unknown options: ${keys.join(', ')}`;
1620
super(message);
1721
this.name = 'UnknownKeysError';
@@ -21,5 +25,3 @@ class UnknownKeysError extends Error {
2125
Error.captureStackTrace(this, UnknownKeysError);
2226
}
2327
}
24-
25-
module.exports = {MissingOptionError, UnknownKeysError};

lib/index.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,2 @@
1-
const {root, section, map, option} = require('./core');
2-
const {MissingOptionError, UnknownKeysError} = require('./errors');
3-
4-
module.exports = {
5-
root, section, map, option,
6-
MissingOptionError, UnknownKeysError
7-
};
1+
export {root, section, map, option} from './core';
2+
export {MissingOptionError, UnknownKeysError} from './errors';

lib/lazy.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,50 @@
1-
const _ = require('lodash');
1+
import _ from 'lodash';
22

3-
const isLazy = Symbol('isLazy');
3+
import type {LazyObject} from './types/lazy';
44

5-
function buildLazyObject(keys, getKeyGetter) {
5+
export const isLazy = Symbol('isLazy');
6+
7+
type SimpleOrLazyObject<T> = T | LazyObject<T>;
8+
9+
export function buildLazyObject<T>(keys: Array<keyof T>, getKeyGetter: (key: keyof T) => () => (SimpleOrLazyObject<T[keyof T]>)): LazyObject<T> {
610
const target = {
711
[isLazy]: true
8-
};
12+
} as LazyObject<T>;
13+
914
for (const key of keys) {
1015
defineLazy(target, key, getKeyGetter(key));
1116
}
17+
1218
return target;
1319
}
1420

15-
function forceParsing(lazyObject) {
21+
export function forceParsing<T>(lazyObject: LazyObject<T>): T {
1622
return _.cloneDeep(lazyObject);
1723
}
1824

19-
function defineLazy(object, key, getter) {
25+
function defineLazy<T>(object: LazyObject<T>, key: keyof T, getter: () => SimpleOrLazyObject<T[keyof T]>): void {
2026
let defined = false;
21-
let value;
27+
let value: T[keyof T];
2228

2329
Object.defineProperty(object, key, {
24-
get() {
30+
get(): T[keyof T] {
2531
if (!defined) {
2632
defined = true;
27-
value = getter();
28-
if (_.isObject(value) && value[isLazy]) {
29-
value = forceParsing(value);
30-
}
33+
const val = getter();
34+
35+
value = isLazyObject(val) ? forceParsing(val) : val;
3136
}
37+
3238
return value;
3339
},
3440
enumerable: true
3541
});
3642
}
3743

38-
module.exports = {forceParsing, buildLazyObject};
44+
function isLazyObject<T>(value: T): value is LazyObject<T> {
45+
return _.isObject(value) && hasOwnProperty(value, isLazy) && value[isLazy] === true;
46+
}
47+
48+
function hasOwnProperty<T extends object, K extends PropertyKey>(obj: T, prop: K): obj is T & Record<K, unknown> {
49+
return obj.hasOwnProperty(prop);
50+
}

lib/locator.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
const _ = require('lodash');
1+
import _ from 'lodash';
22

3-
function parseArgv(argv, cliPrefix) {
3+
import type {LocatorArg, Locator, Node, Prefixes} from './types/locator';
4+
5+
function parseArgv(argv: Array<string>, cliPrefix: string): Array<string> {
46
return argv.reduce(function(argv, arg) {
57
if (!arg.startsWith(cliPrefix) || !_.includes(arg, '=')) {
68
return argv.concat(arg);
@@ -11,20 +13,22 @@ function parseArgv(argv, cliPrefix) {
1113
const value = parts.slice(1).join('=');
1214

1315
return argv.concat(option, value);
14-
}, []);
16+
}, [] as Array<string>);
1517
}
1618

17-
module.exports = function({options, env, argv, envPrefix = '', cliPrefix = '--'}) {
19+
export = function initLocator<Options>({options, env, argv, envPrefix = '', cliPrefix = '--'}: LocatorArg<Options>): Locator<Options> {
1820
const parsedArgv = parseArgv(argv, cliPrefix);
1921

20-
function getNested(option, {namePrefix, envPrefix, cliPrefix}) {
21-
return (subKey) => {
22-
const envName = envPrefix + _.snakeCase(subKey);
23-
const cliFlag = cliPrefix + _.kebabCase(subKey);
22+
function getNested<Options>(option: Options | undefined, {namePrefix, envPrefix, cliPrefix}: Prefixes) {
23+
return <Key extends keyof Options>(subKey: Key): Locator<Options[Key]> => {
24+
const stringSubKey = subKey.toString();
25+
26+
const envName = envPrefix + _.snakeCase(stringSubKey);
27+
const cliFlag = cliPrefix + _.kebabCase(stringSubKey);
2428

2529
const argIndex = parsedArgv.lastIndexOf(cliFlag);
26-
const subOption = _.get(option, subKey);
27-
const newName = namePrefix ? `${namePrefix}.${subKey}` : subKey;
30+
const subOption: Options[Key] = _.get(option, subKey);
31+
const newName = namePrefix ? `${namePrefix}.${stringSubKey}` : stringSubKey;
2832

2933
return mkLocator(
3034
{
@@ -43,11 +47,11 @@ module.exports = function({options, env, argv, envPrefix = '', cliPrefix = '--'}
4347
};
4448
}
4549

46-
function mkLocator(base, prefixes) {
50+
function mkLocator<Options>(base: Node<Options>, prefixes: Prefixes): Locator<Options> {
4751
return _.extend(base, {
4852
nested: getNested(base.option, prefixes),
49-
resetOption: function(newOptions) {
50-
return _.extend({}, base, {
53+
resetOption: function(newOptions: Options): Locator<Options> {
54+
return _.extend({}, base as Locator<Options>, {
5155
option: newOptions,
5256
nested: getNested(newOptions, prefixes)
5357
});

lib/types/common.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type {LazyObject} from './lazy';
2+
import type {MapParser} from './map';
3+
import type {OptionParser} from './option';
4+
import type {SectionParser} from './section';
5+
6+
export type ParsedConfig<Config> = {[Key in keyof Config]: LazyObject<Config[Key]>};
7+
8+
export type Parser<Config, Result> = OptionParser<Config, Result> | SectionParser<Config, Result> | MapParser<Config, Result>;
9+
10+
export interface Rooted<T> {
11+
root: T;
12+
}

0 commit comments

Comments
 (0)