Ваш первый CLI на Swift: от идеи до готового инструмента
Вы когда-нибудь задумывались, как создаются те самые удобные утилиты в терминале — curl, git, brew? Оказывается, написать свою собственную CLI утилиту на Swift не сложнее, чем сделать простое iOS-приложение. Более того, Swift идеально подходит для этого: он быстр, безопасен по памяти и отлично работает как на macOS, так и на Linux.
В этой статье мы с нуля создадим полноценный консольный инструмент, который будет парсить аргументы, выводить цветной текст и даже обрабатывать ошибки. Вы узнаете, как настроить проект через Swift Package Manager, подключить мощную библиотеку ArgumentParser и подготовить утилиту к публикации в Homebrew. Готовы? Погнали!
Почему Swift для CLI — это круто?
Когда речь заходит о создании инструментов командной строки, многие сразу думают о Python, Bash или Go. Но Swift — тёмная лошадка, которая удивляет:
- Скорость на уровне C — ваша утилита будет летать.
- Безопасная работа с памятью — никаких segfault и утечек.
- Кроссплатформенность — Swift отлично работает на Linux, а значит, ваш CLI можно запускать даже на серверах.
- Современный синтаксис — код читается как хорошая проза.
- Огромная экосистема — Swift Package Manager (SPM) даёт доступ к тысячам библиотек.
Кстати, если вы уже пишете на Python и хотите попробовать что-то новое для CLI, Swift станет отличным выбором: вы получите нативный бинарник без зависимостей и с минимальным потреблением памяти.
Настройка проекта: Swift Package Manager
Первым делом создадим новый проект. Откройте терминал и выполните:
mkdir MyCLI
cd MyCLI
swift package init --type executable
Команда создаст стандартную структуру SPM. Обратите внимание на файл Package.swift — это сердце вашего проекта. Отредактируем его, чтобы добавить зависимость от Apple/Swift ArgumentParser:
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "MyCLI",
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0")
],
targets: [
.executableTarget(
name: "MyCLI",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
)
]
)
Теперь выполните swift build. SPM сам скачает библиотеку и соберёт проект. Если всё прошло успешно — вы готовы писать код!
Пишем первую команду: Hello World на стероидах
Откроем Sources/MyCLI/main.swift и напишем простейшую команду, которая выводит приветствие. Но не просто "Hello, World!", а с использованием аргументов:
import ArgumentParser
struct HelloCommand: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "hello",
abstract: "Выводит приветствие",
discussion: "Используйте эту команду, чтобы поприветствовать мир или конкретного человека."
)
@Argument(help: "Имя человека, которого нужно поприветствовать")
var name: String = "World"
@Option(name: .shortAndLong, help: "Количество повторов")
var count: Int = 1
func run() throws {
for _ in 0..<count {
print("Привет, \(name)! 👋")
}
}
}
HelloCommand.main()
Запустите: swift run MyCLI hello --count 3 "iOS Developer". Вы увидите:
Привет, iOS Developer! 👋
Привет, iOS Developer! 👋
Привет, iOS Developer! 👋
Обратите внимание: ArgumentParser сам сгенерировал справку. Попробуйте swift run MyCLI hello --help — вы получите красиво отформатированную документацию по вашей команде. Это одна из главных фишек создания CLI утилит на Swift — минимум кода, максимум пользы.
Продвинутый парсинг аргументов: флаги, подкоманды и валидация
Настоящие утилиты редко ограничиваются одной командой. Давайте добавим подкоманды и флаги. Представьте, что мы делаем инструмент для управления задачами (как мини-Todoist).
Структура подкоманд
import ArgumentParser
struct MyCLI: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "mytask",
subcommands: [Add.self, List.self, Done.self],
defaultSubcommand: List.self
)
}
struct Add: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "add",
abstract: "Добавить новую задачу"
)
@Argument(help: "Текст задачи")
var task: String
@Option(name: .shortAndLong, help: "Приоритет (1-5)")
var priority: Int = 3
func run() throws {
guard priority >= 1 && priority <= 5 else {
throw ValidationError("Приоритет должен быть от 1 до 5")
}
print("✅ Добавлена задача: \(task) [приоритет: \(priority)]")
}
}
struct List: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "list",
abstract: "Показать все задачи"
)
@Flag(name: .shortAndLong, help: "Показать только выполненные")
var done: Bool = false
func run() throws {
if done {
print("📋 Выполненные задачи:")
} else {
print("📋 Текущие задачи:")
}
}
}
struct Done: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "done",
abstract: "Отметить задачу как выполненную"
)
@Argument(help: "ID задачи")
var id: Int
func run() throws {
print("✅ Задача #\(id) выполнена!")
}
}
MyCLI.main()
Теперь у нас три команды: mytask add, mytask list, mytask done. Флаг --done в команде list — это булевый флаг. Валидация встроена: если передать приоритет 6, вы получите понятную ошибку.
Такой подход делает создание инструментов командной строки на Swift невероятно удобным — вы сосредотачиваетесь на логике, а не на парсинге строк.
Цветной вывод и интерактивность
Скучный чёрно-белый вывод — прошлый век. Давайте добавим цвета с помощью ANSI-кодов. Это особенно полезно для CLI утилит на Swift, которые работают в современных терминалах.