Usually I lean on Ruby or Bash for writing command line scripts, but it is becoming increasingly more viable to use Swift for this as well. In the Vapor series, I wanted to write a little script (in Swift) that would generate migration files for me so I wouldn’t have to maintain this myself. For this, I used the Marathon tool, which helps alleviate some of the machinery necessary to use Swift in this way. And what better way to explore this tool than with this author guiding me along. John Sundell joins me in this episode to use Marathon and Swift to write a useful script for Vapor applications. This is a longer episode, so it is split into two parts. Enjoy!
Thanks to John Sundell for joining me on this episode! Continued in Part 2 Episode Links Swift by Sundell - John's excellent blog on Swift GitHub - JohnSundell/Marathon: Marathon makes it easy to write, run and manage your Swift scripts 🏃 Mint · GitHub - A utility for installing Swift packages JohnSundell/Files · GitHub - A Swift package for working with the file system JohnSundell/ShellOut· GitHub - Easily run shell commands from a Swift script or command line tool Installing Marathon I followed the recommended instructions, which involves installing Mint first (😂): $ brew install mint Now that Mint is installed, use it to install Marathon: $ mint run JohnSundell/Marathon Writing Your first script $ marathon create AddMigration This creates an Xcode project in Marathon's support folder, creates the Swift package structure and links in the swift file for us to edit. Any time we want to edit this file, we have to do so using Marathon: $ marathon edit AddMigration Reading Command Line Arguments One of the first things we have to do is read command line arguments. Luckily, Foundation already has us covered there: import Foundation print(CommandLine.arguments) This will print out a single argument, which is the full path to the script being run. If you want to provide some arguments when debugging in Xcode, you can do so by editing the Xcode scheme. Running Marathon Scripts Running the scripts also is done via Marathon: $ marathon run AddMigration <your> <arguments> Working with Files We want to work with the filesystem in this example, so we'll pull in John's library for this, helpfully called "Files". We'll need to tell Marathon keep track of the dependencies that we might want to use. We can do this in multiple ways. The first way is to add the package as a known dependency with Marathon: $ marathon add https://github.com/JohnSundell/Files.git (Later when we want to update Marathon's copy of this, we can run marathon update ...) This adds a cached copy of the dependency in Marathon's support folder, so we can now just import it in our script: import Foundation import Files let folder = Folder.current print("Your running from \(folder.name)") If you run this with Xcode, the output will look something like this: You're running from Debug Program ended with exit code: 0 The folder name is just the single name, if you want the full path, you can use folder.path: You're running from /Users/ben/Library/Developer/Xcode/DerivedData/example-ccknwhxumzpmtsgmrdeepmzbsfnf/Build/Products/Debug/ Program ended with exit code: 0 To create or retrieve a folder, you can use methods on the Folder type: let folder = Folder.current let subfolder = try folder.createSubfolderIfNeeded(withName: "MySubfolder") This would create the folder if it didn't exist already Exiting from scripts with errors See how Xcode noted that the program ended with exit code 0? This is a Unix convention. "0" means successful, anything non-zero is an error. You can use this to communicate error codes, but it is also useful for chaining together commands: first_run_this && then_run_that If the first command returns a non-zero exit code, then the 2nd script will not run. We want to honor the Unix convention of returning a non-zero value if we encounter an error. if CommandLine.arguments.count < 2 { print("Usage: marathon run myScript <arg>") exit(1) } If we try to run this script and don't provide an argument, then the CommandLine.arguments array will only contain 1 value: the path to the script. We then print out a helpful message and exit with the value "1", indicating an error.