Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swift for CLI tools

Swift for CLI tools

A short talk on building CLI tools with Swift.
Covers the current state of the language, CLI basics, and what needs to be done.

tl;dr;
The language is still very new and not as easy to use as some mature folks like Ruby or Python.

# Swift for Command-line tools
## Talk description

Since it's release, Swift has gained a decent traction as a language for
building mobile / desktop apps.
This talk will show the other side of Swift, it's scripting capabilities
and how you can use it for building CLI tools.

We will cover CLI basics, status codes, documentation, pipeline, and
some real life usages.
Looking in the future, can Swift dominate Ruby and bash as a tooling language?

## _Intro_

### Evolution

If you were watching Swift from it's early beginnings, you might have noticed
that executing Swift code got significantly simpler.

`/Applications/Xcode6-Beta1.app/Contents/Developer/usr/bin/xcrun swift -i script.swift`

to

`xcrun swift -i script.swift`

to

`swift -i script.swift`

to

`swift script.swift`

It's noticeable that the core team gives attention to Xcode-less development,
and is trying to make Swift a first class CLI language.

### 'Scripting' vs Compiling

When we talk about scripting, we're mostly referring to interpreted languages.
What makes working with these a pleasure is the ability to quickly just
open a file and execute it inline, like so: `ruby script.rb`.
Languages like Objective-C usually need a bunch of compiler flags and
a compilation step before being executed. This makes working with Ruby, Python,
Javascript and similar languages a pleasure.

Fortunately, some languages like Go have set a good example how to achieve the
scripting-style workflow, while staying compiled languages.
To give you an example, you can execute a Go file with `go run script.go`.
Go compiler will compile the file implicitly, and execute it in place.

Swift went in the Go direction - you can execute a Swift program inline, without
knowing it's being compiled and running a binary.

``` bash
# Execute inline
- $ swift todos.swift
```

The best part is that you can still compile a Swift script into a binary and
ship it, just like Go programs.

``` bash
# Compile and run
- $ swiftc todos.swift -o todos
- chmod +x todos
- todos
```

## _CLI 101_
### Shebang
UNIX looks for the magic comment on the beginning of your script, called
Shebang, Hashbang or even pound-bang.
It contains a hash, exclamation mark and the interpreter command such as
`/usr/bin/ruby`, or `/usr/bin/env ruby`.

When a script with a Shebang is run as a program, the program loader parses the
rest of the script's initial line as an interpreter directive.

### chmod
To make the script executable, we need to give it the right permissions.
Command `chmod` allows us to change file modes or Access Control Lists.
`chmod +x script.swift`

### PATH
If you want to use your Swift program from anywhere, you need to tell your OS
where to find it. UNIX-like systems usually look at the PATH variable.
Assuming we'll put the compiled products in `~/bin`, we need to prepend it to
the standard PATH: `export PATH=~/bin:$PATH`.

### Status codes
When a program terminates, it exits with a code. Zero usually means a
successful exit, and everything else is considered as an error.

If you want to force exit with a code, you can use the built-in `exit()`
function, e.g. `exit(1)`.

One of the useful functions is checking the exit status of the last process
terminated. It's usually stored in the `$?` variable, but there's no support
for it in Swift yet.

### Option parsing
Commands usually take parameters and flags in the same way functions take
arguments. Passing them through your shell is pretty easy, but then comes the
parsing part inside your script.

It's generally a bad idea to do this all by hand; Some languages like Ruby have
OptionParser built in the standard lib.

- Frameworks:
- https://github.com/jakeheis/SwiftCLI
- https://github.com/nomothetis/OptionKit

#### POSIX flags
By the POSIX standard, flags should have a long and short version.
Long flags should start with a double dash, and short ones with only one.
For example, command `generate` should be able to take `--view` and `-v`
and achieve the same thing. The cool thing about abbreviations is that you can
chain them without adding dashes, like so: `generate --view --controller`
should be the same as `generate -vc`. (the latter only works in Silicon Valley)

### Documentation
Every CLI tool should have at least the `--help` documentation. There's also
`man` documentation if you want to document your tool extensively, but most
of the smaller tools don't have it.
The cool thing is, if you use a library like OptionParser, it should generate
the help documentation for you.

### IO
IO is often the most important part of a CLI tool. File and IO modules give you
file-system traversal, various file operations including permissions, and very
importantly - accessing `stdin`, `stdout` and `stderr` input/outputs.

You're mostly either:
- spawning another proccess (`popen`), letting it do some work and listening
to it's outputs
- being spawned and transforming the `stdin` of your own program

### Pipeline
UNIX pipeline is a very flexible way of chaining multiple programs to work
together without knowing about each other.
A pipe connects `stdout` output of the first program with `stdin` of another.
In the given example `A | B`, program A sends to `stdout`, and B receives
into `stdin`. Output from `stderr` is always displayed straight into terminal,
unless you explicitly redirect it to `stdout`, `/dev/null`, or something else.

### String manipulation
When talking about transforming
- Regular expressions (verbose and yucky in Foundation)
- Simple and elegant in Ruby
- split, reverse, join, char index, length,...

### External libraries
For a CLI tool to be insanely cool and fun to build, you often connect some
crazy parts together. Imagine building an alarm clock that also talks to your
coffee machine through it's API and requests a Lyft after you drink the coffee.

You should be able to use these libraries without effort, like with Ruby gems.
Currently there's CocoaPods, but this setup requires Xcode and you probably
don't want to deal with Xcode.

## What's missing
- Option parser
- Regex
- String operations
- BDD from command line
- Simpler framework loading / linking
- Distributing frameworks

--- Attributions
---
- http://sitekamimura.blogspot.com/2014/09/swift-reading-and-writing.html
- http://nomothetis.svbtle.com/swift-for-scripting?utm_campaign=iOS_Dev_Weekly_Issue_167&utm_medium=email&utm_source=iOS%2BDev%2BWeekly
- https://en.wikipedia.org/wiki/Shebang_(Unix)

Marin Usalj

October 16, 2014
Tweet

More Decks by Marin Usalj

Other Decks in Programming

Transcript

  1. COMPILED LANGUAGES $ clang -lobjc -framework Foundation -c script.m -o

    script.o $ chmod +x script.o $ ./script.o Marin Usalj - @supermarin - supermar.in
  2. GOLANG $ go build script.go $ chmod +x script $

    ./script Marin Usalj - @supermarin - supermar.in
  3. SWIFT # Compile and run - $ swiftc todos.swift -o

    todos - chmod +x todos - todos Marin Usalj - @supermarin - supermar.in
  4. CAVEATS $ time swift hello.swift Hello ! 0.33 real 0.04

    user 0.05 sys Marin Usalj - @supermarin - supermar.in
  5. CAVEATS $ swiftc hello.swift $ time ./hello Hello ! 0.00

    real 0.00 user 0.00 sys Marin Usalj - @supermarin - supermar.in
  6. #!/usr/bin/env swift var args = Process.arguments[1..<countElements(Process.arguments)] var name: String? =

    join(" ", args) println("Hello \(name!)!") Marin Usalj - @supermarin - supermar.in
  7. SHEBANG1 #!/usr/bin/env swift 1 Also known as Hashbang, or even

    pound-bang (Wikipedia) Marin Usalj - @supermarin - supermar.in
  8. SHEBANG1 #!/usr/bin/env swift 1 Also known as Hashbang, or even

    pound-bang (Wikipedia) Marin Usalj - @supermarin - supermar.in
  9. SHEBANG1 $ swift hello.swift BECOMES $ ./hello 1 Also known

    as Hashbang, or even pound-bang (Wikipedia) Marin Usalj - @supermarin - supermar.in
  10. $PATH $ export PATH=~/bin:$PATH Tell your OS where to find

    the custom scripts Marin Usalj - @supermarin - supermar.in
  11. STATUS CODES $? == 0 ! $? != 0 "

    Marin Usalj - @supermarin - supermar.in
  12. STATUS CODES if drink == ! println(“NEED SCOTCH”) exit(1) else

    # do work exit(0) end Marin Usalj - @supermarin - supermar.in
  13. OPTION PARSING2 OptionParser.new do |opts| opts.banner = "Usage: xcodebuild [options]

    | xcpretty" opts.on('-t', '--test', 'Use RSpec style output') do printer_opts[:formatter] = XCPretty::RSpec end end 2 Code sample is written in Ruby Marin Usalj - @supermarin - supermar.in
  14. CLIKIT var manager = Manager() manager.register("issue", "Options for issue") {

    argv in println("Say `open`, `close` or `edit`") } manager.run() Marin Usalj - @supermarin - supermar.in
  15. OPTIONKIT import OptionKit let opt1 = Option(trigger:.Mixed("e", "echo")) let opt2

    = Option(trigger:.Mixed("h", "help")) let opt3 = Option(trigger:.Mixed("a", "allow-nothing")) let actualArguments = Array(Process.arguments[1..<Process.arguments.count]) let result = parser.parse(actualArguments) Marin Usalj - @supermarin - supermar.in
  16. Usage: xcodebuild [options] | xcpretty -t, --test Use RSpec style

    output -s, --simple Use simple output (default) -k, --knock Use knock output -f, --formatter PATH Use formatter returned from evaluating the specified Ruby file -c, --color Use colorized output Marin Usalj - @supermarin - supermar.in
  17. IO THE MOST IMPORTANT PART OF A CLI TOOL Marin

    Usalj - @supermarin - supermar.in
  18. IO BEING SPAWNED git status | slug | say Marin

    Usalj - @supermarin - supermar.in
  19. import IO STDIN.eachLine { line in print("PRINTING: \(line)") } file.eachLine

    { line in print("PRINTING: \(line)") } Marin Usalj - @supermarin - supermar.in
  20. import IO readLines(stdin) { line in print("PRINTING: \(line)") } readLines(file)

    { line in print("PRINTING: \(line)") } Marin Usalj - @supermarin - supermar.in
  21. Imagine building an alarm clock that also talks to your

    coffee machine through it's API and requests a Lyft after you drink the coffee. Marin Usalj - @supermarin - supermar.in
  22. TESTS !"" slug #"" bin #"" lib !"" test Marin

    Usalj - @supermarin - supermar.in