From 3a000a7a1e530952f9093601914d271f8e85de6b Mon Sep 17 00:00:00 2001 From: Jules Coynel Date: Sun, 28 Feb 2021 17:02:03 +0000 Subject: [PATCH] Rename package and CLI to app-store-reviews and add page, reviews and territories commands --- Package.swift | 8 +-- Sources/AppStoreReviewsCLI/Reviews.swift | 51 ------------------- Sources/AppStoreReviewsCLI/main.swift | 3 -- .../app-store-reviews/Subcommands/Page.swift | 42 +++++++++++++++ .../Subcommands/Reviews.swift | 37 ++++++++++++++ .../Subcommands/Territories.swift | 22 ++++++++ .../app-store-reviews/Utilities/Output.swift | 12 +++++ .../Utilities/TerritoryExtensions.swift | 4 ++ .../app-store-reviews/VersionOptions.swift | 15 ++++++ Sources/app-store-reviews/main.swift | 21 ++++++++ 10 files changed, 157 insertions(+), 58 deletions(-) delete mode 100644 Sources/AppStoreReviewsCLI/Reviews.swift delete mode 100644 Sources/AppStoreReviewsCLI/main.swift create mode 100644 Sources/app-store-reviews/Subcommands/Page.swift create mode 100644 Sources/app-store-reviews/Subcommands/Reviews.swift create mode 100644 Sources/app-store-reviews/Subcommands/Territories.swift create mode 100644 Sources/app-store-reviews/Utilities/Output.swift create mode 100644 Sources/app-store-reviews/Utilities/TerritoryExtensions.swift create mode 100644 Sources/app-store-reviews/VersionOptions.swift create mode 100644 Sources/app-store-reviews/main.swift diff --git a/Package.swift b/Package.swift index 805cab8..ba412cd 100644 --- a/Package.swift +++ b/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( - name: "AppStoreReviews", + name: "app-store-reviews", platforms: [ .iOS(.v13), .macOS(.v10_15), @@ -15,9 +15,9 @@ let package = Package( "AppStoreReviews", ]), .executable( - name: "AppStoreReviewsCLI", + name: "app-store-reviews", targets: [ - "AppStoreReviewsCLI", + "app-store-reviews", ]) ], dependencies: [ @@ -31,7 +31,7 @@ let package = Package( .product(name: "Logging", package: "swift-log") ]), .target( - name: "AppStoreReviewsCLI", + name: "app-store-reviews", dependencies: [ "AppStoreReviews", .product(name: "ArgumentParser", package: "swift-argument-parser"), diff --git a/Sources/AppStoreReviewsCLI/Reviews.swift b/Sources/AppStoreReviewsCLI/Reviews.swift deleted file mode 100644 index 34198dd..0000000 --- a/Sources/AppStoreReviewsCLI/Reviews.swift +++ /dev/null @@ -1,51 +0,0 @@ -import Foundation -import ArgumentParser -import AppStoreReviews -import Combine - -// swift run AppStoreReviewsCLI 555731861 GB -struct Reviews: ParsableCommand { - @Argument(help: "The ID of the app.") - var appID: Int = 555731861 - - @Argument(help: "App Store country or region.") - var territory: Territory = .GB - - @Argument(help: "The page number.") - var page: Int = 1 - - @Argument(help: "The reviews file output.") - var fileOutput: String = "reviews.json" - - private var fileOutputURL: URL { - URL(fileURLWithPath: fileOutput) - } - - func run() throws { - let page = try Page(appID: appID, territory: territory, page: self.page) - var receivedFeed: Feed? - let cancellable = Downloader() - .fetch(page: page) - .sink { completion in - switch completion { - case .finished: - do { - let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted - let json = try encoder.encode(receivedFeed) - try json.write(to: self.fileOutputURL) - Self.exit() - } catch { - Self.exit(withError: error) - } - case .failure(let error): - Self.exit(withError: error) - } - } receiveValue: { feed in - receivedFeed = feed - } - dispatchMain() - } -} - -extension Territory: ExpressibleByArgument {} diff --git a/Sources/AppStoreReviewsCLI/main.swift b/Sources/AppStoreReviewsCLI/main.swift deleted file mode 100644 index f821dca..0000000 --- a/Sources/AppStoreReviewsCLI/main.swift +++ /dev/null @@ -1,3 +0,0 @@ -import Foundation - -Reviews.main() diff --git a/Sources/app-store-reviews/Subcommands/Page.swift b/Sources/app-store-reviews/Subcommands/Page.swift new file mode 100644 index 0000000..4e54017 --- /dev/null +++ b/Sources/app-store-reviews/Subcommands/Page.swift @@ -0,0 +1,42 @@ +import Foundation +import ArgumentParser +import AppStoreReviews + +/// Encapsulates `page` command behavior. +struct Page: ParsableCommand { + static var configuration = CommandConfiguration( + abstract: "Fetch the App Store Reviews feed page for the provided app ID, territory and page number." + ) + + @Argument(help: "The ID of the app.") + var appID: Int + + @Argument(help: "App Store country or region.") + var territory: Territory + + @Argument(help: "The page number.") + var page: Int + + @Argument(help: "The reviews file output.") + var fileOutput: String + + func run() throws { + let pageToFetch = try AppStoreReviews.Page(appID: appID, territory: territory, page: page) + + let loader = Downloader() + loader.fetch(page: pageToFetch) { result in + switch result { + case .success(let feed): + do { + try Output.write(feed, to: fileOutput) + Self.exit() + } catch { + Self.exit(withError: error) + } + case .failure(let error): + Self.exit(withError: error) + } + } + dispatchMain() + } +} diff --git a/Sources/app-store-reviews/Subcommands/Reviews.swift b/Sources/app-store-reviews/Subcommands/Reviews.swift new file mode 100644 index 0000000..9fce729 --- /dev/null +++ b/Sources/app-store-reviews/Subcommands/Reviews.swift @@ -0,0 +1,37 @@ +import Foundation +import ArgumentParser +import AppStoreReviews + +/// Encapsulates `reviews` command behavior. +struct Reviews: ParsableCommand { + static var configuration = CommandConfiguration( + abstract: "Fetch all the reviews from the App Store Reviews feed for the provided app ID and territory." + ) + + @Argument(help: "The ID of the app.") + var appID: Int + + @Argument(help: "App Store country or region.") + var territory: Territory + + @Argument(help: "The reviews file output.") + var fileOutput: String + + func run() throws { + let loader = ReviewsLoader() + loader.fetchAll(appID: appID, territory: territory) { result in + switch result { + case .success(let reviews): + do { + try Output.write(reviews, to: fileOutput) + Self.exit() + } catch { + Self.exit(withError: error) + } + case .failure(let error): + Self.exit(withError: error) + } + } + dispatchMain() + } +} diff --git a/Sources/app-store-reviews/Subcommands/Territories.swift b/Sources/app-store-reviews/Subcommands/Territories.swift new file mode 100644 index 0000000..4daa710 --- /dev/null +++ b/Sources/app-store-reviews/Subcommands/Territories.swift @@ -0,0 +1,22 @@ +import Foundation +import ArgumentParser +import AppStoreReviews + +/// Encapsulates `territories` command behavior. +struct Territories: ParsableCommand { + static var configuration = CommandConfiguration( + abstract: "Print the list of territories reviews can be fetched for." + ) + + func run() throws { + AppStoreReviews.Territory.allCases.forEach { territory in + /* e.g. + ... + TON Tonga + TR Turkey + ... + */ + print("\(territory.isoCode.padding(toLength: 3, withPad: " ", startingAt: 0)) \(territory.name)") + } + } +} diff --git a/Sources/app-store-reviews/Utilities/Output.swift b/Sources/app-store-reviews/Utilities/Output.swift new file mode 100644 index 0000000..f24e624 --- /dev/null +++ b/Sources/app-store-reviews/Utilities/Output.swift @@ -0,0 +1,12 @@ +import Foundation +import ArgumentParser + +enum Output { + static func write(_ data: Value, to file: String) throws { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + let json = try encoder.encode(data) + let outputURL = URL(fileURLWithPath: file) + try json.write(to: outputURL) + } +} diff --git a/Sources/app-store-reviews/Utilities/TerritoryExtensions.swift b/Sources/app-store-reviews/Utilities/TerritoryExtensions.swift new file mode 100644 index 0000000..26bfff1 --- /dev/null +++ b/Sources/app-store-reviews/Utilities/TerritoryExtensions.swift @@ -0,0 +1,4 @@ +import ArgumentParser +import AppStoreReviews + +extension Territory: ExpressibleByArgument {} diff --git a/Sources/app-store-reviews/VersionOptions.swift b/Sources/app-store-reviews/VersionOptions.swift new file mode 100644 index 0000000..fdeb7c8 --- /dev/null +++ b/Sources/app-store-reviews/VersionOptions.swift @@ -0,0 +1,15 @@ +import ArgumentParser +import AppStoreReviews + +/// Encapsulates `--version` flag behavior. +struct VersionOptions: ParsableArguments { + @Flag(name: .shortAndLong, help: "Print the version and exit") + var version: Bool = false + + func validate() throws { + if version { + print(AppStoreReviews.version) + throw ExitCode.success + } + } +} diff --git a/Sources/app-store-reviews/main.swift b/Sources/app-store-reviews/main.swift new file mode 100644 index 0000000..132064d --- /dev/null +++ b/Sources/app-store-reviews/main.swift @@ -0,0 +1,21 @@ +import ArgumentParser + +/// Collects the command line options that were passed to `app-store-reviews` and dispatches to the +/// appropriate subcommand. +struct AppStoreReviewsCommand: ParsableCommand { + static var configuration = CommandConfiguration( + commandName: "app-store-reviews", + abstract: "Fetch user reviews from the Apple App Stores", + subcommands: [ + Reviews.self, + Page.self, + Territories.self, + ], + defaultSubcommand: nil + ) + + @OptionGroup() + var versionOptions: VersionOptions +} + +AppStoreReviewsCommand.main()