From c2892626d7679aaee6872afe20f3b7bdc99c8c09 Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Wed, 16 Oct 2024 15:13:29 -0400
Subject: [PATCH 01/20] Fix search & uninstall tests.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Tests/masTests/Commands/SearchSpec.swift | 7 +++++--
Tests/masTests/Commands/UninstallSpec.swift | 2 +-
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/Tests/masTests/Commands/SearchSpec.swift b/Tests/masTests/Commands/SearchSpec.swift
index 2496ad0..91a3a45 100644
--- a/Tests/masTests/Commands/SearchSpec.swift
+++ b/Tests/masTests/Commands/SearchSpec.swift
@@ -6,6 +6,7 @@
// Copyright © 2018 mas-cli. All rights reserved.
//
+import Foundation
import Nimble
import Quick
@@ -31,9 +32,11 @@ public class SearchSpec: QuickSpec {
)
storeSearch.apps[mockResult.trackId] = mockResult
expect {
- try Mas.Search.parse(["slack"]).run(storeSearch: storeSearch)
+ try captureStream(stdout) {
+ try Mas.Search.parse(["slack"]).run(storeSearch: storeSearch)
+ }
}
- .toNot(throwError())
+ == " 1111 slack (0.0)\n"
}
it("fails when searching for nonexistent app") {
expect {
diff --git a/Tests/masTests/Commands/UninstallSpec.swift b/Tests/masTests/Commands/UninstallSpec.swift
index 13613b8..bf5890f 100644
--- a/Tests/masTests/Commands/UninstallSpec.swift
+++ b/Tests/masTests/Commands/UninstallSpec.swift
@@ -67,7 +67,7 @@ public class UninstallSpec: QuickSpec {
try uninstall.run(appLibrary: mockLibrary)
}
}
- == " 1111 slack (0.0)\n==> Some App /tmp/Some.app\n==> (not removed, dry run)\n"
+ == "==> Some App /tmp/Some.app\n==> (not removed, dry run)\n"
}
it("fails if there is a problem with the trash command") {
var brokenUninstall = app // make mutable copy
From 57da9e0f51d48e6c71cf5838e75fa33ef3358b51 Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Sun, 20 Oct 2024 08:05:11 -0400
Subject: [PATCH 02/20] Improve `style.md`.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
docs/style.md | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/docs/style.md b/docs/style.md
index e4c48cc..c0487ba 100644
--- a/docs/style.md
+++ b/docs/style.md
@@ -5,7 +5,9 @@
- Use `script/lint` to look for these before committing.
- Note that [two trailing spaces](https://gist.github.com/shaunlebron/746476e6e7a4d698b373)
is intentional in markdown documents to create a line break like `
`, so these should _not_ be removed.
-- End each file with a [newline character](https://unix.stackexchange.com/questions/18743/whats-the-point-in-adding-a-new-line-to-the-end-of-a-file#18789).
+- End each file with a [newline character](
+ https://unix.stackexchange.com/questions/18743/whats-the-point-in-adding-a-new-line-to-the-end-of-a-file#18789
+ ).
## Swift
@@ -14,13 +16,13 @@
- Avoid [force unwrapping optionals](https://blog.timac.org/2017/0628-swift-banning-force-unwrapping-optionals/)
with `!` in production code
- Production code is what gets shipped with the app. Basically, everything under the
- [`mas-cli/`](https://github.com/mas-cli/mas/tree/main/mas-cli) folder.
+ [`Sources/mas`](https://github.com/mas-cli/mas/tree/main/Sources/mas) folder.
- However, force unwrapping is **encouraged** in tests for less code and tests
_should_ break when any expected conditions aren't met.
- Prefer `struct`s over `class`es wherever possible
- Default to marking classes as `final`
- Prefer protocol conformance to class inheritance
-- Break long lines after 120 characters
+- Break lines at 120 characters
- Use 4 spaces for indentation
- Use `let` whenever possible to make immutable variables
- Name all parameters in functions and enum cases
@@ -28,10 +30,7 @@ with `!` in production code
- Let the compiler infer the type whenever possible
- Group computed properties below stored properties
- Use a blank line above and below computed properties
-- Group methods into specific extensions for each level of access control
+- Group functions into separate extensions for each level of access control
- When capitalizing acronyms or initialisms, follow the capitalization of the first letter.
- When using `Void` in function signatures, prefer `()` for arguments and `Void` for return types.
-- Prefer strong `IBOutlet` references.
- Avoid evaluating a weak reference multiple times in the same scope. Strongify first, then use the strong reference.
-- Prefer to name `IBAction` and target/action methods using a verb describing the action it will trigger, instead
- of the user action (e.g., `edit:` instead of `editTapped:`)
From 3d9ea972f9fcc829a5bdfa2ac5d5d3314f718246 Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Sun, 20 Oct 2024 08:09:01 -0400
Subject: [PATCH 03/20] Cleanup code.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Sources/mas/AppStore/Downloader.swift | 2 +-
Sources/mas/Commands/Outdated.swift | 2 --
Sources/mas/Controllers/MasStoreSearch.swift | 8 +++++---
Sources/mas/Formatters/Utilities.swift | 2 +-
Tests/masTests/Commands/UninstallSpec.swift | 2 +-
Tests/masTests/Commands/VersionSpec.swift | 2 +-
Tests/masTests/Controllers/AppLibraryMock.swift | 2 +-
Tests/masTests/Errors/MASErrorTestCase.swift | 2 +-
8 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/Sources/mas/AppStore/Downloader.swift b/Sources/mas/AppStore/Downloader.swift
index 65a2373..5f90035 100644
--- a/Sources/mas/AppStore/Downloader.swift
+++ b/Sources/mas/AppStore/Downloader.swift
@@ -36,7 +36,7 @@ func downloadAll(_ appIDs: [AppID], purchase: Bool = false) -> Promise {
private func downloadWithRetries(_ appID: AppID, purchase: Bool = false, attempts: Int = 3) -> Promise {
SSPurchase().perform(appID: appID, purchase: purchase)
- .recover { error -> Promise in
+ .recover { error in
guard attempts > 1 else {
throw error
}
diff --git a/Sources/mas/Commands/Outdated.swift b/Sources/mas/Commands/Outdated.swift
index f7b3ce5..9c0b1be 100644
--- a/Sources/mas/Commands/Outdated.swift
+++ b/Sources/mas/Commands/Outdated.swift
@@ -10,8 +10,6 @@ import ArgumentParser
import Foundation
import PromiseKit
-import enum Swift.Result
-
extension Mas {
/// Command which displays a list of installed apps which have available updates
/// ready to be installed from the Mac App Store.
diff --git a/Sources/mas/Controllers/MasStoreSearch.swift b/Sources/mas/Controllers/MasStoreSearch.swift
index 9cf2405..6e9325a 100644
--- a/Sources/mas/Controllers/MasStoreSearch.swift
+++ b/Sources/mas/Controllers/MasStoreSearch.swift
@@ -45,7 +45,7 @@ class MasStoreSearch: StoreSearch {
entities += [.iPadSoftware, .iPhoneSoftware]
}
- let results = entities.map { entity -> Promise<[SearchResult]> in
+ let results = entities.map { entity in
guard let url = searchURL(for: appName, inCountry: country, ofEntity: entity) else {
fatalError("Failed to build URL for \(appName)")
}
@@ -70,7 +70,8 @@ class MasStoreSearch: StoreSearch {
}
return firstly {
loadSearchResults(url)
- }.then { results -> Guarantee in
+ }
+ .then { results -> Guarantee in
guard let result = results.first else {
return .value(nil)
}
@@ -104,7 +105,8 @@ class MasStoreSearch: StoreSearch {
private func loadSearchResults(_ url: URL) -> Promise<[SearchResult]> {
firstly {
networkManager.loadData(from: url)
- }.map { data -> [SearchResult] in
+ }
+ .map { data in
do {
return try JSONDecoder().decode(SearchResultList.self, from: data).results
} catch {
diff --git a/Sources/mas/Formatters/Utilities.swift b/Sources/mas/Formatters/Utilities.swift
index c5c940e..fae8ad3 100644
--- a/Sources/mas/Formatters/Utilities.swift
+++ b/Sources/mas/Formatters/Utilities.swift
@@ -69,7 +69,7 @@ func captureStream(
_ stream: UnsafeMutablePointer,
encoding: String.Encoding = .utf8,
_ block: @escaping () throws -> Void
-) throws -> String {
+) rethrows -> String {
let originalFd = fileno(stream)
let duplicateFd = dup(originalFd)
defer {
diff --git a/Tests/masTests/Commands/UninstallSpec.swift b/Tests/masTests/Commands/UninstallSpec.swift
index bf5890f..c6426c3 100644
--- a/Tests/masTests/Commands/UninstallSpec.swift
+++ b/Tests/masTests/Commands/UninstallSpec.swift
@@ -70,7 +70,7 @@ public class UninstallSpec: QuickSpec {
== "==> Some App /tmp/Some.app\n==> (not removed, dry run)\n"
}
it("fails if there is a problem with the trash command") {
- var brokenUninstall = app // make mutable copy
+ var brokenUninstall = app
brokenUninstall.bundlePath = "/dev/null"
mockLibrary.installedApps.append(brokenUninstall)
expect {
diff --git a/Tests/masTests/Commands/VersionSpec.swift b/Tests/masTests/Commands/VersionSpec.swift
index ced12bc..0828749 100644
--- a/Tests/masTests/Commands/VersionSpec.swift
+++ b/Tests/masTests/Commands/VersionSpec.swift
@@ -24,7 +24,7 @@ public class VersionSpec: QuickSpec {
try Mas.Version.parse([]).run()
}
}
- == Package.version + "\n"
+ == "\(Package.version)\n"
}
}
}
diff --git a/Tests/masTests/Controllers/AppLibraryMock.swift b/Tests/masTests/Controllers/AppLibraryMock.swift
index 4a87784..f763280 100644
--- a/Tests/masTests/Controllers/AppLibraryMock.swift
+++ b/Tests/masTests/Controllers/AppLibraryMock.swift
@@ -9,7 +9,7 @@
@testable import mas
class AppLibraryMock: AppLibrary {
- var installedApps = [SoftwareProduct]()
+ var installedApps: [SoftwareProduct] = []
func uninstallApp(app: SoftwareProduct) throws {
if !installedApps.contains(where: { product -> Bool in
diff --git a/Tests/masTests/Errors/MASErrorTestCase.swift b/Tests/masTests/Errors/MASErrorTestCase.swift
index fe0248b..f9e13dd 100644
--- a/Tests/masTests/Errors/MASErrorTestCase.swift
+++ b/Tests/masTests/Errors/MASErrorTestCase.swift
@@ -22,7 +22,7 @@ class MASErrorTestCase: XCTestCase {
var localizedDescription: String {
get { "dummy value" }
set {
- NSError.setUserInfoValueProvider(forDomain: errorDomain) { (_: Error, _: String) -> Any? in
+ NSError.setUserInfoValueProvider(forDomain: errorDomain) { _, _ in
newValue
}
}
From a100f3acd0612db9248a3901f0dcf718d394fb82 Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Sun, 20 Oct 2024 12:39:35 -0400
Subject: [PATCH 04/20] Remove unnecessary generics.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Sources/mas/AppStore/Downloader.swift | 2 +-
Sources/mas/AppStore/ISStoreAccount.swift | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Sources/mas/AppStore/Downloader.swift b/Sources/mas/AppStore/Downloader.swift
index 5f90035..7e0b338 100644
--- a/Sources/mas/AppStore/Downloader.swift
+++ b/Sources/mas/AppStore/Downloader.swift
@@ -19,7 +19,7 @@ import StoreFoundation
/// the promise is rejected with the first error, after all remaining downloads are attempted.
func downloadAll(_ appIDs: [AppID], purchase: Bool = false) -> Promise {
var firstError: Error?
- return appIDs.reduce(Guarantee.value(())) { previous, appID in
+ return appIDs.reduce(Guarantee.value(())) { previous, appID in
previous.then {
downloadWithRetries(appID, purchase: purchase).recover { error in
if firstError == nil {
diff --git a/Sources/mas/AppStore/ISStoreAccount.swift b/Sources/mas/AppStore/ISStoreAccount.swift
index 7dcfd30..7758ea4 100644
--- a/Sources/mas/AppStore/ISStoreAccount.swift
+++ b/Sources/mas/AppStore/ISStoreAccount.swift
@@ -14,7 +14,7 @@ extension ISStoreAccount: StoreAccount {
static var primaryAccount: Promise {
if #available(macOS 10.13, *) {
return race(
- Promise { seal in
+ Promise { seal in
ISServiceProxy.genericShared().accountService.primaryAccount { storeAccount in
seal.fulfill(storeAccount)
}
From 3eaffa5c3e3e0926b2b1d8e6bf77b8c15e726d63 Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Mon, 21 Oct 2024 07:10:31 -0400
Subject: [PATCH 05/20] Improve DocC.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Sources/mas/AppStore/Downloader.swift | 9 +++++----
Sources/mas/Commands/Lucky.swift | 6 ++++--
Sources/mas/Commands/Search.swift | 5 +++--
Sources/mas/Commands/Uninstall.swift | 2 +-
Sources/mas/Controllers/AppLibrary.swift | 12 ++++++------
Sources/mas/Controllers/MasStoreSearch.swift | 13 +++++--------
Sources/mas/Controllers/SoftwareMap.swift | 2 +-
Sources/mas/Controllers/StoreSearch.swift | 10 ++++++++--
Sources/mas/ExternalCommands/ExternalCommand.swift | 2 +-
.../mas/ExternalCommands/OpenSystemCommand.swift | 3 +--
.../mas/ExternalCommands/SysCtlSystemCommand.swift | 5 +++--
Sources/mas/Formatters/SearchResultFormatter.swift | 6 ++++--
Sources/mas/Formatters/Utilities.swift | 5 +++--
Sources/mas/Models/SoftwareProduct.swift | 7 +++++--
Sources/mas/Network/NetworkManager.swift | 7 +++----
Tests/masTests/Commands/AccountSpec.swift | 2 +-
Tests/masTests/Commands/SignInSpec.swift | 2 +-
Tests/masTests/Errors/MASErrorTestCase.swift | 4 +++-
Tests/masTests/Extensions/Bundle+JSON.swift | 3 ++-
Tests/masTests/Network/NetworkSessionMock.swift | 5 -----
.../Network/NetworkSessionMockFromFile.swift | 5 -----
script/version | 2 +-
22 files changed, 61 insertions(+), 56 deletions(-)
diff --git a/Sources/mas/AppStore/Downloader.swift b/Sources/mas/AppStore/Downloader.swift
index 7e0b338..cb5bbc3 100644
--- a/Sources/mas/AppStore/Downloader.swift
+++ b/Sources/mas/AppStore/Downloader.swift
@@ -12,11 +12,12 @@ import StoreFoundation
/// Downloads a list of apps, one after the other, printing progress to the console.
///
-/// - Parameter appIDs: The IDs of the apps to be downloaded
-/// - Parameter purchase: Flag indicating whether the apps needs to be purchased.
-/// Only works for free apps. Defaults to false.
+/// - Parameters:
+/// - appIDs: The IDs of the apps to be downloaded
+/// - purchase: Flag indicating whether the apps needs to be purchased.
+/// Only works for free apps. Defaults to false.
/// - Returns: A promise that completes when the downloads are complete. If any fail,
-/// the promise is rejected with the first error, after all remaining downloads are attempted.
+/// the promise is rejected with the first error, after all remaining downloads are attempted.
func downloadAll(_ appIDs: [AppID], purchase: Bool = false) -> Promise {
var firstError: Error?
return appIDs.reduce(Guarantee.value(())) { previous, appID in
diff --git a/Sources/mas/Commands/Lucky.swift b/Sources/mas/Commands/Lucky.swift
index 3b567fd..a75fbea 100644
--- a/Sources/mas/Commands/Lucky.swift
+++ b/Sources/mas/Commands/Lucky.swift
@@ -10,8 +10,9 @@ import ArgumentParser
import CommerceKit
extension Mas {
- /// Command which installs the first search result. This is handy as many MAS titles
- /// can be long with embedded keywords.
+ /// Command which installs the first search result.
+ ///
+ /// This is handy as many MAS titles can be long with embedded keywords.
struct Lucky: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Install the first result from the Mac App Store"
@@ -54,6 +55,7 @@ extension Mas {
/// - Parameters:
/// - appID: App identifier
/// - appLibrary: Library of installed apps
+ /// - Throws: Any error that occurs while attempting to install the app.
fileprivate func install(appID: AppID, appLibrary: AppLibrary) throws {
// Try to download applications with given identifiers and collect results
if let product = appLibrary.installedApp(withAppID: appID), !force {
diff --git a/Sources/mas/Commands/Search.swift b/Sources/mas/Commands/Search.swift
index 036c61c..79ef211 100644
--- a/Sources/mas/Commands/Search.swift
+++ b/Sources/mas/Commands/Search.swift
@@ -9,8 +9,9 @@
import ArgumentParser
extension Mas {
- /// Search the Mac App Store using the iTunes Search API:
- /// https://performance-partners.apple.com/search-api
+ /// Search the Mac App Store using the iTunes Search API.
+ ///
+ /// See - https://performance-partners.apple.com/search-api
struct Search: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Search for apps from the Mac App Store"
diff --git a/Sources/mas/Commands/Uninstall.swift b/Sources/mas/Commands/Uninstall.swift
index 8265296..632c84b 100644
--- a/Sources/mas/Commands/Uninstall.swift
+++ b/Sources/mas/Commands/Uninstall.swift
@@ -17,7 +17,7 @@ extension Mas {
abstract: "Uninstall app installed from the Mac App Store"
)
- /// Flag indicating that removal shouldn't be performed
+ /// Flag indicating that removal shouldn't be performed.
@Flag(help: "dry run")
var dryRun = false
@Argument(help: "ID of app to uninstall")
diff --git a/Sources/mas/Controllers/AppLibrary.swift b/Sources/mas/Controllers/AppLibrary.swift
index 1db3ff9..ebdcd70 100644
--- a/Sources/mas/Controllers/AppLibrary.swift
+++ b/Sources/mas/Controllers/AppLibrary.swift
@@ -13,10 +13,10 @@ protocol AppLibrary {
/// Entire set of installed apps.
var installedApps: [SoftwareProduct] { get }
- /// Finds an app by ID.
+ /// Finds an app for appID.
///
- /// - Parameter withAppID: MAS ID for app.
- /// - Returns: Software Product of app if found; nil otherwise.
+ /// - Parameter appID: app ID for app.
+ /// - Returns: SoftwareProduct of app if found; nil otherwise.
func installedApp(withAppID appID: AppID) -> SoftwareProduct?
/// Uninstalls an app.
@@ -28,10 +28,10 @@ protocol AppLibrary {
/// Common logic
extension AppLibrary {
- /// Finds an app by ID.
+ /// Finds an app for appID.
///
- /// - Parameter withAppID: MAS ID for app.
- /// - Returns: Software Product of app if found; nil otherwise.
+ /// - Parameter appID: app ID for app.
+ /// - Returns: SoftwareProduct of app if found; nil otherwise.
func installedApp(withAppID appID: AppID) -> SoftwareProduct? {
let appID = NSNumber(value: appID)
return installedApps.first { $0.itemIdentifier == appID }
diff --git a/Sources/mas/Controllers/MasStoreSearch.swift b/Sources/mas/Controllers/MasStoreSearch.swift
index 6e9325a..9e64b72 100644
--- a/Sources/mas/Controllers/MasStoreSearch.swift
+++ b/Sources/mas/Controllers/MasStoreSearch.swift
@@ -34,9 +34,8 @@ class MasStoreSearch: StoreSearch {
/// Searches for an app.
///
- /// - Parameter appName: MAS ID of app
- /// - Parameter completion: A closure that receives the search results or an Error if there is a
- /// problem with the network request. Results array will be empty if there were no matches.
+ /// - Parameter searchTerm: a search term matched against app names
+ /// - Returns: A Promise of an Array of SearchResults matching searchTerm
func search(for appName: String) -> Promise<[SearchResult]> {
// Search for apps for compatible platforms, in order of preference.
// Macs with Apple Silicon can run iPad and iPhone apps.
@@ -115,11 +114,9 @@ class MasStoreSearch: StoreSearch {
}
}
- // App Store pages indicate:
- // - compatibility with Macs with Apple Silicon
- // - (often) a version that is newer than what is listed in search results
- //
- // We attempt to scrape this information here.
+ /// Scrape the app version from the App Store webpage at the given URL.
+ ///
+ /// App Store webpages frequently report a version that is newer than what is reported by the iTunes Search API.
private func scrapeAppStoreVersion(_ pageUrl: URL) -> Promise {
firstly {
networkManager.loadData(from: pageUrl)
diff --git a/Sources/mas/Controllers/SoftwareMap.swift b/Sources/mas/Controllers/SoftwareMap.swift
index 46abea8..b131d42 100644
--- a/Sources/mas/Controllers/SoftwareMap.swift
+++ b/Sources/mas/Controllers/SoftwareMap.swift
@@ -6,7 +6,7 @@
// Copyright © 2020 mas-cli. All rights reserved.
//
-/// Somewhat analogous to CKSoftwareMap
+/// Somewhat analogous to CKSoftwareMap.
protocol SoftwareMap {
func allSoftwareProducts() -> [SoftwareProduct]
func product(for bundleIdentifier: String) -> SoftwareProduct?
diff --git a/Sources/mas/Controllers/StoreSearch.swift b/Sources/mas/Controllers/StoreSearch.swift
index e906cd9..12a46e0 100644
--- a/Sources/mas/Controllers/StoreSearch.swift
+++ b/Sources/mas/Controllers/StoreSearch.swift
@@ -40,7 +40,10 @@ private enum URLAction {
extension StoreSearch {
/// Builds the search URL for an app.
///
- /// - Parameter searchTerm: term for which to search in MAS.
+ /// - Parameters:
+ /// - searchTerm: term for which to search in MAS.
+ /// - country: 2-letter ISO region code of the MAS in which to search.
+ /// - entity: OS platform of apps for which to search.
/// - Returns: URL for the search service or nil if searchTerm can't be encoded.
func searchURL(
for searchTerm: String,
@@ -52,7 +55,10 @@ extension StoreSearch {
/// Builds the lookup URL for an app.
///
- /// - Parameter appID: MAS app identifier.
+ /// - Parameters:
+ /// - appID: MAS app identifier.
+ /// - country: 2-letter ISO region code of the MAS in which to search.
+ /// - entity: OS platform of apps for which to search.
/// - Returns: URL for the lookup service or nil if appID can't be encoded.
func lookupURL(
forAppID appID: AppID,
diff --git a/Sources/mas/ExternalCommands/ExternalCommand.swift b/Sources/mas/ExternalCommands/ExternalCommand.swift
index 40946be..2168a05 100644
--- a/Sources/mas/ExternalCommands/ExternalCommand.swift
+++ b/Sources/mas/ExternalCommands/ExternalCommand.swift
@@ -8,7 +8,7 @@
import Foundation
-/// CLI command
+/// Represents a CLI command.
protocol ExternalCommand {
var binaryPath: String { get set }
diff --git a/Sources/mas/ExternalCommands/OpenSystemCommand.swift b/Sources/mas/ExternalCommands/OpenSystemCommand.swift
index ebce90d..5d5e3cc 100644
--- a/Sources/mas/ExternalCommands/OpenSystemCommand.swift
+++ b/Sources/mas/ExternalCommands/OpenSystemCommand.swift
@@ -8,8 +8,7 @@
import Foundation
-/// Wrapper for the external open system command.
-/// https://ss64.com/osx/open.html
+/// Wrapper for the external 'open' system command (https://ss64.com/osx/open.html).
struct OpenSystemCommand: ExternalCommand {
var binaryPath: String
diff --git a/Sources/mas/ExternalCommands/SysCtlSystemCommand.swift b/Sources/mas/ExternalCommands/SysCtlSystemCommand.swift
index 41d3dcb..cbb24d9 100644
--- a/Sources/mas/ExternalCommands/SysCtlSystemCommand.swift
+++ b/Sources/mas/ExternalCommands/SysCtlSystemCommand.swift
@@ -8,8 +8,9 @@
import Foundation
-/// Wrapper for the external sysctl system command.
-/// https://ss64.com/osx/sysctl.html
+/// Wrapper for the external 'sysctl' system command.
+///
+/// See - https://ss64.com/osx/sysctl.html
struct SysCtlSystemCommand: ExternalCommand {
var binaryPath: String
diff --git a/Sources/mas/Formatters/SearchResultFormatter.swift b/Sources/mas/Formatters/SearchResultFormatter.swift
index eac80a7..96e9414 100644
--- a/Sources/mas/Formatters/SearchResultFormatter.swift
+++ b/Sources/mas/Formatters/SearchResultFormatter.swift
@@ -10,9 +10,11 @@ import Foundation
/// Formats text output for the search command.
enum SearchResultFormatter {
- /// Formats text output with search results.
+ /// Formats search results as text.
///
- /// - Parameter results: Search results with app data
+ /// - Parameters:
+ /// - results: Search results containing app data
+ /// - includePrice: Indicates whether to include prices in the output
/// - Returns: Multiline text output.
static func format(results: [SearchResult], includePrice: Bool = false) -> String {
// find longest appName for formatting, default 50
diff --git a/Sources/mas/Formatters/Utilities.swift b/Sources/mas/Formatters/Utilities.swift
index fae8ad3..3f41341 100644
--- a/Sources/mas/Formatters/Utilities.swift
+++ b/Sources/mas/Formatters/Utilities.swift
@@ -8,14 +8,15 @@
import Foundation
-/// A collection of output formatting helpers
+// A collection of output formatting helpers
-/// Terminal Control Sequence Indicator
+/// Terminal Control Sequence Indicator.
let csi = "\u{001B}["
private var standardError = FileHandle.standardError
extension FileHandle: TextOutputStream {
+ /// Appends the given string to the stream.
public func write(_ string: String) {
guard let data = string.data(using: .utf8) else { return }
write(data)
diff --git a/Sources/mas/Models/SoftwareProduct.swift b/Sources/mas/Models/SoftwareProduct.swift
index aa49d81..7b882c5 100644
--- a/Sources/mas/Models/SoftwareProduct.swift
+++ b/Sources/mas/Models/SoftwareProduct.swift
@@ -19,12 +19,15 @@ protocol SoftwareProduct {
}
extension SoftwareProduct {
- /// Returns bundleIdentifier if appName is empty string.
+ /// - Returns: bundleIdentifier if appName is empty string.
var appNameOrBundleIdentifier: String {
appName.isEmpty ? bundleIdentifier : appName
}
- /// Determines whether the app is considered outdated. Updates that require a higher OS version are excluded.
+ /// Determines whether the app is considered outdated.
+ ///
+ /// Updates that require a higher OS version are excluded.
+ ///
/// - Parameter storeApp: App from search result.
/// - Returns: true if the app is outdated; false otherwise.
func isOutdatedWhenComparedTo(_ storeApp: SearchResult) -> Bool {
diff --git a/Sources/mas/Network/NetworkManager.swift b/Sources/mas/Network/NetworkManager.swift
index 45fa7b8..9c4f496 100644
--- a/Sources/mas/Network/NetworkManager.swift
+++ b/Sources/mas/Network/NetworkManager.swift
@@ -9,11 +9,11 @@
import Foundation
import PromiseKit
-/// Network abstraction
+/// Network abstraction.
class NetworkManager {
private let session: NetworkSession
- /// Designated initializer
+ /// Designated initializer.
///
/// - Parameter session: A networking session.
init(session: NetworkSession = URLSession(configuration: .ephemeral)) {
@@ -28,8 +28,7 @@ class NetworkManager {
/// Loads data asynchronously.
///
- /// - Parameters:
- /// - url: URL to load data from.
+ /// - Parameter url: URL from which to load data.
/// - Returns: A Promise for the Data of the response.
func loadData(from url: URL) -> Promise {
session.loadData(from: url)
diff --git a/Tests/masTests/Commands/AccountSpec.swift b/Tests/masTests/Commands/AccountSpec.swift
index 4cfb4da..db220c4 100644
--- a/Tests/masTests/Commands/AccountSpec.swift
+++ b/Tests/masTests/Commands/AccountSpec.swift
@@ -11,7 +11,7 @@ import Quick
@testable import mas
-// Deprecated test
+/// Deprecated test.
public class AccountSpec: QuickSpec {
override public func spec() {
beforeSuite {
diff --git a/Tests/masTests/Commands/SignInSpec.swift b/Tests/masTests/Commands/SignInSpec.swift
index 799d093..f7f5d33 100644
--- a/Tests/masTests/Commands/SignInSpec.swift
+++ b/Tests/masTests/Commands/SignInSpec.swift
@@ -11,7 +11,7 @@ import Quick
@testable import mas
-// Deprecated test
+/// Deprecated test.
public class SignInSpec: QuickSpec {
override public func spec() {
beforeSuite {
diff --git a/Tests/masTests/Errors/MASErrorTestCase.swift b/Tests/masTests/Errors/MASErrorTestCase.swift
index f9e13dd..0d59cb2 100644
--- a/Tests/masTests/Errors/MASErrorTestCase.swift
+++ b/Tests/masTests/Errors/MASErrorTestCase.swift
@@ -17,7 +17,9 @@ class MASErrorTestCase: XCTestCase {
var nserror: NSError!
/// Convenience property for setting the value which will be use for the localized description
- /// value of the next NSError created. Only used when the NSError does not have a user info
+ /// value of the next NSError created.
+ ///
+ /// Only used when the NSError does not have a user info
/// entry for localized description.
var localizedDescription: String {
get { "dummy value" }
diff --git a/Tests/masTests/Extensions/Bundle+JSON.swift b/Tests/masTests/Extensions/Bundle+JSON.swift
index 34cd6ac..3c9628c 100644
--- a/Tests/masTests/Extensions/Bundle+JSON.swift
+++ b/Tests/masTests/Extensions/Bundle+JSON.swift
@@ -10,7 +10,8 @@ import Foundation
extension Data {
/// Unsafe initializer for loading data from string paths.
- /// - Parameter file: Relative path within the JSON folder
+ ///
+ /// - Parameter fileName: Relative path within the JSON folder
init(from fileName: String) {
let fileURL = Bundle.url(for: fileName)!
try! self.init(contentsOf: fileURL, options: .mappedIfSafe)
diff --git a/Tests/masTests/Network/NetworkSessionMock.swift b/Tests/masTests/Network/NetworkSessionMock.swift
index a286bbb..31274cc 100644
--- a/Tests/masTests/Network/NetworkSessionMock.swift
+++ b/Tests/masTests/Network/NetworkSessionMock.swift
@@ -18,11 +18,6 @@ class NetworkSessionMock: NetworkSession {
var data: Data?
var error: Error?
- /// Immediately passes data and error to completion handler.
- ///
- /// - Parameters:
- /// - url: unused
- /// - completionHandler: Closure which is delivered either data or an error.
func loadData(from _: URL) -> Promise {
guard let data else {
return Promise(error: error ?? MASError.noData)
diff --git a/Tests/masTests/Network/NetworkSessionMockFromFile.swift b/Tests/masTests/Network/NetworkSessionMockFromFile.swift
index a68220d..07b25aa 100644
--- a/Tests/masTests/Network/NetworkSessionMockFromFile.swift
+++ b/Tests/masTests/Network/NetworkSessionMockFromFile.swift
@@ -21,11 +21,6 @@ class NetworkSessionMockFromFile: NetworkSessionMock {
self.responseFile = responseFile
}
- /// Loads data from a file.
- ///
- /// - Parameters:
- /// - url: unused
- /// - completionHandler: Closure which is delivered either data or an error.
override func loadData(from _: URL) -> Promise {
guard let fileURL = Bundle.url(for: responseFile)
else { fatalError("Unable to load file \(responseFile)") }
diff --git a/script/version b/script/version
index 2f977ff..0a3e177 100755
--- a/script/version
+++ b/script/version
@@ -20,7 +20,7 @@ VERSION=$(git describe --abbrev=0 --tags)
VERSION=${VERSION#v}
cat <"Sources/mas/Package.swift"
-// Generated by: script/version
+/// Generated by \`script/version\`.
enum Package {
static let version = "${VERSION}"
}
From 71fbe2e444168c715f8a2e57923c0545fd5f2682 Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Mon, 21 Oct 2024 07:12:32 -0400
Subject: [PATCH 06/20] Improve spacing.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Sources/mas/AppStore/Downloader.swift | 24 +++++++++++--------
Sources/mas/AppStore/ISStoreAccount.swift | 21 +++++++++-------
.../AppStore/PurchaseDownloadObserver.swift | 3 ++-
Sources/mas/AppStore/SSPurchase.swift | 24 +++++++++----------
Sources/mas/Commands/Open.swift | 6 ++---
Sources/mas/Commands/Outdated.swift | 3 ++-
Sources/mas/Commands/Upgrade.swift | 6 +++--
Sources/mas/Commands/Vendor.swift | 8 +++----
Sources/mas/Controllers/MasAppLibrary.swift | 7 +++---
Sources/mas/Controllers/MasStoreSearch.swift | 20 +++++++++-------
Sources/mas/Formatters/Utilities.swift | 4 +++-
.../Controllers/StoreSearchMock.swift | 3 +--
.../Network/NetworkSessionMockFromFile.swift | 5 ++--
13 files changed, 75 insertions(+), 59 deletions(-)
diff --git a/Sources/mas/AppStore/Downloader.swift b/Sources/mas/AppStore/Downloader.swift
index cb5bbc3..bb09f3d 100644
--- a/Sources/mas/AppStore/Downloader.swift
+++ b/Sources/mas/AppStore/Downloader.swift
@@ -20,19 +20,23 @@ import StoreFoundation
/// the promise is rejected with the first error, after all remaining downloads are attempted.
func downloadAll(_ appIDs: [AppID], purchase: Bool = false) -> Promise {
var firstError: Error?
- return appIDs.reduce(Guarantee.value(())) { previous, appID in
- previous.then {
- downloadWithRetries(appID, purchase: purchase).recover { error in
- if firstError == nil {
- firstError = error
- }
+ return
+ appIDs
+ .reduce(Guarantee.value(())) { previous, appID in
+ previous.then {
+ downloadWithRetries(appID, purchase: purchase)
+ .recover { error in
+ if firstError == nil {
+ firstError = error
+ }
+ }
}
}
- }.done {
- if let error = firstError {
- throw error
+ .done {
+ if let error = firstError {
+ throw error
+ }
}
- }
}
private func downloadWithRetries(_ appID: AppID, purchase: Bool = false, attempts: Int = 3) -> Promise {
diff --git a/Sources/mas/AppStore/ISStoreAccount.swift b/Sources/mas/AppStore/ISStoreAccount.swift
index 7758ea4..f1d4fe0 100644
--- a/Sources/mas/AppStore/ISStoreAccount.swift
+++ b/Sources/mas/AppStore/ISStoreAccount.swift
@@ -15,13 +15,15 @@ extension ISStoreAccount: StoreAccount {
if #available(macOS 10.13, *) {
return race(
Promise { seal in
- ISServiceProxy.genericShared().accountService.primaryAccount { storeAccount in
- seal.fulfill(storeAccount)
- }
+ ISServiceProxy.genericShared().accountService
+ .primaryAccount { storeAccount in
+ seal.fulfill(storeAccount)
+ }
},
- after(seconds: 30).then {
- Promise(error: MASError.notSignedIn)
- }
+ after(seconds: 30)
+ .then {
+ Promise(error: MASError.notSignedIn)
+ }
)
} else {
return .value(CKAccountStore.shared().primaryAccount)
@@ -76,9 +78,10 @@ extension ISStoreAccount: StoreAccount {
return race(
signInPromise,
- after(seconds: 30).then {
- Promise(error: MASError.signInFailed(error: nil))
- }
+ after(seconds: 30)
+ .then {
+ Promise(error: MASError.signInFailed(error: nil))
+ }
)
}
}
diff --git a/Sources/mas/AppStore/PurchaseDownloadObserver.swift b/Sources/mas/AppStore/PurchaseDownloadObserver.swift
index 343b49c..fd4add4 100644
--- a/Sources/mas/AppStore/PurchaseDownloadObserver.swift
+++ b/Sources/mas/AppStore/PurchaseDownloadObserver.swift
@@ -9,7 +9,8 @@
import CommerceKit
import StoreFoundation
-@objc class PurchaseDownloadObserver: NSObject, CKDownloadQueueObserver {
+@objc
+class PurchaseDownloadObserver: NSObject, CKDownloadQueueObserver {
let purchase: SSPurchase
var completionHandler: (() -> Void)?
var errorHandler: ((MASError) -> Void)?
diff --git a/Sources/mas/AppStore/SSPurchase.swift b/Sources/mas/AppStore/SSPurchase.swift
index cabfa51..cf712cd 100644
--- a/Sources/mas/AppStore/SSPurchase.swift
+++ b/Sources/mas/AppStore/SSPurchase.swift
@@ -23,7 +23,6 @@ extension SSPurchase {
if purchase {
parameters["macappinstalledconfirmed"] = 1
parameters["pricingParameters"] = "STDQ"
-
} else {
parameters["pricingParameters"] = "STDRDL"
}
@@ -63,19 +62,20 @@ extension SSPurchase {
private func perform() -> Promise {
Promise { seal in
- CKPurchaseController.shared().perform(self, withOptions: 0) { purchase, _, error, response in
- if let error {
- seal.reject(MASError.purchaseFailed(error: error as NSError?))
- return
- }
+ CKPurchaseController.shared()
+ .perform(self, withOptions: 0) { purchase, _, error, response in
+ if let error {
+ seal.reject(MASError.purchaseFailed(error: error as NSError?))
+ return
+ }
- guard response?.downloads.isEmpty == false, let purchase else {
- seal.reject(MASError.noDownloads)
- return
- }
+ guard response?.downloads.isEmpty == false, let purchase else {
+ seal.reject(MASError.noDownloads)
+ return
+ }
- seal.fulfill(purchase)
- }
+ seal.fulfill(purchase)
+ }
}
.then { purchase in
let observer = PurchaseDownloadObserver(purchase: purchase)
diff --git a/Sources/mas/Commands/Open.swift b/Sources/mas/Commands/Open.swift
index 4cc0c99..4ae4247 100644
--- a/Sources/mas/Commands/Open.swift
+++ b/Sources/mas/Commands/Open.swift
@@ -35,13 +35,11 @@ extension Mas {
return
}
- guard let result = try storeSearch.lookup(appID: appID).wait()
- else {
+ guard let result = try storeSearch.lookup(appID: appID).wait() else {
throw MASError.noSearchResultsFound
}
- guard var url = URLComponents(string: result.trackViewUrl)
- else {
+ guard var url = URLComponents(string: result.trackViewUrl) else {
throw MASError.searchFailed
}
url.scheme = masScheme
diff --git a/Sources/mas/Commands/Outdated.swift b/Sources/mas/Commands/Outdated.swift
index 9c0b1be..2164a4b 100644
--- a/Sources/mas/Commands/Outdated.swift
+++ b/Sources/mas/Commands/Outdated.swift
@@ -32,7 +32,8 @@ extension Mas {
appLibrary.installedApps.map { installedApp in
firstly {
storeSearch.lookup(appID: installedApp.itemIdentifier.appIDValue)
- }.done { storeApp in
+ }
+ .done { storeApp in
guard let storeApp else {
if verbose {
printWarning(
diff --git a/Sources/mas/Commands/Upgrade.swift b/Sources/mas/Commands/Upgrade.swift
index cfc7b66..a588af2 100644
--- a/Sources/mas/Commands/Upgrade.swift
+++ b/Sources/mas/Commands/Upgrade.swift
@@ -41,7 +41,8 @@ extension Mas {
print("Upgrading \(apps.count) outdated application\(apps.count > 1 ? "s" : ""):")
print(
apps.map { "\($0.installedApp.appName) (\($0.installedApp.bundleVersion)) -> (\($0.storeApp.version))" }
- .joined(separator: "\n"))
+ .joined(separator: "\n")
+ )
do {
try downloadAll(apps.map(\.installedApp.itemIdentifier.appIDValue)).wait()
@@ -71,7 +72,8 @@ extension Mas {
// only upgrade apps whose local version differs from the store version
firstly {
storeSearch.lookup(appID: installedApp.itemIdentifier.appIDValue)
- }.map { result -> (SoftwareProduct, SearchResult)? in
+ }
+ .map { result -> (SoftwareProduct, SearchResult)? in
guard let storeApp = result, installedApp.isOutdatedWhenComparedTo(storeApp) else {
return nil
}
diff --git a/Sources/mas/Commands/Vendor.swift b/Sources/mas/Commands/Vendor.swift
index bdf13b1..7380ca0 100644
--- a/Sources/mas/Commands/Vendor.swift
+++ b/Sources/mas/Commands/Vendor.swift
@@ -26,13 +26,13 @@ extension Mas {
func run(storeSearch: StoreSearch, openCommand: ExternalCommand) throws {
do {
- guard let result = try storeSearch.lookup(appID: appID).wait()
- else {
+ guard let result = try storeSearch.lookup(appID: appID).wait() else {
throw MASError.noSearchResultsFound
}
- guard let vendorWebsite = result.sellerUrl
- else { throw MASError.noVendorWebsite }
+ guard let vendorWebsite = result.sellerUrl else {
+ throw MASError.noVendorWebsite
+ }
do {
try openCommand.run(arguments: vendorWebsite)
diff --git a/Sources/mas/Controllers/MasAppLibrary.swift b/Sources/mas/Controllers/MasAppLibrary.swift
index d842194..9325af4 100644
--- a/Sources/mas/Controllers/MasAppLibrary.swift
+++ b/Sources/mas/Controllers/MasAppLibrary.swift
@@ -14,9 +14,10 @@ class MasAppLibrary: AppLibrary {
private let softwareMap: SoftwareMap
/// Array of installed software products.
- lazy var installedApps: [SoftwareProduct] = softwareMap.allSoftwareProducts().filter { product in
- product.bundlePath.starts(with: "/Applications/")
- }
+ lazy var installedApps: [SoftwareProduct] = softwareMap.allSoftwareProducts()
+ .filter { product in
+ product.bundlePath.starts(with: "/Applications/")
+ }
/// Internal initializer for providing a mock software map.
/// - Parameter softwareMap: SoftwareMap to use
diff --git a/Sources/mas/Controllers/MasStoreSearch.swift b/Sources/mas/Controllers/MasStoreSearch.swift
index 9e64b72..9680070 100644
--- a/Sources/mas/Controllers/MasStoreSearch.swift
+++ b/Sources/mas/Controllers/MasStoreSearch.swift
@@ -53,9 +53,11 @@ class MasStoreSearch: StoreSearch {
// Combine the results, removing any duplicates.
var seenAppIDs = Set()
- return when(fulfilled: results).flatMapValues { $0 }.filterValues { result in
- seenAppIDs.insert(result.trackId).inserted
- }
+ return when(fulfilled: results)
+ .flatMapValues { $0 }
+ .filterValues { result in
+ seenAppIDs.insert(result.trackId).inserted
+ }
}
/// Looks up app details.
@@ -75,14 +77,14 @@ class MasStoreSearch: StoreSearch {
return .value(nil)
}
- guard let pageUrl = URL(string: result.trackViewUrl)
- else {
+ guard let pageUrl = URL(string: result.trackViewUrl) else {
return .value(result)
}
return firstly {
self.scrapeAppStoreVersion(pageUrl)
- }.map { pageVersion in
+ }
+ .map { pageVersion in
guard let pageVersion,
let searchVersion = Version(tolerant: result.version),
pageVersion > searchVersion
@@ -94,7 +96,8 @@ class MasStoreSearch: StoreSearch {
var result = result
result.version = pageVersion.description
return result
- }.recover { _ in
+ }
+ .recover { _ in
// If we were unable to scrape the App Store page, assume compatibility.
.value(result)
}
@@ -120,7 +123,8 @@ class MasStoreSearch: StoreSearch {
private func scrapeAppStoreVersion(_ pageUrl: URL) -> Promise {
firstly {
networkManager.loadData(from: pageUrl)
- }.map { data in
+ }
+ .map { data in
guard let html = String(data: data, encoding: .utf8),
let capture = MasStoreSearch.appVersionExpression.firstMatch(in: html)?.captures[0],
let version = Version(tolerant: capture)
diff --git a/Sources/mas/Formatters/Utilities.swift b/Sources/mas/Formatters/Utilities.swift
index 3f41341..63e436c 100644
--- a/Sources/mas/Formatters/Utilities.swift
+++ b/Sources/mas/Formatters/Utilities.swift
@@ -18,7 +18,9 @@ private var standardError = FileHandle.standardError
extension FileHandle: TextOutputStream {
/// Appends the given string to the stream.
public func write(_ string: String) {
- guard let data = string.data(using: .utf8) else { return }
+ guard let data = string.data(using: .utf8) else {
+ return
+ }
write(data)
}
}
diff --git a/Tests/masTests/Controllers/StoreSearchMock.swift b/Tests/masTests/Controllers/StoreSearchMock.swift
index f97a1d3..938c2b0 100644
--- a/Tests/masTests/Controllers/StoreSearchMock.swift
+++ b/Tests/masTests/Controllers/StoreSearchMock.swift
@@ -18,8 +18,7 @@ class StoreSearchMock: StoreSearch {
}
func lookup(appID: AppID) -> Promise {
- guard let result = apps[appID]
- else {
+ guard let result = apps[appID] else {
return Promise(error: MASError.noSearchResultsFound)
}
diff --git a/Tests/masTests/Network/NetworkSessionMockFromFile.swift b/Tests/masTests/Network/NetworkSessionMockFromFile.swift
index 07b25aa..2ab1fa1 100644
--- a/Tests/masTests/Network/NetworkSessionMockFromFile.swift
+++ b/Tests/masTests/Network/NetworkSessionMockFromFile.swift
@@ -22,8 +22,9 @@ class NetworkSessionMockFromFile: NetworkSessionMock {
}
override func loadData(from _: URL) -> Promise {
- guard let fileURL = Bundle.url(for: responseFile)
- else { fatalError("Unable to load file \(responseFile)") }
+ guard let fileURL = Bundle.url(for: responseFile) else {
+ fatalError("Unable to load file \(responseFile)")
+ }
do {
return .value(try Data(contentsOf: fileURL, options: .mappedIfSafe))
From d7074db06f28e405793a65803c1c7719caffc93e Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Mon, 21 Oct 2024 07:29:13 -0400
Subject: [PATCH 07/20] Remove unnecessary `else` blocks.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Sources/mas/AppStore/ISStoreAccount.swift | 30 +++++++++++------------
Sources/mas/Commands/Upgrade.swift | 8 +++---
Sources/mas/Errors/MASError.swift | 18 +++++---------
3 files changed, 25 insertions(+), 31 deletions(-)
diff --git a/Sources/mas/AppStore/ISStoreAccount.swift b/Sources/mas/AppStore/ISStoreAccount.swift
index f1d4fe0..414dc29 100644
--- a/Sources/mas/AppStore/ISStoreAccount.swift
+++ b/Sources/mas/AppStore/ISStoreAccount.swift
@@ -25,9 +25,9 @@ extension ISStoreAccount: StoreAccount {
Promise(error: MASError.notSignedIn)
}
)
- } else {
- return .value(CKAccountStore.shared().primaryAccount)
}
+
+ return .value(CKAccountStore.shared().primaryAccount)
}
static func signIn(username: String, password: String, systemDialog: Bool) -> Promise {
@@ -70,20 +70,20 @@ extension ISStoreAccount: StoreAccount {
if systemDialog {
return signInPromise
- } else {
- context.demoMode = true
- context.demoAccountName = username
- context.demoAccountPassword = password
- context.demoAutologinMode = true
-
- return race(
- signInPromise,
- after(seconds: 30)
- .then {
- Promise(error: MASError.signInFailed(error: nil))
- }
- )
}
+
+ context.demoMode = true
+ context.demoAccountName = username
+ context.demoAccountPassword = password
+ context.demoAutologinMode = true
+
+ return race(
+ signInPromise,
+ after(seconds: 30)
+ .then {
+ Promise(error: MASError.signInFailed(error: nil))
+ }
+ )
}
}
}
diff --git a/Sources/mas/Commands/Upgrade.swift b/Sources/mas/Commands/Upgrade.swift
index a588af2..bd23d93 100644
--- a/Sources/mas/Commands/Upgrade.swift
+++ b/Sources/mas/Commands/Upgrade.swift
@@ -60,12 +60,12 @@ extension Mas {
? appLibrary.installedApps
: appIDs.compactMap {
if let appID = AppID($0) {
- // if argument an AppID, lookup app by id using argument
+ // argument is an AppID, lookup app by id using argument
return appLibrary.installedApp(withAppID: appID)
- } else {
- // if argument not an AppID, lookup app by name using argument
- return appLibrary.installedApp(named: $0)
}
+
+ // argument is not an AppID, lookup app by name using argument
+ return appLibrary.installedApp(named: $0)
}
let promises = apps.map { installedApp in
diff --git a/Sources/mas/Errors/MASError.swift b/Sources/mas/Errors/MASError.swift
index 4d4155a..28e6754 100644
--- a/Sources/mas/Errors/MASError.swift
+++ b/Sources/mas/Errors/MASError.swift
@@ -51,29 +51,25 @@ extension MASError: CustomStringConvertible {
case .failed(let error):
if let error {
return "Failed: \(error.localizedDescription)"
- } else {
- return "Failed"
}
+ return "Failed"
case .signInFailed(let error):
if let error {
return "Sign in failed: \(error.localizedDescription)"
- } else {
- return "Sign in failed"
}
+ return "Sign in failed"
case .alreadySignedIn(let accountId):
return "Already signed in as \(accountId)"
case .purchaseFailed(let error):
if let error {
return "Download request failed: \(error.localizedDescription)"
- } else {
- return "Download request failed"
}
+ return "Download request failed"
case .downloadFailed(let error):
if let error {
return "Download failed: \(error.localizedDescription)"
- } else {
- return "Download failed"
}
+ return "Download failed"
case .noDownloads:
return "No downloads began"
case .cancelled:
@@ -94,12 +90,10 @@ extension MASError: CustomStringConvertible {
if let data {
if let unparsable = String(data: data, encoding: .utf8) {
return "Unable to parse response as JSON: \n\(unparsable)"
- } else {
- return "Received defective response"
}
- } else {
- return "Received empty response"
+ return "Received defective response"
}
+ return "Received empty response"
}
}
}
From 06a347c4501f94ad2ba528f07134d9d01319379b Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Mon, 21 Oct 2024 07:35:16 -0400
Subject: [PATCH 08/20] Use `isEmpty` / `beEmpty()`.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Sources/mas/Commands/Upgrade.swift | 2 +-
Tests/masTests/Formatters/AppListFormatterSpec.swift | 2 +-
Tests/masTests/Formatters/SearchResultFormatterSpec.swift | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Sources/mas/Commands/Upgrade.swift b/Sources/mas/Commands/Upgrade.swift
index bd23d93..db825b6 100644
--- a/Sources/mas/Commands/Upgrade.swift
+++ b/Sources/mas/Commands/Upgrade.swift
@@ -33,7 +33,7 @@ extension Mas {
throw error as? MASError ?? .searchFailed
}
- guard apps.count > 0 else {
+ guard !apps.isEmpty else {
printWarning("Nothing found to upgrade")
return
}
diff --git a/Tests/masTests/Formatters/AppListFormatterSpec.swift b/Tests/masTests/Formatters/AppListFormatterSpec.swift
index 98ed50f..42346ae 100644
--- a/Tests/masTests/Formatters/AppListFormatterSpec.swift
+++ b/Tests/masTests/Formatters/AppListFormatterSpec.swift
@@ -25,7 +25,7 @@ public class AppListsFormatterSpec: QuickSpec {
products = []
}
it("formats nothing as empty string") {
- expect(format(products)) == ""
+ expect(format(products)).to(beEmpty())
}
it("can format a single product") {
let product = SoftwareProductMock(
diff --git a/Tests/masTests/Formatters/SearchResultFormatterSpec.swift b/Tests/masTests/Formatters/SearchResultFormatterSpec.swift
index f92731d..10fec4b 100644
--- a/Tests/masTests/Formatters/SearchResultFormatterSpec.swift
+++ b/Tests/masTests/Formatters/SearchResultFormatterSpec.swift
@@ -25,7 +25,7 @@ public class SearchResultsFormatterSpec: QuickSpec {
results = []
}
it("formats nothing as empty string") {
- expect(format(results, false)) == ""
+ expect(format(results, false)).to(beEmpty())
}
it("can format a single result") {
let result = SearchResult(
From e927466dceb9801176e8d7b6f3463f7644b0ca2c Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Mon, 21 Oct 2024 07:38:04 -0400
Subject: [PATCH 09/20] Use `Self`.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Sources/mas/Controllers/MasStoreSearch.swift | 2 +-
Sources/mas/ExternalCommands/SysCtlSystemCommand.swift | 2 +-
Sources/mas/Mas.swift | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Sources/mas/Controllers/MasStoreSearch.swift b/Sources/mas/Controllers/MasStoreSearch.swift
index 9680070..a2cf5b2 100644
--- a/Sources/mas/Controllers/MasStoreSearch.swift
+++ b/Sources/mas/Controllers/MasStoreSearch.swift
@@ -126,7 +126,7 @@ class MasStoreSearch: StoreSearch {
}
.map { data in
guard let html = String(data: data, encoding: .utf8),
- let capture = MasStoreSearch.appVersionExpression.firstMatch(in: html)?.captures[0],
+ let capture = Self.appVersionExpression.firstMatch(in: html)?.captures[0],
let version = Version(tolerant: capture)
else {
return nil
diff --git a/Sources/mas/ExternalCommands/SysCtlSystemCommand.swift b/Sources/mas/ExternalCommands/SysCtlSystemCommand.swift
index cbb24d9..abd5fb8 100644
--- a/Sources/mas/ExternalCommands/SysCtlSystemCommand.swift
+++ b/Sources/mas/ExternalCommands/SysCtlSystemCommand.swift
@@ -24,7 +24,7 @@ struct SysCtlSystemCommand: ExternalCommand {
}
static var isAppleSilicon: Bool = {
- let sysctl = SysCtlSystemCommand()
+ let sysctl = Self()
do {
// Returns 1 on Apple Silicon even when run in an Intel context in Rosetta.
try sysctl.run(arguments: "-in", "hw.optional.arm64")
diff --git a/Sources/mas/Mas.swift b/Sources/mas/Mas.swift
index 1d273fa..5fb8dda 100644
--- a/Sources/mas/Mas.swift
+++ b/Sources/mas/Mas.swift
@@ -36,7 +36,7 @@ struct Mas: ParsableCommand {
)
func validate() throws {
- Mas.initialize()
+ Self.initialize()
}
static func initialize() {
From 490ee2d3385f8cf854923ebaf8b887742c916708 Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Mon, 21 Oct 2024 07:38:51 -0400
Subject: [PATCH 10/20] Fix class names.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Tests/masTests/Formatters/AppListFormatterSpec.swift | 2 +-
Tests/masTests/Formatters/SearchResultFormatterSpec.swift | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Tests/masTests/Formatters/AppListFormatterSpec.swift b/Tests/masTests/Formatters/AppListFormatterSpec.swift
index 42346ae..47b3b18 100644
--- a/Tests/masTests/Formatters/AppListFormatterSpec.swift
+++ b/Tests/masTests/Formatters/AppListFormatterSpec.swift
@@ -11,7 +11,7 @@ import Quick
@testable import mas
-public class AppListsFormatterSpec: QuickSpec {
+public class AppListFormatterSpec: QuickSpec {
override public func spec() {
// static func reference
let format = AppListFormatter.format(products:)
diff --git a/Tests/masTests/Formatters/SearchResultFormatterSpec.swift b/Tests/masTests/Formatters/SearchResultFormatterSpec.swift
index 10fec4b..1689fa0 100644
--- a/Tests/masTests/Formatters/SearchResultFormatterSpec.swift
+++ b/Tests/masTests/Formatters/SearchResultFormatterSpec.swift
@@ -11,7 +11,7 @@ import Quick
@testable import mas
-public class SearchResultsFormatterSpec: QuickSpec {
+public class SearchResultFormatterSpec: QuickSpec {
override public func spec() {
// static func reference
let format = SearchResultFormatter.format(results:includePrice:)
From 586de073892a6a5f704d1dca3654f1e94a60e97c Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Mon, 21 Oct 2024 07:40:14 -0400
Subject: [PATCH 11/20] Improve access control.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Sources/mas/Commands/Lucky.swift | 2 +-
Tests/masTests/Errors/MASErrorTestCase.swift | 6 +++---
Tests/masTests/Network/NetworkManagerTests.swift | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Sources/mas/Commands/Lucky.swift b/Sources/mas/Commands/Lucky.swift
index a75fbea..b79c776 100644
--- a/Sources/mas/Commands/Lucky.swift
+++ b/Sources/mas/Commands/Lucky.swift
@@ -56,7 +56,7 @@ extension Mas {
/// - appID: App identifier
/// - appLibrary: Library of installed apps
/// - Throws: Any error that occurs while attempting to install the app.
- fileprivate func install(appID: AppID, appLibrary: AppLibrary) throws {
+ private func install(appID: AppID, appLibrary: AppLibrary) throws {
// Try to download applications with given identifiers and collect results
if let product = appLibrary.installedApp(withAppID: appID), !force {
printWarning("\(product.appName) is already installed")
diff --git a/Tests/masTests/Errors/MASErrorTestCase.swift b/Tests/masTests/Errors/MASErrorTestCase.swift
index 0d59cb2..fdc044e 100644
--- a/Tests/masTests/Errors/MASErrorTestCase.swift
+++ b/Tests/masTests/Errors/MASErrorTestCase.swift
@@ -13,15 +13,15 @@ import XCTest
class MASErrorTestCase: XCTestCase {
private let errorDomain = "MAS"
- var error: MASError!
- var nserror: NSError!
+ private var error: MASError!
+ private var nserror: NSError!
/// Convenience property for setting the value which will be use for the localized description
/// value of the next NSError created.
///
/// Only used when the NSError does not have a user info
/// entry for localized description.
- var localizedDescription: String {
+ private var localizedDescription: String {
get { "dummy value" }
set {
NSError.setUserInfoValueProvider(forDomain: errorDomain) { _, _ in
diff --git a/Tests/masTests/Network/NetworkManagerTests.swift b/Tests/masTests/Network/NetworkManagerTests.swift
index a92227c..450bb39 100644
--- a/Tests/masTests/Network/NetworkManagerTests.swift
+++ b/Tests/masTests/Network/NetworkManagerTests.swift
@@ -11,7 +11,7 @@ import XCTest
@testable import mas
class NetworkManagerTests: XCTestCase {
- override public func setUp() {
+ override func setUp() {
super.setUp()
Mas.initialize()
}
From 2af2a42a88893f4173796dc429d667d2fad71c1f Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Mon, 21 Oct 2024 07:42:30 -0400
Subject: [PATCH 12/20] Improve parameter names.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Sources/mas/Commands/Upgrade.swift | 6 +++---
Sources/mas/Controllers/MasStoreSearch.swift | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/Sources/mas/Commands/Upgrade.swift b/Sources/mas/Commands/Upgrade.swift
index db825b6..41054d2 100644
--- a/Sources/mas/Commands/Upgrade.swift
+++ b/Sources/mas/Commands/Upgrade.swift
@@ -58,14 +58,14 @@ extension Mas {
let apps: [SoftwareProduct] =
appIDs.isEmpty
? appLibrary.installedApps
- : appIDs.compactMap {
- if let appID = AppID($0) {
+ : appIDs.compactMap { appID in
+ if let appID = AppID(appID) {
// argument is an AppID, lookup app by id using argument
return appLibrary.installedApp(withAppID: appID)
}
// argument is not an AppID, lookup app by name using argument
- return appLibrary.installedApp(named: $0)
+ return appLibrary.installedApp(named: appID)
}
let promises = apps.map { installedApp in
diff --git a/Sources/mas/Controllers/MasStoreSearch.swift b/Sources/mas/Controllers/MasStoreSearch.swift
index a2cf5b2..2dc7ff4 100644
--- a/Sources/mas/Controllers/MasStoreSearch.swift
+++ b/Sources/mas/Controllers/MasStoreSearch.swift
@@ -36,7 +36,7 @@ class MasStoreSearch: StoreSearch {
///
/// - Parameter searchTerm: a search term matched against app names
/// - Returns: A Promise of an Array of SearchResults matching searchTerm
- func search(for appName: String) -> Promise<[SearchResult]> {
+ func search(for searchTerm: String) -> Promise<[SearchResult]> {
// Search for apps for compatible platforms, in order of preference.
// Macs with Apple Silicon can run iPad and iPhone apps.
var entities = [Entity.desktopSoftware]
@@ -45,8 +45,8 @@ class MasStoreSearch: StoreSearch {
}
let results = entities.map { entity in
- guard let url = searchURL(for: appName, inCountry: country, ofEntity: entity) else {
- fatalError("Failed to build URL for \(appName)")
+ guard let url = searchURL(for: searchTerm, inCountry: country, ofEntity: entity) else {
+ fatalError("Failed to build URL for \(searchTerm)")
}
return loadSearchResults(url)
}
From 3a6d6724c9a2743e02581881e97be77d099bd42e Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Mon, 21 Oct 2024 07:43:13 -0400
Subject: [PATCH 13/20] Reorder elements.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
.../SysCtlSystemCommand.swift | 22 +++++++++----------
Sources/mas/Mas.swift | 8 +++----
2 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/Sources/mas/ExternalCommands/SysCtlSystemCommand.swift b/Sources/mas/ExternalCommands/SysCtlSystemCommand.swift
index abd5fb8..1d04a2e 100644
--- a/Sources/mas/ExternalCommands/SysCtlSystemCommand.swift
+++ b/Sources/mas/ExternalCommands/SysCtlSystemCommand.swift
@@ -12,17 +12,6 @@ import Foundation
///
/// See - https://ss64.com/osx/sysctl.html
struct SysCtlSystemCommand: ExternalCommand {
- var binaryPath: String
-
- let process = Process()
-
- let stdoutPipe = Pipe()
- let stderrPipe = Pipe()
-
- init(binaryPath: String = "/usr/sbin/sysctl") {
- self.binaryPath = binaryPath
- }
-
static var isAppleSilicon: Bool = {
let sysctl = Self()
do {
@@ -38,4 +27,15 @@ struct SysCtlSystemCommand: ExternalCommand {
return sysctl.stdout.trimmingCharacters(in: .newlines) == "1"
}()
+
+ let process = Process()
+
+ let stdoutPipe = Pipe()
+ let stderrPipe = Pipe()
+
+ var binaryPath: String
+
+ init(binaryPath: String = "/usr/sbin/sysctl") {
+ self.binaryPath = binaryPath
+ }
}
diff --git a/Sources/mas/Mas.swift b/Sources/mas/Mas.swift
index 5fb8dda..89aeacb 100644
--- a/Sources/mas/Mas.swift
+++ b/Sources/mas/Mas.swift
@@ -35,10 +35,6 @@ struct Mas: ParsableCommand {
]
)
- func validate() throws {
- Self.initialize()
- }
-
static func initialize() {
PromiseKit.conf.Q.map = .global()
PromiseKit.conf.Q.return = .global()
@@ -54,6 +50,10 @@ struct Mas: ParsableCommand {
}
}
}
+
+ func validate() throws {
+ Self.initialize()
+ }
}
typealias AppID = UInt64
From 65218eff7485b89ab678160ba338d588cf909289 Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Mon, 21 Oct 2024 07:43:45 -0400
Subject: [PATCH 14/20] Comment that catch ignores error.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Sources/mas/Network/NetworkManager.swift | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Sources/mas/Network/NetworkManager.swift b/Sources/mas/Network/NetworkManager.swift
index 9c4f496..5f0ab66 100644
--- a/Sources/mas/Network/NetworkManager.swift
+++ b/Sources/mas/Network/NetworkManager.swift
@@ -23,7 +23,9 @@ class NetworkManager {
do {
let url = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Library/Caches/com.mphys.mas-cli")
try FileManager.default.removeItem(at: url)
- } catch {}
+ } catch {
+ // Ignore
+ }
}
/// Loads data asynchronously.
From fcf8ba29f6ff2acfefd40203840c571bef91ff6b Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Mon, 21 Oct 2024 07:41:22 -0400
Subject: [PATCH 15/20] Improve error output.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Sources/mas/Commands/Lucky.swift | 2 +-
Sources/mas/Commands/Reset.swift | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Sources/mas/Commands/Lucky.swift b/Sources/mas/Commands/Lucky.swift
index b79c776..16c7948 100644
--- a/Sources/mas/Commands/Lucky.swift
+++ b/Sources/mas/Commands/Lucky.swift
@@ -44,7 +44,7 @@ extension Mas {
}
guard let appID else {
- fatalError()
+ fatalError("app ID returned from Apple is null")
}
try install(appID: appID, appLibrary: appLibrary)
diff --git a/Sources/mas/Commands/Reset.swift b/Sources/mas/Commands/Reset.swift
index 0ddc210..6698780 100644
--- a/Sources/mas/Commands/Reset.swift
+++ b/Sources/mas/Commands/Reset.swift
@@ -61,7 +61,7 @@ extension Mas {
if kill.terminationStatus != 0, debug {
let output = stderr.fileHandleForReading.readDataToEndOfFile()
- printInfo("killall failed:\r\n\(String(data: output, encoding: String.Encoding.utf8)!)")
+ printError("killall failed:\n\(String(data: output, encoding: String.Encoding.utf8)!)")
}
// Wipe Download Directory
From ee28af69df9dc419ad8b817a4e536fbcbeba9a31 Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Mon, 21 Oct 2024 08:58:18 -0400
Subject: [PATCH 16/20] Eliminate magic numbers.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Sources/mas/AppStore/ISStoreAccount.swift | 6 ++++--
Sources/mas/AppStore/PurchaseDownloadObserver.swift | 1 +
Sources/mas/Formatters/SearchResultFormatter.swift | 6 ++++--
3 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/Sources/mas/AppStore/ISStoreAccount.swift b/Sources/mas/AppStore/ISStoreAccount.swift
index 414dc29..1c8afa0 100644
--- a/Sources/mas/AppStore/ISStoreAccount.swift
+++ b/Sources/mas/AppStore/ISStoreAccount.swift
@@ -10,6 +10,8 @@ import CommerceKit
import PromiseKit
import StoreFoundation
+private let timeout = 30.0
+
extension ISStoreAccount: StoreAccount {
static var primaryAccount: Promise {
if #available(macOS 10.13, *) {
@@ -20,7 +22,7 @@ extension ISStoreAccount: StoreAccount {
seal.fulfill(storeAccount)
}
},
- after(seconds: 30)
+ after(seconds: timeout)
.then {
Promise(error: MASError.notSignedIn)
}
@@ -79,7 +81,7 @@ extension ISStoreAccount: StoreAccount {
return race(
signInPromise,
- after(seconds: 30)
+ after(seconds: timeout)
.then {
Promise(error: MASError.signInFailed(error: nil))
}
diff --git a/Sources/mas/AppStore/PurchaseDownloadObserver.swift b/Sources/mas/AppStore/PurchaseDownloadObserver.swift
index fd4add4..c6709cf 100644
--- a/Sources/mas/AppStore/PurchaseDownloadObserver.swift
+++ b/Sources/mas/AppStore/PurchaseDownloadObserver.swift
@@ -65,6 +65,7 @@ struct ProgressState {
let phase: String
var percentage: String {
+ // swiftlint:disable:next no_magic_numbers
String(format: "%.1f%%", floor(percentComplete * 1000) / 10)
}
}
diff --git a/Sources/mas/Formatters/SearchResultFormatter.swift b/Sources/mas/Formatters/SearchResultFormatter.swift
index 96e9414..7145d87 100644
--- a/Sources/mas/Formatters/SearchResultFormatter.swift
+++ b/Sources/mas/Formatters/SearchResultFormatter.swift
@@ -17,8 +17,10 @@ enum SearchResultFormatter {
/// - includePrice: Indicates whether to include prices in the output
/// - Returns: Multiline text output.
static func format(results: [SearchResult], includePrice: Bool = false) -> String {
- // find longest appName for formatting, default 50
- let maxLength = results.map(\.trackName.count).max() ?? 50
+ guard let maxLength = results.map(\.trackName.count).max() else {
+ return ""
+ }
+
var output = ""
for result in results {
From 87aaba555db65cc3353a059c78502f17c88eeb49 Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Tue, 22 Oct 2024 13:39:27 -0400
Subject: [PATCH 17/20] Remove unnecessary variable type.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Sources/mas/Commands/Upgrade.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Sources/mas/Commands/Upgrade.swift b/Sources/mas/Commands/Upgrade.swift
index 41054d2..4b3740b 100644
--- a/Sources/mas/Commands/Upgrade.swift
+++ b/Sources/mas/Commands/Upgrade.swift
@@ -55,7 +55,7 @@ extension Mas {
appLibrary: AppLibrary,
storeSearch: StoreSearch
) throws -> [(SoftwareProduct, SearchResult)] {
- let apps: [SoftwareProduct] =
+ let apps =
appIDs.isEmpty
? appLibrary.installedApps
: appIDs.compactMap { appID in
From 2edd21803bc40679d989cc28e06dc31e4b89e3f3 Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Wed, 23 Oct 2024 01:32:16 -0400
Subject: [PATCH 18/20] Improve unwrapping.
Cleanup code.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
Sources/mas/AppStore/ISStoreAccount.swift | 2 +-
Sources/mas/Commands/Open.swift | 9 ++++++---
Sources/mas/Commands/Reset.swift | 2 +-
Sources/mas/Controllers/StoreSearch.swift | 8 +++++---
4 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/Sources/mas/AppStore/ISStoreAccount.swift b/Sources/mas/AppStore/ISStoreAccount.swift
index 1c8afa0..f0d27ec 100644
--- a/Sources/mas/AppStore/ISStoreAccount.swift
+++ b/Sources/mas/AppStore/ISStoreAccount.swift
@@ -47,7 +47,7 @@ extension ISStoreAccount: StoreAccount {
let password =
password.isEmpty && !systemDialog
- ? String(validatingUTF8: getpass("Password: "))!
+ ? String(validatingUTF8: getpass("Password: ")) ?? ""
: password
guard !password.isEmpty || systemDialog else {
diff --git a/Sources/mas/Commands/Open.swift b/Sources/mas/Commands/Open.swift
index 4ae4247..82e98de 100644
--- a/Sources/mas/Commands/Open.swift
+++ b/Sources/mas/Commands/Open.swift
@@ -44,15 +44,18 @@ extension Mas {
}
url.scheme = masScheme
+ guard let urlString = url.string else {
+ printError("Unable to construct URL")
+ throw MASError.searchFailed
+ }
do {
- try openCommand.run(arguments: url.string!)
+ try openCommand.run(arguments: urlString)
} catch {
printError("Unable to launch open command")
throw MASError.searchFailed
}
if openCommand.failed {
- let reason = openCommand.process.terminationReason
- printError("Open failed: (\(reason)) \(openCommand.stderr)")
+ printError("Open failed: (\(openCommand.process.terminationReason)) \(openCommand.stderr)")
throw MASError.searchFailed
}
} catch {
diff --git a/Sources/mas/Commands/Reset.swift b/Sources/mas/Commands/Reset.swift
index 6698780..76a32bd 100644
--- a/Sources/mas/Commands/Reset.swift
+++ b/Sources/mas/Commands/Reset.swift
@@ -61,7 +61,7 @@ extension Mas {
if kill.terminationStatus != 0, debug {
let output = stderr.fileHandleForReading.readDataToEndOfFile()
- printError("killall failed:\n\(String(data: output, encoding: String.Encoding.utf8)!)")
+ printError("killall failed:\n\(String(data: output, encoding: .utf8) ?? "Error info not available")")
}
// Wipe Download Directory
diff --git a/Sources/mas/Controllers/StoreSearch.swift b/Sources/mas/Controllers/StoreSearch.swift
index 12a46e0..166e7fd 100644
--- a/Sources/mas/Controllers/StoreSearch.swift
+++ b/Sources/mas/Controllers/StoreSearch.swift
@@ -78,16 +78,18 @@ extension StoreSearch {
return nil
}
- components.queryItems = [
+ var queryItems = [
URLQueryItem(name: "media", value: "software"),
URLQueryItem(name: "entity", value: entity.rawValue),
]
if let country {
- components.queryItems!.append(URLQueryItem(name: "country", value: country))
+ queryItems.append(URLQueryItem(name: "country", value: country))
}
- components.queryItems!.append(URLQueryItem(name: action.queryItemName, value: queryItemValue))
+ queryItems.append(URLQueryItem(name: action.queryItemName, value: queryItemValue))
+
+ components.queryItems = queryItems
return components.url
}
From 4d4dd02cd3d68d5ada3d72388176f08e0173f9aa Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Mon, 21 Oct 2024 07:47:39 -0400
Subject: [PATCH 19/20] Improve lint configurations.
Partial #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
.swift-format | 95 ++++++++++------
.swiftformat | 21 +++-
.swiftlint.yml | 132 +++++++++++++++++++++-
Sources/mas/AppStore/ISStoreAccount.swift | 2 +
Tests/masTests/.swift-format | 62 ++++++++++
Tests/masTests/.swiftlint.yml | 2 +
6 files changed, 266 insertions(+), 48 deletions(-)
create mode 100644 Tests/masTests/.swift-format
diff --git a/.swift-format b/.swift-format
index 9c12fd6..693d879 100644
--- a/.swift-format
+++ b/.swift-format
@@ -1,41 +1,62 @@
{
- "indentation" : {
- "spaces" : 4
+ "indentConditionalCompilationBlocks": false,
+ "indentation": {
+ "spaces": 4
},
- "lineLength" : 120,
- "rules" : {
- "AllPublicDeclarationsHaveDocumentation" : false,
- "AlwaysUseLowerCamelCase" : true,
- "AmbiguousTrailingClosureOverload" : true,
- "BeginDocumentationCommentWithOneLineSummary" : false,
- "DoNotUseSemicolons" : true,
- "DontRepeatTypeInStaticProperties" : true,
- "FileScopedDeclarationPrivacy" : true,
- "FullyIndirectEnum" : true,
- "GroupNumericLiterals" : true,
- "IdentifiersMustBeASCII" : true,
- "NeverForceUnwrap" : false,
- "NeverUseForceTry" : false,
- "NeverUseImplicitlyUnwrappedOptionals" : false,
- "NoAccessLevelOnExtensionDeclaration" : false,
- "NoBlockComments" : true,
- "NoCasesWithOnlyFallthrough" : true,
- "NoEmptyTrailingClosureParentheses" : true,
- "NoLabelsInCasePatterns" : true,
- "NoLeadingUnderscores" : false,
- "NoParensAroundConditions" : true,
- "NoVoidReturnOnFunctionSignature" : true,
- "OneCasePerLine" : true,
- "OneVariableDeclarationPerLine" : true,
- "OnlyOneTrailingClosureArgument" : true,
- "OrderedImports" : true,
- "ReturnVoidInsteadOfEmptyTuple" : true,
- "UseLetInEveryBoundCaseVariable" : true,
- "UseShorthandTypeNames" : true,
- "UseSingleLinePropertyGetter" : true,
- "UseSynthesizedInitializer" : true,
- "UseTripleSlashForDocumentationComments" : true,
- "ValidateDocumentationComments" : false
+ "lineBreakAroundMultilineExpressionChainComponents": true,
+ "lineBreakBeforeControlFlowKeywords": false,
+ "lineBreakBeforeEachArgument": true,
+ "lineBreakBeforeEachGenericRequirement": true,
+ "lineBreakBetweenDeclarationAttributes": true,
+ "lineLength": 120,
+ "maximumBlankLines": 1,
+ "multiElementCollectionTrailingCommas": true,
+ "prioritizeKeepingFunctionOutputTogether": true,
+ "respectsExistingLineBreaks": true,
+ "rules": {
+ "AllPublicDeclarationsHaveDocumentation": true,
+ "AlwaysUseLiteralForEmptyCollectionInit": true,
+ "AlwaysUseLowerCamelCase": true,
+ "AmbiguousTrailingClosureOverload": true,
+ "BeginDocumentationCommentWithOneLineSummary": true,
+ "DoNotUseSemicolons": true,
+ "DontRepeatTypeInStaticProperties": true,
+ "FileScopedDeclarationPrivacy": true,
+ "FullyIndirectEnum": true,
+ "GroupNumericLiterals": true,
+ "IdentifiersMustBeASCII": true,
+ "NeverForceUnwrap": true,
+ "NeverUseForceTry": true,
+ "NeverUseImplicitlyUnwrappedOptionals": true,
+ "NoAccessLevelOnExtensionDeclaration": true,
+ "NoAssignmentInExpressions": true,
+ "NoBlockComments": true,
+ "NoCasesWithOnlyFallthrough": true,
+ "NoEmptyTrailingClosureParentheses": true,
+ "NoLabelsInCasePatterns": true,
+ "NoLeadingUnderscores": true,
+ "NoParensAroundConditions": true,
+ "NoPlaygroundLiterals": true,
+ "NoVoidReturnOnFunctionSignature": true,
+ "OmitExplicitReturns": true,
+ "OneCasePerLine": true,
+ "OneVariableDeclarationPerLine": true,
+ "OnlyOneTrailingClosureArgument": true,
+ "OrderedImports": true,
+ "ReplaceForEachWithForLoop": true,
+ "ReturnVoidInsteadOfEmptyTuple": true,
+ "TypeNamesShouldBeCapitalized": true,
+ "UseEarlyExits": true,
+ "UseLetInEveryBoundCaseVariable": true,
+ "UseShorthandTypeNames": true,
+ "UseSingleLinePropertyGetter": true,
+ "UseSynthesizedInitializer": true,
+ "UseTripleSlashForDocumentationComments": true,
+ "UseWhereClausesInForLoops": true,
+ "ValidateDocumentationComments": true
},
- "version" : 1
+ "spacesAroundRangeFormationOperators": false,
+ "spacesBeforeEndOfLineComments": 1,
+ "TrailingComma": false,
+ "version": 1
}
diff --git a/.swiftformat b/.swiftformat
index 9e09924..90ad275 100644
--- a/.swiftformat
+++ b/.swiftformat
@@ -5,22 +5,33 @@
# https://github.com/nicklockwood/SwiftFormat#config-file
#
---exclude docs/
-
# Disabled rules
---disable blankLinesAroundMark
---disable consecutiveSpaces
--disable hoistAwait
--disable hoistPatternLet
--disable hoistTry
+
+# Enable later
--disable indent
--disable trailingCommas
# Enabled rules (disabled by default)
---enable trailingClosures
+#--enable acronyms
+#--enable blankLinesBetweenImports
+--enable blockComments
+--enable docComments
+--enable isEmpty
+--enable noExplicitOwnership
+#--enable organizeDeclarations
+--enable redundantProperty
+--enable sortSwitchCases
+--enable wrapConditionalBodies
+--enable wrapEnumCases
+--enable wrapMultilineConditionalAssignment
+--enable wrapSwitchCases
# Rule options
--commas always
--extensionacl on-declarations
--importgrouping testable-last
+--lineaftermarks false
--ranges no-space
diff --git a/.swiftlint.yml b/.swiftlint.yml
index 1c9c9e3..4897eef 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -5,11 +5,131 @@
# https://github.com/realm/SwiftLint#configuration
#
---
+opt_in_rules:
+- accessibility_label_for_image
+- accessibility_trait_for_button
+- anonymous_argument_in_multiline_closure
+- array_init
+- attributes
+- closure_end_indentation
+- closure_spacing
+- collection_alignment
+- comma_inheritance
+- conditional_returns_on_newline
+- contains_over_filter_count
+- contains_over_filter_is_empty
+- contains_over_first_not_nil
+- contains_over_range_nil_comparison
+- convenience_type
+- direct_return
+- discarded_notification_center_observer
+- discouraged_assert
+- discouraged_none_name
+- discouraged_object_literal
+- discouraged_optional_boolean
+- discouraged_optional_collection
+- empty_collection_literal
+- empty_count
+- empty_string
+- empty_xctest_method
+- enum_case_associated_values_count
+- expiring_todo
+- explicit_init
+- extension_access_modifier
+- fallthrough
+- fatal_error_message
+- file_name_no_space
+- file_types_order
+- first_where
+- flatmap_over_map_reduce
+- function_default_parameter_at_end
+- ibinspectable_in_extension
+- identical_operands
+- implicit_return
+- implicitly_unwrapped_optional
+- indentation_width
+- joined_default_parameter
+- last_where
+- legacy_multiple
+- let_var_whitespace
+- literal_expression_end_indentation
+- local_doc_comment
+- lower_acl_than_parent
+- missing_docs
+- modifier_order
+- multiline_arguments
+- multiline_arguments_brackets
+- multiline_function_chains
+- multiline_literal_brackets
+- multiline_parameters
+- multiline_parameters_brackets
+- nimble_operator
+- no_empty_block
+- no_extension_access_modifier
+- no_magic_numbers
+- non_overridable_class_declaration
+- nslocalizedstring_key
+- nslocalizedstring_require_bundle
+- object_literal
+- operator_usage_whitespace
+- optional_enum_case_matching
+- overridden_super_call
+- override_in_extension
+- pattern_matching_keywords
+- period_spacing
+- prefer_key_path
+- prefer_self_in_static_references
+- prefer_self_type_over_type_of_self
+- prefer_zero_over_explicit_init
+- private_action
+- private_outlet
+- private_subject
+- private_swiftui_state
+- prohibited_interface_builder
+- prohibited_super_call
+- quick_discouraged_focused_test
+- raw_value_for_camel_cased_codable_enum
+- reduce_into
+- redundant_nil_coalescing
+- redundant_self_in_closure
+- redundant_type_annotation
+- required_enum_case
+- return_value_from_void_function
+- self_binding
+- shorthand_argument
+- shorthand_optional_binding
+- single_test_class
+- sorted_first_last
+- sorted_imports
+- static_operator
+- strict_fileprivate
+- strong_iboutlet
+- superfluous_else
+- switch_case_on_newline
+- test_case_accessibility
+- toggle_bool
+- trailing_closure
+- type_contents_order
+- unavailable_function
+- unhandled_throwing_task
+- unneeded_parentheses_in_closure_argument
+- unowned_variable_capture
+- untyped_error_in_catch
+- unused_parameter
+- vertical_parameter_alignment_on_call
+- vertical_whitespace_closing_braces
+- vertical_whitespace_opening_braces
+- weak_delegate
+- xct_specific_matcher
+- yoda_condition
disabled_rules:
-- non_optional_string_data_conversion
+- function_body_length
- trailing_comma
-excluded:
-- docs
-opening_brace:
- ignore_multiline_function_signatures: true
- ignore_multiline_statement_conditions: true
+file_types_order:
+ order: [
+ [main_type],
+ [supporting_type],
+ [extension],
+ [preview_provider],
+ [library_content_provider]
+ ]
diff --git a/Sources/mas/AppStore/ISStoreAccount.swift b/Sources/mas/AppStore/ISStoreAccount.swift
index f0d27ec..ecb3313 100644
--- a/Sources/mas/AppStore/ISStoreAccount.swift
+++ b/Sources/mas/AppStore/ISStoreAccount.swift
@@ -33,10 +33,12 @@ extension ISStoreAccount: StoreAccount {
}
static func signIn(username: String, password: String, systemDialog: Bool) -> Promise {
+ // swift-format-ignore: UseEarlyExits
if #available(macOS 10.13, *) {
// Signing in is no longer possible as of High Sierra.
// https://github.com/mas-cli/mas/issues/164
return Promise(error: MASError.notSupported)
+ // swiftlint:disable:next superfluous_else
} else {
return
primaryAccount
diff --git a/Tests/masTests/.swift-format b/Tests/masTests/.swift-format
new file mode 100644
index 0000000..571f02f
--- /dev/null
+++ b/Tests/masTests/.swift-format
@@ -0,0 +1,62 @@
+{
+ "indentConditionalCompilationBlocks": false,
+ "indentation": {
+ "spaces": 4
+ },
+ "lineBreakAroundMultilineExpressionChainComponents": true,
+ "lineBreakBeforeControlFlowKeywords": false,
+ "lineBreakBeforeEachArgument": true,
+ "lineBreakBeforeEachGenericRequirement": true,
+ "lineBreakBetweenDeclarationAttributes": true,
+ "lineLength": 120,
+ "maximumBlankLines": 1,
+ "multiElementCollectionTrailingCommas": true,
+ "prioritizeKeepingFunctionOutputTogether": true,
+ "respectsExistingLineBreaks": true,
+ "rules": {
+ "AllPublicDeclarationsHaveDocumentation": false,
+ "AlwaysUseLiteralForEmptyCollectionInit": true,
+ "AlwaysUseLowerCamelCase": true,
+ "AmbiguousTrailingClosureOverload": true,
+ "BeginDocumentationCommentWithOneLineSummary": true,
+ "DoNotUseSemicolons": true,
+ "DontRepeatTypeInStaticProperties": true,
+ "FileScopedDeclarationPrivacy": true,
+ "FullyIndirectEnum": true,
+ "GroupNumericLiterals": true,
+ "IdentifiersMustBeASCII": true,
+ "NeverForceUnwrap": false,
+ "NeverUseForceTry": false,
+ "NeverUseImplicitlyUnwrappedOptionals": true,
+ "NoAccessLevelOnExtensionDeclaration": true,
+ "NoAssignmentInExpressions": true,
+ "NoBlockComments": true,
+ "NoCasesWithOnlyFallthrough": true,
+ "NoEmptyTrailingClosureParentheses": true,
+ "NoLabelsInCasePatterns": true,
+ "NoLeadingUnderscores": true,
+ "NoParensAroundConditions": true,
+ "NoPlaygroundLiterals": true,
+ "NoVoidReturnOnFunctionSignature": true,
+ "OmitExplicitReturns": true,
+ "OneCasePerLine": true,
+ "OneVariableDeclarationPerLine": true,
+ "OnlyOneTrailingClosureArgument": true,
+ "OrderedImports": true,
+ "ReplaceForEachWithForLoop": true,
+ "ReturnVoidInsteadOfEmptyTuple": true,
+ "TypeNamesShouldBeCapitalized": true,
+ "UseEarlyExits": true,
+ "UseLetInEveryBoundCaseVariable": true,
+ "UseShorthandTypeNames": true,
+ "UseSingleLinePropertyGetter": true,
+ "UseSynthesizedInitializer": true,
+ "UseTripleSlashForDocumentationComments": true,
+ "UseWhereClausesInForLoops": true,
+ "ValidateDocumentationComments": true
+ },
+ "spacesAroundRangeFormationOperators": false,
+ "spacesBeforeEndOfLineComments": 1,
+ "TrailingComma": false,
+ "version": 1
+}
diff --git a/Tests/masTests/.swiftlint.yml b/Tests/masTests/.swiftlint.yml
index d234b91..409fabb 100644
--- a/Tests/masTests/.swiftlint.yml
+++ b/Tests/masTests/.swiftlint.yml
@@ -9,3 +9,5 @@ disabled_rules:
- force_cast
- force_try
- function_body_length
+- implicitly_unwrapped_optional
+- no_magic_numbers
From c36a797ac2d5a092fd5971c22a6f9e9acc1021ec Mon Sep 17 00:00:00 2001
From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
Date: Mon, 21 Oct 2024 12:54:31 -0400
Subject: [PATCH 20/20] SwiftLint opt_in_rules: - all
Resolve #592
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
---
.swiftlint.yml | 141 ++++++----------------------------
Tests/masTests/.swiftlint.yml | 1 -
2 files changed, 25 insertions(+), 117 deletions(-)
diff --git a/.swiftlint.yml b/.swiftlint.yml
index 4897eef..3d19626 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -6,125 +6,34 @@
#
---
opt_in_rules:
-- accessibility_label_for_image
-- accessibility_trait_for_button
-- anonymous_argument_in_multiline_closure
-- array_init
-- attributes
-- closure_end_indentation
-- closure_spacing
-- collection_alignment
-- comma_inheritance
-- conditional_returns_on_newline
-- contains_over_filter_count
-- contains_over_filter_is_empty
-- contains_over_first_not_nil
-- contains_over_range_nil_comparison
-- convenience_type
-- direct_return
-- discarded_notification_center_observer
-- discouraged_assert
-- discouraged_none_name
-- discouraged_object_literal
-- discouraged_optional_boolean
-- discouraged_optional_collection
-- empty_collection_literal
-- empty_count
-- empty_string
-- empty_xctest_method
-- enum_case_associated_values_count
-- expiring_todo
-- explicit_init
-- extension_access_modifier
-- fallthrough
-- fatal_error_message
-- file_name_no_space
-- file_types_order
-- first_where
-- flatmap_over_map_reduce
-- function_default_parameter_at_end
-- ibinspectable_in_extension
-- identical_operands
-- implicit_return
-- implicitly_unwrapped_optional
-- indentation_width
-- joined_default_parameter
-- last_where
-- legacy_multiple
-- let_var_whitespace
-- literal_expression_end_indentation
-- local_doc_comment
-- lower_acl_than_parent
-- missing_docs
-- modifier_order
-- multiline_arguments
-- multiline_arguments_brackets
-- multiline_function_chains
-- multiline_literal_brackets
-- multiline_parameters
-- multiline_parameters_brackets
-- nimble_operator
-- no_empty_block
-- no_extension_access_modifier
-- no_magic_numbers
-- non_overridable_class_declaration
-- nslocalizedstring_key
-- nslocalizedstring_require_bundle
-- object_literal
-- operator_usage_whitespace
-- optional_enum_case_matching
-- overridden_super_call
-- override_in_extension
-- pattern_matching_keywords
-- period_spacing
-- prefer_key_path
-- prefer_self_in_static_references
-- prefer_self_type_over_type_of_self
-- prefer_zero_over_explicit_init
-- private_action
-- private_outlet
-- private_subject
-- private_swiftui_state
-- prohibited_interface_builder
-- prohibited_super_call
-- quick_discouraged_focused_test
-- raw_value_for_camel_cased_codable_enum
-- reduce_into
-- redundant_nil_coalescing
-- redundant_self_in_closure
-- redundant_type_annotation
-- required_enum_case
-- return_value_from_void_function
-- self_binding
-- shorthand_argument
-- shorthand_optional_binding
-- single_test_class
-- sorted_first_last
-- sorted_imports
-- static_operator
-- strict_fileprivate
-- strong_iboutlet
-- superfluous_else
-- switch_case_on_newline
-- test_case_accessibility
-- toggle_bool
-- trailing_closure
-- type_contents_order
-- unavailable_function
-- unhandled_throwing_task
-- unneeded_parentheses_in_closure_argument
-- unowned_variable_capture
-- untyped_error_in_catch
-- unused_parameter
-- vertical_parameter_alignment_on_call
-- vertical_whitespace_closing_braces
-- vertical_whitespace_opening_braces
-- weak_delegate
-- xct_specific_matcher
-- yoda_condition
+- all
disabled_rules:
+- balanced_xctest_lifecycle
+- closure_body_length
+- contrasted_opening_brace
+- explicit_acl
+- explicit_enum_raw_value
+- explicit_top_level_acl
+- explicit_type_interface
+- file_header
+- file_name
+- final_test_case
+- force_unwrapping
- function_body_length
+- inert_defer
+- legacy_objc_type
+- no_grouping_extension
+- number_separator
+- one_declaration_per_file
+- prefer_nimble
+- prefixed_toplevel_constant
+- quick_discouraged_call
+- quick_discouraged_pending_test
+- required_deinit
+- sorted_enum_cases
- trailing_comma
+- unused_capture_list
+- vertical_whitespace_between_cases
file_types_order:
order: [
[main_type],
diff --git a/Tests/masTests/.swiftlint.yml b/Tests/masTests/.swiftlint.yml
index 409fabb..df984c3 100644
--- a/Tests/masTests/.swiftlint.yml
+++ b/Tests/masTests/.swiftlint.yml
@@ -8,6 +8,5 @@
disabled_rules:
- force_cast
- force_try
-- function_body_length
- implicitly_unwrapped_optional
- no_magic_numbers