Swift Argument Parser

repository·main·Indexed 25 days ago

https://github.com/apple/swift-argument-parser

A library for creating command-line tools in Swift. It enables developers to define command-line interfaces using property wrappers like @Flag, @Option, and @Argument, and provides automatic argument parsing, help generation, and error reporting. It supports synchronous commands via ParsableCommand, asynchronous logic via AsyncParsableCommand, and complex command hierarchies through CommandConfiguration.

Tokens
13.2K
Snippets
37
Records
75
Agent score
84%

What's inside swift-argument-parser

  1. Install Zsh completions

    main

    To install completions for Z shell, follow the method corresponding to your setup:

    With oh-my-zsh

    Copy the generated script to your .oh-my-zsh/completions directory. The filename must follow the format _<command_name> (e.g., _example).

    $ example --generate-completion-script zsh > ~/.oh-my-zsh/completions/_example

    Without oh-my-zsh

    1. Add a completion directory to your fpath and enable autoloading in ~/.zshrc:
    fpath=(~/.zsh/completion $fpath)
    autoload -U compinit
    compinit
    1. Create the directory ~/.zsh/completion.
    2. Copy the generated script into that directory.
  2. Customize command help text

    main

    You can configure a command's help display by providing an abstract, discussion, or custom usage string within the CommandConfiguration.

    • abstract: A short summary of the command.
    • discussion: Detailed information about the command's behavior.
    • usage: A custom string defining how the command should be invoked.
    struct Repeat: ParsableCommand {
        static let configuration = CommandConfiguration(
            abstract: "Repeats your input phrase.",
            usage: """
                repeat <phrase>
                repeat --count <count> <phrase>
                """,
            discussion: """
                Prints to stdout forever, or until you halt the program.
                """
        )
    
        @Argument(help: "The phrase to repeat.")
        var phrase: String
    
        @Option(help: "How many times to repeat.")
        var count: Int? = nil
    
        mutating func run() throws {
            for _ in 0..<(count ?? 2) {
                print(phrase) 
            }
        }
    }
  3. Define commands and subcommands in a tree structure

    main

    You can build complex command-line tools by nesting ParsableCommand types. A root command can specify its subcommands and an optional defaultSubcommand using the CommandConfiguration property.

    To create a hierarchy:

    1. Define a root ParsableCommand.
    2. Use CommandConfiguration(subcommands: [...]) to list child commands.
    3. For nested subcommands, define a child command that also provides a CommandConfiguration with its own subcommands list.

    You can also use commandName in CommandConfiguration to override the default name derived from the type name, or aliases to provide alternative names for invoking a command.

    struct Math: ParsableCommand {
        static let configuration = CommandConfiguration(
            abstract: "A utility for performing maths.",
            subcommands: [Add.self, Multiply.self, Statistics.self],
            defaultSubcommand: Add.self
        )
    }
    
    // A nested subcommand structure
    extension Math {
        struct Statistics: ParsableCommand {
            static let configuration = CommandConfiguration(
                commandName: "stats",
                abstract: "Calculate descriptive statistics.",
                subcommands: [Average.self, StandardDeviation.self]
            )
        }
    }
  4. Install Bash completions

    main

    To install completions for Bash, follow the method corresponding to your setup:

    With bash-completion

    Copy the generated script to the /usr/local/etc/bash_completion.d directory.

    Without bash-completion

    1. Copy the generated script to a directory of your choice (e.g., ~/.bash_completions/).
    2. Add a source command for that file to your ~/.bash_profile or ~/.bashrc:
    source ~/.bash_completions/example.bash
  5. Add ArgumentParser as a dependency

    main

    To use ArgumentParser in your Swift project, add swift-argument-parser as a package dependency in your Package.swift file and include the ArgumentParser product in your executable target's dependencies.

    // swift-tools-version:6.0
    import PackageDescription
    
    let package = Package(
        name: "Count",
        dependencies: [
            .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.7.0"),
        ],
        targets: [
            .executableTarget(
                name: "count",
                dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")]),
        ]
    )
  6. Set the entry point of your command-line tool

    main

    To tell the Swift compiler which command is the entry point of your executable, use the @main attribute on your root ParsableCommand struct.

    Important: If your project contains a main.swift file, you must either remove it or rename it to match your command's filename (e.g., Math.swift) to avoid conflicts, as the Swift compiler allows only one entry point.

    @main
    struct Math: ParsableCommand {
        static let configuration = CommandConfiguration(
            subcommands: [Add.self]
        )
    }
  7. Parse arguments manually using ParsableArguments

    main

    For simple scripts where you don't want to use @main, you can define a type conforming to ParsableArguments and parse command-line inputs manually.

    Use parseOrExit(_:) to return an initialized instance or exit the process with an error message and code if parsing fails. Alternatively, use the throwing parse(_:) method if you want to handle errors manually.

    To exit the script with a specific error and include usage information, use exit(withError:) passing a ValidationError.

    struct SelectOptions: ParsableArguments {
        @Option var count: Int = 1
        @Argument var elements: [String] = []
    }
    
    // Parse and exit on failure
    let options = SelectOptions.parseOrExit()
    
    // Or parse and catch errors manually
    do {
        let options = try SelectOptions.parse(CommandLine.arguments)
    } catch {
        // Handle error
    }
    
    // Exit with a validation error and usage info
    guard options.elements.count >= options.count else {
        let error = ValidationError("Please specify a 'count' less than the number of elements.")
        SelectOptions.exit(withError: error)
    }
  8. Provide basic help text for arguments, options, and flags

    main

    You can add help descriptions to @Argument, @Option, or @Flag properties by passing a string literal to the help parameter. These strings appear in the automatically-generated help screen (triggered by -h or --help).

    struct Example: ParsableCommand {
        @Flag(help: "Display extra information while processing.")
        var verbose = false
    
        @Option(help: "The number of extra lines to show.")
        var extraLines = 0
    
        @Argument(help: "The input file.")
        var inputFile: String?
    }
  9. Use async/await in commands with AsyncParsableCommand

    main

    To use asynchronous code within your command-line tool's run() method, follow these steps:

    1. Declare conformance to AsyncParsableCommand for your root command (even if the root command itself doesn't use async code).
    2. Apply the @main attribute to the root command. (Note: If you have a main.swift file, you must rename it to the name of the command to avoid conflicts with @main).
    3. For any specific command requiring asynchronous logic, declare conformance to AsyncParsableCommand and mark its run() method as async.

    Subcommands that do not require asynchronous code do not need any changes and can continue using standard ParsableCommand conformance.

    import Foundation
    import ArgumentParser
    
    @main
    struct CountLines: AsyncParsableCommand {
        @Argument(transform: URL.init(fileURLWithPath:))
        var inputFile: URL
    
        mutating func run() async throws {
            let fileHandle = try FileHandle(forReadingFrom: inputFile)
            let lineCount = try await fileHandle.bytes.lines.reduce(into: 0) 
                { count, _ in count += 1 }
            print(lineCount)
        }
    }
  10. Control argument visibility

    main

    You can hide arguments from the standard help screen using ArgumentHelp or @OptionGroup visibility settings:

    • .hidden: The argument is only visible in the extended help screen (triggered by --help-hidden).
    • .private: The argument is hidden from both the standard and extended help screens.

    Example of setting visibility on individual properties:

    @Flag(help: ArgumentHelp("Show extra info.", visibility: .hidden))
    var verbose: Bool = false
    
    @Flag(help: ArgumentHelp("Use the legacy format.", visibility: .private))
    var useLegacyFormat: Bool = false