We’re delighted to announce
ArgumentParser
, a new open-source library that makes it straightforward — even enjoyable! — to parse command-line arguments in Swift.Building a Command-Line Tool
To show you what using the
ArgumentParser
library is like, we’re going to create a utility that generates random numbers. Like many command-line tools, this one will gradually accrue features, and we’ll see how ArgumentParser
helps both authors and users keep everything straight.Here’s the desired interface for our
random
utility:> random 20 17 > random 100 89 > random Error: Missing expected argument '<high-value>' Usage: random <high-value>
Let’s define a
Random
type that expects a single integer argument — highValue
— and then prints a random number between 1 and highValue
:import ArgumentParser struct Random: ParsableCommand { @Argument() var highValue: Int func run() { print(Int.random(in: 1...highValue)) } } Random.main()
That’s it!
- The
@Argument
property wrapper indicates that the property should be drawn from a command-line argument.
- Calling
main()
on ourParsableCommand
type kicks off parsing and, if parsing is successful, runs the command-line tool.
- The library synthesizes help and error messages that guide users toward successful usage, using all the information we’ve given it: the
highValue
property’s name and type, and the name of our command type.
Because
highValue
is defined as an Int
, only valid inputs are recognized, with no manual parsing or casting necessary on your part:> random ZZZ Error: The value 'ZZZ' is invalid for '<high-value>' Usage: random <high-value>
Customizing Validation and Help
A good command-line tool documents and validates its inputs. Let’s add some descriptive text and implement the
validate()
method, so that we can catch the case where a user gives a too-low value for highValue
:struct Random: ParsableCommand { static let configuration = CommandConfiguration( abstract: "Chooses a random number between 1 and your input.") @Argument(help: "The highest value to pick.") var highValue: Int func validate() throws { guard highValue >= 1 else { throw ValidationError("'<high-value>' must be at least 1.") } } func run() { print(Int.random(in: 1...highValue)) } }
Our tool is now smarter about the values it can accept and includes rich documentation in the auto-generated help screen:
> random 0 Error: '<high-value>' must be at least 1. Usage: random <high-value> > random --help OVERVIEW: Chooses a random number between 1 and your input. USAGE: random <high-value> ARGUMENTS: <high-value> The highest value to pick. OPTIONS: -h, --help Show help information.
Modern command-line tools, such as Git and the Swift Package Manager, use subcommands to group related tools in a command tree. Using
ArgumentParser
, you build interfaces like this by declaring each subcommand as a separate type.Let’s implement a subcommand by moving our existing logic into a nested
Number
type:…and listing the subcommand in the root command’s configuration:
ArgumentParser
takes care of the rest!Adding a Subcommand
To complete our tool, let’s add a second subcommand to
pick
an element from a list that you supply as arguments:The
Pick
command accepts a count
option and expects an array of elements
to choose from:The
@Option
property wrapper indicates that a property should be read from command-line arguments, using the name of the property to make a key-value pair.The final version of our
random
utility is less than 50 lines of code! It automatically detects which subcommand the user has given, parses that subcommand’s arguments, and calls its run()
method. If you leave out a subcommand, the library calls the Random
command’s default implementation of run()
, which simply prints the command’s help screen:Design Goals
When designing
ArgumentParser
, we had the following goals in mind:- Encouraging best command-line interface practices and supporting progressive understanding of the library.
- Enabling projects ranging from simple, one-off scripts to complex tools, including nested subcommands and rich help information.
- Eliminating the boilerplate typically associated with parsing command-line arguments, reducing repetition and the chance for errors.
These design goals led us to a design that uses Swift’s type system, as well as features like property wrappers and reflection, to implicitly build an interface from your custom type declarations.
ArgumentParser
is the result.Why Now?
The Swift project includes several command-line tools written in Swift — some shipped as part of the Swift toolchain, and some used for building and testing. In particular, SwiftPM includes an argument parser, in its
TSCUtility
library, that has grown to support SwiftPM’s needs, but was never intended for wider adoption.We’ll be working to adopt
ArgumentParser
across the Swift project, and invite you to experiment with the library, give feedback, and get involved in its ongoing development!Learn More
In addition to what we’ve seen so far,
ArgumentParser
supports --flag
arguments for Boolean or enumerable properties, multiple names for options and flags, encapsulating groups of arguments, and much more. You can learn more by visiting the repository’s README, browsing the guides in the documentation folder, and reading the in-source symbol documentation.You can also explore the Swift project’s in-flight adoption of
ArgumentParser
:swift-format
uses some advanced features, like custom option values and hidden flags.
What’s Next?
In the near term, there are a couple additional features that need to be added so that SwiftPM can adopt
ArgumentParser
without regressing in functionality — you can find those features tracked as issues in the repository. Once SwiftPM adoption is complete, we’d like to adopt the library in the Swift rewrite of the Swift compiler driver, as well.Along with those integrations, we’d like to work with the community toward defining the requirements of a 1.0 release. What other features are critical for widespread adoption in a variety of environments, such as on the server, Windows, and other platforms? What other customization points are most important? The more people using
ArgumentParser
, the better we’ll be able to answer these questions together.Get Involved
Your experience, feedback, and contributions are greatly encouraged!
- Discuss the library and get help in the ArgumentParser forum
- Open an issue with problems you find or ideas you have for improvements,
- And as always, pull requests are welcome!