The Interactive Swift Argument Parser Guide - Part II: Flags
After the first post explaining the basics of the Swift Argument Parser, the second part is here! The last subject it explored were arguments. In this part youβll learn about flags, name specifications, flag inversions, and enumerable flags.
Flags
A flag is a used to define or modify the behavior of a command, and is passed with a single or double dash. Common examples can be -d (or --debug) and --verbose when wanting the command to output more information, or the famous -f (or --force) present in many Git operations. You can think of a flag as a switch, turning a behavior on or off.
To add a flag to your command, use the @Flag property wrapper:
struct MyTool: ParsableCommand {
@Flag var verbose = false
func run() {
if verbose {
print("Starting Flags example at (Date())")
}
let result = someInt()
print("Random int:", result)
if verbose {
print("Flags example finished at (Date())")
}
}
} A Simple Flag: Interactive Example
You can try the following commands:
my-toolmy-tool --verbose
Name Specification
By default, Boolean flags are initialized with NameSpecification.long, which means only the long form of the flag is accepted. When using names that have more than one word, a camel case name is converted to a hyphen separated label. For example, generateOnly becomes --generate-only.
To customize the property input, you can pass a NameSpecification in the name argument. Using the verbose property from the example above:
@Flagwill only accept--verbose@Flag(name: .short)will only accept-v@Flag(name: .shortAndLong)will accept both--verboseand-v
To allow even more customization, check out the documentation for the .customShort(...) and .customLong(...) methods.
Flag Inversion
With Boolean flags, you might want to allow a pair of inverted labels to control the value of a property. There are two FlagInversion options available: enable/disable, and no.
In the example below, the command will accept --enable-cache and --disable-cache. As no value is set to the cache property, one of the flags is required.
@Flag(inversion: .prefixedEnableDisable)
var cache = false A simpler option is the .prefixedNo inversion. The example below set the open property to true, unless the command receives the --no-open flag.
@Flag(inversion: .prefixedNo)
var open = true This flag initializer with a FlagInversion parameter also accepts a NameSpecification, allowing to mix the inversion with a custom name:
@Flag(name: .customLong("encrypt"), inversion: .prefixedNo)
var shouldUseEncryption = true In the example above, shouldUseEncryption defaults to true, and accepts both --encrypt and --no-encrypt.
Flag Inversion: Interactive Example
You can try the following commands:
flags --enable-cacheorflags --disable-cacheflags --no-encryptorflags --encryptflags --no-openorflags --open
Customizing with Enumerable Flags
Enumerable flags allow your tool to have a flag with one or more predefined values. To show a practical, example, imagine you are building a tool that helps uploading your iOS application to different places for distribution:
enum AppDistribution: String, EnumerableFlag {
case appstore
case testflight
case enterprise
case altstore
} Then, declare a property in your command that has the AppDistribution type:
struct Deploy: AsyncParsableCommand {
@Flag var distribution: AppDistribution
func run() {
switch distribution {
print("Selected distribution: (distribution)")
}
}
} In the example above, distribution must have a single value of the enum. Alternatively, the property wrapper can also accept more than one value if its type is set to an array of EnumerableFlag, in this case [AppDistribution] instead of a single value:
struct Deploy: AsyncParsableCommand {
@Flag var distributions: [AppDistribution]
func run() {
print("Selected distribution types are: (distributions)")
}
} Multiple Enumerable Flags: Interactive Example
You can try the following commands:
deploydeploy --appstoredeploy --testflight --altstore
Enumerable Flags Custom Naming
You can customize the name specification for enumerable flags as well, although itβs not as simple as regular flags. To do so, implement the name(for value: Self) -> NameSpecification method from the EnumerableFlag protocol:
extension AppDistribution {
static func name(for value: Self) -> NameSpecification {
switch value {
case .altstore:
return [.customShort("l"), .long]
default:
return .shortAndLong
}
}
} The code above will add the default .shortAndLong name specification to all enum cases, except for the .altstore case. Because both .appstore and .altstore begin with an a, one of them must have a different short name. For that reason, .customShort("l") is returned (where l is the second character in the word alt), and the user could pass deploy -a -l for enabling both.
Explore Further
The second part of this guide ends here, and feel free to reach us at X or Mastodon if you have any comments or questions.
The third and final part, covering options and exiting can be read here.
You can find the sample code used for the examples in this repo.
Finally, you might also read the docs for the Swift Argument Parser on the following:
See you at the next post. Have a good one!
