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. Swift
    FOR COMMAND-LINE TOOLS
    Marin Usalj - @supermarin - supermar.in

    View Slide

  2. Swift
    FOR COMMAND-LINE TOOLS
    Marin Usalj - @supermarin - supermar.in

    View Slide

  3. EVOLUTION
    Marin Usalj - @supermarin - supermar.in

    View Slide

  4. /Applications/Xcode6-Beta1.app/Contents/
    Developer/usr/bin/xcrun swift -i
    hello.swift
    Marin Usalj - @supermarin - supermar.in

    View Slide

  5. xcrun swift -i hello.swift
    Marin Usalj - @supermarin - supermar.in

    View Slide

  6. swift -i hello.swift
    Marin Usalj - @supermarin - supermar.in

    View Slide

  7. swift hello.swift
    Marin Usalj - @supermarin - supermar.in

    View Slide

  8. XCODE-LESS DEVELOPMENT
    Marin Usalj - @supermarin - supermar.in

    View Slide

  9. SCRIPTING VS
    COMPILING
    Marin Usalj - @supermarin - supermar.in

    View Slide

  10. INTERPRETED LANGUAGES
    $ruby script.rb
    #Hello world!
    Marin Usalj - @supermarin - supermar.in

    View Slide

  11. INTERPRETED LANGUAGES
    $python script.py
    #Hello workd!
    Marin Usalj - @supermarin - supermar.in

    View Slide

  12. Marin Usalj - @supermarin - supermar.in

    View Slide

  13. COMPILED LANGUAGES
    $ clang -lobjc -framework Foundation -c script.m -o script.o
    $ chmod +x script.o
    $ ./script.o
    Marin Usalj - @supermarin - supermar.in

    View Slide

  14. GOLANG
    $ go run script.go
    Marin Usalj - @supermarin - supermar.in

    View Slide

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

    View Slide

  16. GOLANG
    $ go install script.go
    $ script
    Marin Usalj - @supermarin - supermar.in

    View Slide

  17. SWIFT
    WENT IN THE GO DIRECTION
    Marin Usalj - @supermarin - supermar.in

    View Slide

  18. SWIFT
    # Execute inline
    $ swift todos.swift
    Marin Usalj - @supermarin - supermar.in

    View Slide

  19. SWIFT
    # Compile and run
    - $ swiftc todos.swift -o todos
    - chmod +x todos
    - todos
    Marin Usalj - @supermarin - supermar.in

    View Slide

  20. CAVEATS
    $ time swift hello.swift
    Hello !
    0.33 real 0.04 user 0.05 sys
    Marin Usalj - @supermarin - supermar.in

    View Slide

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

    View Slide

  22. CLI 101
    Marin Usalj - @supermarin - supermar.in

    View Slide

  23. Marin Usalj - @supermarin - supermar.in

    View Slide

  24. #!/usr/bin/env swift
    var args = Process.arguments[1..var name: String? = join(" ", args)
    println("Hello \(name!)!")
    Marin Usalj - @supermarin - supermar.in

    View Slide

  25. $ ./hello.swift SLUG
    Hello SLUG!
    Marin Usalj - @supermarin - supermar.in

    View Slide

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

    View Slide

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

    View Slide

  28. SHEBANG1
    $ swift hello.swift
    BECOMES
    $ ./hello
    1 Also known as Hashbang, or even pound-bang (Wikipedia)
    Marin Usalj - @supermarin - supermar.in

    View Slide

  29. CHMOD
    $ chmod +x hello
    $ ./hello
    Marin Usalj - @supermarin - supermar.in

    View Slide

  30. $PATH
    $ export PATH=~/bin:$PATH
    Tell your OS where to find the custom scripts
    Marin Usalj - @supermarin - supermar.in

    View Slide

  31. STATUS CODES
    $? == 0 !
    $? != 0 "
    Marin Usalj - @supermarin - supermar.in

    View Slide

  32. STATUS CODES
    if drink == !
    println(“NEED SCOTCH”)
    exit(1)
    else
    # do work
    exit(0)
    end
    Marin Usalj - @supermarin - supermar.in

    View Slide

  33. 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

    View Slide

  34. OPTION PARSING
    DON’T WRITE BY HAND. USE FRAMEWORKS
    Marin Usalj - @supermarin - supermar.in

    View Slide

  35. CLIKIT
    KYLE FULLER
    Marin Usalj - @supermarin - supermar.in

    View Slide

  36. 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

    View Slide

  37. OPTIONKIT
    ALEXANDROS SALAZAR
    Marin Usalj - @supermarin - supermar.in

    View Slide

  38. 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..let result = parser.parse(actualArguments)
    Marin Usalj - @supermarin - supermar.in

    View Slide

  39. POSIX FLAGS
    --FLAG
    -F
    Marin Usalj - @supermarin - supermar.in

    View Slide

  40. POSIX FLAGS
    --VIEW --CONTROLLER
    -VC
    Marin Usalj - @supermarin - supermar.in

    View Slide

  41. DOCUMENTATION
    Marin Usalj - @supermarin - supermar.in

    View Slide

  42. git --help
    man git
    Marin Usalj - @supermarin - supermar.in

    View Slide

  43. 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

    View Slide

  44. OPTION PARSER GENERATES
    DOCS FOR YOU
    Marin Usalj - @supermarin - supermar.in

    View Slide

  45. IO
    THE MOST IMPORTANT PART OF A CLI TOOL
    Marin Usalj - @supermarin - supermar.in

    View Slide

  46. IO
    SPAWNING (POPEN)
    Marin Usalj - @supermarin - supermar.in

    View Slide

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

    View Slide

  48. READING / WRITING
    Marin Usalj - @supermarin - supermar.in

    View Slide

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

    View Slide

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

    View Slide

  51. FILE
    FILESYSTEM TRAVERSAL
    Marin Usalj - @supermarin - supermar.in

    View Slide

  52. NSFILEMANAGER APIS
    Marin Usalj - @supermarin - supermar.in

    View Slide

  53. NSFILEMANAGER APIS
    Marin Usalj - @supermarin - supermar.in

    View Slide

  54. File.join(Dir.home, "code")
    # ~/code
    File.expand_path("~/code/file.rb")
    # /Users/supermarin/code/file.rb
    Marin Usalj - @supermarin - supermar.in

    View Slide

  55. Pain points
    Marin Usalj - @supermarin - supermar.in

    View Slide

  56. Marin Usalj - @supermarin - supermar.in

    View Slide

  57. EXTERNAL LIBRARIES
    Marin Usalj - @supermarin - supermar.in

    View Slide

  58. 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

    View Slide

  59. RUBYGEMS
    COCOAPODS
    Marin Usalj - @supermarin - supermar.in

    View Slide

  60. PEOPLE ABUSING /LIBRARY/FRAMEWORKS
    REQUIRES SUDO
    Marin Usalj - @supermarin - supermar.in

    View Slide

  61. FILE STRUCTURE
    --XCODE--
    Marin Usalj - @supermarin - supermar.in

    View Slide

  62. FILE STRUCTURE
    !"" slug
    #"" bin
    !"" lib
    Marin Usalj - @supermarin - supermar.in

    View Slide

  63. TESTS
    Marin Usalj - @supermarin - supermar.in

    View Slide

  64. TESTS
    !"" slug
    #"" bin
    #"" lib
    !"" test
    Marin Usalj - @supermarin - supermar.in

    View Slide

  65. $ swift test
    Marin Usalj - @supermarin - supermar.in

    View Slide

  66. STRING MANIPULATION
    Marin Usalj - @supermarin - supermar.in

    View Slide

  67. REGEX
    Marin Usalj - @supermarin - supermar.in

    View Slide

  68. DISTRIBUTING FRAMEWORKS
    Marin Usalj - @supermarin - supermar.in

    View Slide

  69. Q/A
    MARIN USALJ | @SUPERMARIN | SUPERMAR.IN
    Marin Usalj - @supermarin - supermar.in

    View Slide

  70. Thank you
    Marin Usalj - @supermarin - supermar.in

    View Slide