NEW: Master Swift design patterns with my latest book! >>

Write your scripts in Swift with Beak

Paul Hudson    January 8th 2018    @twostraws

If you’re visiting here it’s because you’re already sold on Swift as a great language for apps, but have you ever considered using it for scripting? If not, a new GitHub project from Yonas Kolb is here to convince you otherwise: it’s called Beak and it lets your run Swift code straight from the command line.

To get started, run these three commands from your Mac’s terminal:

git clone https://github.com/yonaskolb/Beak.git
cd Beak
swift run beak run install

That builds and installs Beak, which means it’s ready to run any Swift scripts you need.

Let’s try it out now. Create a new Swift file on your desktop called factors.swift and give it this content:

import Foundation

public func calculate() {
    let number = 100
    let factors = (1...number).filter { number % $0 == 0 }
    print(factors.reduce("Factors: ") { $0 + String($1) + " " })
}

That creates a single function called calculate(), which creates an array of numbers that divide equally into 100, then prints them out as a single string.

To run that using Beak you need to write a command that does the following:

  • Specifies factors.swift as its input filename.
  • Tells it to run a single function from the file.
  • Provides the function name to run.

Run this command now:

beak --path factors.swift run calculate

You specify the function name you want to run, so a single Swift script can contain as many related functions as you want.

As well as run you can also use list to show the list of available functions:

beak --path factors.swift list

That will show you there’s only function available, which is our calculate() function.

Tip: Beak can only see functions that are marked as public. If you want a function for internal use only – i.e., not exposed by the list command – just don’t declare it as public.

Right now our function uses a hard-coded input value, but Beak can process arguments from the command-line and convert them into function arguments.

Try changing the function to this:

public func calculate(number: Int) {
    let factors = (1...number).filter { number % $0 == 0 }
    print(factors.reduce("Factors: ") { $0 + String($1) + " " })
}

When using the calculate() method now, you need to run it like this:

beak --path factors.swift run calculate --number 1000

Now that we have a command that takes input, you might want to try using the “function” option for Beak, which provides more details on a specific function. For example:

beak --path factors.swift function calculate

That will say calculate() takes one argument called number, which is an Int. However, if you want more useful output you should add some Markdown comments to your code, like this:

/// Calculates the factors for a given number
/// - Parameters:
///   - number: the input number to use
public func calculate(number: Int) {
    let factors = (1...number).filter { number % $0 == 0 }
    print(factors.reduce("Factors: ") { $0 + String($1) + " " })
}

These comments are exactly the same format used by Xcode and other Swift tools, so you should already be familiar with them – if not, now would be a good time to investigate my Pro Swift book! If you run Beak’s “function” command now you’ll get much more useful help.

At this point I hope you’re certainly curious to try Swift scripting yourself, but before we’re done I want to demonstrate one last, important feature: cleaning up the commands.

So far we’ve been using commands like this one:

beak --path factors.swift run calculate --number 1000

While it’s certainly nice and clear, it’s also supremely clumsy. Fortunately Beak supports shebang lines, which means you can add a special line to the start of your Swift scripts that allows you to make your scripts into standalone commands.

Modify factors.swift to this:

#!/usr/bin/env beak run --path
import Foundation

/// Calculates the factors for a given number
/// - Parameters:
///   - number: the input number to use
public func calculate(number: Int) {
    let factors = (1...number).filter { number % $0 == 0 }
    print(factors.reduce("Factors: ") { $0 + String($1) + " " })
}

That first line is the shebang line: it tells Unix systems that the rest of this file should be passed to Beak as its input.

Now run this command from your terminal:

chmod a+x factors.swift

That marks factors.swift as being executable (X) for all (A) users. You can now run this instead:

./factors.swift calculate --number 100 

That’s slightly better, but now try running this command:

mv factors.swift /usr/local/bin/factors

That moves factors.swift to the directory containing your local commands, while also renaming it just to “factors”.

Now your command becomes this:

factors calculate --number 100

To make it even shorter (because why not?), you can mark the parameter as unnamed like this:

public func calculate(_ number: Int) {

(Note: make sure you edit /usr/local/bin/factors!)

Because the parameter is unnamed, you no longer need to specify --number on the command line. So, your command becomes just this:

factors calculate 100

I think you’ll agree that’s much easier to remember!

So, go and give Beak a try. Swift is slowly evolving into a full-stack language (Server-Side Swift, anyone?), and Beak is just one more great step in that direction.

Link: Beak

 

About the author

Paul Hudson is the creator of Hacking with Swift, the most comprehensive series of Swift books in the world. He's also the editor of Swift Developer News, the maintainer of the Swift Knowledge Base, and Mario Kart world champion. OK, so that last part isn't true. If you're curious you can learn more here.

Click here to visit the Hacking with Swift store >>