The Interactive Swift Argument Parser Guide - Part II: Flags

Published: April 23, 2024
Updated: May 2, 2024

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-tool
  • my-tool --verbose
Welcome to the Swift Toolkit Terminal Simulator

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:

  • @Flag will only accept --verbose
  • @Flag(name: .short) will only accept -v
  • @Flag(name: .shortAndLong) will accept both --verbose and -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-cache or flags --disable-cache
  • flags --no-encrypt or flags --encrypt
  • flags --no-open or flags --open
Welcome to the Swift Toolkit Terminal Simulator

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:

  • deploy
  • deploy --appstore
  • deploy --testflight --altstore
Welcome to the Swift Toolkit Terminal Simulator

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](https://x.com/SwiftToolkit) or [Mastodon](https://mastodon.social/@SwiftToolkit) 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:

Pure human brain content, no Generative AI added
Swift, Xcode, the Swift Package Manager, iOS, macOS, watchOS and Mac are trademarks of Apple Inc., registered in the U.S. and other countries.

© Swift Toolkit, 2024