Add packages/argparse
This commit is contained in:
parent
15d54639ab
commit
47859ffa34
@ -9,7 +9,8 @@
|
|||||||
"@rondo/image-upload": "file:packages/image-upload",
|
"@rondo/image-upload": "file:packages/image-upload",
|
||||||
"@rondo/tasq": "file:packages/tasq",
|
"@rondo/tasq": "file:packages/tasq",
|
||||||
"@rondo/jsonrpc": "file:packages/jsonrpc",
|
"@rondo/jsonrpc": "file:packages/jsonrpc",
|
||||||
"@rondo/scripts": "file:packages/scripts"
|
"@rondo/scripts": "file:packages/scripts",
|
||||||
|
"@rondo/argparse": "file:packages/argparse"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcrypt": "^3.0.0",
|
"@types/bcrypt": "^3.0.0",
|
||||||
|
|||||||
1
packages/argparse/README.md
Normal file
1
packages/argparse/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# argparse
|
||||||
18
packages/argparse/jest.config.js
Normal file
18
packages/argparse/jest.config.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
module.exports = {
|
||||||
|
roots: [
|
||||||
|
'<rootDir>/src'
|
||||||
|
],
|
||||||
|
transform: {
|
||||||
|
'^.+\\.tsx?$': 'ts-jest'
|
||||||
|
},
|
||||||
|
testRegex: '(/__tests__/.*|\\.(test|spec))\\.tsx?$',
|
||||||
|
moduleFileExtensions: [
|
||||||
|
'ts',
|
||||||
|
'tsx',
|
||||||
|
'js',
|
||||||
|
'jsx'
|
||||||
|
],
|
||||||
|
setupFiles: ['<rootDir>/jest.setup.js'],
|
||||||
|
maxConcurrency: 1,
|
||||||
|
verbose: false
|
||||||
|
}
|
||||||
0
packages/argparse/jest.setup.js
Normal file
0
packages/argparse/jest.setup.js
Normal file
14
packages/argparse/package.json
Normal file
14
packages/argparse/package.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "@rondo/argparse",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"test": "jest",
|
||||||
|
"lint": "tslint --project .",
|
||||||
|
"compile": "tsc",
|
||||||
|
"clean": "rm -rf lib/"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"types": "lib/index.d.ts",
|
||||||
|
"devDependencies": {},
|
||||||
|
"module": "lib/index.js"
|
||||||
|
}
|
||||||
37
packages/argparse/src/argparse.test.ts
Normal file
37
packages/argparse/src/argparse.test.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import {argparse} from './argparse'
|
||||||
|
|
||||||
|
describe('argparse', () => {
|
||||||
|
|
||||||
|
it('parses args', () => {
|
||||||
|
const args = argparse(['--one', '1', '--two', '2', '--four'], {
|
||||||
|
one: {
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
two: {
|
||||||
|
type: 'number',
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
four: {
|
||||||
|
type: 'boolean',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const one: string = args.one
|
||||||
|
const two: number = args.two
|
||||||
|
const four: boolean = args.four
|
||||||
|
|
||||||
|
expect(one).toBe('1')
|
||||||
|
expect(two).toBe(2)
|
||||||
|
expect(four).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws when required args missing', () => {
|
||||||
|
expect(() => argparse([], {
|
||||||
|
one: {
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})).toThrowError(/missing required/i)
|
||||||
|
})
|
||||||
|
})
|
||||||
103
packages/argparse/src/argparse.ts
Normal file
103
packages/argparse/src/argparse.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
export type TArgTypeName = 'string' | 'number' | 'boolean'
|
||||||
|
export type TArgType<T extends TArgTypeName> =
|
||||||
|
T extends 'string'
|
||||||
|
? string
|
||||||
|
: T extends 'number'
|
||||||
|
? number
|
||||||
|
: T extends 'boolean'
|
||||||
|
? boolean
|
||||||
|
: never
|
||||||
|
|
||||||
|
export interface IArgConfig<T extends TArgTypeName> {
|
||||||
|
type: T
|
||||||
|
default?: TArgType<T>
|
||||||
|
required?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IArgsConfig {
|
||||||
|
[arg: string]: IArgConfig<TArgTypeName>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TArgs<T> = {
|
||||||
|
[k in keyof T]: T[k] extends IArgConfig<infer A> ?
|
||||||
|
TArgType<A> : never
|
||||||
|
}
|
||||||
|
|
||||||
|
const iterate = <T>(arr: T[]) => {
|
||||||
|
let i = -1
|
||||||
|
return {
|
||||||
|
hasNext() {
|
||||||
|
return i < arr.length - 1
|
||||||
|
},
|
||||||
|
next(): T {
|
||||||
|
return arr[++i]
|
||||||
|
},
|
||||||
|
peek(): T {
|
||||||
|
return arr[i + 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert(cond: boolean, message: string) {
|
||||||
|
if (!cond) {
|
||||||
|
throw new Error('Error parsing arguments: ' + message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function argparse<T extends object>(
|
||||||
|
args: string[],
|
||||||
|
config: T extends IArgsConfig ? T : never,
|
||||||
|
): TArgs<T> {
|
||||||
|
const result = {} as TArgs<T>
|
||||||
|
const it = iterate(args)
|
||||||
|
|
||||||
|
const usedArgs: Record<string, true> = {}
|
||||||
|
const requiredArgs = Object.keys(config).reduce((obj, arg) => {
|
||||||
|
const argConfig = config[arg]
|
||||||
|
if (argConfig.default !== undefined) {
|
||||||
|
result[arg] = argConfig.default
|
||||||
|
}
|
||||||
|
obj[arg] = !!argConfig.required
|
||||||
|
return obj
|
||||||
|
}, {} as Record<string, boolean>)
|
||||||
|
|
||||||
|
while(it.hasNext()) {
|
||||||
|
const arg = it.next()
|
||||||
|
assert(arg.substring(0, 2) === '--', 'Arguments must start with --')
|
||||||
|
const argName = arg.substring(2)
|
||||||
|
const argConfig = config[argName]
|
||||||
|
assert(!!argConfig, 'Unknown argument: ' + arg)
|
||||||
|
delete requiredArgs[argName]
|
||||||
|
usedArgs[argName] = true
|
||||||
|
const peek = it.peek()
|
||||||
|
switch(argConfig.type) {
|
||||||
|
case 'string':
|
||||||
|
assert(it.hasNext(), 'Value of argument must be a string: ' + arg)
|
||||||
|
result[argName] = it.next()
|
||||||
|
continue
|
||||||
|
case 'number':
|
||||||
|
const num = parseInt(it.next(), 10)
|
||||||
|
assert(!isNaN(num), 'Value of argument must be a number: ' + arg)
|
||||||
|
result[argName] = num
|
||||||
|
continue
|
||||||
|
case 'boolean':
|
||||||
|
if (peek === 'true') {
|
||||||
|
it.next()
|
||||||
|
result[argName] = true
|
||||||
|
} else if (peek === 'false') {
|
||||||
|
it.next()
|
||||||
|
result[argName] = false
|
||||||
|
} else {
|
||||||
|
result[argName] = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
throw new Error('Unknown type:' + argConfig.type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!Object.keys(requiredArgs).length, 'Missing required args: ' +
|
||||||
|
Object.keys(requiredArgs).map(r => '--' + r))
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
1
packages/argparse/src/index.ts
Normal file
1
packages/argparse/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './argparse'
|
||||||
7
packages/argparse/tsconfig.esm.json
Normal file
7
packages/argparse/tsconfig.esm.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "esm"
|
||||||
|
},
|
||||||
|
"references": []
|
||||||
|
}
|
||||||
9
packages/argparse/tsconfig.json
Normal file
9
packages/argparse/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.common.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "lib",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"references": [
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user