Meninunes
Published in

Meninunes

Swift for Command-Line Tools

Creating a CLI in Swift

Although we shouldn’t create a CLI (Command Line Interface) for everything, all developers that I know, usually love a good command-line tool and I’m not an exception to that rule! 😹

I’ve already created some CLIs for myself (and my company folks) in the past using other programming languages (i.e.: Bash, Python, NodeJS)… you can check some of them which are Open Source:

Today, I would like to show you how to create a CLI purely in Swift. It should be easy to quickly set up new CLIs to automate your repetitive tasks or a tool for your colleagues at work.

You can find the project on Github.

Creating the project

First of all, we need to create the project

mkdir saymynamecli
cd saymynamecli
swift package init --type executable

Indicating to the swift command that we want a project of “ — type executable” it will automatically create an appropriate base project for command-line tools, containing the following structure:

Creating executable package: saymynamecli
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/saymynamecli/main.swift
Creating Tests/
Creating Tests/saymynamecliTests/saymynamecliTests.swift

At this point, if you build and run, you should see something like this

swift run
Build complete!
Hello, world!

That’s it! You've already built your first awesome CLI in Swift 😛

Ok, I know, it’s not very useful. Let’s improve it a little bit to have a better boilerplate for real projects in the future.

Adding the “Swift-Argument-Parser” framework

Accepting arguments is a fundamental requirement for almost every CLI. That allows users to customize the actions they want to perform, similar to what we did in the first step above when we indicated the type of the project; that’s an argument provided by the swift CLI.

The most common (and official) package to handle arguments in Swift is the Swift Argument Parser library. It’s Open Source and maintained by Apple!

If you remember, when the project was built, a Package.swift file was created. And if you are familiar with iOS projects nowadays, this is the file where we declare the project’s dependencies. So, let’s declare the first dependency of our AwesomeCLI project:

import PackageDescriptionlet package = Package(
name: "saymynamecli",
products: [
.executable(name: "saymynamecli", targets: ["saymynamecli"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.4.4")
],
targets: [
.target(name: "saymynamecli", dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
]),
.testTarget(name: "saymynamecliTests", dependencies: ["saymynamecli"]),
]
)

After the dependency is declared, run this command in the terminal to fetch the dependency.

swift package update

Adding user inputs support

Now that we have the ability to parse arguments in the project, let’s change the main.swift implementation to allow the user to input values to the program:

import ArgumentParser
import Foundation
struct cli: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "A Swift CLI to say names.",
version: "0.0.1"
)
@Argument
var name: String
@Flag(help: "Whether to say the name capitalized.")
var capitalized: Bool = false
// MARK: - Init func run() throws {
let strName = capitalized ? name.capitalized : name
print("Hello, \(strName)!")
}
}
cli.main()

We have added an argument for the name and an option to define how our command line should behave. Now our CLI behaves differently, depending on the arguments that are passed when we run it:

swift run saymynamecli "steve jobs"
// Hello, steve jobs!
swift run saymynamecli "steve jobs" --capitalized
// Hello, Steve Jobs!

There are three different input types available in the framework:
Arguments, Options and Flags.
Check the official documentation

The ArgumentParser framework also gives us an important functionality out-of-the-box. If the user didn’t provide the minimum required inputs, as in our example, where there’s no default value defined for the name argument, running the command without any arguments (or with the flag -h, — help), a helpful piece of information is automatically displayed:

swift run saymynamecliError: Missing expected argument '<name>'OVERVIEW: A Swift CLI to say names.USAGE: cli <name> [--capitalized]ARGUMENTS:
<name>
OPTIONS:
--capitalized Whether to say the name capitalized.
--version Show the version.
-h, --help Show help information.

Local Usage (release version)

Building and running through the swift command is just for local development. Later, when it’s finished, you will want to be able to run it from any folder on your machine. The easiest way to do so is to build it as release mode, and copy the executable to the /usr/local/bin folder, making it globally available on your machine.

swift build --configuration release
cp -f .build/release/banner /usr/local/bin/banner

Distributing

There are two main common ways to distribute our tool:

For this example, I’ve chosen the latter. To do so, you just have to publish your tool to Github, create a release and then install it locally:

brew install mint
mint install lnfnunes/saymynamecli-swift
# Usage (from anywhere)
saymynamecli Nunera
// Hello, Nunera!

Now that it’s installed, you can run it from within any folder from the terminal, just like any other CLI you are used to, without having to build the tool before using it.

Conclusion

This is just another language option to the wonderful world of command-line tools. We have learned how to use Swift Package Manager (SPM) and the Swift Argument Parser framework makes it a pleasant development process to parse the user inputs.

Thank you for reading! If you have any questions or feedback, please drop me a comment and share it.

You can find the project on Github.

--

--

--

Web Development by — Leandro “Little Big” Nunes

Recommended from Medium

Clean Android Emulator and iOS Simulator Screen Capture on Mac

Mocking iOS 15 Section Headers

Comparing Android & iOS TextView

SwiftUI — @Published

How to integrate subscriptions in your iOS app using Glassfy and Swift.

Swift — Leveraging the power of first-class functions

Implementing SwiftUI’s OnChange Modifier for iOS 13

Computer on desk

Swift code tips: Class vs Static

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Leandro Nunes

Leandro Nunes

Software Engineer @ CI&T | https://lnfnunes.com.br

More from Medium

iOS library support both Cocoapods and Swift Package Manager

Build your First WatchOS App with SwiftUI— Counter App — Part 2

UIViewController Lifecycle

Remote Notification in iOS Simulator — Xcode 11.4 or later & iOS 13.4 or later