69
This post introduces Optique, a new library created to address the pervasive problem of repetitive and often messy validation code in CLI tools. The author was motivated by the observation that nearly every CLI tool reinvents the wheel with similar validation patterns for dependent options, mutually exclusive options, and environment-specific requirements. Optique leverages parser combinators and TypeScript's type inference to ensure that CLI arguments are parsed directly into valid configurations, eliminating the need for manual validation. By describing the desired CLI configuration with Optique, TypeScript automatically infers the types and constraints, catching potential bugs at compile time. The author shares their experience of deleting large chunks of validation code and simplifying refactoring tasks. Optique aims to provide a more robust and maintainable approach to CLI argument parsing, potentially saving developers from writing the same validation logic repeatedly.
Counterpoint: Yes, parse don’t validate, but CLIs should not be dealing with dependency management.
I love Python’s
argparse
because:FileType
as a target)curl <URL>
notcurl --url <URL>
.server --serve --port 8080
andserver --reload
with rules for mix-and-matching those, doserver serve --port 8080
andserver reload
with two separate subparsers.--xml --json
, do-f [xml|json]
.or(
pattern of yours IMO should always be replaced by a subparser (which can use inheritance!). As a user the options’ data model should be immediately intuitive to me as I look at the--help
and having mutually exclusive flags forces the user to do the extra work of dependency management. Don’t doserver --env prod --auth abc --ssl
, doserver serve prod --auth abc --ssl
whereprod
is its own subparser inheriting fromAbstractServeParser
or whatever.Thinking of CLI flags as a direct mapping to runtime variables is the fundamental mistake here I think. A CLI should be a mapping to the set(s) of behavior(s) of your application. A good CLI may have mandatory positional arguments but has 0 mandatory flags, 0 mutually exclusive flags, and if it implements multiple separate behaviors should be a tree of subparsers. Any mandatory or mutually exclusive flags should be an immediate warning that you’re not being very UNIX-y in your CLI design.