Build a Command-Line Tool With Swift
With Swift Package Manager, we can build a command-line tool with Swift effortlessly.
Today, let’s build a command-line tool to help us capitalize title from scratch.
Creating an Executable Package
mkdir TitleCapitalizer
cd TitleCapitalizer
swift package init --type executable
With the command above, Swift Package Manager will create all we need for a command-line tool including a .gitignore
file.
We can build and run it right away.
swift build # We can skip this command since `swift run` will do it for us
swift run
The output will be Hello, world!
as the main.swift
file created by Swift Package Manager.
print("Hello, world!")
Build the Title Capitalizer
Let’s build our title capitalizer with APA style capitalization rules.
- Capitalize the first word of the title/heading and of any subtitle/subheading;
- Capitalize all “major” words (nouns, verbs, adjectives, adverbs, and pronouns) in the title/heading, including the second part of hyphenated major words (e.g., Self-Report not Self-report); and
- Capitalize all words of four letters or more.
I am not sure if the rules above are enough, but let’s build our title capitalizer according to the rules as an example.
import NaturalLanguage
let text = "build a command-line tool with Swift"
var capitalized = ""
let tagger = NLTagger(tagSchemes: [.lexicalClass])
let tags: [NLTag] = [.noun, .verb, .adjective, .adverb, .pronoun]
tagger.string = text
tagger.enumerateTags(in: text.startIndex..<text.endIndex, unit: .word, scheme: .lexicalClass) { (tag, tokenRange) -> Bool in
var token = String(text[tokenRange])
if token.count >= 4 || tag.flatMap({ tags.contains($0) }) == true { // 3. or 2.
// A string extension from
// https://www.hackingwithswift.com/example-code/strings/how-to-capitalize-the-first-letter-of-a-string
token.capitalizeFirstLetter()
}
capitalized.append(token)
return true
}
capitalized.capitalizeFirstLetter() // 1.
print(capitalized)
Since NaturalLanguage
is only available in macOS 10.14 or newer, we have to set the platforms in Package.swift
file as blow.
let package = Package(
name: "TitleCapitalizer",
platforms: [
.macOS(.v10_14),
],
...
)
swift run
again, we will get the result—”Build a Command-Line Tool With Swift”.
Get Input
Let’s get the input instead of hard coding it.
// The first arugment will be the path to the executable file
let text = CommandLine.arguments.dropFirst().first
We also have to validate there is a text. If there is not, print an error message and exit with a failure.
guard let text = CommandLine.arguments.dropFirst().first else {
print("No string to capitalize.")
exit(EXIT_FAILURE)
}
Let’s test it.
$ swift run
No string to capitalize.
# We have run TitleCapitalizer specifically
# Otherwise `swift run` will try to run a executable product named "build a command-line tool with Swift"
$ swift run TitleCapitalizer "build a command-line tool with Swift"
Build a Command-Line Tool With Swift
It works great!
Swift Argument Parser
Let’s assume we want to support other capitalization styles, so we want to run our tool like below.
$ swift run TitleCapitalizer "build a command-line tool with Swift"
Build a Command-Line Tool With Swift
$ swift run TitleCapitalizer --style apa "build a command-line tool with Swift"
Build a Command-Line Tool With Swift
$ swift run TitleCapitalizer -s chicago "build a command-line tool with Swift"
Build a Command-Line Tool with Swift
It’s tedious to complete this need by getting arguments directly from CommandLine.arguments
. But there is a package that can help us out—Swift Argument Parser.
Add Swift Argument Parser as a Dependency
Add Swift Argument Parser to the dependencies of our package in Package.swift
file.
let package = Package(
...
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.4"),
],
...
)
Also add it to the dependencies of our target, so that we can improve it as ArgumentParser in our source file.
.target(
name: "TitleCapitalizer",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
]),
Refactor Using ArgumentParser
It’s simple to use ArgumentParser
. All we need to do is to implement ParsableCommand
and wrap all arguments with corresponding property wrapper.
import ArgumentParser
import NaturalLanguage
struct TitleCapitalizer: ParsableCommand {
enum Style: String, ExpressibleByArgument {
case apa, chicago
}
@Option(name: .shortAndLong, default: .apa, help: "The style used to capitalize.")
var style: Style
@Argument(help: "The text to capitalize.")
var text: String
func run() throws {
// Capitalize the text here according to the style
}
}
TitleCapitalizer.main()
With the code above, we are supporting the option to specify the style to capitalize.
And thanks to Swift Argument Parser, we got a help page automatically.
$ swift run TitleCapitalizer --help
USAGE: title-capitalizer [--style <style>] <text>
ARGUMENTS:
<text> The text to capitalize.
OPTIONS:
-s, --style <style> The style used to capitalize. (default: apa)
-h, --help Show help information.
Where to Go From Here
Use Everywhere
Run the following command to be able to run our command-line tool everywhere.
export PATH=$PATH:/path/to/package/.build/debug
Share by Mint 🌱
Add an executable
to the package’s products
list.
let package = Package(
...
products: [
.executable(name: "TitleCapitalizer", targets: ["TitleCapitalizer"]),
],
...
)
Release the package to GitHub.
That’s it. The user can install your command-line tool by:
mint install github_name/repo_name
You may also consider adding your package to the README.md
file of Mint.