diff --git a/Sources/mas/Commands/Home.swift b/Sources/mas/Commands/Home.swift index 6bdf00c6d..d53208f29 100644 --- a/Sources/mas/Commands/Home.swift +++ b/Sources/mas/Commands/Home.swift @@ -17,8 +17,8 @@ extension MAS { abstract: "Open app's Mac App Store web page in the default web browser" ) - @Argument(help: "App ID") - var appID: AppID + @Argument(help: ArgumentHelp("App ID", valueName: "app-id")) + var appIDs: [AppID] /// Runs the command. func run() async throws { @@ -26,13 +26,15 @@ extension MAS { } func run(searcher: AppStoreSearcher) async throws { - let result = try await searcher.lookup(appID: appID) + for appID in appIDs { + let result = try await searcher.lookup(appID: appID) - guard let url = URL(string: result.trackViewUrl) else { - throw MASError.runtimeError("Unable to construct URL from: \(result.trackViewUrl)") - } + guard let url = URL(string: result.trackViewUrl) else { + throw MASError.runtimeError("Unable to construct URL from: \(result.trackViewUrl)") + } - try await url.open() + try await url.open() + } } } } diff --git a/Sources/mas/Commands/Info.swift b/Sources/mas/Commands/Info.swift index e1d76bd3d..98aece231 100644 --- a/Sources/mas/Commands/Info.swift +++ b/Sources/mas/Commands/Info.swift @@ -16,8 +16,8 @@ extension MAS { abstract: "Display app information from the Mac App Store" ) - @Argument(help: "App ID") - var appID: AppID + @Argument(help: ArgumentHelp("App ID", valueName: "app-id")) + var appIDs: [AppID] /// Runs the command. func run() async throws { @@ -25,12 +25,16 @@ extension MAS { } func run(searcher: AppStoreSearcher) async throws { - do { - print(AppInfoFormatter.format(app: try await searcher.lookup(appID: appID))) - } catch let error as MASError { - throw error - } catch { - throw MASError.searchFailed + var separator = "" + for appID in appIDs { + do { + print("", AppInfoFormatter.format(app: try await searcher.lookup(appID: appID)), separator: separator) + separator = "\n" + } catch let error as MASError { + throw error + } catch { + throw MASError.searchFailed + } } } } diff --git a/Sources/mas/Commands/Lucky.swift b/Sources/mas/Commands/Lucky.swift index 589f59348..13e2a4f16 100644 --- a/Sources/mas/Commands/Lucky.swift +++ b/Sources/mas/Commands/Lucky.swift @@ -25,7 +25,7 @@ extension MAS { @Flag(help: "Force reinstall") var force = false @Argument(help: "Search term") - var searchTerm: String + var searchTerm: [String] /// Runs the command. func run() async throws { @@ -33,26 +33,18 @@ extension MAS { } func run(installedApps: [InstalledApp], searcher: AppStoreSearcher) async throws { - var appID: AppID? - do { - let results = try await searcher.search(for: searchTerm) + let results = try await searcher.search(for: searchTerm.joined(separator: " ")) guard let result = results.first else { throw MASError.noSearchResultsFound } - appID = result.trackId + try await install(appID: result.trackId, installedApps: installedApps) } catch let error as MASError { throw error } catch { throw MASError.searchFailed } - - guard let appID else { - fatalError("app ID returned from Apple is null") - } - - try await install(appID: appID, installedApps: installedApps) } /// Installs an app. diff --git a/Sources/mas/Commands/Search.swift b/Sources/mas/Commands/Search.swift index 49c049498..dcd3973a7 100644 --- a/Sources/mas/Commands/Search.swift +++ b/Sources/mas/Commands/Search.swift @@ -19,7 +19,7 @@ extension MAS { @Flag(help: "Display the price of each app") var price = false @Argument(help: "Search term") - var searchTerm: String + var searchTerm: [String] func run() async throws { try await run(searcher: ITunesSearchAppStoreSearcher()) @@ -27,7 +27,7 @@ extension MAS { func run(searcher: AppStoreSearcher) async throws { do { - let results = try await searcher.search(for: searchTerm) + let results = try await searcher.search(for: searchTerm.joined(separator: " ")) if results.isEmpty { throw MASError.noSearchResultsFound } diff --git a/Sources/mas/Commands/Uninstall.swift b/Sources/mas/Commands/Uninstall.swift index cf5f17a8f..1b2e1b4a0 100644 --- a/Sources/mas/Commands/Uninstall.swift +++ b/Sources/mas/Commands/Uninstall.swift @@ -19,8 +19,8 @@ extension MAS { /// Flag indicating that removal shouldn't be performed. @Flag(help: "Perform dry run") var dryRun = false - @Argument(help: "App ID") - var appID: AppID + @Argument(help: ArgumentHelp("App ID", valueName: "app-id")) + var appIDs: [AppID] /// Runs the uninstall command. func run() async throws { @@ -43,9 +43,9 @@ extension MAS { throw MASError.runtimeError("Failed to switch effective user from 'root' to '\(username)'") } - let installedApps = installedApps.filter { $0.id == appID } + let installedApps = installedApps.filter { appIDs.contains($0.id) } guard !installedApps.isEmpty else { - throw MASError.notInstalled(appID: appID) + throw MASError.notInstalled(appIDs: appIDs) } if dryRun { diff --git a/Sources/mas/Commands/Vendor.swift b/Sources/mas/Commands/Vendor.swift index af5944910..b26d65b6c 100644 --- a/Sources/mas/Commands/Vendor.swift +++ b/Sources/mas/Commands/Vendor.swift @@ -17,8 +17,8 @@ extension MAS { abstract: "Open vendor's app web page in the default web browser" ) - @Argument(help: "App ID") - var appID: AppID + @Argument(help: ArgumentHelp("App ID", valueName: "app-id")) + var appIDs: [AppID] /// Runs the command. func run() async throws { @@ -26,17 +26,19 @@ extension MAS { } func run(searcher: AppStoreSearcher) async throws { - let result = try await searcher.lookup(appID: appID) + for appID in appIDs { + let result = try await searcher.lookup(appID: appID) - guard let urlString = result.sellerUrl else { - throw MASError.noVendorWebsite - } + guard let urlString = result.sellerUrl else { + throw MASError.noVendorWebsite + } - guard let url = URL(string: urlString) else { - throw MASError.runtimeError("Unable to construct URL from: \(urlString)") - } + guard let url = URL(string: urlString) else { + throw MASError.runtimeError("Unable to construct URL from: \(urlString)") + } - try await url.open() + try await url.open() + } } } } diff --git a/Sources/mas/Errors/MASError.swift b/Sources/mas/Errors/MASError.swift index 74d7330ac..d3002e7f1 100644 --- a/Sources/mas/Errors/MASError.swift +++ b/Sources/mas/Errors/MASError.swift @@ -32,7 +32,7 @@ enum MASError: Error, Equatable { case noVendorWebsite - case notInstalled(appID: AppID) + case notInstalled(appIDs: [AppID]) case uninstallFailed(error: NSError?) case macOSUserMustBeRoot @@ -92,8 +92,8 @@ extension MASError: CustomStringConvertible { appID.unknownMessage case .noVendorWebsite: "App does not have a vendor website" - case .notInstalled(let appID): - "No apps installed with app ID \(appID)" + case .notInstalled(let appIDs): + "No apps installed with app ID \(appIDs.map { String($0) }.joined(separator: ", "))" case .uninstallFailed(let error): if let error { "Uninstall failed: \(error.localizedDescription)" diff --git a/Tests/masTests/Commands/UninstallSpec.swift b/Tests/masTests/Commands/UninstallSpec.swift index 23cd4e790..c12d2c432 100644 --- a/Tests/masTests/Commands/UninstallSpec.swift +++ b/Tests/masTests/Commands/UninstallSpec.swift @@ -30,7 +30,7 @@ public final class UninstallSpec: QuickSpec { try MAS.Uninstall.parse(["--dry-run", String(appID)]).run(installedApps: []) ) ) - == (MASError.notInstalled(appID: appID), "", "") + == (MASError.notInstalled(appIDs: [appID]), "", "") } it("finds an app") { expect( @@ -48,7 +48,7 @@ public final class UninstallSpec: QuickSpec { try MAS.Uninstall.parse([String(appID)]).run(installedApps: []) ) ) - == (MASError.notInstalled(appID: appID), "", "") + == (MASError.notInstalled(appIDs: [appID]), "", "") } it("removes an app") { expect(