Writing scripts for the command line in Swift using Beak


(Cihat Gündüz) #1

Here’s a framework I found recently which I find really amazing: https://github.com/yonaskolb/Beak

The big advantage of Beak is that we can write methods in Swift and run them on the command line very easily. Beak automatically analyzes the Swift file, finds all public methods and their parameters and makes the methods available as sub commands. Here’s an example beak.swift file:

import Foundation

public func hello(name: String, useExclamationMark: Bool = true) {
    let output = "Hello \(name)"
    let suffix = useExclamationMark ? "!" : "."
    print(output + suffix)
}

With Beak installed you can run the method from command line like this:

$ beak run hello --name "World"
// Hello World!
$ beak run hello --name "World" --useExclamationMark false
// Hello World.

Note that the parameter name is required since it doesn’t have a default value.

But this isn’t all Beak does for us. We just saw a Hello World example, but in the real world we need to do more sophisticated work which means it would be really helpful to use third party libraries. Beak has us covered, let’s try adding some useful libraries for Swift code to be run on the command line:

// beak: kareman/SwiftShell @ .upToNextMajor(from: "4.0.0")
// beak: onevcat/Rainbow @ .upToNextMajor(from: "3.0.3")

import Foundation
import SwiftShell
import Rainbow

// MARK: - Tasks
/// Installs project dependencies.
public func installDependencies() throws {
    let command = "carthage bootstrap --platform ios --cache-builds"
    print("Installing dependencies via Carthage: '\(command)'", level: .info)
    try runAndPrint(bash: command)
}

/// Updates project dependencies.
public func updateDependencies() throws {
    let command = "carthage update --platform ios --cache-builds"
    print("Updating dependencies via Carthage: \(command)", level: .info)
    try runAndPrint(bash: command)
}

// MARK: - Helpers
private enum PrintLevel {
    case info
    case warning
    case error
}

private func print(_ message: String, level: PrintLevel) {
    switch level {
    case .info:
        print("ℹ️ ", message.lightBlue)

    case .warning:
        print("⚠️ ", message.yellow)

    case .error:
        print("❌ ", message.red)
    }
}

Now we can run a single and short task to install project dependencies like this:

$ beak run installDependencies
// ℹ️  Installing dependencies via Carthage: 'carthage bootstrap --platform ios --cache-builds'
// *** Checking out Nimble at "v7.0.3"
// *** Checking out HandyUIKit at "1.6.0"
// *** Checking out HandySwift at "2.4.0"

Amazing, Beak just installed two dependencies (SwiftShell and Rainbow) and included them into the file automagically and ran the code as expected. That was easy! Beak can install all libraries that support the Swift Package Manager which there are a lot of.

But our file is starting to become long and code completion for a single file isn’t well in Xcode, especially it doesn’t even work for dependency code. Beak makes even that a breeze. Just run beak edit and it will generate an Xcode project and setup all dependencies within the project. Just edit the main.swift file within the project and copy its content to your file beak.swift once you’re done editing. This way we can make full use of Xcode auto completion and see the compiler warnings and errors live while coding.

Here’s an example real-world usage of a Beak file on our NewProjectTemplate GitHub project: https://github.com/JamitLabs/NewProjectTemplate-iOS/blob/stable/beak.swift

Look how Beak made it way simpler on getting started with a new project in comparison to before.

Isn’t this amazing? :star_struck: Thank you, Jonas Kolb.