diff --git a/getting-started/_installing.md b/_includes/getting-started/_installing.md similarity index 84% rename from getting-started/_installing.md rename to _includes/getting-started/_installing.md index 4e642e8f2..d5ed961b3 100644 --- a/getting-started/_installing.md +++ b/_includes/getting-started/_installing.md @@ -1,6 +1,8 @@ ## Installing Swift -If you don't have Swift installed, [install it first](/install). To test that you have Swift installed, run `swift --version` in the terminal. +If you don't have Swift installed, [install it first](/install). + +To test that you have Swift installed, run `swift --version` in the terminal. ### Swift Package Manager diff --git a/getting-started/_use-cases.html b/getting-started/_use-cases.html index f07832daf..bb51e71b6 100644 --- a/getting-started/_use-cases.html +++ b/getting-started/_use-cases.html @@ -3,32 +3,42 @@ Below are some examples of the many use cases of Swift. \ No newline at end of file + diff --git a/getting-started/cli-swiftpm/index.md b/getting-started/cli-swiftpm/index.md new file mode 100644 index 000000000..0c48e6e74 --- /dev/null +++ b/getting-started/cli-swiftpm/index.md @@ -0,0 +1,230 @@ +--- +layout: page +title: Build a Command-line Tool +--- + +{% include getting-started/_installing.md %} + +## Bootstrapping + +Let’s write a small application with our new Swift development environment. +To start, we’ll use SwiftPM to make a new project for us. In your terminal of choice run: + +~~~bash +❯ mkdir MyCLI +❯ cd MyCLI +❯ swift package init --name MyCLI --type executable +~~~ + +This will generate a new directory called MyCLI with the following files: + +~~~no-highlight +. +├── Package.swift +├── README.md +├── Sources +│   └── MyCLI +│   └── MyCLI.swift +└── Tests + └── MyCLITests + └── MyCLITests.swift +~~~ + +`Package.swift` is the manifest file for Swift. It’s where you keep metadata for your project, as well as dependencies. + +`Sources/MyCLI/MyCLI.swift` is the application entry point and where we’ll write our application code. +`Test/MyCLITests/MyCLITests.swift` is where we can write tests for our application. + +In fact, SwiftPM generated a "Hello, world!" project for us, including some unit tests! + +We can run the tests by running `swift test` in our terminal. + +~~~bash +❯ swift test +Building for debugging... +[6/6] Linking MyCLIPackageTests +Build complete! (16.53s) +Test Suite 'All tests' started at 2023-01-12 13:38:22.393 +Test Suite 'MyCLIPackageTests.xctest' started at 2023-01-12 13:38:22.394 +Test Suite 'MyCLITests' started at 2023-01-12 13:38:22.394 +Test Case '-[MyCLITests.MyCLITests testExample]' started. +Test Case '-[MyCLITests.MyCLITests testExample]' passed (0.003 seconds). +Test Suite 'MyCLITests' passed at 2023-01-12 13:38:22.397. + Executed 1 test, with 0 failures (0 unexpected) in 0.003 (0.003) seconds +Test Suite 'MyCLIPackageTests.xctest' passed at 2023-01-12 13:38:22.398. + Executed 1 test, with 0 failures (0 unexpected) in 0.003 (0.004) seconds +Test Suite 'All tests' passed at 2023-01-12 13:38:22.398. + Executed 1 test, with 0 failures (0 unexpected) in 0.003 (0.005) seconds +~~~ + +We can also run the program by running `swift run` in our terminal. + +~~~bash +❯ swift run MyCLI +[3/3] Linking MyCLI +Hello, World! +~~~ + +## Adding dependencies + +Swift based applications are usually composed from libraries that provide useful functionality. + +In this project, we’ll use a package called [swift-figlet](https://github.com/tomerd/swift-figlet) which will help us make ASCII art. + +You can find more interesting libraries on [Swift Package Index](https://swiftpackageindex.com) -- the unofficial package index for Swift. + +To do so, we extend our `Package.swift` file with the following information: + +~~~swift +// swift-tools-version: 5.7 + +import PackageDescription + +let package = Package( +name: "MyCLI", + products: [ + .executable(name: "MyCLI", targets: ["MyCLI"]) + ], + dependencies: [ + .package(url: "https://github.com/tomerd/swift-figlet", branch: "main"), + ], + targets: [ + .executableTarget( + name: "MyCLI", + dependencies: [ + .product(name: "Figlet", package: "swift-figlet"), + ] + ), + .testTarget( + name: "MyCLITests", + dependencies: ["MyCLI"] + ), + ] +) +~~~ + +Running `swift build` will instruct SwiftPM to install the new dependencies and then proceed to build the code. + +Running this command also created a new file for us, `Package.resolved`. +This file is a snapshot of the exact versions of the dependencies we are using locally. + +To use this dependency, we can open `MyCLI.swift`, remove everything that’s in there (it’s just an example), and add this line to it: + +~~~swift +import Figlet +~~~ + +This line means that we can now use the `Figlet` module that the `swift-figlet` package exports. + +## A small application + +Now let’s write a small application with our new dependency. In our `MyCLI.swift`, add the following code: + +~~~swift +import Figlet // from the previous step + +@main +struct FigletTool { + static func main() { + Figlet.say("Hello, Swift!") + } +} +~~~ + +Now lets remove the default unit test since we changes the tools' code. +Replace the example content of `MyCLITests.swift` with the following code: + +~~~swift +@testable import MyCLI +import XCTest + +final class MyCLITests: XCTestCase {} +~~~ + +Once we save that, we can run our application with `swift run` +Assuming everything went well, you should see your application print this to the screen: + +~~~no-highlight +_ _ _ _ _ __ _ _ +| | | | ___ | | | | ___ ___ __ __ (_) / _| | |_ | | +| |_| | / _ \ | | | | / _ \ / __| \ \ /\ / / | | | |_ | __| | | +| _ | | __/ | | | | | (_) | _ \__ \ \ V V / | | | _| | |_ |_| +|_| |_| \___| |_| |_| \___/ ( ) |___/ \_/\_/ |_| |_| \__| (_) + |/ +~~~ + +## Argument parsing + +Most command line tools need to be able to parse command line arguments. + +To add this capability to our application, we add a dependency on [swift-argument-parser](https://github.com/apple/swift-argument-parser). + +To do so, we extend our `Package.swift` file with the following information: + +~~~swift +// swift-tools-version: 5.7 + +import PackageDescription + +let package = Package( + name: "swift-swift", + dependencies: [ + .package(url: "https://github.com/tomerd/swift-figlet", branch: "main"), + .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"), + ], + products: [ + .executable(name: "MyCLI", targets: ["MyCLI"]) + ], + targets: [ + .executableTarget( + name: "MyCLI", + dependencies: [ + .product(name: "Figlet", package: "swift-figlet"), + .product(name: "ArgumentParser", package: "swift-argument-parser"), + ] + ), + .testTarget( + name: "MyCLITests", + dependencies: ["MyCLI"] + ), + ] +) +~~~ + +We can now import the argument parsing module provided by `swift-argument-parser` and use it in our application: + +~~~swift +import ArgumentParser +import Figlet + +@main +struct FigletTool: ParsableCommand { + @Option(help: "Specify the input") + public var input: String + + public func run() throws { + Figlet.say(self.input) + } +} +~~~ + +For more information about how [swift-argument-parser](https://github.com/apple/swift-argument-parser) parses command line options, see [swift-argument-parser documentation](https://github.com/apple/swift-argument-parser) documentation. + +Once we save that, we can run our application with `swift run MyCLI --input 'Hello, world!'` + +Note we need to specify the executable in this case, so we can pass the `input` argument to it. + +Assuming everything went well, you should see your application print this to the screen: + +~~~no-highlight +_ _ _ _ _ _ _ +| | | | ___ | | | | ___ __ __ ___ _ __ | | __| | | | +| |_| | / _ \ | | | | / _ \ \ \ /\ / / / _ \ | '__| | | / _` | | | +| _ | | __/ | | | | | (_) | _ \ V V / | (_) | | | | | | (_| | |_| +|_| |_| \___| |_| |_| \___/ ( ) \_/\_/ \___/ |_| |_| \__,_| (_) + |/ +~~~ + +--- + +Find the source code for this guide at [https://github.com/apple/swift-getting-started-cli](https://github.com/apple/swift-getting-started-cli) diff --git a/getting-started/cli-tool/index.md b/getting-started/cli-tool/index.md deleted file mode 100644 index 9dfafa783..000000000 --- a/getting-started/cli-tool/index.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: page -title: Build a Command-line Tool ---- - -TODO \ No newline at end of file diff --git a/getting-started/index.md b/getting-started/index.md index 02de44e07..650c56f21 100644 --- a/getting-started/index.md +++ b/getting-started/index.md @@ -3,6 +3,6 @@ layout: page title: Getting Started --- -{% include_relative _installing.md %} +{% include getting-started/_installing.md %} {% include_relative _use-cases.html %} -{% include_relative _go-further.html %} \ No newline at end of file +{% include_relative _go-further.html %} diff --git a/getting-started/library-swiftpm/index.md b/getting-started/library-swiftpm/index.md new file mode 100644 index 000000000..9af1151e1 --- /dev/null +++ b/getting-started/library-swiftpm/index.md @@ -0,0 +1,124 @@ +--- +layout: page +title: Build a library +--- + +{% include getting-started/_installing.md %} + +## Bootstrapping + +Let’s write a small application with our new Swift development environment. +To start, we’ll use SwiftPM to make a new project for us. In your terminal of choice run: + +~~~bash +❯ mkdir MyLibrary +❯ cd MyLibrary +❯ swift package init --name MyLibrary --type library +~~~ + +This will generate a new directory called hello-swift with the following files: + +~~~no-highlight +. +├── Package.swift +├── README.md +├── Sources +│   └── MyLibrary +│   └── MyLibrary.swift +└── Tests + └── MyLibraryTests + └── MyLibraryTests.swift +~~~ + +`Package.swift` is the manifest file for Swift. It’s where you keep metadata for your project, as well as dependencies. + +`Sources/MyLibrary/MyLibrary.swift` is the library initial source file and where we’ll write our library code. +`Test/MyLibraryTests/MyLibraryTests.swift` is where we can write tests for our library. + +In fact, SwiftPM generated a "Hello, world!" project for us, including some unit tests! +We can run the tests by running `swift test` in our terminal. + +~~~bash +❯ swift test +Building for debugging... +[4/4] Compiling MyLibraryTests MyLibraryTests.swift +Build complete! (1.30s) +Test Suite 'All tests' started at 2023-01-12 12:05:56.127 +Test Suite 'MyLibraryPackageTests.xctest' started at 2023-01-12 12:05:56.128 +Test Suite 'MyLibraryTests' started at 2023-01-12 12:05:56.128 +Test Case '-[MyLibraryTests.MyLibraryTests testExample]' started. +Test Case '-[MyLibraryTests.MyLibraryTests testExample]' passed (0.005 seconds). +Test Suite 'MyLibraryTests' passed at 2023-01-12 12:05:56.133. + Executed 1 test, with 0 failures (0 unexpected) in 0.005 (0.005) seconds +Test Suite 'MyLibraryPackageTests.xctest' passed at 2023-01-12 12:05:56.133. + Executed 1 test, with 0 failures (0 unexpected) in 0.005 (0.005) seconds +Test Suite 'All tests' passed at 2023-01-12 12:05:56.133. + Executed 1 test, with 0 failures (0 unexpected) in 0.005 (0.007) seconds +~~~ + +## A small library + +Now let’s write a small library. +Replace the example content of `MyLibrary.swift` with the following code: + +~~~swift +import Foundation + +struct Email: CustomStringConvertible { + var description: String + + public init(_ emailString: String) throws { + let regex = #"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,64}"# + guard let _ = emailString.range(of: regex, options: .regularExpression) else { + throw InvalidEmailError(email: emailString) + } + self.description = emailString + } +} + +private struct InvalidEmailError: Error { + let email: String +} +~~~ + +Now lets add a unit test for this strongly typed Email API. +Replace the example content of `MyLibraryTests.swift` with the following code: + +~~~swift +@testable import MyLibrary +import XCTest + +final class MyLibraryTests: XCTestCase { + func testEmail() throws { + let email = try Email("john.appleseed@apple.com") + XCTAssertEqual(email.description, "john.appleseed@apple.com") + + XCTAssertThrowsError(try Email("invalid")) + } +} +~~~ + +Once we save that, we can run our application with `swift run` +Assuming everything went well, we can run the tests successfully again: + +~~~no-highlight +❯ swift test +Building for debugging... +[3/3] Linking swift-libraryPackageTests +Build complete! (0.84s) +Test Suite 'All tests' started at 2023-01-03 16:22:45.070 +Test Suite 'swift-libraryPackageTests.xctest' started at 2023-01-03 16:22:45.071 +Test Suite 'swift_libraryTests' started at 2023-01-03 16:22:45.071 +Test Case '-[swift_libraryTests.swift_libraryTests testEmail]' started. +Test Case '-[swift_libraryTests.swift_libraryTests testEmail]' passed (0.005 seconds). +Test Suite 'swift_libraryTests' passed at 2023-01-03 16:22:45.076. + Executed 1 test, with 0 failures (0 unexpected) in 0.005 (0.005) seconds +Test Suite 'swift-libraryPackageTests.xctest' passed at 2023-01-03 16:22:45.076. + Executed 1 test, with 0 failures (0 unexpected) in 0.005 (0.005) seconds +Test Suite 'All tests' passed at 2023-01-03 16:22:45.076. + Executed 1 test, with 0 failures (0 unexpected) in 0.005 (0.007) seconds +~~~ + +--- + +Find the source code for this guide at [https://github.com/apple/swift-getting-started-package-library](https://github.com/apple/swift-getting-started-package-library)