The Command Line Toolbox A Crash Course on building your own CLI - - PowerPoint PPT Presentation

the command line toolbox
SMART_READER_LITE
LIVE PREVIEW

The Command Line Toolbox A Crash Course on building your own CLI - - PowerPoint PPT Presentation

The Command Line Toolbox A Crash Course on building your own CLI tools Michael Bates !" #$ Kentucky Swing Dance Learn Swift Louisville Organizer " Day job: Python & JS CLI tools: Swift Goals Learn Tips & Tricks


slide-1
SLIDE 1

The Command Line Toolbox

A Crash Course on building your own CLI tools

slide-2
SLIDE 2

Michael Bates Kentucky !" Swing Dance #$ Learn Swift Louisville Organizer Day job: Python & JS CLI tools: Swift "

slide-3
SLIDE 3

Goals Learn Tips & Tricks Discover Open Source Packages Build your own tools!

slide-4
SLIDE 4

Command Lines Shells Necessary ! Super-powerful ! Not great languages !

slide-5
SLIDE 5

! Running Scripts ! Building Interfaces ! Effective I/O

slide-6
SLIDE 6

! Running Scripts

slide-7
SLIDE 7

! Running Scripts

  • 1. Single executable file
  • 2. Swift Package Manager
slide-8
SLIDE 8

Single Executable File

//FoodPls.swift print(" !"#$%&'()*+,-./01 ".randomElement()!)

slide-9
SLIDE 9

Single Executable File

//FoodPls.swift print(" !"#$%&'()*+,-./01 ".randomElement()!) > swift FoodPls.swift !

slide-10
SLIDE 10

Single Executable File

//FoodPls.swift print(" !"#$%&'()*+,-./01 ".randomElement()!) > swift FoodPls.swift ! > foodpls "

slide-11
SLIDE 11

Getting Fancy

  • 1. chmod +x FoodPls.swift
slide-12
SLIDE 12

Getting Fancy

  • 1. chmod +x FoodPls.swift
  • 2. #!/usr/bin/xcrun swift
slide-13
SLIDE 13

Getting Fancy

  • 1. chmod +x FoodPls.swift
  • 2. #!/usr/bin/xcrun swift
  • 3. cp FoodPls.swift /usr/local/bin/foodpls
slide-14
SLIDE 14

Getting Fancy

  • 1. chmod +x FoodPls.swift
  • 2. #!/usr/bin/xcrun swift
  • 3. cp FoodPls.swift /usr/local/bin/foodpls

> foodpls !

slide-15
SLIDE 15

Swift Package Manger Executable target

slide-16
SLIDE 16

Swift Package Manger Executable target — from scratch

> xcrun swift package init --type=executable

slide-17
SLIDE 17

Swift Package Manger Executable target — existing package

targets: [ .target(name: "foodpls", dependencies: []), // ... ] // Sources/foodpls/main.swift

slide-18
SLIDE 18

Swift Package Manager "Installing" the binary

> swift build > cp .build/debug/foodpls /usr/local/bin/

slide-19
SLIDE 19

Marathon !

by John Sundell github.com/JohnSundell/Marathon

slide-20
SLIDE 20

Marathon ! ✔ Write, run, and install single-file scripts ✔ Install dependencies

slide-21
SLIDE 21

Marathon !

> marathon run FoodPls.swift !

slide-22
SLIDE 22

Marathon !

> marathon edit FoodPls.swift ! Updating packages... ✏ Opening foodpls.xcodeproj/

slide-23
SLIDE 23

Marathon !

> marathon install FoodPls.swift ! Compiling script... Installing binary... " FoodPls.swift installed at /usr/local/bin/foodpls

slide-24
SLIDE 24

Marathon !

import Files // marathon:https://github.com/JohnSundell/Files.git

slide-25
SLIDE 25

Marathon !

import Files // marathon:https://github.com/JohnSundell/Files.git

> marathon add https://github.com/JohnSundell/Files.git

slide-26
SLIDE 26

Marathon !

> marathon install https://example.com/script.swift

GitHub Gists: !

slide-27
SLIDE 27

Mint !

by Yonas Kolb github.com/yonaskolb/mint

slide-28
SLIDE 28

Mint ! Install executables from any Swift Package — Carthage — SwiftLint — Sourcery — Your own tools!

slide-29
SLIDE 29

Mint !

> mint install realm/SwiftLint ! Finding latest version of SwiftLint ! Resolved latest version of SwiftLint to 0.28.0 ! Cloning https://github.com/realm/SwiftLint.git 0.28.0... ! Building SwiftLint Package with SPM... ! Installing SwiftLint... ! Installed SwiftLint 0.28.0 ! Linked swiftlint 0.28.0 to /usr/local/bin. > swiftlint version 0.28.0

slide-30
SLIDE 30

Mint !

> mint run realm/SwiftLint@0.22.0 swiftlint

slide-31
SLIDE 31

Mint ! Mintfile

Carthage/Carthage realm/SwiftLint krzysztofzablocki/Sourcery@0.15.0 ...

Better than a readme !

slide-32
SLIDE 32

Gotchas — Mint ! and Marathon Building from source Check your version Swiftenv considered harmful: Package.swift not found

slide-33
SLIDE 33

! Building Interfaces

slide-34
SLIDE 34

! Building Interfaces Complex Behavior + Good UI =

slide-35
SLIDE 35

! Building Interfaces Complex Behavior + Good UI = GUI ! Animations CLI ! Sub-commands

slide-36
SLIDE 36

! Building Interfaces

> todos add "buy milk"

slide-37
SLIDE 37

! Building Interfaces

> todos add "buy milk"

  • 1. DIY sub-commands
  • 2. Packages
slide-38
SLIDE 38

DIY Sub-Commands

let args = CommandLine.arguments.dropfirst()

slide-39
SLIDE 39

DIY Sub-Commands

let args = CommandLine.arguments.dropfirst()

dropFirst: removes program name

slide-40
SLIDE 40

DIY Sub-Commands

let args = CommandLine.arguments.dropfirst() let cmd = args[0]

!

slide-41
SLIDE 41

DIY Sub-Commands

let args = CommandLine.arguments.dropfirst() enum Command: String { case add // rm, list, etc. } let cmd = args.first.map(Command(rawValue:))

slide-42
SLIDE 42

DIY Sub-Commands

switch cmd { case nil: fatalError("Unrecognized command") case .add: addTodo(title: CommandLine.arguments[1]) }

slide-43
SLIDE 43

DIY Sub-Commands

switch cmd { case nil: fatalError("Unrecognized command") case .add: addTodo(title: CommandLine.arguments[1]) } > todos add "buy milk"

slide-44
SLIDE 44

DIY Sub-Commands Good for simple scripts ! Doesn't scale well ! Manual type conversion !

slide-45
SLIDE 45

DIY Sub-Commands

> todos help Available commands: add Create a new task do Complete tasks by ID edit Change the title of a task ls List outstanding tasks rm Remove tasks undo Un-complete tasks by ID

See mklbtz/finch for a full implementation of this!

slide-46
SLIDE 46

Commander

by Kyle Fuller github.com/kylef/Commander

slide-47
SLIDE 47

Commander

import Commander Group { $0.command("add") { (title: String) in addTask(title: title) } }.run()

slide-48
SLIDE 48

Commander

import Commander Group { $0.command("add") { (title: String) in addTask(title: title) } }.run() > todos add "buy milk"

slide-49
SLIDE 49

Commander Type inference ! Good for medium complexity Automatic Numeric & Array conversion

slide-50
SLIDE 50

Commandant

by Carthage github.com/Carthage/Commandant

slide-51
SLIDE 51

Commandant

import Commandant let commands = CommandRegistry<String>() commands.register(AddCommand(manager: try TaskManager())) commands.register(HelpCommand(registry: commands)) commands.main(defaultVerb: "help") { error in print(error) }

slide-52
SLIDE 52

Commandant

struct AddCommand: CommandProtocol { let verb = "add" let function = "Create a new task" func run(_ options: Options) -> Result<Void, String> { // ... } }

slide-53
SLIDE 53

Commandant

extension AddCommand { struct Options: OptionsProtocol { let title: String static func evaluate(_ m: CommandMode)

  • > Result<Options, CommandantError<String>> {

return Options.init <*> m <|* Argument<String>(usage: "Title for task") } } }

slide-54
SLIDE 54

Commandant

extension AddCommand { struct Options: OptionsProtocol { let title: String static func evaluate(_ m: CommandMode)

  • > Result<Options, CommandantError<String>> {

return Options.init <*> m <|* Argument<String>(usage: "Title for task") } } }

> todos add "buy milk"

slide-55
SLIDE 55

Commandant Good for complex tools with lots of options Battle-tested by Carthage ! Custom operators !

slide-56
SLIDE 56

Beak !

by Yonas Kolb github.com/yonaskolb/beak

slide-57
SLIDE 57

Beak ! Static-analysis with SourceKit Generated interface & help Dependency management like Mint

slide-58
SLIDE 58

Beak !

// beak.swift /// Create a new task public func add(title: String) {}

slide-59
SLIDE 59

Beak !

// beak.swift /// Create a new task public func add(title: String) {} > beak list Functions: add: Create a new task > beak run add --title="buy milk"

slide-60
SLIDE 60

Beak !

// beak.swift /// Create a new task public func add(_ title: String) {} > beak list Functions: add: Create a new task > beak run add "buy milk"

slide-61
SLIDE 61

Beak ! Great for task-runners ! Use with Mint for super-productivity

slide-62
SLIDE 62

! Effective I/O

slide-63
SLIDE 63

! Effective I/O

  • 1. stdin
  • 2. stderr
  • 3. Exit codes
  • 4. Files
slide-64
SLIDE 64

stdin

func readline() -> String?

slide-65
SLIDE 65

stdin

print("What is your name?") print("> ", terminator: "") let name = readLine() ?? "stranger" print("Hello, \(name)")

slide-66
SLIDE 66

stdin

print("What is your name?") print("> ", terminator: "") let name = readLine() ?? "stranger" print("Hello, \(name)") What is your name? > Michael Hello, Michael

slide-67
SLIDE 67

stdin

print("What is your name?") print("> ", terminator: "") let name = readLine() ?? "stranger" print("Hello, \(name)") What is your name? > ^D Hello, stranger

slide-68
SLIDE 68

stdin

while let input = readLine() { // ... }

slide-69
SLIDE 69

stdin

sequence(first: "", next: { _ in readLine(strippingNewline: false) }).joined()

slide-70
SLIDE 70

stderr

slide-71
SLIDE 71

stderr

func print<Target>(_: Any..., to: inout Target) where Target : TextOutputStream

slide-72
SLIDE 72

stderr

func print<Target>(_: Any..., to: inout Target) where Target : TextOutputStream

/// Returns the file handle associated with the standard error file class var standardError: FileHandle { get }

slide-73
SLIDE 73

stderr

extension FileHandle: TextOutputStream { public func write(_ string: String) { if let data = string.data(using: .utf8) { self.write(data) } } }

slide-74
SLIDE 74

stderr

func errorPrint(_ item: Any) { var stderr = FileHandle.standardError print(item, to: &stderr) }

slide-75
SLIDE 75

Error Codes

slide-76
SLIDE 76

Error Codes

fatalError("oops!")

Lots of stack dump info !

slide-77
SLIDE 77

Error Codes

import Darwin exit(-1)

slide-78
SLIDE 78

Files

by John Sundell github.com/JohnSundell/Files

slide-79
SLIDE 79

Files Wrapper around Foundation APIs Very convenient to use !

slide-80
SLIDE 80

Files

let folder = try Folder(path: "/users/john/folder") let file = try folder.createFile(named: "file.json") try file.write(string: "{\"hello\": \"world\"}") try file.delete() try folder.delete()

slide-81
SLIDE 81

Files

for file in try Folder(path: "MyFolder").files { try file.rename(to: file.nameWithoutExtension.capitalized) }

slide-82
SLIDE 82

Files

let origin = try Folder(path: "/users/john/folderA") let target = try Folder(path: "/users/john/folderB") try origin.files.move(to: target)

slide-83
SLIDE 83

Files ✔ SPM ✔ Carthage ✔ CocoaPods

slide-84
SLIDE 84

Wrap-up

slide-85
SLIDE 85

Wrap-up 1. ! Running Scripts — single-files & packages — JohnSundell/Marathon — yonaskolb/Mint #

slide-86
SLIDE 86

Wrap-up

  • 1. Building Interfaces

— DIY — kylef/commander — Carthage/Commandant — yonaskolb/beak

slide-87
SLIDE 87

Wrap-up

  • 1. Effective I/O

— stdin — stderr — error codes — JohnSundell/Files

slide-88
SLIDE 88

The Command Line Toolbox

A Crash Course on building your own CLI tools Michael Bates — @mklbtz — mklbtz.com