diff --git a/MasKit/Commands/I.swift b/MasKit/Commands/I.swift new file mode 100644 index 000000000..e8ef08952 --- /dev/null +++ b/MasKit/Commands/I.swift @@ -0,0 +1,69 @@ +// +// Install.swift +// mas-cli +// +// Created by Andrew Naylor on 21/08/2015. +// Copyright (c) 2015 Andrew Naylor. All rights reserved. +// + +import Commandant +import CommerceKit + +/// Installs previously purchased apps from the Mac App Store. +public struct InstallCommand: CommandProtocol { + public typealias Options = InstallOptions + public let verb = "i" + public let function = "Install from the Mac App Store" + + private let appLibrary: AppLibrary + + /// Public initializer. + public init() { + self.init(appLibrary: MasAppLibrary()) + } + + /// Internal initializer. + /// - Parameter appLibrary: AppLibrary manager. + init(appLibrary: AppLibrary = MasAppLibrary()) { + self.appLibrary = appLibrary + } + + /// Runs the command. + public func run(_ options: Options) -> Result<(), MASError> { + // Try to download applications with given identifiers and collect results + let downloadResults = options.appIds.compactMap { (appId) -> MASError? in + if let product = appLibrary.installedApp(forId: appId), !options.forceInstall { + printWarning("\(product.appName) is already installed") + return nil + } + + return download(appId) + } + + switch downloadResults.count { + case 0: + return .success(()) + case 1: + return .failure(downloadResults[0]) + default: + return .failure(.downloadFailed(error: nil)) + } + } +} + +public struct InstallOptions: OptionsProtocol { + let appIds: [UInt64] + let forceInstall: Bool + + public static func create(_ appIds: [Int]) -> (_ forceInstall: Bool) -> InstallOptions { + return { forceInstall in + InstallOptions(appIds: appIds.map { UInt64($0) }, forceInstall: forceInstall) + } + } + + public static func evaluate(_ mode: CommandMode) -> Result> { + return create + <*> mode <| Argument(usage: "app ID(s) to install") + <*> mode <| Switch(flag: nil, key: "force", usage: "force reinstall") + } +} diff --git a/MasKit/Commands/Ls.swift b/MasKit/Commands/Ls.swift new file mode 100644 index 000000000..74268ec96 --- /dev/null +++ b/MasKit/Commands/Ls.swift @@ -0,0 +1,47 @@ +// +// List.swift +// mas-cli +// +// Created by Andrew Naylor on 21/08/2015. +// Copyright (c) 2015 Andrew Naylor. All rights reserved. +// + +import Commandant + +/// Command which lists all installed apps. +public struct ListCommand: CommandProtocol { + public typealias Options = NoOptions + public let verb = "ls" + public let function = "Lists apps from the Mac App Store which are currently installed" + + private let appLibrary: AppLibrary + + /// Public initializer. + /// - Parameter appLibrary: AppLibrary manager. + public init() { + self.init(appLibrary: MasAppLibrary()) + } + + /// Internal initializer. + /// - Parameter appLibrary: AppLibrary manager. + init(appLibrary: AppLibrary = MasAppLibrary()) { + self.appLibrary = appLibrary + } + + /// Runs the command. + public func run(_: Options) -> Result<(), MASError> { + let products = appLibrary.installedApps + if products.isEmpty { + print("No installed apps found") + return .success(()) + } + for product in products { + var appName = product.appName + if appName == "" { + appName = product.bundleIdentifier + } + print("\(product.itemIdentifier) \(appName) (\(product.bundleVersion))") + } + return .success(()) + } +} diff --git a/MasKit/Commands/Remove.swift b/MasKit/Commands/Remove.swift new file mode 100644 index 000000000..4242b3d6f --- /dev/null +++ b/MasKit/Commands/Remove.swift @@ -0,0 +1,80 @@ +// +// Upgrade.swift +// mas-cli +// +// Created by Ben Chatelain on 2018-12-27. +// Copyright © 2015 Andrew Naylor. All rights reserved. +// + +import Commandant +import CommerceKit +import StoreFoundation + +/// Command which uninstalls apps managed by the Mac App Store. +public struct UninstallCommand: CommandProtocol { + public typealias Options = UninstallOptions + public let verb = "remove" + public let function = "Uninstall app installed from the Mac App Store" + + private let appLibrary: AppLibrary + + /// Public initializer. + /// - Parameter appLibrary: AppLibrary manager. + public init() { + self.init(appLibrary: MasAppLibrary()) + } + + /// Internal initializer. + /// - Parameter appLibrary: AppLibrary manager. + init(appLibrary: AppLibrary = MasAppLibrary()) { + self.appLibrary = appLibrary + } + + /// Runs the uninstall command. + /// + /// - Parameter options: UninstallOptions (arguments) for this command + /// - Returns: Success or an error. + public func run(_ options: Options) -> Result<(), MASError> { + let appId = UInt64(options.appId) + + guard let product = appLibrary.installedApp(forId: appId) else { + return .failure(.notInstalled) + } + + if options.dryRun { + printInfo("\(product.appName) \(product.bundlePath)") + printInfo("(not removed, dry run)") + + return .success(()) + } + + do { + try appLibrary.uninstallApp(app: product) + } catch { + return .failure(.uninstallFailed) + } + + return .success(()) + } +} + +/// Options for the uninstall command. +public struct UninstallOptions: OptionsProtocol { + /// Numeric app ID + let appId: Int + + /// Flag indicating that removal shouldn't be performed + let dryRun: Bool + + static func create(_ appId: Int) -> (_ dryRun: Bool) -> UninstallOptions { + return { dryRun in + UninstallOptions(appId: appId, dryRun: dryRun) + } + } + + public static func evaluate(_ mode: CommandMode) -> Result> { + return create + <*> mode <| Argument(usage: "ID of app to uninstall") + <*> mode <| Switch(flag: nil, key: "dry-run", usage: "dry run") + } +} diff --git a/MasKit/Commands/Rm.swift b/MasKit/Commands/Rm.swift new file mode 100644 index 000000000..1d1e3e54f --- /dev/null +++ b/MasKit/Commands/Rm.swift @@ -0,0 +1,80 @@ +// +// Upgrade.swift +// mas-cli +// +// Created by Ben Chatelain on 2018-12-27. +// Copyright © 2015 Andrew Naylor. All rights reserved. +// + +import Commandant +import CommerceKit +import StoreFoundation + +/// Command which uninstalls apps managed by the Mac App Store. +public struct UninstallCommand: CommandProtocol { + public typealias Options = UninstallOptions + public let verb = "rm" + public let function = "Uninstall app installed from the Mac App Store" + + private let appLibrary: AppLibrary + + /// Public initializer. + /// - Parameter appLibrary: AppLibrary manager. + public init() { + self.init(appLibrary: MasAppLibrary()) + } + + /// Internal initializer. + /// - Parameter appLibrary: AppLibrary manager. + init(appLibrary: AppLibrary = MasAppLibrary()) { + self.appLibrary = appLibrary + } + + /// Runs the uninstall command. + /// + /// - Parameter options: UninstallOptions (arguments) for this command + /// - Returns: Success or an error. + public func run(_ options: Options) -> Result<(), MASError> { + let appId = UInt64(options.appId) + + guard let product = appLibrary.installedApp(forId: appId) else { + return .failure(.notInstalled) + } + + if options.dryRun { + printInfo("\(product.appName) \(product.bundlePath)") + printInfo("(not removed, dry run)") + + return .success(()) + } + + do { + try appLibrary.uninstallApp(app: product) + } catch { + return .failure(.uninstallFailed) + } + + return .success(()) + } +} + +/// Options for the uninstall command. +public struct UninstallOptions: OptionsProtocol { + /// Numeric app ID + let appId: Int + + /// Flag indicating that removal shouldn't be performed + let dryRun: Bool + + static func create(_ appId: Int) -> (_ dryRun: Bool) -> UninstallOptions { + return { dryRun in + UninstallOptions(appId: appId, dryRun: dryRun) + } + } + + public static func evaluate(_ mode: CommandMode) -> Result> { + return create + <*> mode <| Argument(usage: "ID of app to uninstall") + <*> mode <| Switch(flag: nil, key: "dry-run", usage: "dry run") + } +} diff --git a/MasKit/Commands/Update.swift b/MasKit/Commands/Update.swift new file mode 100644 index 000000000..c8c47575e --- /dev/null +++ b/MasKit/Commands/Update.swift @@ -0,0 +1,46 @@ +// +// Update.swift +// mas-cli +// +// Created by Andrew Naylor on 30/12/2015. +// Copyright © 2015 Andrew Naylor. All rights reserved. +// + +import Commandant +import Foundation + +/// Command which updates mas. +public struct UpdateCommand: CommandProtocol { + public typealias Options = NoOptions + public let verb = "update" + public let function = "Update MAS" + + private let appLibrary: AppLibrary + + /// Public initializer. + /// - Parameter appLibrary: AppLibrary manager. + public init() { + self.init(appLibrary: MasAppLibrary()) + } + + /// Internal initializer. + /// - Parameter appLibrary: AppLibrary manager. + init(appLibrary: AppLibrary = MasAppLibrary()) { + self.appLibrary = appLibrary + } + + /// Runs the command. + public func run(_: Options) -> Result<(), MASError> { + print("Updating MAS…") + + let task = Process() + task.launchPath = getenv("HOMEBREW_BREW_FILE") + task.arguments = ["upgrade", "mas"] + //let pipe = Pipe() + //task.standardOutput = pipe + //task.standardError = pipe + task.launch() + task.waitUntilExit() + return task.terminationStatus + } +} diff --git a/mas-cli.xcodeproj/project.pbxproj b/mas-cli.xcodeproj/project.pbxproj index ccae1eb73..48c1fcc56 100644 --- a/mas-cli.xcodeproj/project.pbxproj +++ b/mas-cli.xcodeproj/project.pbxproj @@ -110,6 +110,11 @@ F85DA8B0240C32FA00FE5650 /* SoftwareMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85DA8AF240C32FA00FE5650 /* SoftwareMap.swift */; }; F85DA8B2240CBAFE00FE5650 /* CKSoftwareMap+SoftwareMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85DA8B1240CBAFE00FE5650 /* CKSoftwareMap+SoftwareMap.swift */; }; F88CB8E12404DAAD00B691B5 /* OpenSystemCommandSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88CB8E02404DAAD00B691B5 /* OpenSystemCommandSpec.swift */; }; + F88F258824F30A6300EC60D5 /* I.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88F258324F30A6300EC60D5 /* I.swift */; }; + F88F258924F30A6300EC60D5 /* Ls.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88F258424F30A6300EC60D5 /* Ls.swift */; }; + F88F258A24F30A6300EC60D5 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88F258524F30A6300EC60D5 /* Update.swift */; }; + F88F258B24F30A6300EC60D5 /* Rm.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88F258624F30A6300EC60D5 /* Rm.swift */; }; + F88F258C24F30A6300EC60D5 /* Remove.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88F258724F30A6300EC60D5 /* Remove.swift */; }; F8FB715B20F2B41400F56FDC /* MasKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8FB715220F2B41400F56FDC /* MasKit.framework */; }; F8FB716220F2B41400F56FDC /* MasKit.h in Headers */ = {isa = PBXBuildFile; fileRef = F8FB715420F2B41400F56FDC /* MasKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; F8FB716A20F2B4DD00F56FDC /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0F238A1B87569C00AE40CD /* Downloader.swift */; }; @@ -326,6 +331,11 @@ F85DA8AF240C32FA00FE5650 /* SoftwareMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftwareMap.swift; sourceTree = ""; }; F85DA8B1240CBAFE00FE5650 /* CKSoftwareMap+SoftwareMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CKSoftwareMap+SoftwareMap.swift"; sourceTree = ""; }; F88CB8E02404DAAD00B691B5 /* OpenSystemCommandSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSystemCommandSpec.swift; sourceTree = ""; }; + F88F258324F30A6300EC60D5 /* I.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = I.swift; sourceTree = ""; }; + F88F258424F30A6300EC60D5 /* Ls.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Ls.swift; sourceTree = ""; }; + F88F258524F30A6300EC60D5 /* Update.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Update.swift; sourceTree = ""; }; + F88F258624F30A6300EC60D5 /* Rm.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Rm.swift; sourceTree = ""; }; + F88F258724F30A6300EC60D5 /* Remove.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Remove.swift; sourceTree = ""; }; F8FB715220F2B41400F56FDC /* MasKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MasKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F8FB715420F2B41400F56FDC /* MasKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MasKit.h; sourceTree = ""; }; F8FB715520F2B41400F56FDC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -669,18 +679,23 @@ children = ( ED0F23861B87537200AE40CD /* Account.swift */, B594B14B21D8983700F3AC59 /* Home.swift */, + F88F258324F30A6300EC60D5 /* I.swift */, 900A1E801DBAC8CB0069B1A8 /* Info.swift */, ED0F237E1B87522400AE40CD /* Install.swift */, ED0F23821B87533A00AE40CD /* List.swift */, + F88F258424F30A6300EC60D5 /* Ls.swift */, 8078FAA71EC4F2FB004B5B3F /* Lucky.swift */, B5DBF80C21DEE4E600F3B151 /* Open.swift */, ED0F23841B87536A00AE40CD /* Outdated.swift */, 75FB3E751F9F7841005B6F20 /* Purchase.swift */, + F88F258724F30A6300EC60D5 /* Remove.swift */, EDCBF9521D89AC6F000039C6 /* Reset.swift */, + F88F258624F30A6300EC60D5 /* Rm.swift */, 693A98981CBFFA760004D3B4 /* Search.swift */, EDC90B641C70045E0019E396 /* SignIn.swift */, EDE296521C700F4300554778 /* SignOut.swift */, B594B11F21D53A8200F3AC59 /* Uninstall.swift */, + F88F258524F30A6300EC60D5 /* Update.swift */, EDD3B3621C34709400B56B88 /* Upgrade.swift */, B5DBF80E21DEEB7B00F3B151 /* Vendor.swift */, EDB6CE8B1BAEC3D400648B4D /* Version.swift */, @@ -1009,6 +1024,7 @@ files = ( F8FB716F20F2B4DD00F56FDC /* Account.swift in Sources */, B576FE2A21E4240B0016B39D /* AppInfoFormatter.swift in Sources */, + F88F258B24F30A6300EC60D5 /* Rm.swift in Sources */, B594B12721D5825800F3AC59 /* AppLibrary.swift in Sources */, F85DA8B2240CBAFE00FE5650 /* CKSoftwareMap+SoftwareMap.swift in Sources */, B594B12B21D5837200F3AC59 /* CKSoftwareProduct+SoftwareProduct.swift in Sources */, @@ -1031,6 +1047,7 @@ B576FDF721E107AA0016B39D /* OpenSystemCommand.swift in Sources */, F8FB717420F2B4DD00F56FDC /* Outdated.swift in Sources */, 75FB3E761F9F7841005B6F20 /* Purchase.swift in Sources */, + F88F258924F30A6300EC60D5 /* Ls.swift in Sources */, F8FB716C20F2B4DD00F56FDC /* PurchaseDownloadObserver.swift in Sources */, F8FB717520F2B4DD00F56FDC /* Reset.swift in Sources */, F8FB717620F2B4DD00F56FDC /* Search.swift in Sources */, @@ -1046,7 +1063,10 @@ B594B15021D8998000F3AC59 /* StoreSearch.swift in Sources */, B576FE0E21E1D6310016B39D /* String+PercentEncoding.swift in Sources */, B594B12021D53A8200F3AC59 /* Uninstall.swift in Sources */, + F88F258824F30A6300EC60D5 /* I.swift in Sources */, F8FB717920F2B4DD00F56FDC /* Upgrade.swift in Sources */, + F88F258A24F30A6300EC60D5 /* Update.swift in Sources */, + F88F258C24F30A6300EC60D5 /* Remove.swift in Sources */, B576FE0221E1139E0016B39D /* URLSession+NetworkSession.swift in Sources */, F8FB717D20F2B4DD00F56FDC /* Utilities.swift in Sources */, B5DBF80F21DEEB7B00F3B151 /* Vendor.swift in Sources */, diff --git a/mas/main.swift b/mas/main.swift index a477332f0..2ac03121e 100644 --- a/mas/main.swift +++ b/mas/main.swift @@ -23,8 +23,10 @@ registry.register(AccountCommand()) registry.register(HomeCommand()) registry.register(InfoCommand()) registry.register(InstallCommand()) +registry.register(ICommand()) registry.register(PurchaseCommand()) registry.register(ListCommand()) +registry.register(LsCommand()) registry.register(LuckyCommand()) registry.register(OpenCommand()) registry.register(OutdatedCommand()) @@ -33,6 +35,9 @@ registry.register(SearchCommand()) registry.register(SignInCommand()) registry.register(SignOutCommand()) registry.register(UninstallCommand()) +registry.register(RemoveCommand()) +registry.register(RmCommand()) +registry.register(UpdateCommand()) registry.register(UpgradeCommand()) registry.register(VendorCommand()) registry.register(VersionCommand())