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))
$ 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
● 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
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
Create a Xcode project!
● New Project
● Mac > Command Line Tool
● 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
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?")
}
}
🙈 No tests!
● Yes, we can create a Test Target
● Problem: no way to reach our code (no module defined)
🙈 No tests!
● “Solution”: add each file to the Test target’s (🙈 x 2)
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
The Static Library route
● Add a Library target
● Static library
● Move all code there
● Add a test target for your library
🥇 Best way to modularize?
● SPM!
○ For 3rd party dependencies
○ For our own code (local packages)
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
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
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
🖥 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)
🖥 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
🔍 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.
🔍 Argument parsing
● 🎁 https://github.com/apple/swift-argument-parser
● make our main struct conform to ParsableCommand
import ArgumentParser
@main
struct MainChiquitoSay: ParsableCommand {
...
🔍 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
🗂 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
}
}
}
🚀 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)")
}
}
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
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
If you want more...
https://github.com/migueldeicaza/TermKit
https://github.com/migueldeicaza/TurboSwift
...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
Thank you!
That’s all folks
Diego Freniche | Realm Mobile Developer Advocate | @dfreniche