The state of Swift scripting, writing the script
This is the second part of my posts about the sate of scripting with Swift. In the previous post I wrote about what I expect from a language toolset when writing scripts and where Swift still needs some improvements. In this post I want to do a quick overview of the libraries that I used to write the script.
The examples of this posts are taken from the READMEs of each library. I recommend you to read them for more examples.
One of the things that a script needs is the ability to process the launch arguments. Foundation lets you access those arguments quite easily with the Process.arguments
array, but usually you want to use some library that gives you a structured access to them instead of having to scan the array and process it yourself in every new script.
There are many libraries that offer a way of doing it, but Commander is my preferred option. The thing that makes it different is that it allows you to describe your commands with their arguments and options. Even better, it allows you to write the code for each command in its own closure which gives you an easy way to structure your scripts.
command(
Option("name", "world"),
Option("count", 1, description: "The number of times to print.")
) { name, count in
for _ in 0..<count {
print("Hello \(name)")
}
}
It even let’s you give descriptions to commands and arguments that are automatically exposed to the usual --help
command.
$ hello --help
Usage:
$ hello
Options:
--name
--count - The number of times to print.
This way of structuring the script in commands is really nice, but if your script is way simpler you can just use any other library to just read the arguments when you need them.
Interactive
Swiftline is a nice library that has multiple features. One of them is parsing the arguments, but as I said I prefer to use Commander for that.
The feature that I’m most interested in is the ability to turn the scripts in something more interactive in a quick and easy way.
let age = ask("How old are you?", type: Int.self)
Asking for user input on its own is not that difficult, but this library offers a set of methods for the most common cases of input. Swiftline will keep asking the user for correct input until it validates the type that you are expecting. It even allows you to add custom validation or asking the user to confirm the input.
How old are you?
None
You must enter a valid Integer.
? Error
You must enter a valid Integer.
? 5
5
But it doesn’t only ask for an arbitrary input, you can also ask the user to pick one option from a specific set of choices that you offer.
let choice = choose("Whats your favorite programming language? ",
choices: "Swift", "Objective C", "Ruby", "Python", "Java :S")
1. Swift
2. Objective C
3. Ruby
4. Python
5. Java :S
Whats your favorite programming language?
And let’s not forget about the input that every script asks for the user. Yes or No?
let choice = agree("Are you sure you want to `rm -rf /` ?")
Are you sure you want to `rm -rf /` ? What!
Please enter "yes" or "no".
Are you sure you want to `rm -rf /` ? Wait
Please enter "yes" or "no".
Are you sure you want to `rm -rf /` ? No
You entered false
Colors
I don’t tend to use colors in the output of my scripts as usually techy users will have a custom palette on the terminal and who knows how readable will it be. But for display errors in red I usually do it. Swiftline allows you to just call a couple of short methods in any string to give it a color.
print("Red String".f.Red)
JSON
As I already stated on my analysis of Swift JSON libraries I use Decodable in my projects. But usually in scripts I don’t need to decode JSON into my model, I just need an easy access to some keys of the JSON so I usually use SwiftyJSON for that.
let name = json["name"].stringValue
I like it because visually is just like accessing a dictionary with a simple method that determines the type that I’m expecting.
Foundation
Even with all the nice libraries that are out there I still end up using Foundation for quite a bunch of things. For example, I’m sure that are libraries to read and write files but I found myself quite comfortable with just using NSFileManager
or even the convenience methods that some types have like string.writeToURL
.
Even for network calls NSURLSession
is more than enough. Although to use it in a script there are some tweaks that are needed, mainly making it synchronous using a semaphore.
No ABI
The funny part about the history of this script was that I was writing it when the Swift team announced that the ABI was postponed after Swift 3.
I’m happy with that delay, as I prefer to have a good and stable language (in this case the ABI) before rushing it to quickly just to make marketing happy.
But this simple script was one of those cases where I would have needed ABI compatibility. I wanted to deploy the script in a linux server, but Swift 2 doesn’t have linux compatibility so I had to port the script to Swift 3, except that without ABI compatibility means that I need to wait for all the libraries to be updated for the upcoming version of Swift. :(
Conclusions
After all the characters I’ve written I still firmly believe that Swift will be a great language for scripting. I don’t have complains about the language itself or the libraries, as you have seen in this post. The main concerns are about the tools around them. But we don’t have to forget that is still a young language and I have high hopes in the evolution of it.