Skip to content

Commit

Permalink
chore: rewrite package to typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
oldskytree committed Feb 12, 2022
1 parent d84e204 commit 751efda
Show file tree
Hide file tree
Showing 30 changed files with 647 additions and 425 deletions.
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
104 changes: 104 additions & 0 deletions config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { root, map, section, option } from './lib';
import { buildLazyObject } from './lib/lazy';

// const map1 = map(section({
// r: option({
// defaultValue: '1',
// map: (v) => +v
// }),
// t: option({
// defaultValue: 0,
// map: (v) => v.toString()
// })
// }),
// {'': {
// r: 's',
// t: 0
// }}
// );

// const config = root(, {envPrefix: '', cliPrefix: ''})({
// options: {},
// env: process.env,
// argv: process.argv
// });

// console.log(config);

const rootOption = option({
defaultValue: 'root',
map: (v: string): string[] => [].concat(v)
});
const tralalaOption = option({
defaultValue: 1234
})
const mainSection = section({
r: rootOption,
t: tralalaOption
});
const fileMap = map(mainSection, {
'tralala': {
r: [''],
t: 0
}
});
const configParser = root(fileMap, {
envPrefix: '',
cliPrefix: ''
});
const config2 = configParser({
options: {},
env: process.env,
argv: process.argv
});

type A = {
system: {
parallelLimit: number;
};
browsers: {
[x: string]: {
calibrate: boolean;
windowSize: number;
};
};
};

const parser = root(section({
system: section({
parallelLimit: option({
defaultValue: 123,
parseEnv: Number,
parseCli: Number,
map: (value, config, node) => {
return value * 2;
}
})
}),
browsers: map(section({
calibrate: option({
defaultValue: true
}),
windowSize: option({
defaultValue: 1024
})
}), {'': {}})
}), {
envPrefix: '',
cliPrefix: ''
});

const parsedConfig = parser({
options: {
browsers: {
'': {calibrate: false, }
},
system: {parallelLimit: 1}
},
env: process.env,
argv: process.argv
});

const a = root(section({
id: option({defaultValue: 1})
}), { envPrefix: '', cliPrefix: '' })({ options: { id: 1 }, env: process.env, argv: process.argv });
100 changes: 0 additions & 100 deletions lib/core.js

This file was deleted.

119 changes: 119 additions & 0 deletions lib/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import _ from 'lodash';

import { MissingOptionError, UnknownKeysError } from './errors';
import { buildLazyObject, forceParsing } from './lazy';
import initLocator from './locator';

import type { LazyObject } from './types/lazy';
import type { RootParsedConfig } from './types/common';
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';
import type { Locator } from './types/locator';

type Parser<T, R = any> = OptionParser<T, R> | SectionParser<T, R> | MapParser<T, R>;

/**
* Single option
*/
export function option<T, S = T, R = any>({
defaultValue,
parseCli = _.identity,
parseEnv = _.identity,
validate = _.noop,
map: mapFunc = _.identity
}: OptionParserConfig<T, S, R>): OptionParser<S, R> {
const validateFunc: typeof validate = validate;

return (locator, parsed) => {
const config = parsed.root;
const currNode = locator.parent ? _.get(parsed, locator.parent) : config;

let value: unknown;
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 = _.isFunction(defaultValue)
? defaultValue(config, currNode)
: defaultValue;
} else {
throw new MissingOptionError(locator.name);
}

validateFunc(value, config, currNode);

return mapFunc(value, config, currNode);
};
}

/**
* Object with fixed properties.
* Any unknown property will be reported as error.
*/
export function section<T, R = any>(properties: SectionProperties<T, R>): SectionParser<T, R> {
const expectedKeys = _.keys(properties) as Array<keyof T>;

return (locator, config) => {
const unknownKeys = _.difference(
_.keys(locator.option),
expectedKeys as Array<string>
);

if (unknownKeys.length > 0) {
throw new UnknownKeysError(
unknownKeys.map((key) => `${locator.name}.${key}`)
);
}

const lazyResult = buildLazyObject(expectedKeys, (key) => {
const parser = properties[key];

return () => parser(locator.nested(key) as Locator<DeepPartial<T[keyof T]>>, config);
});

_.set(config, locator.name, lazyResult);

return lazyResult;
};
}

/**
* Object with user-specified keys and values,
* parsed by valueParser.
*/
export 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> {
return (locator, config) => {
if (locator.option === undefined) {
if (!defaultValue) {
return {} as LazyObject<T>;
}
locator = locator.resetOption(defaultValue);
}

const optionsToParse = Object.keys(locator.option as Record<string, V>);
const lazyResult = buildLazyObject<Record<string, V>>(optionsToParse, (key) => {
return () => valueParser(locator.nested(key) as Locator<DeepPartial<T[keyof T]>>, config);
});
_.set(config, locator.name, lazyResult);

return lazyResult;
};
}

export function root<T>(rootParser: RootParser<T>, {envPrefix, cliPrefix}: RootPrefixes): ConfigParser<T> {
return ({options, env, argv}) => {
const rootLocator = initLocator({options, env, argv, envPrefix, cliPrefix});
const parsed = rootParser(rootLocator, {} as RootParsedConfig<T>);

return forceParsing(parsed);
};
}
14 changes: 8 additions & 6 deletions lib/errors.js → lib/errors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
class MissingOptionError extends Error {
constructor(optionName) {
export class MissingOptionError extends Error {
public optionName: string;

constructor(optionName: string) {
const message = `${optionName} is required`;
super(message);
this.name = 'MissingOptionError';
Expand All @@ -10,8 +12,10 @@ class MissingOptionError extends Error {
}
}

class UnknownKeysError extends Error {
constructor(keys) {
export class UnknownKeysError extends Error {
public keys: Array<string>;

constructor(keys: Array<string>) {
const message = `Unknown options: ${keys.join(', ')}`;
super(message);
this.name = 'UnknownKeysError';
Expand All @@ -21,5 +25,3 @@ class UnknownKeysError extends Error {
Error.captureStackTrace(this, UnknownKeysError);
}
}

module.exports = {MissingOptionError, UnknownKeysError};
7 changes: 0 additions & 7 deletions lib/index.js

This file was deleted.

2 changes: 2 additions & 0 deletions lib/index.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';
Loading

0 comments on commit 751efda

Please sign in to comment.