Publicidad
Publicidad

Más contenido relacionado

Publicidad

Último(20)

Publicidad

MobileConf 2021 Slides: Let's build macOS CLI Utilities using Swift

  1. Let's build macOS CLI Utilities ...using Swift! Diego Freniche | Realm Developer Advocate | @dfreniche
  2. Why CLI tools in Swift? ● small scripts to scratch an itch ● to automate things ● because they look cool
  3. Demo Time! ● based on Cowsay ● brew install cowsay ● for extra dramatism try cowsay Hello, World && say "Hello, World"
  4. Demo Time!
  5. The quick & dirty Just-One-File approach #!/usr/bin/swift func fibonacci(_ n: Int) -> Int { if n <= 2 { return 1 } else { return fibonacci(n - 1) + fibonacci(n - 2) } } print(fibonacci(10))
  6. $ swift fibonacci.swift lib/swift -module-name fibonacci -target-sdk-version 12.0.0 55 Running it! $ swift -v fibonacci.swift Apple Swift version 5.5 (swiftlang-1300.0.27.6 clang-1300.0.27.2) Target: x86_64-apple-macosx11.0 /Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchai n/usr/bin/swift-frontend -frontend -interpret fibonacci.swift -enable-objc-interop -stack-check -sdk /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Develope r/SDKs/MacOSX12.0.sdk -color-diagnostics -new-driver-path /Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchai n/usr/bin/swift-driver -resource-dir /Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchai n/usr/lib/swift -module-name fibonacci -target-sdk-version 12.0.0
  7. ● only one file per script ● no include other .swift file ● no easy way to add 3rd party code ● We need to launch it using a bash script most likely * * Hint: we don’t want bash, that’s the whole point of this talk! ⚠ Limitations of Swift Scripts
  8. Command line tools 🛠 are software. Structuring their code in massive files that read like bash scripts is missing an opportunity to leverage the abstractions of the programming language and platform to have a codebase that is easier to reason about. * * https://twitter.com/pedropbuendia/status/1230414793191890950?s=20 🙏Always write good code
  9. Create a Xcode project! ● New Project ● Mac > Command Line Tool
  10. ● put code in main.swift ● hit Cmd + R ● watch for output in Xcode's Terminal ● Starting point: main.swift CLI tool new project: Apple Template
  11. Program Starting point, a better way // We can call this file whatever we want, just not main.swift // also, no code can be in main.swift import Foundation @main struct MainChiquitoSay { static func main() { print("Tea duck queen?") } }
  12. 🙈 No tests! ● Yes, we can create a Test Target ● Problem: no way to reach our code (no module defined)
  13. 🙈 No tests! ● “Solution”: add each file to the Test target’s (🙈 x 2)
  14. We need a better way! Modularize the code!
  15. How to add tests? Modules! ● Create a Framework and use it from the CLI app. ● BUT ○ We need to install that Framework in our system (dynamically linked) ● We can do the same with a Static Library
  16. The Static Library route ● Add a Library target ● Static library ● Move all code there ● Add a test target for your library
  17. 🥇 Best way to modularize? ● SPM! ○ For 3rd party dependencies ○ For our own code (local packages)
  18. Managing 3rd party dependencies Go to Project > Swift Packages 1. Add new packages 2. See list of packages 3. Change versions, etc. File > Packages: bunch of interesting options
  19. My default Pack Of Packages 🎁 ● swift-argument-parser: ○ CLI arguments parsing, help, etc. ○ https://github.com/apple/swift-argument-parser ● ANSITerminal: ○ Prints ANSI escape sequences, easy ○ https://github.com/pakLebah/ANSITerminal ● Files: ○ Makes using Files and Folders easy ○ https://github.com/johnsundell/files ● SwiftFigletKit: ○ https://github.com/dfreniche/SwiftFiglet
  20. Main pieces of the CLI puzzle 🧩 🖥 Printing/reading to/from console 🎨 Nice ANSI colors 🔍 Argument Parsing 🗂 Working with Files and Folders 🚀 Launching other Processes 󰝊 Background Jobs
  21. 🖥 Printing to Console ● just use good old print ● use print(message, separator: "", terminator: "") if you don't want CR + LF before your message ● 🎨 Nice ANSI colors if possible ○ 🎁 https://github.com/pakLebah/ANSITerminal ○ print("ChiquitoSay starting".green)
  22. 🖥 Reading from Console ● readLine(), readLine(strippingNewline: true) ● returns String?, so better do readLine() && "" ● readLine does not work in Playgrounds 🙈 https://developer.apple.com/documentation/swift/1641199-readline
  23. 🔍 Argument parsing ARGUMENTS: <image-url> URL with the image to show <message> The message to print OPTIONS: --print-banner Print a text banner instead of image --say-text Say text aloud -h, --help Show help information.
  24. 🔍 Argument parsing ● 🎁 https://github.com/apple/swift-argument-parser ● make our main struct conform to ParsableCommand import ArgumentParser @main struct MainChiquitoSay: ParsableCommand { ...
  25. 🔍 Argument parsing import ArgumentParser @main struct MainChiquitoSay: ParsableCommand { @Flag(help: "Print a text banner instead of image") var printBanner = false @Argument(help: "URL with the image to show") var imageURL: String
  26. Passing arguments from Xcode
  27. 🗂 Working with Files and Folders SPM: https://github.com/JohnSundell/Files extension Folder { public static func cd(path: String) -> Folder { do { let parent = try Folder.init(path: path) return parent } catch { return Folder.current } } }
  28. 🚀 Launching other processes /// - command: full path to the program we want to launch /// - arguments: Array of arguments passed to that program static func launch(command: String, arguments:[String] = []) { let url = URL(fileURLWithPath: command) do { try Process.run(url, arguments: arguments) { (process) in print("ndidFinish: (!process.isRunning)") } } catch { print("Error opening file (error)") } }
  29. 󰝊Background Jobs
  30. 󰝊Background Jobs ● we can't use GCD here ● If we launch a background process it will be killed: our app will continue and finish before the BG process finishes ● So we need a way to wait ● we'll use Semaphores more info on GCD + CLI here: https://stackoverflow.com/questions/8366195/using-grand-central-dispatch-outsi de-of-an-application-or-runloop
  31. 󰝊Background Jobs let sema = DispatchSemaphore( value: 0) // define a background task let task = URLSession.shared.dataTask(with: imageURL) { (data, response, error) in sema.wait() // decrement semaphore, no more operations at the time // do something that takes time sema.signal() end = true // signals the process to continue }; task.resume() // launch the task in a new thread
  32. “Continuous Integration” LOL
  33. If you want more... https://github.com/migueldeicaza/TermKit https://github.com/migueldeicaza/TurboSwift
  34. ...and more ● fdir: ○ Lists directory contents adding comments to files ○ https://github.com/dfreniche/fdir ● ChiquitoSay: ○ A meme creator ○ https://github.com/dfreniche/chiquitosay ● Memo: ○ A really minimalistic memo app that stores messages in a local Realm DB ○ https://github.com/dfreniche/memo
  35. Thank you! That’s all folks Diego Freniche | Realm Mobile Developer Advocate | @dfreniche
Publicidad