From f4f60d8f27b6f5e33cb69ff64e7c8bbfca365919 Mon Sep 17 00:00:00 2001 From: mosamer Date: Thu, 25 Jan 2018 09:44:03 +0100 Subject: [PATCH 1/7] initialize with HTTP client instead of droplet Instead of injecting the concrete type `Droplet`, use `ClientFactoryProtoco` instead. This applied to; - GitHub client - Travis CI - SlackClient - Bob --- Sources/Bob/APIs/GitHub/GitHub.swift | 8 ++++---- Sources/Bob/APIs/TravisCI/TravisCI.swift | 12 ++++++------ Sources/Bob/Core/Bob.swift | 6 +++--- Sources/Bob/Core/Slack/SlackClient.swift | 8 ++++---- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Sources/Bob/APIs/GitHub/GitHub.swift b/Sources/Bob/APIs/GitHub/GitHub.swift index cce50b3..8aef897 100644 --- a/Sources/Bob/APIs/GitHub/GitHub.swift +++ b/Sources/Bob/APIs/GitHub/GitHub.swift @@ -104,12 +104,12 @@ public class GitHub { private let base64LoginData: String private let repoUrl: String - private let drop: Droplet - public init(config: Configuration, droplet: Droplet) { + private let client: ClientFactoryProtocol + public init(config: Configuration, client: ClientFactoryProtocol) { let authString = config.username + ":" + config.personalAccessToken self.base64LoginData = authString.data(using: .utf8)!.base64EncodedString() self.repoUrl = config.repoUrl - self.drop = droplet + self.client = client } private func uri(at path: String) -> String { @@ -118,7 +118,7 @@ public class GitHub { private func perform(_ request: Request) throws -> JSON { request.headers[HeaderKey("Authorization")] = "Basic " + self.base64LoginData - let response = try self.drop.client.respond(to: request) + let response = try self.client.respond(to: request) if response.status.isSuccessfulRequest { if let json = response.json { return json diff --git a/Sources/Bob/APIs/TravisCI/TravisCI.swift b/Sources/Bob/APIs/TravisCI/TravisCI.swift index 203e392..69714f9 100644 --- a/Sources/Bob/APIs/TravisCI/TravisCI.swift +++ b/Sources/Bob/APIs/TravisCI/TravisCI.swift @@ -54,7 +54,7 @@ extension Dictionary where Key: ExpressibleByStringLiteral, Value: Any { } /// Used for communication with TravisCI api -public class TravisCI { +public final class TravisCI { /// Configuration needed for authentication with the api @@ -70,13 +70,14 @@ public class TravisCI { } private let config: Configuration - private let drop: Droplet + private let client: ClientFactoryProtocol /// Initializes the object with provided configuration /// /// - Parameter config: Configuration to use - public init(config: Configuration, droplet: Droplet) { + /// - Parameter client: HTTP Client factory to use + public init(config: Configuration, client: ClientFactoryProtocol) { self.config = config - self.drop = droplet + self.client = client } @@ -102,10 +103,9 @@ public class TravisCI { request.headers[HeaderKey("Travis-API-Version")] = "3" request.headers[HeaderKey("Content-Type")] = "application/json" - let response = try self.drop.client.respond(to: request) + let response = try self.client.respond(to: request) if !response.status.isSuccessfulRequest { throw "Error: `" + request.uri.description + "` - " + response.status.reasonPhrase } } - } diff --git a/Sources/Bob/Core/Bob.swift b/Sources/Bob/Core/Bob.swift index c875fcc..6e1444b 100644 --- a/Sources/Bob/Core/Bob.swift +++ b/Sources/Bob/Core/Bob.swift @@ -44,9 +44,9 @@ public class Bob { /// Initializer /// /// - Parameter configuration: Configuration for setup - /// - Parameter droplet: Droplet - public init(config: Configuration, droplet: Droplet) { - self.slackClient = SlackClient(token: config.slackToken, droplet: droplet) + /// - Parameter client: HTTP Client to use + public init(config: Configuration, client: ClientFactoryProtocol) { + self.slackClient = SlackClient(token: config.slackToken, client: client) self.factory = CommandFactory(commands: [HelloCommand(), VersionCommand()]) self.processor = CommandProcessor(factory: self.factory) self.executor = CommandExecutor() diff --git a/Sources/Bob/Core/Slack/SlackClient.swift b/Sources/Bob/Core/Slack/SlackClient.swift index a5d92fc..f03239e 100644 --- a/Sources/Bob/Core/Slack/SlackClient.swift +++ b/Sources/Bob/Core/Slack/SlackClient.swift @@ -41,15 +41,15 @@ extension ClientFactoryProtocol { class SlackClient { private let token: String - private let droplet: Droplet - init(token: String, droplet: Droplet) { + private let client: ClientFactoryProtocol + init(token: String, client: ClientFactoryProtocol) { self.token = token - self.droplet = droplet + self.client = client } func connect(onMessage: @escaping (_ message: String, _ sender: MessageSender) -> Void) throws { - let rtmResponse = try self.droplet.client.loadRealtimeApi(token: self.token) + let rtmResponse = try self.client.loadRealtimeApi(token: self.token) guard let webSocketURL = rtmResponse.json?["url"]?.string else { throw "Unable to retrieve `url` from slack. Reason \(rtmResponse.status.reasonPhrase). Raw response \(rtmResponse.data)" } try WebSocketFactory.shared.connect(to: webSocketURL) { (socket) in From 68bf90353d5375d869e0655c0914dbcbb810953c Mon Sep 17 00:00:00 2001 From: mosamer Date: Thu, 25 Jan 2018 09:53:01 +0100 Subject: [PATCH 2/7] resolve `TravisCI.Configuration` from vapor config file --- Sources/Bob/APIs/TravisCI/TravisCI.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Sources/Bob/APIs/TravisCI/TravisCI.swift b/Sources/Bob/APIs/TravisCI/TravisCI.swift index 69714f9..ccd5807 100644 --- a/Sources/Bob/APIs/TravisCI/TravisCI.swift +++ b/Sources/Bob/APIs/TravisCI/TravisCI.swift @@ -109,3 +109,18 @@ public final class TravisCI { } } } + + +extension Config { + /// Resolves configured Travis CI configuration + func resolveTravisConfiguration() throws -> TravisCI.Configuration { + guard let url = self["bob", "travis-repo-url"]?.string else { + throw "Unable to find Travis CI repo URL. It should be found in \" Configs/bob.json\" under the key \"travis-repo-url\"." + } + + guard let token = self["bob", "travis-token"]?.string else { + throw "Unable to find Travis CI access token. It should be found in \" Configs/bob.json\" under the key \"travis-token\"." + } + return TravisCI.Configuration(repoUrl: url, token: token) + } +} From 5caa29a3ab5eedcaae90fc709b8024186268efc1 Mon Sep 17 00:00:00 2001 From: mosamer Date: Thu, 25 Jan 2018 09:58:03 +0100 Subject: [PATCH 3/7] resolve `GitHub.Configuration` from vapor config file --- Sources/Bob/APIs/GitHub/GitHub.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Sources/Bob/APIs/GitHub/GitHub.swift b/Sources/Bob/APIs/GitHub/GitHub.swift index 8aef897..5bac425 100644 --- a/Sources/Bob/APIs/GitHub/GitHub.swift +++ b/Sources/Bob/APIs/GitHub/GitHub.swift @@ -288,3 +288,19 @@ public class GitHub { } } + +extension Config { + /// Resolves configured GitHub configuration + func resolveGitHubConfiguration() throws -> GitHub.Configuration { + guard let user = self["bob", "github-username"]?.string else { + throw "Unable to find GitHub username. It should be found in \" Configs/bob.json\" under the key \"github-username\"." + } + guard let token = self["bob", "github-access-token"]?.string else { + throw "Unable to find GitHub personal access token. It should be found in \" Configs/bob.json\" under the key \"github-access-token\"." + } + guard let repoURL = self["bob", "github-repo-url"]?.string else { + throw "Unable to find GitHub repository URL. It should be found in \" Configs/bob.json\" under the key \"github-repo-url\"." + } + return GitHub.Configuration(username: user, personalAccessToken: token, repoUrl: repoURL) + } +} From d42db646443a2f66807b5dc11f7fcf561325df90 Mon Sep 17 00:00:00 2001 From: mosamer Date: Thu, 25 Jan 2018 10:00:55 +0100 Subject: [PATCH 4/7] resolve `Bob.Configuration` from vapor config file --- Sources/Bob/Core/Bob.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Sources/Bob/Core/Bob.swift b/Sources/Bob/Core/Bob.swift index 6e1444b..5d8032f 100644 --- a/Sources/Bob/Core/Bob.swift +++ b/Sources/Bob/Core/Bob.swift @@ -102,3 +102,13 @@ fileprivate extension CommandFactory { } } + +extension Config { + /// Resolves configured Bob configuration + func resolveBobConfiguration() throws -> Bob.Configuration { + guard let token = self["bob", "slack-token"]?.string else { + throw "Unable to find Slack access token. It should be found in \" Configs/bob.json\" under the key \"slack-token\"." + } + return Bob.Configuration(slackToken: token) + } +} From 46f30996fba2b71b547b635112cc06a98d703f05 Mon Sep 17 00:00:00 2001 From: mosamer Date: Thu, 25 Jan 2018 10:05:03 +0100 Subject: [PATCH 5/7] support initializing from vapor Config Conform to `ConfigInitializable` protocol to allow of initialization with `Config` inline with Vapor documentation. Following conformance implementation were added, - Bob - TravisCI - GitHub --- Sources/Bob/APIs/GitHub/GitHub.swift | 10 +++++++++- Sources/Bob/APIs/TravisCI/TravisCI.swift | 8 ++++++++ Sources/Bob/Core/Bob.swift | 10 +++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Sources/Bob/APIs/GitHub/GitHub.swift b/Sources/Bob/APIs/GitHub/GitHub.swift index 5bac425..3f15959 100644 --- a/Sources/Bob/APIs/GitHub/GitHub.swift +++ b/Sources/Bob/APIs/GitHub/GitHub.swift @@ -82,7 +82,7 @@ public struct Commit { } /// Used for communicating with the GitHub api -public class GitHub { +public final class GitHub { /// Configuration needed for authentication with the api public struct Configuration { @@ -304,3 +304,11 @@ extension Config { return GitHub.Configuration(username: user, personalAccessToken: token, repoUrl: repoURL) } } + +extension GitHub: ConfigInitializable { + public convenience init(config: Config) throws { + let configuration = try config.resolveGitHubConfiguration() + let client = try config.resolveClient() + self.init(config: configuration, client: client) + } +} diff --git a/Sources/Bob/APIs/TravisCI/TravisCI.swift b/Sources/Bob/APIs/TravisCI/TravisCI.swift index ccd5807..9b423a5 100644 --- a/Sources/Bob/APIs/TravisCI/TravisCI.swift +++ b/Sources/Bob/APIs/TravisCI/TravisCI.swift @@ -124,3 +124,11 @@ extension Config { return TravisCI.Configuration(repoUrl: url, token: token) } } + +extension TravisCI: ConfigInitializable { + public convenience init(config: Config) throws { + let configuration = try config.resolveTravisConfiguration() + let client = try config.resolveClient() + self.init(config: configuration, client: client) + } +} diff --git a/Sources/Bob/Core/Bob.swift b/Sources/Bob/Core/Bob.swift index 5d8032f..77c3bd4 100644 --- a/Sources/Bob/Core/Bob.swift +++ b/Sources/Bob/Core/Bob.swift @@ -20,7 +20,7 @@ import Foundation import Vapor -public class Bob { +public final class Bob { static let version: String = "1.0.2" @@ -112,3 +112,11 @@ extension Config { return Bob.Configuration(slackToken: token) } } + +extension Bob: ConfigInitializable { + public convenience init(config: Config) throws { + let configuration = try config.resolveBobConfiguration() + let client = try config.resolveClient() + self.init(config: configuration, client: client) + } +} From ec863f8cee458dd1bfcf9c5c0546e0832291c761 Mon Sep 17 00:00:00 2001 From: mosamer Date: Thu, 25 Jan 2018 10:44:37 +0100 Subject: [PATCH 6/7] update `Readme` --- Readme.md | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/Readme.md b/Readme.md index b7736ab..d195773 100644 --- a/Readme.md +++ b/Readme.md @@ -28,29 +28,48 @@ You can see which version of Bob is currently running by typing `version`
The script to run for each target is specified when the command is instantiated.
For example:
```swift +let config = try Config() let buildTargets = [ TravisTarget(name: "staging", script: Script("fastlane ios distribute_staging")), TravisTarget(name: "testflight", script: Script("fastlane ios distribute_testflight")), ] -let buildCommand = TravisScriptCommand(name: "build", config: travisConfig, targets: buildTargets, defaultBranch: "Develop") +let travisCI = try TravisCI(config: config) +let buildCommand = TravisScriptCommand(name: "build", travis: travisCI, targets: buildTargets, defaultBranch: BranchName("develop")) try bob.register(buildCommand) ``` -would register a command with the name `build`. Typing `build staging` would start the lane `distribute_staging` on Travis. - +would register a command with the name `build`. Typing `build staging` would start the lane `distribute_staging` on Travis. +Your `bob.json` file should look like this (replace your token & urls) +```json +{ + "travis-repo-url": "<# Url of the repo. Along the lines of https://api.travis-ci.com/repo/{owner%2Frepo} #>", + "travis-token": <# Travis CI access token #> + <# other configurations #> +} +``` ### Align Version iOS specific command used to change the `CFBundleShortVersionString` and `CFBundleVersion` values in specified `.plist` files.
For example:
```swift +let config = try Config() let plistPaths: [String] = [ "App/Info.plist", "siriKit/Info.plist", "siriKitUI/Info.plist" ] -let alignCommand = AlignVersionCommand(config: gitHubConfig, defaultBranch: "Develop", plistPaths: plistPaths, author: author) +let gitHub = try GitHub(config: config) +let alignCommand = AlignVersionCommand(gitHub: gitHub, defaultBranch: BranchName("develop"), plistPaths: plistPaths, author: Author(name: "bob", email: "bob@example.com")) try bob.register(alignCommand) ``` would register a command that can be invoked by typing `align 3.0 4`. Bob would then create a commit on GitHub by changing the 3 specified files. - +Your `bob.json` file should look like this (replace your token & urls) +```json +{ + "github-username": <# Username of authenticated Github user #>, + "github-access-token": <# Persoanl access token for the authenticated user #>, + "github-repo-url": <# Url of the repo. Alogn the lines of https://api.github.com/repos/{owner}/{repo} #>, + <# other configurations #> +} +``` ## Getting started ### Creating a bot on Slack @@ -85,6 +104,15 @@ You can delete the unused template files by running: rm -rf Sources/App/Controllers rm -rf Sources/App/Models ``` + +All of your configured tokens will reside in `bob.json` file in the `Config` folder. It should look like this (replace your token), and you can add this file to your `.gitignore`. +```json +{ + "slack-token": <# Slack bot token #>, + <# other configurations #> +} +``` + All of your custom code will reside in the `Sources/App` folder.
Create an Xcode project by running ```bash @@ -94,9 +122,9 @@ Change the `Sources/App/main.swift` file to: ```swift import Bob -let config = Bob.Configuration(slackToken: "your-slack-token") +let config = try Config() let bob = Bob(config: config) - +<# register commands #> try bob.start() ``` and you're good to go. Select the `App` scheme and run it. You can now send messages to `Bob` via Slack, and it will respond. From d5bfa81b4994b00d37462595cf78219623963fde Mon Sep 17 00:00:00 2001 From: mosamer Date: Thu, 25 Jan 2018 11:40:50 +0100 Subject: [PATCH 7/7] move config file name into a constant --- Sources/Bob/APIs/GitHub/GitHub.swift | 6 +++--- Sources/Bob/APIs/TravisCI/TravisCI.swift | 4 ++-- Sources/Bob/Core/Bob.swift | 5 +++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Sources/Bob/APIs/GitHub/GitHub.swift b/Sources/Bob/APIs/GitHub/GitHub.swift index 3f15959..b58fc99 100644 --- a/Sources/Bob/APIs/GitHub/GitHub.swift +++ b/Sources/Bob/APIs/GitHub/GitHub.swift @@ -292,13 +292,13 @@ public final class GitHub { extension Config { /// Resolves configured GitHub configuration func resolveGitHubConfiguration() throws -> GitHub.Configuration { - guard let user = self["bob", "github-username"]?.string else { + guard let user = self[Bob.configFile, "github-username"]?.string else { throw "Unable to find GitHub username. It should be found in \" Configs/bob.json\" under the key \"github-username\"." } - guard let token = self["bob", "github-access-token"]?.string else { + guard let token = self[Bob.configFile, "github-access-token"]?.string else { throw "Unable to find GitHub personal access token. It should be found in \" Configs/bob.json\" under the key \"github-access-token\"." } - guard let repoURL = self["bob", "github-repo-url"]?.string else { + guard let repoURL = self[Bob.configFile, "github-repo-url"]?.string else { throw "Unable to find GitHub repository URL. It should be found in \" Configs/bob.json\" under the key \"github-repo-url\"." } return GitHub.Configuration(username: user, personalAccessToken: token, repoUrl: repoURL) diff --git a/Sources/Bob/APIs/TravisCI/TravisCI.swift b/Sources/Bob/APIs/TravisCI/TravisCI.swift index 9b423a5..afc977c 100644 --- a/Sources/Bob/APIs/TravisCI/TravisCI.swift +++ b/Sources/Bob/APIs/TravisCI/TravisCI.swift @@ -114,11 +114,11 @@ public final class TravisCI { extension Config { /// Resolves configured Travis CI configuration func resolveTravisConfiguration() throws -> TravisCI.Configuration { - guard let url = self["bob", "travis-repo-url"]?.string else { + guard let url = self[Bob.configFile, "travis-repo-url"]?.string else { throw "Unable to find Travis CI repo URL. It should be found in \" Configs/bob.json\" under the key \"travis-repo-url\"." } - guard let token = self["bob", "travis-token"]?.string else { + guard let token = self[Bob.configFile, "travis-token"]?.string else { throw "Unable to find Travis CI access token. It should be found in \" Configs/bob.json\" under the key \"travis-token\"." } return TravisCI.Configuration(repoUrl: url, token: token) diff --git a/Sources/Bob/Core/Bob.swift b/Sources/Bob/Core/Bob.swift index 77c3bd4..90c44c9 100644 --- a/Sources/Bob/Core/Bob.swift +++ b/Sources/Bob/Core/Bob.swift @@ -23,7 +23,8 @@ import Vapor public final class Bob { static let version: String = "1.0.2" - + static let configFile: String = "bob" + /// Struct containing all properties needed for Bob to function public struct Configuration { public let slackToken: String @@ -106,7 +107,7 @@ fileprivate extension CommandFactory { extension Config { /// Resolves configured Bob configuration func resolveBobConfiguration() throws -> Bob.Configuration { - guard let token = self["bob", "slack-token"]?.string else { + guard let token = self[Bob.configFile, "slack-token"]?.string else { throw "Unable to find Slack access token. It should be found in \" Configs/bob.json\" under the key \"slack-token\"." } return Bob.Configuration(slackToken: token)