Skip to content

kazupon/args-tokens

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

args-tokens

Version JSR InstallSize CI

parseArgs tokens compatibility and more high-performance parser

✨ Features

  • βœ… High performance
  • βœ… util.parseArgs token compatibility
  • βœ… ES Modules and modern JavaScript
  • βœ… Type safe
  • βœ… Zero dependencies
  • βœ… Universal runtime

🐱 Motivation

  • Although Node.js parseArgs can return tokens, that the short options are not in the format I expect. Of course, I recoginize the background of this issue.
  • parseArgs gives the command line args parser a useful util, so the resolution of the options values and the parsing of the tokens are tightly coupled. As a result, Performance is sacrificed. Of course, I recoginize that's the trade-off.

⏱️ Benchmark

With mitata:

pnpm bench:mitata

> [email protected] bench:mitata /path/to/projects/args-tokens
> node --expose-gc bench/mitata.js

clk: ~2.87 GHz
cpu: Apple M1 Max
runtime: node 18.19.1 (arm64-darwin)

benchmark                                       avg (min … max) p75 / p99    (min … top 1%)
--------------------------------------------------------------- -------------------------------
util.parseArgs                                     4.16 Β΅s/iter   4.20 Β΅s β–ˆ
                                            (4.09 Β΅s … 4.29 Β΅s)   4.28 Β΅s β–ˆβ–ˆ β–…β–…β–…       β–…
                                        (  1.36 kb …   1.52 kb)   1.37 kb β–ˆβ–ˆβ–β–ˆβ–ˆβ–ˆβ–ˆβ–…β–…β–ˆβ–…β–β–ˆβ–ˆβ–β–β–…β–β–ˆβ–…β–ˆ

args-tokens parse (equivalent to util.parseArgs)   1.65 Β΅s/iter   1.66 Β΅s    β–ˆ
                                            (1.61 Β΅s … 1.80 Β΅s)   1.79 Β΅s β–…β–ƒ β–ˆβ–‚ β–„
                                        (  1.95 kb …   2.66 kb)   1.97 kb β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–†β–ˆβ–„β–ƒβ–ƒβ–…β–ƒβ–β–ƒβ–ƒβ–β–„β–β–β–β–‚

args-tokens parseArgs                            729.56 ns/iter 734.11 ns         β–ˆ
                                        (697.43 ns … 797.08 ns) 774.93 ns        β–‚β–ˆβ–…β–‚
                                        (  2.87 kb …   3.54 kb)   3.11 kb β–‚β–‚β–ƒβ–‡β–†β–…β–†β–ˆβ–ˆβ–ˆβ–ˆβ–ƒβ–ƒβ–„β–‚β–‚β–‚β–‚β–‚β–β–‚

args-tokens resolveArgs                          886.78 ns/iter 887.70 ns       β–ˆ
                                        (853.96 ns … 978.89 ns) 957.24 ns       β–ˆ
                                        (  2.51 kb …   2.87 kb)   2.79 kb β–‚β–ƒβ–ˆβ–ƒβ–„β–…β–ˆβ–„β–ƒβ–‚β–‚β–ƒβ–ƒβ–‚β–‚β–‚β–‚β–‚β–β–β–

                                                 β”Œ                                            ┐
                                  util.parseArgs ─■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 4.16 Β΅s
args-tokens parse (equivalent to util.parseArgs) ─■■■■■■■■■ 1.65 Β΅s
                           args-tokens parseArgs ─ 729.56 ns
                         args-tokens resolveArgs ─■■ 886.78 ns
                                                 β””                                            β”˜

With vitest:

pnpm bench:vitest

> [email protected] bench:vitest /path/to/projects/args-tokens
> vitest bench --run

Benchmarking is an experimental feature.
Breaking changes might not follow SemVer, please pin Vitest's version when using it.

 RUN  v3.0.5 /path/to/projects/args-tokens


 βœ“ bench/vitest.bench.js > parse and resolve 1350ms
     name                       hz     min     max    mean     p75     p99    p995    p999     rme  samples
   Β· util.parseArgs     221,285.36  0.0041  0.2700  0.0045  0.0044  0.0054  0.0063  0.0629  Β±0.38%   110643
   Β· args-tokens parse  527,127.11  0.0017  0.2153  0.0019  0.0019  0.0023  0.0027  0.0055  Β±0.38%   263564   fastest

 βœ“ bench/vitest.bench.js > parseArgs 1434ms
     name                   hz     min     max    mean     p75     p99    p995    p999     rme  samples
   Β· node:util      235,217.05  0.0039  0.2665  0.0043  0.0042  0.0048  0.0058  0.0139  Β±0.43%   117609
   Β· args-tokens  1,307,135.24  0.0006  0.1737  0.0008  0.0008  0.0009  0.0010  0.0016  Β±0.43%   653568   fastest

 BENCH  Summary

  args-tokens parse - bench/vitest.bench.js > parse and resolve
    2.38x faster than util.parseArgs

  args-tokens - bench/vitest.bench.js > parseArgs
    5.56x faster than node:util

❓ What's different about parseArgs tokens?

The token output for the short option -x=v is different:

import { parseArgs as parseArgsNode } from 'node:util'
import { parseArgs } from 'args-tokens'

// Node.js parseArgs tokens
const { tokens: tokensNode } = parseArgsNode({
  allowPositionals: true,
  strict: false,
  args: ['-a=1'],
  tokens: true
})
console.log(tokensNode)

//   ({
//     kind: 'option',
//     name: 'a',
//     rawName: '-a',
//     index: 0,
//     value: undefined,
//     inlineValue: undefined
//   },
//   {
//     kind: 'option',
//     name: '=',
//     rawName: '-=',
//     index: 0,
//     value: undefined,
//     inlineValue: undefined
//   },
//   {
//     kind: 'option',
//     name: '1',
//     rawName: '-1',
//     index: 0,
//     value: undefined,
//     inlineValue: undefined
//   })
// ]

// args-tokens parseArgs tokens
const tokens = parseArgs(['-a=1'])
console.log(tokens)

// [
//   {
//     kind: 'option',
//     name: 'a',
//     rawName: '-a',
//     index: 0,
//     value: undefined,
//     inlineValue: undefined
//   },
//   { kind: 'option', index: 0, value: '1', inlineValue: true }
// ]

πŸ’Ώ Installation

# npm
npm install --save args-tokens

## yarn
yarn add args-tokens

## pnpm
pnpm add args-tokens

πŸ¦• Deno

deno add jsr:@kazupon/args-tokens

πŸ₯Ÿ Bun

bun add args-tokens

πŸš€ Usage

Parse args to tokens

parseArgs will transform arguments into tokens. This function is useful if you want to analyze arguments yourself based on the tokens. It's faster than parseArgs of node:util because it only focuses on token transformation.

import { parseArgs } from 'args-tokens' // for Node.js and Bun
// import { parseArgs } from 'jsr:@kazupon/args-tokens' // for Deno

const tokens = parseArgs(['--foo', 'bar', '-x', '--bar=baz'])
// do something with using tokens
// ...
console.log('tokens:', tokens)

Resolve args values with tokens and arg option schema

resolveArgs is a useful function when you want to resolve values from the tokens obtained by parseArgs.

import { parseArgs, resolveArgs } from 'args-tokens' // for Node.js and Bun
// import { parseArgs, resolveArgs } from 'jsr:@kazupon/args-tokens' // for Deno

const args = ['dev', '-p=9131', '--host=example.com', '--mode=production']
const tokens = parseArgs(args)
const { values, positionals } = resolveArgs(
  {
    help: {
      type: 'boolean',
      short: 'h'
    },
    version: {
      type: 'boolean',
      short: 'v'
    },
    port: {
      type: 'number',
      short: 'p',
      default: 8080
    },
    mode: {
      type: 'string',
      short: 'm'
    },
    host: {
      type: 'string',
      short: 'o',
      required: true
    }
  },
  tokens
)
console.log('values:', values)
console.log('positionals:', positionals)

Convenient argument parsing

Using the parse you can transform the arguments into tokens and resolve the argument values once:

import { parse } from 'args-tokens' // for Node.js and Bun
// import { parse } from 'jsr:@kazupon/args-tokens' // for Deno

const args = ['dev', '-p=9131', '--host=example.com', '--mode=production']
const { values, positionals } = parse(args, {
  options: {
    help: {
      type: 'boolean',
      short: 'h'
    },
    version: {
      type: 'boolean',
      short: 'v'
    },
    port: {
      type: 'number',
      short: 'p',
      default: 8080
    },
    mode: {
      type: 'string',
      short: 'm'
    },
    host: {
      type: 'string',
      short: 'o',
      required: true
    }
  }
})
console.log('values:', values)
console.log('positionals:', positionals)

Node.js parseArgs tokens compatible

If you want to use the same short options tokens as returned Node.js parseArgs, you can use allowCompatible parse option on parseArgs:

import { parseArgs as parseArgsNode } from 'node:util'
import { parseArgs } from 'args-tokens'
import { deepStrictEqual } from 'node:assert'

const args = ['-a=1', '2']

// Node.js parseArgs tokens
const { tokens: tokensNode } = parseArgsNode({
  allowPositionals: true,
  strict: false,
  args,
  tokens: true
})

// args-tokens parseArgs tokens
const tokens = parseArgs(['-a=1'], { allowCompatible: true }) // add `allowCompatible` option

// validate
deepStrictEqual(tokensNode, tokens)

ArgSchema Reference

The ArgSchema interface defines the configuration for command-line arguments. This schema is similar to Node.js util.parseArgs but with extended features.

Schema Properties

type (required)

Type of the argument value:

  • 'string': Text value (default if not specified)
  • 'boolean': True/false flag (can be negatable with --no- prefix)
  • 'number': Numeric value (parsed as integer or float)
  • 'enum': One of predefined string values (requires choices property)
  • 'positional': Non-option argument by position
  • 'custom': Custom parsing with user-defined parse function
{
  name: { type: 'string' },        // --name value
  verbose: { type: 'boolean' },     // --verbose or --no-verbose
  port: { type: 'number' },         // --port 3000
  level: { type: 'enum', choices: ['debug', 'info'] },
  file: { type: 'positional' },     // first positional arg
  config: { type: 'custom', parse: JSON.parse }
}

short (optional)

Single character alias for the long option name. Allows users to use -x instead of --extended-option.

{
  verbose: {
    type: 'boolean',
    short: 'v'  // Enables both --verbose and -v
  },
  port: {
    type: 'number',
    short: 'p'  // Enables both --port 3000 and -p 3000
  }
}

description (optional)

Human-readable description used for help text generation and documentation.

{
  config: {
    type: 'string',
    description: 'Path to configuration file'
  },
  timeout: {
    type: 'number',
    description: 'Request timeout in milliseconds'
  }
}

required (optional)

Marks the argument as required. When true, the argument must be provided or an ArgResolveError will be thrown.

{
  input: {
    type: 'string',
    required: true,  // Must be provided: --input file.txt
    description: 'Input file path'
  },
  source: {
    type: 'positional',
    required: true   // First positional argument must exist
  }
}

multiple (optional)

Allows the argument to accept multiple values. The resolved value becomes an array.

  • For options: can be specified multiple times (--tag foo --tag bar)
  • For positional: collects remaining positional arguments
{
  tags: {
    type: 'string',
    multiple: true,  // --tags foo --tags bar β†’ ['foo', 'bar']
    description: 'Tags to apply'
  },
  files: {
    type: 'positional',
    multiple: true   // Collects all remaining positional args
  }
}

negatable (optional)

Enables negation for boolean arguments using --no- prefix. Only applicable to type: 'boolean'.

{
  color: {
    type: 'boolean',
    negatable: true,
    default: true,
    description: 'Enable colorized output'
  }
  // Usage: --color (true), --no-color (false)
}

choices (optional)

Array of allowed string values for enum-type arguments. Required when type: 'enum'.

{
  logLevel: {
    type: 'enum',
    choices: ['debug', 'info', 'warn', 'error'],
    default: 'info',
    description: 'Logging verbosity level'
  },
  format: {
    type: 'enum',
    choices: ['json', 'yaml', 'toml'],
    description: 'Output format'
  }
}

default (optional)

Default value used when the argument is not provided. The type must match the argument's type property.

{
  host: {
    type: 'string',
    default: 'localhost'  // string default
  },
  verbose: {
    type: 'boolean',
    default: false        // boolean default
  },
  port: {
    type: 'number',
    default: 8080         // number default
  },
  level: {
    type: 'enum',
    choices: ['low', 'high'],
    default: 'low'        // must be in choices
  }
}

toKebab (optional)

Converts the argument name from camelCase to kebab-case for CLI usage. A property like maxCount becomes available as --max-count.

{
  maxRetries: {
    type: 'number',
    toKebab: true,        // Accessible as --max-retries
    description: 'Maximum retry attempts'
  },
  enableLogging: {
    type: 'boolean',
    toKebab: true         // Accessible as --enable-logging
  }
}

parse (optional)

Custom parsing function for type: 'custom' arguments. Required when type: 'custom'. Should throw an Error if parsing fails.

{
  config: {
    type: 'custom',
    parse: (value) => {
      try {
        return JSON.parse(value)  // Parse JSON config
      } catch {
        throw new Error('Invalid JSON configuration')
      }
    },
    description: 'JSON configuration object'
  },
  date: {
    type: 'custom',
    parse: (value) => {
      const date = new Date(value)
      if (isNaN(date.getTime())) {
        throw new Error('Invalid date format')
      }
      return date
    }
  }
}

πŸ™Œ Contributing guidelines

If you are interested in contributing to args-tokens, I highly recommend checking out the contributing guidelines here. You'll find all the relevant information such as how to make a PR, how to setup development) etc., there.

πŸ’– Credits

This project is inspired by:

🀝 Sponsors

The development of Gunish is supported by my OSS sponsors!

sponsor

©️ License

MIT

About

parseArgs tokens compatibility and more high-performance parser

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

Packages

No packages published

Contributors 6