The Interactive Swift Argument Parser Guide - Part I

Published: March 21, 2024
Updated: March 28, 2024

About to build your next command line tool? Remember how powerful the Swift Argument Parser is with this handy and interactive guide!

This first part will cover the basics of the argument parser, how to create a new tool, commands and subcommands, and arguments.

The second part is coming soon, and will explore flags, options, option groups, and exiting.

Creating a New Tool

The Swift Package Manager allows developers to create a new tool from scratch with a single command:

swift package init --type tool

This will create a new package, with an executable target, that has only one dependency: the Swift Argument Parser.

Defining Commands

The core functionality of a command line tool (a CLI) is running commands. If you ran the command above, you will see a struct that conforms to ParsableCommand and declares the run() method.

Here’s how you can define the starting point of your tool, the main command, if you already have an executable that can import the argument parser:

import ArgumentParser

@main
struct MyTool: ParsableCommand {
    mutating func run() throws {
        print("Hello, world, from my new tool!")
    }
}

The argument parser will call this method when your tool runs, as MyTool is marked with the @main attribute. If your struct doesn’t implement the run method, the default implementation will print the command’s help. You can run the my-tool command in the Terminal Simulator below:

Welcome to the Swift Toolkit Terminal Simulator

In the next sections you can find how to make it more flexible and powerful, by adding subcommands and customizing input with arguments, flags, and options.

All sample code from now on will omit the import statement and the @main attributes from now on.

Async Commands

In many cases, you will want your tool to perform asynchronous operations. Fortunately, the team behind the argument parser created AsyncParsableCommand, a version of the protocol above that supports async:

@main
struct MyAsyncTool: AsyncParsableCommand {
    mutating func run() async throws {
        let result = try await someAsyncCall()
        print("Done!, result is: (result)")
    }
}

Configuring a Command

You can configure a command by implementing the configuration property. By default, the command name, usage, discussion, (and more) are inferred automatically by the argument parser. In the code below you can see some of them that stand out. Head over to the documentation to check the other properties and their default values.

struct MyTool: ParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "cli", // defaults to the type name, hyphen-separated and lowercase
        abstract: "A one line description",
        usage: "When nil, generates it based on the name, arguments, flags and options",
        discussion: "A longer discussion about what this command does",
    )
    
    // mutating func run() throws {}
}

You are not required to declare a configuration, unless you want to add subcommands.

Subcommands

Often, building CLI tools requires commands to be grouped according to a subject, or area.

For example, if the tool talks to a web service to perform some operations, it might require the ability to allow users to sign in, to check the current session, or to log out. All these commands belong to the same area: authentication. One desired way of calling them could be the following:

  • my-tool auth login
  • my-tool auth me
  • my-tool auth logout

This is what you would need to enable them using the argument parser, given that each subcommand (Login, Me and Logout) is a type that conforms to ParsableCommand:

struct MyTool: ParsableCommand {
    static let configuration = CommandConfiguration(
        subcommands: [Auth.self]
    )
}

struct Auth: ParsableCommand {
    static let configuration = CommandConfiguration(
        subcommands: [Login.self, Me.self, Logout.self],
        defaultSubcommand: Me.self
    )
}

Notice how you can choose also a default subcommand - this way, running my-tool auth is the same as running my-tool auth me. You can experiment with these examples in the terminal below.

Interactive Terminal: Subcommands You can try the following commands:
  • my-tool
  • my-tool auth login
  • my-tool auth me
  • my-tool auth logout
  • my-tool auth
Welcome to the Swift Toolkit Terminal Simulator

Arguments

An argument is an unnamed parameter that a command accepts:

struct MyTool: ParsableCommand {
    @Argument
    var text: String

    mutating func run() throws {
        print(String(text.reversed()))
    }
}
Interactive Terminal: Arguments 1 You can try the following commands:
  • my-tool
  • my-tool olleh
Welcome to the Swift Toolkit Terminal Simulator

It’s important to note that as arguments are unnamed, they are parsed according to the position they are passed when running the command, and mapped as the order they were declared. Taking the example from the argument parser documentation:

struct Greet: ParsableCommand {
    @Argument var name: String
    @Argument var greeting: String = "Hello"

    mutating func run() {
        print("(greeting) (name)!")
    }
}

When running this command, the first argument will be name, and the second, if present, will be greeting. Notice how arguments can be optional or have default values.

Interactive Terminal: Arguments 2 You can try the following commands:
  • greet
  • greet Taylor
  • greet Taylor Hey
Welcome to the Swift Toolkit Terminal Simulator

Explore Further

Here we wrap up the first part of this guide, and I hope you learned and enjoyed it! The seconds part of this guide is coming soon.

In the meantime, check out the sample code used for the examples in this repo.

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