Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions Sources/mas/Commands/Home.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,24 @@ 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 {
try await run(searcher: ITunesSearchAppStoreSearcher())
}

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()
}
}
}
}
20 changes: 12 additions & 8 deletions Sources/mas/Commands/Info.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,25 @@ 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 {
try await run(searcher: ITunesSearchAppStoreSearcher())
}

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
}
}
}
}
Expand Down
14 changes: 3 additions & 11 deletions Sources/mas/Commands/Lucky.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,26 @@ 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 {
try await run(installedApps: await installedApps, searcher: ITunesSearchAppStoreSearcher())
}

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.
Expand Down
4 changes: 2 additions & 2 deletions Sources/mas/Commands/Search.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ 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())
}

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
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/mas/Commands/Uninstall.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
22 changes: 12 additions & 10 deletions Sources/mas/Commands/Vendor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,28 @@ 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 {
try await run(searcher: ITunesSearchAppStoreSearcher())
}

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()
}
}
}
}
6 changes: 3 additions & 3 deletions Sources/mas/Errors/MASError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ enum MASError: Error, Equatable {

case noVendorWebsite

case notInstalled(appID: AppID)
case notInstalled(appIDs: [AppID])
case uninstallFailed(error: NSError?)
case macOSUserMustBeRoot

Expand Down Expand Up @@ -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)"
Expand Down
4 changes: 2 additions & 2 deletions Tests/masTests/Commands/UninstallSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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(
Expand Down