Add packages/argparse

This commit is contained in:
Jerko Steiner 2019-08-05 18:11:46 +07:00
parent 15d54639ab
commit 47859ffa34
10 changed files with 192 additions and 1 deletions

View File

@ -9,7 +9,8 @@
"@rondo/image-upload": "file:packages/image-upload",
"@rondo/tasq": "file:packages/tasq",
"@rondo/jsonrpc": "file:packages/jsonrpc",
"@rondo/scripts": "file:packages/scripts"
"@rondo/scripts": "file:packages/scripts",
"@rondo/argparse": "file:packages/argparse"
},
"devDependencies": {
"@types/bcrypt": "^3.0.0",

View File

@ -0,0 +1 @@
# argparse

View 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
}

View File

View 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"
}

View 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)
})
})

View 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
}

View File

@ -0,0 +1 @@
export * from './argparse'

View File

@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "esm"
},
"references": []
}

View File

@ -0,0 +1,9 @@
{
"extends": "../tsconfig.common.json",
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
},
"references": [
]
}