the command line toolbox
play

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


  1. The Command Line Toolbox A Crash Course on building your own CLI tools

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

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

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

  5. ! Running Scripts ! Building Interfaces ! Effective I/O

  6. ! Running Scripts

  7. ! Running Scripts 1. Single executable file 2. Swift Package Manager

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

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

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

  11. Getting Fancy 1. chmod +x FoodPls.swift

  12. Getting Fancy 1. chmod +x FoodPls.swift 2. #!/usr/bin/xcrun swift

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

  14. Getting Fancy 1. chmod +x FoodPls.swift 2. #!/usr/bin/xcrun swift 3. cp FoodPls.swift /usr/local/bin/foodpls ! > foodpls

  15. Swift Package Manger Executable target

  16. Swift Package Manger Executable target — from scratch > xcrun swift package init --type=executable

  17. Swift Package Manger Executable target — existing package targets: [ .target(name: "foodpls", dependencies: []), // ... ] // Sources/foodpls/main.swift

  18. Swift Package Manager "Installing" the binary > swift build > cp .build/debug/foodpls /usr/local/bin/

  19. ! Marathon by John Sundell github.com/JohnSundell/Marathon

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

  21. ! Marathon ! > marathon run FoodPls.swift

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

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

  24. ! Marathon import Files // marathon:https://github.com/JohnSundell/Files.git

  25. ! Marathon import Files // marathon:https://github.com/JohnSundell/Files.git > marathon add https://github.com/JohnSundell/Files.git

  26. ! Marathon > marathon install https://example.com/script.swift ! GitHub Gists:

  27. ! Mint by Yonas Kolb github.com/yonaskolb/mint

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

  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

  30. ! Mint > mint run realm/SwiftLint@0.22.0 swiftlint

  31. ! Mint Mintfile Carthage/Carthage realm/SwiftLint krzysztofzablocki/Sourcery@0.15.0 ... ! Better than a readme

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

  33. ! Building Interfaces

  34. ! Building Interfaces Complex Behavior + Good UI =

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

  36. ! Building Interfaces > todos add "buy milk"

  37. ! Building Interfaces > todos add "buy milk" 1. DIY sub-commands 2. Packages

  38. DIY Sub-Commands let args = CommandLine.arguments.dropfirst()

  39. DIY Sub-Commands let args = CommandLine.arguments.dropfirst() dropFirst : removes program name

  40. DIY Sub-Commands let args = CommandLine.arguments.dropfirst() let cmd = args[0] !

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

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

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

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

  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!

  46. Commander by Kyle Fuller github.com/kylef/Commander

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

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

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

  50. Commandant by Carthage github.com/Carthage/Commandant

  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) }

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

  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") } } }

  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"

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

  56. ! Beak by Yonas Kolb github.com/yonaskolb/beak

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

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

  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"

  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"

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

  62. ! Effective I/O

  63. ! Effective I/O 1. stdin 2. stderr 3. Exit codes 4. Files

  64. stdin func readline() -> String?

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

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

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

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

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

  70. stderr

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

  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 }

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

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

  75. Error Codes

  76. Error Codes fatalError("oops!") ! Lots of stack dump info

  77. Error Codes import Darwin exit(-1)

  78. Files by John Sundell github.com/JohnSundell/Files

  79. Files Wrapper around Foundation APIs ! Very convenient to use

Recommend


More recommend