Skip to content

Commit 1178c03

Browse files
author
oldskytree
committed
chore: rewrite package on typescript
1 parent fbdfb46 commit 1178c03

26 files changed

+442
-323
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: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,37 @@
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 { LazyObject } from './types/lazy';
8+
import type { RootParsedConfig } from './types/common';
9+
import type { MapParser } from './types/map';
10+
import type { OptionParser, OptionParserConfig } from './types/option';
11+
import type { RootParser, RootPrefixes, ConfigParser } from './types/root';
12+
import type { SectionParser, SectionProperties } from './types/section';
13+
import type { DeepPartial } from './types/utils';
14+
import type { Locator } from './types/locator';
15+
16+
type Parser<T, R = any> = OptionParser<T, R> | SectionParser<T, R> | MapParser<T, R>;
517

618
/**
719
* Single option
820
*/
9-
function option({
21+
export function option<T, S = T, R = any>({
1022
defaultValue,
1123
parseCli = _.identity,
1224
parseEnv = _.identity,
1325
validate = _.noop,
1426
map: mapFunc = _.identity
15-
}) {
27+
}: OptionParserConfig<T, S, R>): OptionParser<S, R> {
28+
const validateFunc: typeof validate = validate;
29+
1630
return (locator, parsed) => {
1731
const config = parsed.root;
18-
const currNode = locator.parent ? _.get(config, locator.parent) : config;
32+
const currNode = locator.parent ? _.get(parsed, locator.parent) : config;
1933

20-
let value;
34+
let value: unknown;
2135
if (locator.cliOption !== undefined) {
2236
value = parseCli(locator.cliOption);
2337
} else if (locator.envVar !== undefined) {
@@ -31,7 +45,8 @@ function option({
3145
} else {
3246
throw new MissingOptionError(locator.name);
3347
}
34-
validate(value, config, currNode);
48+
49+
validateFunc(value, config, currNode);
3550

3651
return mapFunc(value, config, currNode);
3752
};
@@ -41,13 +56,15 @@ function option({
4156
* Object with fixed properties.
4257
* Any unknown property will be reported as error.
4358
*/
44-
function section(properties) {
45-
const expectedKeys = _.keys(properties);
59+
export function section<T, R = any>(properties: SectionProperties<T, R>): SectionParser<T, R> {
60+
const expectedKeys = _.keys(properties) as Array<keyof T>;
61+
4662
return (locator, config) => {
4763
const unknownKeys = _.difference(
4864
_.keys(locator.option),
49-
expectedKeys
65+
expectedKeys as Array<string>
5066
);
67+
5168
if (unknownKeys.length > 0) {
5269
throw new UnknownKeysError(
5370
unknownKeys.map((key) => `${locator.name}.${key}`)
@@ -56,7 +73,8 @@ function section(properties) {
5673

5774
const lazyResult = buildLazyObject(expectedKeys, (key) => {
5875
const parser = properties[key];
59-
return () => parser(locator.nested(key), config);
76+
77+
return () => parser(locator.nested(key) as Locator<DeepPartial<T[keyof T]>>, config);
6078
});
6179

6280
_.set(config, locator.name, lazyResult);
@@ -69,32 +87,33 @@ function section(properties) {
6987
* Object with user-specified keys and values,
7088
* parsed by valueParser.
7189
*/
72-
function map(valueParser, defaultValue) {
90+
export function map<T extends Record<string, any>, V extends T[string] = T[string], R = any>(
91+
valueParser: Parser<V, R>,
92+
defaultValue: DeepPartial<Record<string, V>>
93+
): MapParser<Record<string, V>, R> {
7394
return (locator, config) => {
7495
if (locator.option === undefined) {
7596
if (!defaultValue) {
76-
return {};
97+
return {} as LazyObject<T>;
7798
}
7899
locator = locator.resetOption(defaultValue);
79100
}
80101

81-
const optionsToParse = Object.keys(locator.option);
82-
const lazyResult = buildLazyObject(optionsToParse, (key) => {
83-
return () => valueParser(locator.nested(key), config);
102+
const optionsToParse = Object.keys(locator.option as Record<string, V>);
103+
const lazyResult = buildLazyObject<Record<string, V>>(optionsToParse, (key) => {
104+
return () => valueParser(locator.nested(key) as Locator<DeepPartial<T[keyof T]>>, config);
84105
});
85106
_.set(config, locator.name, lazyResult);
86107

87108
return lazyResult;
88109
};
89110
}
90111

91-
function root(rootParser, {envPrefix, cliPrefix}) {
112+
export function root<T>(rootParser: RootParser<T>, {envPrefix, cliPrefix}: RootPrefixes): ConfigParser<T> {
92113
return ({options, env, argv}) => {
93114
const rootLocator = initLocator({options, env, argv, envPrefix, cliPrefix});
94-
const parsed = {};
95-
rootParser(rootLocator, parsed);
96-
return forceParsing(parsed.root);
115+
const parsed = rootParser(rootLocator, {} as RootParsedConfig<T>);
116+
117+
return forceParsing(parsed);
97118
};
98119
}
99-
100-
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+
public 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+
public 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: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,52 @@
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+
export function buildLazyObject<T>(keys: Array<keyof T>, getKeyGetter: (key: keyof T) => () => (T[keyof T] | LazyObject<T[keyof T]>)): LazyObject<T> {
68
const target = {
79
[isLazy]: true
8-
};
10+
} as LazyObject<T>;
11+
912
for (const key of keys) {
1013
defineLazy(target, key, getKeyGetter(key));
1114
}
15+
1216
return target;
1317
}
1418

15-
function forceParsing(lazyObject) {
19+
export function forceParsing<T>(lazyObject: LazyObject<T>): T {
1620
return _.cloneDeep(lazyObject);
1721
}
1822

19-
function defineLazy(object, key, getter) {
23+
function defineLazy<T>(object: LazyObject<T>, key: keyof T, getter: () => T[keyof T] | LazyObject<T[keyof T]>): void {
2024
let defined = false;
21-
let value;
25+
let value: T[keyof T];
2226

2327
Object.defineProperty(object, key, {
24-
get() {
28+
get(): T[keyof T] {
2529
if (!defined) {
2630
defined = true;
27-
value = getter();
28-
if (_.isObject(value) && value[isLazy]) {
29-
value = forceParsing(value);
31+
const val = getter();
32+
33+
if (isLazyObject(val)) {
34+
value = forceParsing(val);
35+
} else {
36+
value = val;
3037
}
3138
}
39+
3240
return value;
3341
},
3442
enumerable: true
3543
});
3644
}
3745

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

lib/locator.ts

Lines changed: 14 additions & 15 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,20 @@ 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 default function<T>({options, env, argv, envPrefix = '', cliPrefix = '--'}: LocatorArg<T>): Locator<T> {
1820
const parsedArgv = parseArgv(argv, cliPrefix);
1921

20-
function getNested(option, {namePrefix, envPrefix, cliPrefix}) {
22+
function getNested<T extends {}>(option: T | undefined, {namePrefix, envPrefix, cliPrefix}: Prefixes): (key: keyof T) => Locator<T[keyof T]> {
2123
return (subKey) => {
22-
const envName = envPrefix + _.snakeCase(subKey);
23-
const cliFlag = cliPrefix + _.kebabCase(subKey);
24+
const envName = envPrefix + _.snakeCase(subKey.toString());
25+
const cliFlag = cliPrefix + _.kebabCase(subKey.toString());
2426

2527
const argIndex = parsedArgv.lastIndexOf(cliFlag);
2628
const subOption = _.get(option, subKey);
27-
const newName = namePrefix ? `${namePrefix}.${subKey}` : subKey;
29+
const newName = namePrefix ? `${namePrefix}.${subKey}` : subKey.toString();
2830

2931
return mkLocator(
3032
{
@@ -43,14 +45,11 @@ module.exports = function({options, env, argv, envPrefix = '', cliPrefix = '--'}
4345
};
4446
}
4547

46-
function mkLocator(base, prefixes) {
48+
function mkLocator<T>(base: Node<T>, prefixes: Prefixes): Locator<T> {
4749
return _.extend(base, {
4850
nested: getNested(base.option, prefixes),
49-
resetOption: function(newOptions) {
50-
return _.extend({}, base, {
51-
option: newOptions,
52-
nested: getNested(newOptions, prefixes)
53-
});
51+
resetOption: function<T>(newOptions: T): Locator<T> {
52+
return mkLocator({ ...base, option: newOptions }, prefixes);
5453
}
5554
});
5655
}
@@ -64,7 +63,7 @@ module.exports = function({options, env, argv, envPrefix = '', cliPrefix = '--'}
6463
cliOption: undefined
6564
},
6665
{
67-
namePrefix: '',
66+
namePrefix: 'root',
6867
envPrefix,
6968
cliPrefix
7069
}

lib/types/common.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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<T> = {[K in keyof T]: LazyObject<T[K]>};
7+
8+
export type RootParsedConfig<T> = ParsedConfig<{root: LazyObject<T>}>;
9+
10+
export type Parser<T, R = any> = OptionParser<T, R> | SectionParser<T, R> | MapParser<T, R>;

0 commit comments

Comments
 (0)