1. How to use @Option with custom ExpressibleByArgument types (enums from strings)
Protocol Requirements:
The ExpressibleByArgument protocol requires implementing:
init?(argument: String)- initializes from a string argumentvar defaultValueDescription: String- description for help textstatic var allValueStrings: [String]- list of all possible valuesstatic var allValueDescriptions: [String: String]- descriptions for each value
For RawRepresentable Enums (easiest approach):
The library provides a default implementation for RawRepresentable types like string-backed enums. You only need to declare conformance:
enum ReleaseMode: String, ExpressibleByArgument {
case debug, release
}
struct Example: ParsableCommand {
@Option var mode: ReleaseMode
}Color Example from the codebase: https://github.com/apple/swift-argument-parser/blob/main/Examples/color/Color.swift
enum ColorOptions: String, CaseIterable, ExpressibleByArgument {
case red
case blue
case yellow
public var defaultValueDescription: String {
switch self {
case .red: return "A red color."
case .blue: return "A blue color."
case .yellow: return "A yellow color."
}
}
public var description: String {
switch self {
case .red: return "A red color."
case .blue: return "A blue color."
case .yellow: return "A yellow color."
}
}
}
struct Color: ParsableCommand {
@Option(help: "Your favorite color.")
var fav: ColorOptions
}For Custom Types:
Implement ExpressibleByArgument directly:
struct Path: ExpressibleByArgument {
var pathString: String
init?(argument: String) {
self.pathString = argument
}
}2. How to make optional options (parameters that may or may not be provided)
Using Optional types:
Options with Optional types implicitly default to nil:
@Option var secondColor: ColorOptions? = nil // Optional
@Option var count: Int? // Also optional (nil default)From the Repeat example:
@Option(help: "How many times to repeat 'phrase'.")
var count: Int? = nilIn Color example:
@Option(
help: .init("Your second favorite color.", discussion: "This is optional."))
var second: ColorOptions?3. How to use @Argument for positional arguments
Basic required argument:
@Argument var phrase: String // RequiredOptional argument with default:
@Argument var greeting: String = "Hello" // Optional with default
@Argument var inputFile: URL? // OptionalArray of arguments:
@Argument var files: [String] = []From CountLines example:
@Argument(
help: "A file to count lines in. If omitted, counts the lines of stdin.",
completion: .file(),
transform: URL.init(fileURLWithPath:))
var inputFile: URL? = nilFrom Math example:
@Argument(help: "A group of integers to operate on.")
var values: [Int] = []4. How to set up command configuration (name, abstract, discussion)
Using CommandConfiguration:
https://github.com/apple/swift-argument-parser/blob/main/Sources/ArgumentParser/Documentation.docc/Extensions/CommandConfiguration.md
static let configuration = CommandConfiguration(
commandName: "stats", // Optional: override command name
abstract: "Calculate statistics.",
discussion: "More detailed explanation here",
usage: "Custom usage string",
version: "1.0.0", // Adds --version support
subcommands: [Average.self, StandardDeviation.self],
defaultSubcommand: Average.self,
helpNames: [.long, .short],
aliases: ["st", "stats-cmd"]
)From Math example:
struct Math: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "A utility for performing maths.",
version: "1.0.0",
subcommands: [Add.self, Multiply.self, Statistics.self],
defaultSubcommand: Add.self)
}
struct Add: ParsableCommand {
static let configuration =
CommandConfiguration(abstract: "Print the sum of the values.")
}
struct Multiply: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Print the product of the values.",
aliases: ["mul"])
}5. How to use AsyncParsableCommand for async operations
Steps to implement:
- Declare conformance to
AsyncParsableCommand(instead ofParsableCommand) - Apply the
@mainattribute to the root command - Mark the
run()method asasync
From CountLines example:
@main
@available(macOS 12, iOS 15, visionOS 1, tvOS 15, watchOS 8, *)
struct CountLines: AsyncParsableCommand {
@Argument(
help: "A file to count lines in. If omitted, counts the lines of stdin.",
completion: .file(),
transform: URL.init(fileURLWithPath:))
var inputFile: URL? = nil
@Option(help: "Only count lines with this prefix.")
var prefix: String? = nil
@Flag(help: "Include extra information in the output.")
var verbose = false
var fileHandle: FileHandle {
get throws {
guard let inputFile else {
return .standardInput
}
return try FileHandle(forReadingFrom: inputFile)
}
}
mutating func run() async throws {
var lineCount = 0
for try await line in try fileHandle.bytes.lines {
if let prefix {
lineCount += line.starts(with: prefix) ? 1 : 0
} else {
lineCount += 1
}
}
printCount(lineCount)
}
}6. How to handle arrays/repeated options (e.g., --custom-vocabulary word1 --custom-vocabulary word2)
Array parsing strategies:
// Default: parse one value per option, repeat option multiple times
@Option var files: [String] = []
// Usage: command --files file1.swift --files file2.swift
// Parse multiple values up to next option
@Option(parsing: .upToNextOption) var files: [String]
// Usage: command --files file1.swift file2.swift
// Unconditional single value (can capture dash-prefixed values)
@Option(parsing: .unconditionalSingleValue) var files: [String]
// Usage: command --files file1.swift --files --verbose
// Remaining: capture all remaining values
@Option(parsing: .remaining) var passthrough: [String]
// Usage: command --passthrough --foo 1 --bar 2From the DeclaringArguments documentation:
struct Example: ParsableCommand {
@Option(parsing: .upToNextOption) var files: [String] = []
@Flag var verbose = false
mutating func run() {
print("Verbose: \\(verbose), files: \\(files)")
}
}Usage examples:
$ example --file file1.swift file2.swift
Verbose: false, files: ["file1.swift", "file2.swift"]
$ example --file file1.swift file2.swift --verbose
Verbose: true, files: ["file1.swift", "file2.swift"]7. How ExpressibleByArgument protocol works for custom types
Protocol Definition: https://github.com/apple/swift-argument-parser/blob/main/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift
The protocol has 4 requirements:
public protocol ExpressibleByArgument: _SendableMetatype {
/// Creates from a command-line string
init?(argument: String)
/// Description shown as default value in help
var defaultValueDescription: String { get }
/// All possible string values (for help display)
static var allValueStrings: [String] { get }
/// Descriptions for each possible value
static var allValueDescriptions: [String: String] { get }
/// Default completion kind for shell completions
static var defaultCompletionKind: CompletionKind { get }
}Default Implementations:
-
RawRepresentable types - automatic implementation:
- The library automatically generates
init?(argument:)forRawRepresentabletypes - Only declare conformance:
enum Mode: String, ExpressibleByArgument {}
- The library automatically generates
-
CaseIterable types - automatic list generation:
allValueStringsautomatically returns all case namesdefaultCompletionKindreturns.list(allValueStrings)
-
Standard library types - already conform:
- Int, Int8-64, UInt, UInt8-64
- Float, Double
- Bool
- String
Custom Implementation Example:
struct CustomPath: ExpressibleByArgument {
var path: String
init?(argument: String) {
guard !argument.isEmpty else { return nil }
self.path = argument
}
var defaultValueDescription: String {
path
}
}Transform Functions Alternative:
For complex types you don't own, use transform closures:
enum Format {
case text
case other(String)
init(_ string: String) throws {
if string == "text" {
self = .text
} else {
self = .other(string)
}
}
}
struct Example: ParsableCommand {
@Argument(transform: Format.init)
var format: Format
}Additional Useful Features
struct Select: ParsableCommand {
@Option var count: Int = 1
@Argument var elements: [String] = []
mutating func validate() throws {
guard count >= 1 else {
throw ValidationError("Please specify a 'count' of at least 1.")
}
guard !elements.isEmpty else {
throw ValidationError("Please provide at least one element.")
}
guard count <= elements.count else {
throw ValidationError("Count cannot exceed number of elements.")
}
}
mutating func run() {
print(elements.shuffled().prefix(count).joined(separator: "\n"))
}
}Option Groups: Share options across multiple commands:
struct Options: ParsableArguments {
@Flag(name: [.customLong("hex-output"), .customShort("x")])
var hexadecimalOutput = false
@Argument var values: [Int] = []
}
struct Add: ParsableCommand {
@OptionGroup var options: Options
mutating func run() {
let result = options.values.reduce(0, +)
print(result)
}
}Name Customization:
@Flag(name: .long) // Use property name as long flag
@Flag(name: .short) // Use first letter as short flag
@Option(name: .customLong("count"))
@Option(name: [.customShort("I"), .long])
@Flag(name: .shortAndLong) // Both short and long namesAll of this information comes from the following key files in the swift-argument-parser repository:
- https://github.com/apple/swift-argument-parser/blob/main/Sources/ArgumentParser/Documentation.docc/Articles/DeclaringArguments.md
- https://github.com/apple/swift-argument-parser/blob/main/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift
- https://github.com/apple/swift-argument-parser/blob/main/Examples/ (color, repeat, math, count-lines)
- https://github.com/apple/swift-argument-parser/blob/main/Sources/ArgumentParser/Parsable Properties/Option.swift
- https://github.com/apple/swift-argument-parser/blob/main/Sources/ArgumentParser/Parsable Properties/Argument.swift