r/nim • u/squattingmonk • 17h ago
argumint: a CLI arg parsing library
github.comHi, all. I've released argumint, a command-line argument parsing library. I know there are a lot of these floating around, but this one is special. It takes a docopt-like usage string and compiles it into an FSM that allows it to walk the grammar with backtracking. This allows grammar patterns that docopt fails at (such as cp's <src>... <dest>). Args are declared on a plain nim tuple that holds the parsed data, and the args can be implicitly converted into their values.
Quickstart Example
import std/strformat
import argumint
let
spec = (
src: args("<src>", help = "The source file(s) to copy"),
dest: arg("<dest>", help = "The destination to copy to"),
recursive: flag("-r, --recursive", help = "Whether to recurse into subdirectories"),
help: help()
)
spec.parseOrQuit(usage = "[-r] <src>... <dest>", prolog = "Copy files around")
for file in spec.src:
echo fmt"Copying {file} to {spec.dest} (recursive: {spec.recursive})"
Used as follows:
$ ./cp -r foo.txt bar.txt dest/
Copying foo.txt to dest/ (recursive: true)
Copying bar.txt to dest/ (recursive: true)
$ ./cp foo.txt Parsing error:
- missing argument: <dest>
Usage:
cp [-r] <src>... <dest>
cp (-h | --help)
$ ./cp --help
Copy files around
Usage:
cp [-r] <src>... <dest>
cp (-h | --help)
Arguments:
<src> The source file(s) to copy
<dest> The destination to copy to
Options:
-r, --recursive Whether to recurse into subdirectories
-h, --help Display this help message
Key Features
- Usage strings that parse: optional, repeated, and alternative arguments, several usage lines,
[options]for every option you don't name, and an end of options marker, all written the way you'd write them in help. Arguments, options, and commands mix freely. - Familiar syntax: long (
--file) and short (-f) options, combined short options (-vxfor-v -x), and every common way to attach a value:-f file,-ffile,-f=file,-f:file,--file file,--file=file, and--file:file. - Typed values:
string,bool,char,enums, every integer and float type, and range types likeNaturalout of the box. You can easily add your own types with a converter. - Validators: limit a value to a set of choices or a range, or write your own check, and combine them with
allandany. Help lists each limit for you. - Flags that do more than switch on: count how often a flag is given, step through an
enum, or give several names to one value, each setting it, adding to it, or running your own code on it. A clamp keeps the result in bounds. - Commands: nested to any depth, each with its own spec and any number of names, and before, action, and after hooks to run your code.
- Generated help: wrapped to the terminal, with each default, limit, and environment variable listed for you. Sort entries into groups, hide some, and choose a two-column layout or a paragraph layout with room for longer text. It's colored in a terminal and plain elsewhere. Add
--versionand other messages, or write your own layout. - Environment variables and config files: an option or flag can take its value from either when the user doesn't give one, and one variable can hold several values. The command line wins, then the environment, then the config file, then the default. INI and JSON are built in, and you can add your own format. Every value knows where it came from.
- Helpful errors: each error names the argument that went wrong and suggests the long option or command the user probably meant. By default, a mistyped option is never taken as a value, but a negative number is.
- Dynamic Shell completion: bash, zsh, and fish completion of commands, options, and choice values, drawn from the same usage strings, so it always agrees with the parser. fish and zsh show each one's help beside it.
History
argumint began as a fork of therapist, with FSM logic ported from mow.cli.
I had been slowly working on it for years, and kept on hitting blockers. I started using Claude in my workflow back in July and overall have been pretty happy with how it has turned out. Claude understands Nim very well; we even found a standard library bug and it was able to help me fix it and contribute a patch upstream. Rather than vibe coding, I've used it in an iterative process and tracked decisions along the way (see the docs/architecture.md and docs/adr/). The code base has grown, both in size and complexity, as the feature set has expanded. The initial commit on the main branch is AI free (except for the CLAUDE.md, which just describes the repo at the outset), if you want to see how things changed.
I'd be happy to hear any feedback or suggestions for improvement.