tiny-parse-argv
Advanced tools
Comparing version 1.0.2 to 2.0.0
import type { Options, ParsedArgs } from './types'; | ||
declare const parseArgv: (argv?: string[], options?: Options) => ParsedArgs; | ||
declare const parseArgv: (argv: string[], options?: Options) => ParsedArgs; | ||
export default parseArgv; |
/* IMPORT */ | ||
import minimist from 'minimist'; | ||
import process from 'node:process'; | ||
import { isBoolean, isOverridable, set, uniq, without, zip } from './utils.js'; | ||
/* HELPERS */ | ||
const getAliasesMap = (aliases = {}) => { | ||
const map = {}; | ||
for (const key in aliases) { | ||
const values = uniq([key, ...aliases[key]]); | ||
for (const value of values) { | ||
if (value in map) | ||
continue; | ||
map[value] = without(values, value); | ||
} | ||
} | ||
return map; | ||
}; | ||
const getAliasedSet = (aliases, values = []) => { | ||
const valuesAliases = values.flatMap(value => aliases[value] || []); | ||
const valuesAliased = new Set([...values, ...valuesAliases]); | ||
return valuesAliased; | ||
}; | ||
const setAliased = (target, key, value, aliases) => { | ||
set(target, key, value); | ||
aliases[key]?.forEach(alias => { | ||
set(target, alias, value); | ||
}); | ||
}; | ||
const parseDoubleHyphen = (argv) => { | ||
const index = argv.indexOf('--'); | ||
if (index < 0) | ||
return [argv, []]; | ||
const parse = argv.slice(0, index); | ||
const preserve = argv.slice(index + 1); | ||
return [parse, preserve]; | ||
}; | ||
const parseWithRegExp = (argv, re, callback) => { | ||
return argv.flatMap(arg => { | ||
const match = re.exec(arg); | ||
if (!match) | ||
return arg; | ||
return callback(...match); | ||
}); | ||
}; | ||
const parseCharSeparator = (argv) => { | ||
const re = /^-([a-zA-Z0-9]{2,})([^]*)$/; | ||
return parseWithRegExp(argv, re, (_, chars) => chars.split('').map(char => `-${char}`)); | ||
}; | ||
const parseEqualsSeparator = (argv) => { | ||
const re = /^(--?[^=][^=]*?)=([^]*)$/; | ||
return parseWithRegExp(argv, re, (_, key, value) => [key, value]); | ||
}; | ||
const parseImplicitSeparator = (argv) => { | ||
const re = /^(--?(?:no-)?\S*?[a-zA-Z]\S*?)((?:[0-9\/]|-(?=$))[^]*)$/; | ||
return parseWithRegExp(argv, re, (_, key, value) => [key, value]); | ||
}; | ||
const parseProto = (argv) => { | ||
const re = /^--?(no-)?(__proto__|prototype|constructor)$/; | ||
return argv.filter((arg, index) => !re.test(arg) && !re.test(argv[index - 1])); | ||
}; | ||
const parseOption = (arg) => { | ||
const optionRe = /^(--?)([^]+)$/; | ||
const match = optionRe.exec(arg); | ||
if (!match) | ||
return; | ||
return match[2]; | ||
}; | ||
const parseOptionNegation = (arg) => { | ||
const negationRe = /^no-([^]+)$/; | ||
const match = negationRe.exec(arg); | ||
if (!match) | ||
return [arg, true]; | ||
return [match[1], false]; | ||
}; | ||
const parseValue = (key, value, booleans, strings) => { | ||
if (booleans.has(key)) { | ||
if (value === 'true') | ||
return true; | ||
if (value === 'false') | ||
return false; | ||
} | ||
if (!strings.has(key)) { | ||
const numberRe = /^0[xX][0-9a-fA-F]+$|^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][-+]?\d+)?$/; | ||
if (numberRe.test(value)) { | ||
return Number(value); | ||
} | ||
} | ||
return String(value); | ||
}; | ||
/* MAIN */ | ||
const parseArgv = (argv = process.argv, options) => { | ||
return minimist(argv, options); | ||
const parseArgv = (argv, options = {}) => { | ||
const aliases = getAliasesMap(options.alias); | ||
const booleans = getAliasedSet(aliases, options.boolean); | ||
const strings = getAliasedSet(aliases, options.string); | ||
const defaults = options.default || {}; | ||
const [parse, preserve] = parseDoubleHyphen(argv); | ||
const parsed = { _: [], '--': preserve }; | ||
const args = parseCharSeparator(parseImplicitSeparator(parseEqualsSeparator(parseProto(parse)))); | ||
let optionPrev = ''; | ||
for (let i = 0, l = args.length; i < l; i++) { | ||
const arg = args[i]; | ||
const option = parseOption(arg); | ||
if (option) { // Option | ||
const [key, positive] = parseOptionNegation(option); | ||
if (isOverridable(parsed[key])) { // Maybe we are setting this option multiple times | ||
const value = (strings.has(key) ? '' : positive); | ||
setAliased(parsed, key, value, aliases); | ||
} | ||
optionPrev = option; | ||
} | ||
else { // Value or Argument | ||
const value = parseValue(optionPrev, arg, booleans, strings); | ||
if (optionPrev && (!booleans.has(optionPrev) || isBoolean(value))) { // Value | ||
setAliased(parsed, optionPrev, value, aliases); | ||
} | ||
else { // Argument | ||
parsed._.push(String(value)); | ||
} | ||
optionPrev = ''; | ||
} | ||
} | ||
return { ...zip(booleans, false), ...defaults, ...parsed }; | ||
}; | ||
/* EXPORT */ | ||
export default parseArgv; |
@@ -1,49 +0,12 @@ | ||
declare type Options = { | ||
/** | ||
* A string or array of strings argument names to always treat as strings | ||
*/ | ||
string?: string | string[]; | ||
/** | ||
* A boolean, string or array of strings to always treat as booleans. If true will treat | ||
* all double hyphenated arguments without equals signs as boolean (e.g. affects `--foo`, not `-f` or `--foo=bar`) | ||
*/ | ||
boolean?: boolean | string | string[]; | ||
/** | ||
* An object mapping string names to strings or arrays of string argument names to use as aliases | ||
*/ | ||
alias?: { | ||
[key: string]: string | string[]; | ||
}; | ||
/** | ||
* An object mapping string argument names to default values | ||
*/ | ||
default?: { | ||
[key: string]: any; | ||
}; | ||
/** | ||
* When true, populate argv._ with everything after the first non-option | ||
*/ | ||
stopEarly?: boolean; | ||
/** | ||
* A function which is invoked with a command line parameter not defined in the opts | ||
* configuration object. If the function returns false, the unknown option is not added to argv | ||
*/ | ||
unknown?: (arg: string) => boolean; | ||
/** | ||
* When true, populate argv._ with everything before the -- and argv['--'] with everything after the --. | ||
* Note that with -- set, parsing for arguments still stops after the `--`. | ||
*/ | ||
'--'?: boolean; | ||
type Options = { | ||
boolean?: string[]; | ||
string?: string[]; | ||
alias?: Record<string, string[]>; | ||
default?: Partial<Record<string, any>>; | ||
}; | ||
declare type ParsedArgs = { | ||
type ParsedArgs = { | ||
[arg: string]: any; | ||
/** | ||
* If opts['--'] is true, populated with everything after the -- | ||
*/ | ||
'--'?: string[]; | ||
/** | ||
* Contains all the arguments that didn't have an option associated with them | ||
*/ | ||
_: string[]; | ||
'_': string[]; | ||
'--': string[]; | ||
}; | ||
export type { Options, ParsedArgs }; |
{ | ||
"name": "tiny-parse-argv", | ||
"repository": "github:fabiospampinato/tiny-parse-argv", | ||
"description": "A tiny function for parsing process.argv.", | ||
"version": "1.0.2", | ||
"description": "A tiny function for parsing process.argv, a modern rewrite of a sensible subset of minimist.", | ||
"version": "2.0.0", | ||
"type": "module", | ||
@@ -14,3 +14,5 @@ "main": "dist/index.js", | ||
"compile:watch": "tsex compile --watch", | ||
"prepublishOnly": "npm run clean && npm run compile" | ||
"test": "tsex test", | ||
"test:watch": "tsex test --watch", | ||
"prepublishOnly": "npm run clean && npm run compile && npm run test" | ||
}, | ||
@@ -22,11 +24,7 @@ "keywords": [ | ||
], | ||
"dependencies": { | ||
"minimist": "^1.2.7" | ||
}, | ||
"devDependencies": { | ||
"@types/minimist": "^1.2.2", | ||
"@types/node": "^18.11.9", | ||
"tsex": "^1.1.2", | ||
"typescript": "^4.8.4" | ||
"fava": "^0.0.8", | ||
"tsex": "^1.1.5", | ||
"typescript": "^4.9.5" | ||
} | ||
} |
@@ -1,7 +0,37 @@ | ||
> **Note**: This is just a type-checked wrapper over [minimist](https://github.com/minimistjs/minimist). The underlying implementation may change in the future. | ||
# Tiny Parse Argv | ||
A tiny function for parsing `process.argv`. | ||
A tiny function for parsing `process.argv`, a modern rewrite of a sensible subset of [`minimist`](https://github.com/minimistjs/minimist). | ||
## Features | ||
The following features are provided: | ||
- Built-in TypeScript types, and pretty clean and understandable code. | ||
- Single/multiple implicit/explicit shorthand flags: `-f`, `-f some`, `-f 123`, `-f123`, `-abc`, `-abc 123`, `-abc123`, `-f some -f other`. | ||
- Single/multiple implicit/explicit longhand flags: `--foo`, `--foo some`, `--foo 123`, `--foo=123`, `--foo=some`, `--foo some --foo other`. | ||
- Explicitly negated flags are `false` by default: `--no-foo`, `--no-bar`. | ||
- Arguments: `./app.sh with some list of arguments`. | ||
- Values that would be interpreted as numbers if they were JavaScript are coerced to numbers automatically. | ||
- Flags that could lead to prototype pollution issues are safely ignored. | ||
- `options.boolean`: the value for the listed flags will always be coerced to a boolean. | ||
- `options.string`: the value for the listed flags will always be coerced to a string. | ||
- `options.alias`: if any aliased flag is assigned then all the aliases for it will be assigned too, automatically. | ||
- `options.default`: an object containing default values, which will be used if not overridded by the `argv` array. | ||
- `--`: a special flag that stops parsing, everything after it will be copied, untouched, into the `--` property of the return object. | ||
## Differences with `minimist` | ||
The following differences exist compared to `minimist`: | ||
- `option['--']` set to `false` is not supported, it's as if it's always set to `true`. | ||
- `option.boolean` set to `true` is not supported, you should always explicitly list all your supported boolean flags instead. | ||
- `option.boolean` set to a single string is not supported, always provide an array of flags instead. | ||
- `option.string` set to a single string is not supported, always provide an array of flags instead. | ||
- `option.alias` mapping to a single string is not supported, always provide an array of aliases instead. | ||
- `option.unknown` is not supported, you should handle unknown flags at another level of abstraction. | ||
- `option.stopEarly` is not supported, it's as if it's always set to `false`. | ||
- Dotted flags are not supported, so their paths will not be expanded, you can use [`path-prop`](https://github.com/fabiospampinato/path-prop)'s `unflat` function for that. | ||
Other than that it should work pretty much identically, since we are basically using the same tests. | ||
## Install | ||
@@ -18,3 +48,4 @@ | ||
parseArgv ( process.argv ); | ||
parseArgv ([ '-f', '--foo', 'some', 'argument', '--', '--app-flag' ]); | ||
// => { f: true, foo: 'some', _: ['argument'], '--': ['--app-flag'] } | ||
``` | ||
@@ -21,0 +52,0 @@ |
209
src/index.ts
/* IMPORT */ | ||
import minimist from 'minimist'; | ||
import process from 'node:process'; | ||
import {isBoolean, isOverridable, set, uniq, without, zip} from './utils'; | ||
import type {Options, ParsedArgs} from './types'; | ||
/* HELPERS */ | ||
const getAliasesMap = ( aliases: Record<string, string[]> = {} ): Partial<Record<string, string[]>> => { | ||
const map: Partial<Record<string, string[]>> = {}; | ||
for ( const key in aliases ) { | ||
const values = uniq ([ key, ...aliases[key] ]); | ||
for ( const value of values ) { | ||
if ( value in map ) continue; | ||
map[value] = without ( values, value ); | ||
} | ||
} | ||
return map; | ||
}; | ||
const getAliasedSet = ( aliases: Partial<Record<string, string[]>>, values: string[] = [] ): Set<string> => { | ||
const valuesAliases = values.flatMap ( value => aliases[value] || [] ); | ||
const valuesAliased = new Set ([ ...values, ...valuesAliases ]); | ||
return valuesAliased; | ||
}; | ||
const setAliased = ( target: any, key: string, value: any, aliases: Partial<Record<string, string[]>> ): void => { | ||
set ( target, key, value ); | ||
aliases[key]?.forEach ( alias => { | ||
set ( target, alias, value ); | ||
}); | ||
}; | ||
const parseDoubleHyphen = ( argv: string[] ): [parse: string[], preserve: string[]] => { | ||
const index = argv.indexOf ( '--' ); | ||
if ( index < 0 ) return [argv, []]; | ||
const parse = argv.slice ( 0, index ); | ||
const preserve = argv.slice ( index + 1 ); | ||
return [parse, preserve]; | ||
}; | ||
const parseWithRegExp = ( argv: string[], re: RegExp, callback: ( ...args: string[] ) => string[] ): string[] => { | ||
return argv.flatMap ( arg => { | ||
const match = re.exec ( arg ); | ||
if ( !match ) return arg; | ||
return callback ( ...match ); | ||
}); | ||
}; | ||
const parseCharSeparator = ( argv: string[] ): string[] => { | ||
const re = /^-([a-zA-Z0-9]{2,})([^]*)$/; | ||
return parseWithRegExp ( argv, re, ( _, chars ) => chars.split ( '' ).map ( char => `-${char}` ) ); | ||
}; | ||
const parseEqualsSeparator = ( argv: string[] ): string[] => { | ||
const re = /^(--?[^=][^=]*?)=([^]*)$/; | ||
return parseWithRegExp ( argv, re, ( _, key, value ) => [key, value] ); | ||
}; | ||
const parseImplicitSeparator = ( argv: string[] ): string[] => { | ||
const re = /^(--?(?:no-)?\S*?[a-zA-Z]\S*?)((?:[0-9\/]|-(?=$))[^]*)$/; | ||
return parseWithRegExp ( argv, re, ( _, key, value ) => [key, value] ); | ||
}; | ||
const parseProto = ( argv: string[] ): string[] => { | ||
const re = /^--?(no-)?(__proto__|prototype|constructor)$/; | ||
return argv.filter ( ( arg, index ) => !re.test ( arg ) && !re.test ( argv[index - 1] ) ); | ||
}; | ||
const parseOption = ( arg: string ): string | undefined => { | ||
const optionRe = /^(--?)([^]+)$/; | ||
const match = optionRe.exec ( arg ); | ||
if ( !match ) return; | ||
return match[2]; | ||
}; | ||
const parseOptionNegation = ( arg: string ): [key: string, positive: boolean] => { | ||
const negationRe = /^no-([^]+)$/; | ||
const match = negationRe.exec ( arg ); | ||
if ( !match ) return [arg, true]; | ||
return [match[1], false]; | ||
}; | ||
const parseValue = ( key: string, value: string, booleans: Set<string>, strings: Set<string> ): string | number | boolean => { | ||
if ( booleans.has ( key ) ) { | ||
if ( value === 'true' ) return true; | ||
if ( value === 'false' ) return false; | ||
} | ||
if ( !strings.has ( key ) ) { | ||
const numberRe = /^0[xX][0-9a-fA-F]+$|^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][-+]?\d+)?$/; | ||
if ( numberRe.test ( value ) ) { | ||
return Number ( value ); | ||
} | ||
} | ||
return String ( value ); | ||
}; | ||
/* MAIN */ | ||
const parseArgv = ( argv: string[] = process.argv, options?: Options ): ParsedArgs => { | ||
const parseArgv = ( argv: string[], options: Options = {} ): ParsedArgs => { | ||
return minimist ( argv, options ); | ||
const aliases = getAliasesMap ( options.alias ); | ||
const booleans = getAliasedSet ( aliases, options.boolean ); | ||
const strings = getAliasedSet ( aliases, options.string ); | ||
const defaults = options.default || {}; | ||
const [parse, preserve] = parseDoubleHyphen ( argv ); | ||
const parsed: ParsedArgs = { _: [], '--': preserve }; | ||
const args = parseCharSeparator ( parseImplicitSeparator ( parseEqualsSeparator ( parseProto ( parse ) ) ) ); | ||
let optionPrev: string = ''; | ||
for ( let i = 0, l = args.length; i < l; i++ ) { | ||
const arg = args[i]; | ||
const option = parseOption ( arg ); | ||
if ( option ) { // Option | ||
const [key, positive] = parseOptionNegation ( option ); | ||
if ( isOverridable ( parsed[key] ) ) { // Maybe we are setting this option multiple times | ||
const value = ( strings.has ( key ) ? '' : positive ); | ||
setAliased ( parsed, key, value, aliases ); | ||
} | ||
optionPrev = option; | ||
} else { // Value or Argument | ||
const value = parseValue ( optionPrev, arg, booleans, strings ); | ||
if ( optionPrev && ( !booleans.has ( optionPrev ) || isBoolean ( value ) ) ) { // Value | ||
setAliased ( parsed, optionPrev, value, aliases ); | ||
} else { // Argument | ||
parsed._.push ( String ( value ) ); | ||
} | ||
optionPrev = ''; | ||
} | ||
} | ||
return { ...zip ( booleans, false ), ...defaults, ...parsed }; | ||
}; | ||
@@ -15,0 +216,0 @@ |
/* MAIN */ | ||
//TODO: Type these more strictly, if it doesn't cause too many troubles with generics... | ||
type Options = { | ||
/** | ||
* A string or array of strings argument names to always treat as strings | ||
*/ | ||
string?: string | string[], | ||
/** | ||
* A boolean, string or array of strings to always treat as booleans. If true will treat | ||
* all double hyphenated arguments without equals signs as boolean (e.g. affects `--foo`, not `-f` or `--foo=bar`) | ||
*/ | ||
boolean?: boolean | string | string[], | ||
/** | ||
* An object mapping string names to strings or arrays of string argument names to use as aliases | ||
*/ | ||
alias?: { [key: string]: string | string[] }, | ||
/** | ||
* An object mapping string argument names to default values | ||
*/ | ||
default?: { [key: string]: any }, | ||
/** | ||
* When true, populate argv._ with everything after the first non-option | ||
*/ | ||
stopEarly?: boolean, | ||
/** | ||
* A function which is invoked with a command line parameter not defined in the opts | ||
* configuration object. If the function returns false, the unknown option is not added to argv | ||
*/ | ||
unknown?: ( arg: string ) => boolean, | ||
/** | ||
* When true, populate argv._ with everything before the -- and argv['--'] with everything after the --. | ||
* Note that with -- set, parsing for arguments still stops after the `--`. | ||
*/ | ||
'--'?: boolean | ||
boolean?: string[], | ||
string?: string[], | ||
alias?: Record<string, string[]>, | ||
default?: Partial<Record<string, any>> | ||
}; | ||
@@ -46,12 +15,4 @@ | ||
[arg: string]: any, | ||
/** | ||
* If opts['--'] is true, populated with everything after the -- | ||
*/ | ||
'--'?: string[], | ||
/** | ||
* Contains all the arguments that didn't have an option associated with them | ||
*/ | ||
_: string[] | ||
'_': string[], | ||
'--': string[] | ||
}; | ||
@@ -58,0 +19,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
34940
0
3
15
1236
55
1
- Removedminimist@^1.2.7
- Removedminimist@1.2.8(transitive)