-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
158: Building a Command-Line Package with Swift Argument Parser
Imagine you're walking into a high-end deli. You don't just walk up to the counter and scream "SANDWICH!" at the staff. You follow a specific protocol: you pick a base sandwich (the command), you specify the bread (an option), and maybe you ask for it to be toasted (a flag). The person taking your order is essentially a parser; they take your spoken, unstructured request and translate it into a precise ticket that the kitchen staff can actually execute without asking you twenty follow-up questions.
That's exactly what swift-argument-parser does for your code. In the old days (and in some languages still), you'd have to manually sift through an array of strings called CommandLine.arguments, writing messy if-else blocks to figure out if the user typed --verbose or -v. It was brittle and a nightmare to maintain. This library turns that process into a declarative structure where your code defines the "menu," and the library handles the translation.
Defining Your Tool's Menu
Let's say we're building a tool called LogScraper. Its job is to sift through a massive server log file and find specific error messages. To do this, we need a few things: a path to the file, a keyword to search for, and a way to toggle "detailed" mode.
import ArgumentParser
struct LogScraper: ParsableCommand {
static var configuration = CommandConfiguration(
commandName: "logscraper",
abstract: "A utility for extracting specific errors from server logs."
)
@Option(name: .shortAndLong, help: "The keyword to search for in the logs.")
var keyword: String
@Option(name: .customShort("f"), help: "The path to the log file.")
var file: String
@Flag(name: .shortAndLong, help: "Show the line number for every match.")
var verbose = false
func run() throws {
print("Searching for '\(keyword)' in \(file)...")
if verbose {
print("Verbose mode enabled: Including line numbers.")
}
// Logic to actually read the file and filter lines goes here
}
}
Notice how I used @Option and @Flag. In our deli analogy, the keyword and file are the "bread" and "meat"—they require specific values. The verbose variable is the "toasted" request—it's either there or it isn't. One thing I love about this approach is that the library automatically generates a --help command for you. You don't have to write a single line of documentation code; the help strings you provide in the property wrappers are used to build a professional-looking CLI manual.
Adding Subcommands for More Power
Sometimes a single command isn't enough. Maybe LogScraper needs to do more than just search; maybe it needs to "clean" the logs or "summarize" them. This is where subcommands come in. Instead of making one giant command with fifty options, we create a hierarchy.
I usually recommend creating a main "entry point" command that doesn't do much itself but acts as a container for other commands. It looks like this:
struct LogTool: ParsableCommand {
static var configuration = CommandConfiguration(
commandName: "logtool",
abstract: "The ultimate log management suite.",
subcommands: [Scrape.self, Clean.self]
)
}
struct Scrape: ParsableCommand {
// All the logic from our previous LogScraper example goes here
func run() throws { /* ... */ }
}
struct Clean: ParsableCommand {
@Option(help: "The age of logs to delete in days.")
var days: Int
func run() throws {
print("Cleaning logs older than \(days) days...")
}
}
Now, the user interacts with your tool like this: logtool scrape --keyword "404" or logtool clean --days 30. By separating these into different structs, you keep your logic isolated. The "Scrape" logic doesn't need to know anything about how "Clean" works, which makes your codebase much easier to test as it grows.
Handling the Execution Flow
You might be wondering where the actual execution starts. In a Swift Package, you'll typically have a main.swift file or use the @main attribute on your top-level command. When you call LogTool.main(), the library takes over. It reads the CommandLine.arguments, validates that the user provided the required options, converts the strings into the correct types (like turning "30" into an Int for our days option), and finally calls the run() method of the matched command.
If the user forgets a required argument or types a flag that doesn't exist, the library catches that before run() is ever called, prints a helpful error message, and exits with a non-zero status code. This saves you from writing a mountain of validation logic.
📋 Practical Task
Build a "Project File Organizer" CLI
Your task is to create a command-line tool using swift-argument-parser that helps a developer organize their project folder. The tool should be named organizer and support the following requirements:
- Main Command: The tool should have a root command called
organizer. - Subcommand: Create a subcommand called
sort. - The Sort Logic: The
sortcommand needs:- A required
--directory(or-d) option to specify which folder to organize. - An optional
--extension(or-e) option to only move files of a certain type (e.g.,.swiftor.png). - A
--dry-runflag that, when present, prints what would happen without actually moving any files.
- A required
Implement the run() method for the sort command so that it prints a summary of the action. For example: "Dry run: Would move all .swift files in /Users/dev/project to /Users/dev/project/swift_files".
There are no comments for now.