diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..0b591edf3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Trailing spaces may be intentional in markdown documents, so these should not +# be removed. +# https://gist.github.com/shaunlebron/746476e6e7a4d698b373 +**/*.md whitespace=-blank-at-eol diff --git a/.hound.yml b/.hound.yml index a7f94570c..88e27f479 100644 --- a/.hound.yml +++ b/.hound.yml @@ -15,8 +15,3 @@ rubocop: shellcheck: enabled: true - config_file: .shellcheck.yml - -swiftlint: - enabled: true - config_file: .swiftlint.yml diff --git a/.ruby-version b/.ruby-version index 35cee72dc..4a36342fc 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.4.3 +3.0.0 diff --git a/.shellcheck.yml b/.shellcheck.yml deleted file mode 100644 index db364cf25..000000000 --- a/.shellcheck.yml +++ /dev/null @@ -1,8 +0,0 @@ -# -# .shellcheck.yml -# mas-cli -# -# ---- -exclude: - - script/sort.pl diff --git a/.swift-format b/.swift-format new file mode 100644 index 000000000..d9da89a8a --- /dev/null +++ b/.swift-format @@ -0,0 +1,41 @@ +{ + "indentation" : { + "spaces" : 4 + }, + "lineLength" : 120, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLowerCamelCase" : true, + "AmbiguousTrailingClosureOverload" : true, + "BeginDocumentationCommentWithOneLineSummary" : true, + "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 + }, + "version" : 1 +} diff --git a/.swiftformat b/.swiftformat index ba0409d0b..69f1ff746 100644 --- a/.swiftformat +++ b/.swiftformat @@ -5,14 +5,19 @@ # https://github.com/nicklockwood/SwiftFormat#config-file # ---exclude Carthage/ +--exclude Carthage/,docs/ # Disabled rules --disable blankLinesAroundMark +--disable consecutiveSpaces +--disable hoistPatternLet +--disable indent # Enabled rules (disabled by default) --enable trailingClosures # Rule options ---commas inline ---importgrouping testable-top +--commas always +--extensionacl on-declarations +--importgrouping testable-last +--ranges no-space diff --git a/.swiftlint.yml b/.swiftlint.yml index fb1508be9..98d7c395d 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -8,3 +8,9 @@ excluded: - Carthage - docs + +opening_brace: + allow_multiline_func: true + +trailing_comma: + mandatory_comma: true diff --git a/Brewfile b/Brewfile index 07e64f334..61f7eebc2 100644 --- a/Brewfile +++ b/Brewfile @@ -1,7 +1,11 @@ brew "carthage" brew "make" -brew "mint" -brew "shellcheck" +brew "shfmt" +brew "swift-format" +brew "swiftformat" + +# Already installed on GitHub Actions runner. +# brew "swiftlint" tap "kylef/formulae" brew "swiftenv" diff --git a/Brewfile.lock.json b/Brewfile.lock.json index 08d9eb0e0..ce7247b05 100644 --- a/Brewfile.lock.json +++ b/Brewfile.lock.json @@ -1,48 +1,29 @@ { "entries": { "brew": { - "mint": { - "version": "0.16.0", + "carthage": { + "version": "0.37.0", "bottle": { "rebuild": 0, "cellar": ":any_skip_relocation", - "prefix": "/usr/local", + "prefix": "/opt/homebrew", "root_url": "https://homebrew.bintray.com/bottles", "files": { "arm64_big_sur": { - "url": "https://homebrew.bintray.com/bottles/mint-0.16.0.arm64_big_sur.bottle.tar.gz", - "sha256": "eaf4c91e17438d0968ff29a6429c55f93c0aa02614f2c3f7a1a4b106375dd085" + "url": "https://homebrew.bintray.com/bottles/carthage-0.37.0.arm64_big_sur.bottle.tar.gz", + "sha256": "cd0c716682b5b094b82a589fb79def4eb696f70a3fd92423923a5cb86c2c79b3" }, "big_sur": { - "url": "https://homebrew.bintray.com/bottles/mint-0.16.0.big_sur.bottle.tar.gz", - "sha256": "599c2482d15b729dc72ffa23d38599d551a42b70b81079b9a573cd91bc78d8d0" - }, - "catalina": { - "url": "https://homebrew.bintray.com/bottles/mint-0.16.0.catalina.bottle.tar.gz", - "sha256": "376d67667e9003d503368e39d89a2592dd91daec615310bb2fad3d9ee971d8a8" - } - } - } - }, - "shellcheck": { - "version": "0.7.1_1", - "bottle": { - "rebuild": 0, - "cellar": ":any_skip_relocation", - "prefix": "/usr/local", - "root_url": "https://homebrew.bintray.com/bottles", - "files": { - "big_sur": { - "url": "https://homebrew.bintray.com/bottles/shellcheck-0.7.1_1.big_sur.bottle.tar.gz", - "sha256": "5ba3cfe883216a700c133e35c3f1612ab70eaca07cfb43bf6ed427b72dd7d552" + "url": "https://homebrew.bintray.com/bottles/carthage-0.37.0.big_sur.bottle.tar.gz", + "sha256": "0770b4dd885f3018031c2d27fc090a34027d5856a248f33fa2a415d58da74632" }, "catalina": { - "url": "https://homebrew.bintray.com/bottles/shellcheck-0.7.1_1.catalina.bottle.tar.gz", - "sha256": "bd66df0992ced04f98883eada3f14e620f6c76f268cae2adb182b52d3bba1858" + "url": "https://homebrew.bintray.com/bottles/carthage-0.37.0.catalina.bottle.tar.gz", + "sha256": "8a07c198835cb179d4054313b199ce126e64bb9414eaaa91f55162a4aed63134" }, "mojave": { - "url": "https://homebrew.bintray.com/bottles/shellcheck-0.7.1_1.mojave.bottle.tar.gz", - "sha256": "bcb393cf5a259c69fdf7ef1725243b48c5653b9db17d2cd51ad140ab2d7de9c1" + "url": "https://homebrew.bintray.com/bottles/carthage-0.37.0.mojave.bottle.tar.gz", + "sha256": "7fb777ac169aa4cb05683f0f8bfb5b56dbb0b0e8b673df995ef2fb2bbe0d90d2" } } } @@ -51,8 +32,8 @@ "version": "4.3", "bottle": { "rebuild": 1, - "cellar": "/usr/local/Cellar", - "prefix": "/usr/local", + "cellar": "/opt/homebrew/Cellar", + "prefix": "/opt/homebrew", "root_url": "https://homebrew.bintray.com/bottles", "files": { "arm64_big_sur": { @@ -78,59 +59,86 @@ } } }, - "swiftenv": { - "version": "1.4.0", - "bottle": false - }, - "carthage": { - "version": "0.37.0", + "shfmt": { + "version": "3.2.4", "bottle": { "rebuild": 0, "cellar": ":any_skip_relocation", - "prefix": "/usr/local", + "prefix": "/opt/homebrew", "root_url": "https://homebrew.bintray.com/bottles", "files": { "arm64_big_sur": { - "url": "https://homebrew.bintray.com/bottles/carthage-0.37.0.arm64_big_sur.bottle.tar.gz", - "sha256": "cd0c716682b5b094b82a589fb79def4eb696f70a3fd92423923a5cb86c2c79b3" + "url": "https://homebrew.bintray.com/bottles/shfmt-3.2.4.arm64_big_sur.bottle.tar.gz", + "sha256": "7b494228f9839518b1cbdfc997fbe2a532be09455c2015cd0d943009fc9e5059" }, "big_sur": { - "url": "https://homebrew.bintray.com/bottles/carthage-0.37.0.big_sur.bottle.tar.gz", - "sha256": "0770b4dd885f3018031c2d27fc090a34027d5856a248f33fa2a415d58da74632" + "url": "https://homebrew.bintray.com/bottles/shfmt-3.2.4.big_sur.bottle.tar.gz", + "sha256": "358a7e5a10551dd48ded79d50e587d41ae2910eb700241cba5e1272759923f82" }, "catalina": { - "url": "https://homebrew.bintray.com/bottles/carthage-0.37.0.catalina.bottle.tar.gz", - "sha256": "8a07c198835cb179d4054313b199ce126e64bb9414eaaa91f55162a4aed63134" + "url": "https://homebrew.bintray.com/bottles/shfmt-3.2.4.catalina.bottle.tar.gz", + "sha256": "c317ad8439c40c66664c00fb4a3b30ed945c86712381ce29b842a2c9bf64ad0d" }, "mojave": { - "url": "https://homebrew.bintray.com/bottles/carthage-0.37.0.mojave.bottle.tar.gz", - "sha256": "7fb777ac169aa4cb05683f0f8bfb5b56dbb0b0e8b673df995ef2fb2bbe0d90d2" + "url": "https://homebrew.bintray.com/bottles/shfmt-3.2.4.mojave.bottle.tar.gz", + "sha256": "08ad18eec7fb8b813b03b3b9e19d0557edd17bcdb2b373bd80087769efd619f7" + } + } + } + }, + "swift-format": { + "version": "0.50300.0", + "bottle": { + "rebuild": 0, + "cellar": ":any_skip_relocation", + "prefix": "/opt/homebrew", + "root_url": "https://homebrew.bintray.com/bottles", + "files": { + "arm64_big_sur": { + "url": "https://homebrew.bintray.com/bottles/swift-format-0.50300.0.arm64_big_sur.bottle.tar.gz", + "sha256": "75fa25fe584857edcac70f44e1bf5f2c1ab8cea794cab40955da080f0f2b1061" + }, + "big_sur": { + "url": "https://homebrew.bintray.com/bottles/swift-format-0.50300.0.big_sur.bottle.tar.gz", + "sha256": "7db963099096dac3d24d2d3095286791c55837506c12d8ebde3560c2c169890b" + }, + "catalina": { + "url": "https://homebrew.bintray.com/bottles/swift-format-0.50300.0.catalina.bottle.tar.gz", + "sha256": "21776a6b8f2417f3d2171536f6788948f3c5e8e4f1681cd4cf088ebd828c307b" } } } }, - "swiftlint": { - "version": "0.43.1", + "swiftformat": { + "version": "0.47.13", "bottle": { "rebuild": 0, "cellar": ":any_skip_relocation", - "prefix": "/usr/local", + "prefix": "/opt/homebrew", "root_url": "https://homebrew.bintray.com/bottles", "files": { "arm64_big_sur": { - "url": "https://homebrew.bintray.com/bottles/swiftlint-0.43.1.arm64_big_sur.bottle.tar.gz", - "sha256": "e1b633e61793b924f5875e4812b49184c91fc6580bfd497ab650fe13fbbe8d8f" + "url": "https://homebrew.bintray.com/bottles/swiftformat-0.47.13.arm64_big_sur.bottle.tar.gz", + "sha256": "fdcdb5e94b9c4d66a32d4515ba3d2db4057865f96aefab0e41fdeaf2879f4f89" }, "big_sur": { - "url": "https://homebrew.bintray.com/bottles/swiftlint-0.43.1.big_sur.bottle.tar.gz", - "sha256": "90faabe65db0f6bc43c3752b3b6d541e7e23cd0f368035dcef57503d74ed9581" + "url": "https://homebrew.bintray.com/bottles/swiftformat-0.47.13.big_sur.bottle.tar.gz", + "sha256": "cbb7a9803926d8bfaacf5c1a7d4cd07d8fe2255b1885be3ae2ffd8414e4c5292" }, "catalina": { - "url": "https://homebrew.bintray.com/bottles/swiftlint-0.43.1.catalina.bottle.tar.gz", - "sha256": "c1396dec887bf6d7986c35f38101955fb1a5c527ad4cd459174b3841dfa62239" + "url": "https://homebrew.bintray.com/bottles/swiftformat-0.47.13.catalina.bottle.tar.gz", + "sha256": "3a13e9b1f4a63bc03601897944f71dd4e6942788531dc060322d20fc6b36d2fd" + }, + "mojave": { + "url": "https://homebrew.bintray.com/bottles/swiftformat-0.47.13.mojave.bottle.tar.gz", + "sha256": "4e0691f12a5ef3b01eba9f41091b8f3457bc65151bfad057a45e8de9525074cd" } } } + }, + "swiftenv": { + "version": "1.4.0", + "bottle": false } }, "tap": { @@ -141,18 +149,10 @@ }, "system": { "macos": { - "catalina": { - "HOMEBREW_VERSION": "2.4.16-186-gd0e1595", - "HOMEBREW_PREFIX": "/usr/local", - "Homebrew/homebrew-core": "642b748a2f1029ee2afb0188a5ced5d543be4456", - "CLT": "1103.0.32.62", - "Xcode": "11.6", - "macOS": "10.15.6" - }, "big_sur": { "HOMEBREW_VERSION": "3.0.9-55-g3bfa59b", - "HOMEBREW_PREFIX": "/usr/local", - "Homebrew/homebrew-core": "2fec1a1e5430c9b9f9fdb386f1ece5850b599ebf", + "HOMEBREW_PREFIX": "/opt/homebrew", + "Homebrew/homebrew-core": "79dc67e162fb8d7b82ef38f88e7363647e35aa7d", "CLT": "12.4.0.0.1.1610135815", "Xcode": "12.4", "macOS": "11.2.3" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b35dd4145..367d941ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,9 +23,7 @@ We love pull requests from everyone. By participating in this project, you agree `git checkout -b awesome-feature master` - Please avoid working [directly on the master branch](https://softwareengineering.stackexchange.com/questions/223400/when-should-i-stop-committing-to-master-on-new-projects). - Make commits of logical units. -- Check for unnecessary whitespace with `git diff --check` 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. +- Run script/format before committing your changes. Fix anything that isn't automatically fixed by the linters. - Push your topic branch to your fork and [submit a pull request](https://github.com/mas-cli/mas/compare/master...your-username:topic-branch). Some things that will increase the chance that your pull request is accepted: diff --git a/Gemfile.lock b/Gemfile.lock index a0c203d30..6c8e2b4b9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,4 +66,4 @@ DEPENDENCIES xcpretty BUNDLED WITH - 2.2.14 + 2.2.15 diff --git a/Makefile b/Makefile index 444deb0e4..a12208623 100644 --- a/Makefile +++ b/Makefile @@ -109,10 +109,6 @@ install: uninstall: script/uninstall -.PHONY: sort -sort: - script/sort - .PHONY: lint lint: script/lint diff --git a/MasKit/AppStore/CKSoftwareMap+SoftwareMap.swift b/MasKit/AppStore/CKSoftwareMap+SoftwareMap.swift index a5b3f6d2c..5993e4d6c 100644 --- a/MasKit/AppStore/CKSoftwareMap+SoftwareMap.swift +++ b/MasKit/AppStore/CKSoftwareMap+SoftwareMap.swift @@ -11,10 +11,10 @@ import CommerceKit // MARK: - SoftwareProduct extension CKSoftwareMap: SoftwareMap { func allSoftwareProducts() -> [SoftwareProduct] { - return allProducts() ?? [] + allProducts() ?? [] } func product(for bundleIdentifier: String) -> SoftwareProduct? { - return product(forBundleIdentifier: bundleIdentifier) + product(forBundleIdentifier: bundleIdentifier) } } diff --git a/MasKit/AppStore/Downloader.swift b/MasKit/AppStore/Downloader.swift index db82264db..1d8ae020c 100644 --- a/MasKit/AppStore/Downloader.swift +++ b/MasKit/AppStore/Downloader.swift @@ -21,7 +21,7 @@ func download(_ adamId: UInt64, purchase: Bool = false) -> MASError? { } guard let storeAccount = account as? ISStoreAccount - else { fatalError("Unable to cast StoreAccount to ISStoreAccount") } + else { fatalError("Unable to cast StoreAccount to ISStoreAccount") } let purchase = SSPurchase(adamId: adamId, account: storeAccount, purchase: purchase) var purchaseError: MASError? diff --git a/MasKit/AppStore/ISStoreAccount.swift b/MasKit/AppStore/ISStoreAccount.swift index e2211a49c..8f0d0a956 100644 --- a/MasKit/AppStore/ISStoreAccount.swift +++ b/MasKit/AppStore/ISStoreAccount.swift @@ -11,7 +11,7 @@ import StoreFoundation extension ISStoreAccount: StoreAccount { static var primaryAccountIsPresentAndSignedIn: Bool { - return CKAccountStore.shared().primaryAccountIsPresentAndSignedIn + CKAccountStore.shared().primaryAccountIsPresentAndSignedIn } static var primaryAccount: StoreAccount? { diff --git a/MasKit/AppStore/PurchaseDownloadObserver.swift b/MasKit/AppStore/PurchaseDownloadObserver.swift index b754bc6a6..2f4970812 100644 --- a/MasKit/AppStore/PurchaseDownloadObserver.swift +++ b/MasKit/AppStore/PurchaseDownloadObserver.swift @@ -20,8 +20,9 @@ import StoreFoundation func downloadQueue(_ queue: CKDownloadQueue, statusChangedFor download: SSDownload) { guard download.metadata.itemIdentifier == purchase.itemIdentifier, - let status = download.status else { - return + let status = download.status + else { + return } if status.isFailed || status.isCancelled { @@ -41,8 +42,9 @@ import StoreFoundation func downloadQueue(_: CKDownloadQueue, changedWithRemoval download: SSDownload) { guard download.metadata.itemIdentifier == purchase.itemIdentifier, - let status = download.status else { - return + let status = download.status + else { + return } clearLine() @@ -62,7 +64,7 @@ struct ProgressState { let phase: String var percentage: String { - return String(format: "%.1f%%", arguments: [floor(percentComplete * 100)]) + String(format: "%.1f%%", arguments: [floor(percentComplete * 100)]) } } @@ -76,7 +78,7 @@ func progress(_ state: ProgressState) { let completeLength = Int(state.percentComplete * Float(barLength)) var bar = "" - for index in 0 ..< barLength { + for index in 0.. Result<(), MASError> { + public func run(_: Options) -> Result { if let account = ISStoreAccount.primaryAccount { print(String(describing: account.identifier)) } else { diff --git a/MasKit/Commands/Home.swift b/MasKit/Commands/Home.swift index 1cc81c1d4..47abe2805 100644 --- a/MasKit/Commands/Home.swift +++ b/MasKit/Commands/Home.swift @@ -20,14 +20,16 @@ public struct HomeCommand: CommandProtocol { private var openCommand: ExternalCommand /// Designated initializer. - public init(storeSearch: StoreSearch = MasStoreSearch(), - openCommand: ExternalCommand = OpenSystemCommand()) { + public init( + storeSearch: StoreSearch = MasStoreSearch(), + openCommand: ExternalCommand = OpenSystemCommand() + ) { self.storeSearch = storeSearch self.openCommand = openCommand } /// Runs the command. - public func run(_ options: HomeOptions) -> Result<(), MASError> { + public func run(_ options: HomeOptions) -> Result { do { guard let result = try storeSearch.lookup(app: options.appId) else { print("No results found") @@ -61,11 +63,11 @@ public struct HomeOptions: OptionsProtocol { let appId: Int static func create(_ appId: Int) -> HomeOptions { - return HomeOptions(appId: appId) + HomeOptions(appId: appId) } public static func evaluate(_ mode: CommandMode) -> Result> { - return create + create <*> mode <| Argument(usage: "ID of app to show on MAS Preview") } } diff --git a/MasKit/Commands/Info.swift b/MasKit/Commands/Info.swift index d6e172668..61a31852d 100644 --- a/MasKit/Commands/Info.swift +++ b/MasKit/Commands/Info.swift @@ -23,7 +23,7 @@ public struct InfoCommand: CommandProtocol { } /// Runs the command. - public func run(_ options: InfoOptions) -> Result<(), MASError> { + public func run(_ options: InfoOptions) -> Result { do { guard let result = try storeSearch.lookup(app: options.appId) else { print("No results found") @@ -47,11 +47,11 @@ public struct InfoOptions: OptionsProtocol { let appId: Int static func create(_ appId: Int) -> InfoOptions { - return InfoOptions(appId: appId) + InfoOptions(appId: appId) } public static func evaluate(_ mode: CommandMode) -> Result> { - return create + create <*> mode <| Argument(usage: "ID of app to show info") } } diff --git a/MasKit/Commands/Install.swift b/MasKit/Commands/Install.swift index f16db8bb2..92e696824 100644 --- a/MasKit/Commands/Install.swift +++ b/MasKit/Commands/Install.swift @@ -29,7 +29,7 @@ public struct InstallCommand: CommandProtocol { } /// Runs the command. - public func run(_ options: Options) -> Result<(), MASError> { + public func run(_ options: Options) -> Result { // 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 { @@ -56,13 +56,13 @@ public struct InstallOptions: OptionsProtocol { let forceInstall: Bool public static func create(_ appIds: [Int]) -> (_ forceInstall: Bool) -> InstallOptions { - return { forceInstall in + { forceInstall in InstallOptions(appIds: appIds.map { UInt64($0) }, forceInstall: forceInstall) } } public static func evaluate(_ mode: CommandMode) -> Result> { - return create + create <*> mode <| Argument(usage: "app ID(s) to install") <*> mode <| Switch(flag: nil, key: "force", usage: "force reinstall") } diff --git a/MasKit/Commands/List.swift b/MasKit/Commands/List.swift index 7665fe835..e041e81af 100644 --- a/MasKit/Commands/List.swift +++ b/MasKit/Commands/List.swift @@ -29,7 +29,7 @@ public struct ListCommand: CommandProtocol { } /// Runs the command. - public func run(_: Options) -> Result<(), MASError> { + public func run(_: Options) -> Result { let products = appLibrary.installedApps if products.isEmpty { print("No installed apps found") diff --git a/MasKit/Commands/Lucky.swift b/MasKit/Commands/Lucky.swift index bc0bbd4b5..520ac86df 100644 --- a/MasKit/Commands/Lucky.swift +++ b/MasKit/Commands/Lucky.swift @@ -28,14 +28,16 @@ public struct LuckyCommand: CommandProtocol { /// Internal initializer. /// - Parameter appLibrary: AppLibrary manager. /// - Parameter storeSearch: Search manager. - init(appLibrary: AppLibrary = MasAppLibrary(), - storeSearch: StoreSearch = MasStoreSearch()) { + init( + appLibrary: AppLibrary = MasAppLibrary(), + storeSearch: StoreSearch = MasStoreSearch() + ) { self.appLibrary = appLibrary self.storeSearch = storeSearch } /// Runs the command. - public func run(_ options: Options) -> Result<(), MASError> { + public func run(_ options: Options) -> Result { var appId: Int? do { @@ -65,17 +67,18 @@ public struct LuckyCommand: CommandProtocol { /// - appId: App identifier /// - options: command opetions. /// - Returns: Result of the operation. - fileprivate func install(_ appId: UInt64, options: Options) -> Result<(), MASError> { + fileprivate func install(_ appId: UInt64, options: Options) -> Result { // Try to download applications with given identifiers and collect results - let downloadResults = [appId].compactMap { (appId) -> MASError? in - if let product = appLibrary.installedApp(forId: appId), !options.forceInstall { - printWarning("\(product.appName) is already installed") - return nil + let downloadResults = [appId] + .compactMap { (appId) -> MASError? in + if let product = appLibrary.installedApp(forId: appId), !options.forceInstall { + printWarning("\(product.appName) is already installed") + return nil + } + + return download(appId) } - return download(appId) - } - switch downloadResults.count { case 0: return .success(()) @@ -92,13 +95,13 @@ public struct LuckyOptions: OptionsProtocol { let forceInstall: Bool public static func create(_ appName: String) -> (_ forceInstall: Bool) -> LuckyOptions { - return { forceInstall in + { forceInstall in LuckyOptions(appName: appName, forceInstall: forceInstall) } } public static func evaluate(_ mode: CommandMode) -> Result> { - return create + create <*> mode <| Argument(usage: "the app name to install") <*> mode <| Switch(flag: nil, key: "force", usage: "force reinstall") } diff --git a/MasKit/Commands/Open.swift b/MasKit/Commands/Open.swift index 761ead965..5c00ac84d 100644 --- a/MasKit/Commands/Open.swift +++ b/MasKit/Commands/Open.swift @@ -23,14 +23,16 @@ public struct OpenCommand: CommandProtocol { private var systemOpen: ExternalCommand /// Designated initializer. - public init(storeSearch: StoreSearch = MasStoreSearch(), - openCommand: ExternalCommand = OpenSystemCommand()) { + public init( + storeSearch: StoreSearch = MasStoreSearch(), + openCommand: ExternalCommand = OpenSystemCommand() + ) { self.storeSearch = storeSearch systemOpen = openCommand } /// Runs the command. - public func run(_ options: OpenOptions) -> Result<(), MASError> { + public func run(_ options: OpenOptions) -> Result { do { if options.appId == markerValue { // If no app ID is given, just open the MAS GUI app @@ -39,20 +41,20 @@ public struct OpenCommand: CommandProtocol { } guard let appId = Int(options.appId) - else { - print("Invalid app ID") - return .failure(.noSearchResultsFound) + else { + print("Invalid app ID") + return .failure(.noSearchResultsFound) } guard let result = try storeSearch.lookup(app: appId) - else { - print("No results found") - return .failure(.noSearchResultsFound) + else { + print("No results found") + return .failure(.noSearchResultsFound) } guard var url = URLComponents(string: result.trackViewUrl) - else { - return .failure(.searchFailed) + else { + return .failure(.searchFailed) } url.scheme = masScheme @@ -83,11 +85,11 @@ public struct OpenOptions: OptionsProtocol { var appId: String static func create(_ appId: String) -> OpenOptions { - return OpenOptions(appId: appId) + OpenOptions(appId: appId) } public static func evaluate(_ mode: CommandMode) -> Result> { - return create + create <*> mode <| Argument(defaultValue: markerValue, usage: "the app ID") } } diff --git a/MasKit/Commands/Outdated.swift b/MasKit/Commands/Outdated.swift index 8c04b0075..320db1536 100644 --- a/MasKit/Commands/Outdated.swift +++ b/MasKit/Commands/Outdated.swift @@ -32,25 +32,27 @@ public struct OutdatedCommand: CommandProtocol { } /// Runs the command. - public func run(_: Options) -> Result<(), MASError> { + public func run(_: Options) -> Result { for installedApp in appLibrary.installedApps { do { if let storeApp = try storeSearch.lookup(app: installedApp.itemIdentifier.intValue) { if installedApp.bundleVersion != storeApp.version { - print(""" -\(installedApp.itemIdentifier) \(installedApp.appName) (\(installedApp.bundleVersion) -> \(storeApp.version)) -""") + print( + """ + \(installedApp.itemIdentifier) \(installedApp.appName) \ + (\(installedApp.bundleVersion) -> \(storeApp.version)) + """) } } else { - printWarning(""" -Identifier \(installedApp.itemIdentifier) not found in store. Was expected to identify \(installedApp.appName). -""") + printWarning( + """ + Identifier \(installedApp.itemIdentifier) not found in store. \ + Was expected to identify \(installedApp.appName). + """) } } catch { // Bubble up MASErrors - // swiftlint:disable force_cast - return .failure(error is MASError ? error as! MASError : .searchFailed) - // swiftlint:enable force_cast + return .failure(error as? MASError ?? .searchFailed) } } return .success(()) diff --git a/MasKit/Commands/Purchase.swift b/MasKit/Commands/Purchase.swift index 3282d72c8..40b80ae8d 100644 --- a/MasKit/Commands/Purchase.swift +++ b/MasKit/Commands/Purchase.swift @@ -28,7 +28,7 @@ public struct PurchaseCommand: CommandProtocol { } /// Runs the command. - public func run(_ options: Options) -> Result<(), MASError> { + public func run(_ options: Options) -> Result { // 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) { @@ -54,11 +54,11 @@ public struct PurchaseOptions: OptionsProtocol { let appIds: [UInt64] public static func create(_ appIds: [Int]) -> PurchaseOptions { - return PurchaseOptions(appIds: appIds.map { UInt64($0) }) + PurchaseOptions(appIds: appIds.map { UInt64($0) }) } public static func evaluate(_ mode: CommandMode) -> Result> { - return create + create <*> mode <| Argument(usage: "app ID(s) to install") } } diff --git a/MasKit/Commands/Reset.swift b/MasKit/Commands/Reset.swift index 7c905d43f..37d6c2d1a 100644 --- a/MasKit/Commands/Reset.swift +++ b/MasKit/Commands/Reset.swift @@ -18,24 +18,22 @@ public struct ResetCommand: CommandProtocol { public init() {} /// Runs the command. - public func run(_ options: Options) -> Result<(), MASError> { - /* - The "Reset Application" command in the Mac App Store debug menu performs - the following steps - - - killall Dock - - killall storeagent (storeagent no longer exists) - - rm com.apple.appstore download directory - - clear cookies (appears to be a no-op) - - As storeagent no longer exists we will implement a slight variant and kill all - App Store-associated processes - - storeaccountd - - storeassetd - - storedownloadd - - storeinstalld - - storelegacy - */ + public func run(_ options: Options) -> Result { + // The "Reset Application" command in the Mac App Store debug menu performs + // the following steps + // + // - killall Dock + // - killall storeagent (storeagent no longer exists) + // - rm com.apple.appstore download directory + // - clear cookies (appears to be a no-op) + // + // As storeagent no longer exists we will implement a slight variant and kill all + // App Store-associated processes + // - storeaccountd + // - storeassetd + // - storedownloadd + // - storeinstalld + // - storelegacy // Kill processes let killProcs = [ @@ -44,7 +42,7 @@ public struct ResetCommand: CommandProtocol { "storeassetd", "storedownloadd", "storeinstalld", - "storelegacy" + "storelegacy", ] let kill = Process() @@ -83,11 +81,11 @@ public struct ResetOptions: OptionsProtocol { let debug: Bool public static func create(debug: Bool) -> ResetOptions { - return ResetOptions(debug: debug) + ResetOptions(debug: debug) } public static func evaluate(_ mode: CommandMode) -> Result> { - return create + create <*> mode <| Switch(flag: nil, key: "debug", usage: "Enable debug mode") } } diff --git a/MasKit/Commands/Search.swift b/MasKit/Commands/Search.swift index 94f855667..f3bd86f49 100644 --- a/MasKit/Commands/Search.swift +++ b/MasKit/Commands/Search.swift @@ -24,7 +24,7 @@ public struct SearchCommand: CommandProtocol { self.storeSearch = storeSearch } - public func run(_ options: Options) -> Result<(), MASError> { + public func run(_ options: Options) -> Result { do { let resultList = try storeSearch.search(for: options.appName) if resultList.resultCount <= 0 || resultList.results.isEmpty { @@ -51,13 +51,13 @@ public struct SearchOptions: OptionsProtocol { let price: Bool public static func create(_ appName: String) -> (_ price: Bool) -> SearchOptions { - return { price in + { price in SearchOptions(appName: appName, price: price) } } public static func evaluate(_ mode: CommandMode) -> Result> { - return create + create <*> mode <| Argument(usage: "the app name to search") <*> mode <| Option(key: "price", defaultValue: false, usage: "Show price of found apps") } diff --git a/MasKit/Commands/SignIn.swift b/MasKit/Commands/SignIn.swift index ce76af693..bb595f4b4 100644 --- a/MasKit/Commands/SignIn.swift +++ b/MasKit/Commands/SignIn.swift @@ -18,7 +18,7 @@ public struct SignInCommand: CommandProtocol { public init() {} /// Runs the command. - public func run(_ options: Options) -> Result<(), MASError> { + public func run(_ options: Options) -> Result { if #available(macOS 10.13, *) { return .failure(.signInDisabled) } @@ -54,13 +54,11 @@ public struct SignInOptions: OptionsProtocol { let dialog: Bool static func create(username: String) -> (_ password: String) -> (_ dialog: Bool) -> SignInOptions { - return { password in { dialog in - SignInOptions(username: username, password: password, dialog: dialog) - } } + { password in { dialog in SignInOptions(username: username, password: password, dialog: dialog) } } } public static func evaluate(_ mode: CommandMode) -> Result> { - return create + create <*> mode <| Argument(usage: "Apple ID") <*> mode <| Argument(defaultValue: "", usage: "Password") <*> mode <| Option(key: "dialog", defaultValue: false, usage: "Complete login with graphical dialog") diff --git a/MasKit/Commands/SignOut.swift b/MasKit/Commands/SignOut.swift index 40e9822ef..efdf46791 100644 --- a/MasKit/Commands/SignOut.swift +++ b/MasKit/Commands/SignOut.swift @@ -17,7 +17,7 @@ public struct SignOutCommand: CommandProtocol { public init() {} /// Runs the command. - public func run(_: Options) -> Result<(), MASError> { + public func run(_: Options) -> Result { if #available(macOS 10.13, *) { let accountService: ISAccountService = ISServiceProxy.genericShared().accountService accountService.signOut() diff --git a/MasKit/Commands/Uninstall.swift b/MasKit/Commands/Uninstall.swift index eee9d1e9f..ebf0111a9 100644 --- a/MasKit/Commands/Uninstall.swift +++ b/MasKit/Commands/Uninstall.swift @@ -34,7 +34,7 @@ public struct UninstallCommand: CommandProtocol { /// /// - Parameter options: UninstallOptions (arguments) for this command /// - Returns: Success or an error. - public func run(_ options: Options) -> Result<(), MASError> { + public func run(_ options: Options) -> Result { let appId = UInt64(options.appId) guard let product = appLibrary.installedApp(forId: appId) else { @@ -67,13 +67,13 @@ public struct UninstallOptions: OptionsProtocol { let dryRun: Bool static func create(_ appId: Int) -> (_ dryRun: Bool) -> UninstallOptions { - return { dryRun in + { dryRun in UninstallOptions(appId: appId, dryRun: dryRun) } } public static func evaluate(_ mode: CommandMode) -> Result> { - return create + create <*> mode <| Argument(usage: "ID of app to uninstall") <*> mode <| Switch(flag: nil, key: "dry-run", usage: "dry run") } diff --git a/MasKit/Commands/Upgrade.swift b/MasKit/Commands/Upgrade.swift index f9bbd439b..b1d5770ea 100644 --- a/MasKit/Commands/Upgrade.swift +++ b/MasKit/Commands/Upgrade.swift @@ -31,22 +31,22 @@ public struct UpgradeCommand: CommandProtocol { } /// Runs the command. - public func run(_ options: Options) -> Result<(), MASError> { + public func run(_ options: Options) -> Result { do { let apps = - try ( - options.apps.count == 0 - ? appLibrary.installedApps - : options.apps.compactMap { - if let appId = UInt64($0) { - // if argument a UInt64, lookup app by id using argument - return appLibrary.installedApp(forId: appId) - } else { - // if argument not a UInt64, lookup app by name using argument - return appLibrary.installedApp(named: $0) - } - } - ).compactMap {(installedApp: SoftwareProduct) -> SoftwareProduct? in + try + (options.apps.count == 0 + ? appLibrary.installedApps + : options.apps.compactMap { + if let appId = UInt64($0) { + // if argument a UInt64, lookup app by id using argument + return appLibrary.installedApp(forId: appId) + } else { + // if argument not a UInt64, lookup app by name using argument + return appLibrary.installedApp(named: $0) + } + }) + .compactMap { (installedApp: SoftwareProduct) -> SoftwareProduct? in // only upgrade apps whose local version differs from the store version if let storeApp = try storeSearch.lookup(app: installedApp.itemIdentifier.intValue) { return storeApp.version != installedApp.bundleVersion @@ -63,7 +63,7 @@ public struct UpgradeCommand: CommandProtocol { } print("Upgrading \(apps.count) outdated application\(apps.count > 1 ? "s" : ""):") - print(apps.map {"\($0.appName) (\($0.bundleVersion))"}.joined(separator: ", ")) + print(apps.map { "\($0.appName) (\($0.bundleVersion))" }.joined(separator: ", ")) var updatedAppCount = 0 var failedUpgradeResults = [MASError]() @@ -88,9 +88,7 @@ public struct UpgradeCommand: CommandProtocol { } } catch { // Bubble up MASErrors - // swiftlint:disable force_cast - return .failure(error is MASError ? error as! MASError : .searchFailed) - // swiftlint:enable force_cast + return .failure(error as? MASError ?? .searchFailed) } } } @@ -99,11 +97,11 @@ public struct UpgradeOptions: OptionsProtocol { let apps: [String] static func create(_ apps: [String]) -> UpgradeOptions { - return UpgradeOptions(apps: apps) + UpgradeOptions(apps: apps) } public static func evaluate(_ mode: CommandMode) -> Result> { - return create + create <*> mode <| Argument(defaultValue: [], usage: "app(s) to upgrade") } } diff --git a/MasKit/Commands/Vendor.swift b/MasKit/Commands/Vendor.swift index 73794da6a..8fa0ce51b 100644 --- a/MasKit/Commands/Vendor.swift +++ b/MasKit/Commands/Vendor.swift @@ -20,23 +20,25 @@ public struct VendorCommand: CommandProtocol { private var openCommand: ExternalCommand /// Designated initializer. - public init(storeSearch: StoreSearch = MasStoreSearch(), - openCommand: ExternalCommand = OpenSystemCommand()) { + public init( + storeSearch: StoreSearch = MasStoreSearch(), + openCommand: ExternalCommand = OpenSystemCommand() + ) { self.storeSearch = storeSearch self.openCommand = openCommand } /// Runs the command. - public func run(_ options: VendorOptions) -> Result<(), MASError> { + public func run(_ options: VendorOptions) -> Result { do { guard let result = try storeSearch.lookup(app: options.appId) - else { - print("No results found") - return .failure(.noSearchResultsFound) + else { + print("No results found") + return .failure(.noSearchResultsFound) } guard let vendorWebsite = result.sellerUrl - else { throw MASError.noVendorWebsite } + else { throw MASError.noVendorWebsite } do { try openCommand.run(arguments: vendorWebsite) @@ -65,11 +67,11 @@ public struct VendorOptions: OptionsProtocol { let appId: Int static func create(_ appId: Int) -> VendorOptions { - return VendorOptions(appId: appId) + VendorOptions(appId: appId) } public static func evaluate(_ mode: CommandMode) -> Result> { - return create + create <*> mode <| Argument(usage: "the app ID to show the vendor's website") } } diff --git a/MasKit/Commands/Version.swift b/MasKit/Commands/Version.swift index d4684362c..bdf3c608e 100644 --- a/MasKit/Commands/Version.swift +++ b/MasKit/Commands/Version.swift @@ -17,7 +17,7 @@ public struct VersionCommand: CommandProtocol { public init() {} /// Runs the command. - public func run(_: Options) -> Result<(), MASError> { + public func run(_: Options) -> Result { let plist = Bundle.main.infoDictionary if let versionString = plist?["CFBundleShortVersionString"] { print(versionString) diff --git a/MasKit/Controllers/AppLibrary.swift b/MasKit/Controllers/AppLibrary.swift index 26f5ef4f7..ab6d28e46 100644 --- a/MasKit/Controllers/AppLibrary.swift +++ b/MasKit/Controllers/AppLibrary.swift @@ -64,6 +64,6 @@ extension AppLibrary { /// - Parameter appName: Full title of an app. /// - Returns: Software Product of app if found; nil otherwise. public func installedApp(named appName: String) -> SoftwareProduct? { - return installedApps.first { $0.appName == appName } + installedApps.first { $0.appName == appName } } } diff --git a/MasKit/Controllers/MasAppLibrary.swift b/MasKit/Controllers/MasAppLibrary.swift index 705ab8135..c1bc8fc1d 100644 --- a/MasKit/Controllers/MasAppLibrary.swift +++ b/MasKit/Controllers/MasAppLibrary.swift @@ -15,7 +15,7 @@ public class MasAppLibrary: AppLibrary { /// Array of installed software products. public lazy var installedApps: [SoftwareProduct] = { - return softwareMap.allSoftwareProducts() + softwareMap.allSoftwareProducts() }() /// Internal initializer for providing a mock software map. @@ -29,7 +29,7 @@ public class MasAppLibrary: AppLibrary { /// - Parameter bundleId: Bundle identifier of app. /// - Returns: Software Product of app if found; nil otherwise. public func installedApp(forBundleId bundleId: String) -> SoftwareProduct? { - return softwareMap.product(for: bundleId) + softwareMap.product(for: bundleId) } /// Uninstalls an app. @@ -66,6 +66,6 @@ public class MasAppLibrary: AppLibrary { /// /// - Returns: true if the current user is root; false otherwise private func userIsRoot() -> Bool { - return NSUserName() == "root" + NSUserName() == "root" } } diff --git a/MasKit/Controllers/MasStoreSearch.swift b/MasKit/Controllers/MasStoreSearch.swift index 0d9cb0c25..b4f677860 100644 --- a/MasKit/Controllers/MasStoreSearch.swift +++ b/MasKit/Controllers/MasStoreSearch.swift @@ -22,17 +22,17 @@ public class MasStoreSearch: StoreSearch { /// - Throws: Error if there is a problem with the network request. public func search(for appName: String) throws -> SearchResultList { guard let url = searchURL(for: appName) - else { throw MASError.urlEncoding } + else { throw MASError.urlEncoding } let result = networkManager.loadDataSync(from: url) // Unwrap network result guard case let .success(data) = result - else { - if case let .failure(error) = result { - throw error - } - throw MASError.noData + else { + if case let .failure(error) = result { + throw error + } + throw MASError.noData } do { @@ -50,24 +50,24 @@ public class MasStoreSearch: StoreSearch { /// - Throws: Error if there is a problem with the network request. public func lookup(app appId: Int) throws -> SearchResult? { guard let url = lookupURL(forApp: appId) - else { throw MASError.urlEncoding } + else { throw MASError.urlEncoding } let result = networkManager.loadDataSync(from: url) // Unwrap network result guard case let .success(data) = result - else { - if case let .failure(error) = result { - throw error - } - throw MASError.noData + else { + if case let .failure(error) = result { + throw error + } + throw MASError.noData } do { let results = try JSONDecoder().decode(SearchResultList.self, from: data) guard let searchResult = results.results.first - else { return nil } + else { return nil } return searchResult } catch { diff --git a/MasKit/Controllers/StoreSearch.swift b/MasKit/Controllers/StoreSearch.swift index cb1a73735..fcc6a6b21 100644 --- a/MasKit/Controllers/StoreSearch.swift +++ b/MasKit/Controllers/StoreSearch.swift @@ -28,7 +28,7 @@ extension StoreSearch { /// - Parameter appName: Name of app to find. /// - Returns: String URL for the search service or nil if appName can't be encoded. func searchURLString(forApp appName: String) -> String? { - if let urlEncodedAppName = appName.URLEncodedString { + if let urlEncodedAppName = appName.urlEncodedString { return "https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(urlEncodedAppName)" } return nil @@ -48,6 +48,6 @@ extension StoreSearch { /// - Parameter appId: MAS app identifier. /// - Returns: String URL for the lookup service. func lookupURLString(forApp appId: Int) -> String? { - return "https://itunes.apple.com/lookup?id=\(appId)" + "https://itunes.apple.com/lookup?id=\(appId)" } } diff --git a/MasKit/Errors/MASError.swift b/MasKit/Errors/MASError.swift index f1d0f1d70..2f594cee9 100644 --- a/MasKit/Errors/MASError.swift +++ b/MasKit/Errors/MASError.swift @@ -39,12 +39,14 @@ extension MASError: CustomStringConvertible { return "Not signed in" case .signInDisabled: - return "The 'signin' command has been disabled on this macOS version. " + - "Please sign into the Mac App Store app manually." + - "\nFor more info see: " + - "https://github.com/mas-cli/mas/issues/164" - - case let .signInFailed(error): + return """ + The 'signin' command has been disabled on this macOS version. \ + Please sign into the Mac App Store app manually. + For more info see: \ + https://github.com/mas-cli/mas/issues/164 + """ + + case .signInFailed(let error): if let error = error { return "Sign in failed: \(error.localizedDescription)" } else { @@ -54,14 +56,14 @@ extension MASError: CustomStringConvertible { case .alreadySignedIn: return "Already signed in" - case let .purchaseFailed(error): + case .purchaseFailed(let error): if let error = error { return "Download request failed: \(error.localizedDescription)" } else { return "Download request failed" } - case let .downloadFailed(error): + case .downloadFailed(let error): if let error = error { return "Download failed: \(error.localizedDescription)" } else { diff --git a/MasKit/Extensions/Dictionary+StringOrEmpty.swift b/MasKit/Extensions/Dictionary+StringOrEmpty.swift index 1d1f614c9..b7b658728 100644 --- a/MasKit/Extensions/Dictionary+StringOrEmpty.swift +++ b/MasKit/Extensions/Dictionary+StringOrEmpty.swift @@ -10,6 +10,6 @@ import Foundation extension Dictionary { func stringOrEmpty(key: Key) -> String { - return self[key] as? String ?? "" + self[key] as? String ?? "" } } diff --git a/MasKit/Extensions/String+PercentEncoding.swift b/MasKit/Extensions/String+PercentEncoding.swift index 51d936c79..121ae3be5 100644 --- a/MasKit/Extensions/String+PercentEncoding.swift +++ b/MasKit/Extensions/String+PercentEncoding.swift @@ -8,9 +8,9 @@ import Foundation -public extension String { +extension String { /// Return an URL encoded string - var URLEncodedString: String? { - return addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) + public var urlEncodedString: String? { + addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) } } diff --git a/MasKit/ExternalCommands/ExternalCommand.swift b/MasKit/ExternalCommands/ExternalCommand.swift index bf606c03e..04eec21e5 100644 --- a/MasKit/ExternalCommands/ExternalCommand.swift +++ b/MasKit/ExternalCommands/ExternalCommand.swift @@ -38,15 +38,15 @@ extension ExternalCommand { } public var exitCode: Int? { - return Int(process.terminationStatus) + Int(process.terminationStatus) } public var succeeded: Bool { - return exitCode == 0 + exitCode == 0 } public var failed: Bool { - return !succeeded + !succeeded } /// Runs the command. diff --git a/MasKit/Formatters/AppInfoFormatter.swift b/MasKit/Formatters/AppInfoFormatter.swift index 26040b979..91fefd01b 100644 --- a/MasKit/Formatters/AppInfoFormatter.swift +++ b/MasKit/Formatters/AppInfoFormatter.swift @@ -18,8 +18,9 @@ struct AppInfoFormatter { let headline = [ "\(app.trackName)", "\(app.version)", - "[\(app.price ?? 0)]" - ].joined(separator: " ") + "[\(app.price ?? 0)]", + ] + .joined(separator: " ") return [ headline, @@ -27,8 +28,9 @@ struct AppInfoFormatter { "Released: \(humanReadableDate(app.currentVersionReleaseDate))", "Minimum OS: \(app.minimumOsVersion)", "Size: \(humanReadableSize(app.fileSizeBytes ?? "0"))", - "From: \(app.trackViewUrl)" - ].joined(separator: "\n") + "From: \(app.trackViewUrl)", + ] + .joined(separator: "\n") } /// Formats a file size. diff --git a/MasKit/Formatters/AppListFormatter.swift b/MasKit/Formatters/AppListFormatter.swift index fdd80501e..b1e06ce01 100644 --- a/MasKit/Formatters/AppListFormatter.swift +++ b/MasKit/Formatters/AppListFormatter.swift @@ -10,7 +10,6 @@ import Foundation /// Formats text output for the search command. struct AppListFormatter { - static let idColumnMinWidth = 10 static let nameColumnMinWidth = 50 @@ -20,8 +19,10 @@ struct AppListFormatter { /// - Returns: Multiliune text outoutp. static func format(products: [SoftwareProduct]) -> String { // find longest appName for formatting, default 50 - let maxLength = products.map { $0.appNameOrBbundleIdentifier } - .max(by: { $1.count > $0.count })?.count + let maxLength = + products.map(\.appNameOrBbundleIdentifier) + .max(by: { $1.count > $0.count })? + .count ?? nameColumnMinWidth var output: String = "" diff --git a/MasKit/Formatters/SearchResultFormatter.swift b/MasKit/Formatters/SearchResultFormatter.swift index 413a43ba1..3d0c7d23e 100644 --- a/MasKit/Formatters/SearchResultFormatter.swift +++ b/MasKit/Formatters/SearchResultFormatter.swift @@ -16,7 +16,8 @@ struct SearchResultFormatter { /// - Returns: Multiliune text outoutp. static func format(results: [SearchResult], includePrice: Bool = false) -> String { // find longest appName for formatting, default 50 - let maxLength = results.map { $0.trackName }.max(by: { $1.count > $0.count })?.count + let maxLength = + results.map(\.trackName).max(by: { $1.count > $0.count })?.count ?? 50 var output: String = "" diff --git a/MasKit/Models/SearchResult.swift b/MasKit/Models/SearchResult.swift index ed65b9905..21fd43c9c 100644 --- a/MasKit/Models/SearchResult.swift +++ b/MasKit/Models/SearchResult.swift @@ -21,19 +21,21 @@ public struct SearchResult: Decodable { public var trackViewUrl: String public var version: String - init(bundleId: String = "", - currentVersionReleaseDate: String = "", - fileSizeBytes: String = "0", - formattedPrice: String = "Free", - minimumOsVersion: String = "", - price: Double = 0.0, - sellerName: String = "", - sellerUrl: String = "", - trackId: Int = 0, - trackCensoredName: String = "", - trackName: String = "", - trackViewUrl: String = "", - version: String = "") { + init( + bundleId: String = "", + currentVersionReleaseDate: String = "", + fileSizeBytes: String = "0", + formattedPrice: String = "Free", + minimumOsVersion: String = "", + price: Double = 0.0, + sellerName: String = "", + sellerUrl: String = "", + trackId: Int = 0, + trackCensoredName: String = "", + trackName: String = "", + trackViewUrl: String = "", + version: String = "" + ) { self.bundleId = bundleId self.currentVersionReleaseDate = currentVersionReleaseDate self.fileSizeBytes = fileSizeBytes diff --git a/MasKit/Models/SoftwareProduct.swift b/MasKit/Models/SoftwareProduct.swift index 5f3a156d7..230697b56 100644 --- a/MasKit/Models/SoftwareProduct.swift +++ b/MasKit/Models/SoftwareProduct.swift @@ -18,7 +18,7 @@ public protocol SoftwareProduct { // MARK: - Equatable extension SoftwareProduct { static func == (lhs: Self, rhs: Self) -> Bool { - return lhs.appName == rhs.appName + lhs.appName == rhs.appName && lhs.bundleIdentifier == rhs.bundleIdentifier && lhs.bundlePath == rhs.bundlePath && lhs.bundleVersion == rhs.bundleVersion diff --git a/MasKit/Network/NetworkManager.swift b/MasKit/Network/NetworkManager.swift index 547d6bb9c..b6f4e162a 100644 --- a/MasKit/Network/NetworkManager.swift +++ b/MasKit/Network/NetworkManager.swift @@ -30,7 +30,8 @@ public class NetworkManager { /// - completionHandler: Closure where result is delivered. func loadData(from url: URL, completionHandler: @escaping (NetworkResult) -> Void) { session.loadData(from: url) { (data: Data?, error: Error?) in - let result: NetworkResult = data != nil + let result: NetworkResult = + data != nil ? .success(data!) : .failure(error!) completionHandler(result) diff --git a/MasKit/Network/NetworkResult.swift b/MasKit/Network/NetworkResult.swift index 758e71da6..8724a4f34 100644 --- a/MasKit/Network/NetworkResult.swift +++ b/MasKit/Network/NetworkResult.swift @@ -14,10 +14,10 @@ enum NetworkResult { extension NetworkResult: Equatable { static func == (lhs: NetworkResult, rhs: NetworkResult) -> Bool { switch (lhs, rhs) { - case let (.success(data1), .success(data2)): + case (.success(let data1), .success(let data2)): return data1 == data2 - case let (.failure(error1), .failure(error2)): + case (.failure(let error1), .failure(let error2)): return error1.localizedDescription == error2.localizedDescription default: diff --git a/MasKitTests/Commands/AccountCommandSpec.swift b/MasKitTests/Commands/AccountCommandSpec.swift index cf13c202f..b4971ea85 100644 --- a/MasKitTests/Commands/AccountCommandSpec.swift +++ b/MasKitTests/Commands/AccountCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class AccountCommandSpec: QuickSpec { override func spec() { describe("Account command") { diff --git a/MasKitTests/Commands/HomeCommandSpec.swift b/MasKitTests/Commands/HomeCommandSpec.swift index 50ce298a5..ce73704fb 100644 --- a/MasKitTests/Commands/HomeCommandSpec.swift +++ b/MasKitTests/Commands/HomeCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class HomeCommandSpec: QuickSpec { override func spec() { let result = SearchResult( @@ -27,15 +28,19 @@ class HomeCommandSpec: QuickSpec { } it("fails to open app with invalid ID") { let result = cmd.run(HomeCommand.Options(appId: -999)) - expect(result).to(beFailure { error in - expect(error) == .searchFailed - }) + expect(result) + .to( + beFailure { error in + expect(error) == .searchFailed + }) } it("can't find app with unknown ID") { let result = cmd.run(HomeCommand.Options(appId: 999)) - expect(result).to(beFailure { error in - expect(error) == .noSearchResultsFound - }) + expect(result) + .to( + beFailure { error in + expect(error) == .noSearchResultsFound + }) } it("opens app on MAS Preview") { storeSearch.apps[result.trackId] = result diff --git a/MasKitTests/Commands/InfoCommandSpec.swift b/MasKitTests/Commands/InfoCommandSpec.swift index 8806bb645..0da85134d 100644 --- a/MasKitTests/Commands/InfoCommandSpec.swift +++ b/MasKitTests/Commands/InfoCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class InfoCommandSpec: QuickSpec { override func spec() { let result = SearchResult( @@ -26,14 +27,14 @@ class InfoCommandSpec: QuickSpec { let storeSearch = StoreSearchMock() let cmd = InfoCommand(storeSearch: storeSearch) let expectedOutput = """ - Awesome App 1.0 [2.0] - By: Awesome Dev - Released: 2019-01-07 - Minimum OS: 10.14 - Size: 1 KB - From: https://awesome.app + Awesome App 1.0 [2.0] + By: Awesome Dev + Released: 2019-01-07 + Minimum OS: 10.14 + Size: 1 KB + From: https://awesome.app - """ + """ describe("Info command") { beforeEach { @@ -41,15 +42,19 @@ class InfoCommandSpec: QuickSpec { } it("fails to open app with invalid ID") { let result = cmd.run(InfoCommand.Options(appId: -999)) - expect(result).to(beFailure { error in - expect(error) == .searchFailed - }) + expect(result) + .to( + beFailure { error in + expect(error) == .searchFailed + }) } it("can't find app with unknown ID") { let result = cmd.run(InfoCommand.Options(appId: 999)) - expect(result).to(beFailure { error in - expect(error) == .noSearchResultsFound - }) + expect(result) + .to( + beFailure { error in + expect(error) == .noSearchResultsFound + }) } it("displays app details") { storeSearch.apps[result.trackId] = result diff --git a/MasKitTests/Commands/InstallCommandSpec.swift b/MasKitTests/Commands/InstallCommandSpec.swift index aa56eeff3..54ef2a6f7 100644 --- a/MasKitTests/Commands/InstallCommandSpec.swift +++ b/MasKitTests/Commands/InstallCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class InstallCommandSpec: QuickSpec { override func spec() { describe("install command") { diff --git a/MasKitTests/Commands/ListCommandSpec.swift b/MasKitTests/Commands/ListCommandSpec.swift index 834dae6e3..1878fe2b3 100644 --- a/MasKitTests/Commands/ListCommandSpec.swift +++ b/MasKitTests/Commands/ListCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class ListCommandSpec: QuickSpec { override func spec() { describe("list command") { diff --git a/MasKitTests/Commands/LuckyCommandSpec.swift b/MasKitTests/Commands/LuckyCommandSpec.swift index 2f6388264..49adfea0a 100644 --- a/MasKitTests/Commands/LuckyCommandSpec.swift +++ b/MasKitTests/Commands/LuckyCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class LuckyCommandSpec: QuickSpec { override func spec() { describe("lucky command") { diff --git a/MasKitTests/Commands/OpenCommandSpec.swift b/MasKitTests/Commands/OpenCommandSpec.swift index 647d71dda..97c2ec5e9 100644 --- a/MasKitTests/Commands/OpenCommandSpec.swift +++ b/MasKitTests/Commands/OpenCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2019 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class OpenCommandSpec: QuickSpec { override func spec() { let result = SearchResult( @@ -27,15 +28,19 @@ class OpenCommandSpec: QuickSpec { } it("fails to open app with invalid ID") { let result = cmd.run(OpenCommand.Options(appId: "-999")) - expect(result).to(beFailure { error in - expect(error) == .searchFailed - }) + expect(result) + .to( + beFailure { error in + expect(error) == .searchFailed + }) } it("can't find app with unknown ID") { let result = cmd.run(OpenCommand.Options(appId: "999")) - expect(result).to(beFailure { error in - expect(error) == .noSearchResultsFound - }) + expect(result) + .to( + beFailure { error in + expect(error) == .noSearchResultsFound + }) } it("opens app in MAS") { storeSearch.apps[result.trackId] = result diff --git a/MasKitTests/Commands/OutdatedCommandSpec.swift b/MasKitTests/Commands/OutdatedCommandSpec.swift index 012757890..44bebe301 100644 --- a/MasKitTests/Commands/OutdatedCommandSpec.swift +++ b/MasKitTests/Commands/OutdatedCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class OutdatedCommandSpec: QuickSpec { override func spec() { describe("outdated command") { diff --git a/MasKitTests/Commands/PurchaseCommandSpec.swift b/MasKitTests/Commands/PurchaseCommandSpec.swift index 29d030e2a..800832a13 100644 --- a/MasKitTests/Commands/PurchaseCommandSpec.swift +++ b/MasKitTests/Commands/PurchaseCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2020 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class PurchaseCommandSpec: QuickSpec { override func spec() { describe("purchase command") { diff --git a/MasKitTests/Commands/ResetCommandSpec.swift b/MasKitTests/Commands/ResetCommandSpec.swift index c139b0e5b..52c685ff0 100644 --- a/MasKitTests/Commands/ResetCommandSpec.swift +++ b/MasKitTests/Commands/ResetCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class ResetCommandSpec: QuickSpec { override func spec() { describe("reset command") { diff --git a/MasKitTests/Commands/SearchCommandSpec.swift b/MasKitTests/Commands/SearchCommandSpec.swift index 617c0b45b..042610d2a 100644 --- a/MasKitTests/Commands/SearchCommandSpec.swift +++ b/MasKitTests/Commands/SearchCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class SearchCommandSpec: QuickSpec { override func spec() { let result = SearchResult( @@ -36,9 +37,11 @@ class SearchCommandSpec: QuickSpec { let search = SearchCommand(storeSearch: storeSearch) let searchOptions = SearchOptions(appName: "nonexistent", price: false) let result = search.run(searchOptions) - expect(result).to(beFailure { error in - expect(error) == .noSearchResultsFound - }) + expect(result) + .to( + beFailure { error in + expect(error) == .noSearchResultsFound + }) } } } diff --git a/MasKitTests/Commands/SignInCommandSpec.swift b/MasKitTests/Commands/SignInCommandSpec.swift index e06fee0ee..537dc8619 100644 --- a/MasKitTests/Commands/SignInCommandSpec.swift +++ b/MasKitTests/Commands/SignInCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class SignInCommandSpec: QuickSpec { override func spec() { describe("signn command") { diff --git a/MasKitTests/Commands/SignOutCommandSpec.swift b/MasKitTests/Commands/SignOutCommandSpec.swift index 9079a047e..ad6de3ee6 100644 --- a/MasKitTests/Commands/SignOutCommandSpec.swift +++ b/MasKitTests/Commands/SignOutCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class SignOutCommandSpec: QuickSpec { override func spec() { describe("signout command") { diff --git a/MasKitTests/Commands/UninstallCommandSpec.swift b/MasKitTests/Commands/UninstallCommandSpec.swift index e18a896ec..f988eba6e 100644 --- a/MasKitTests/Commands/UninstallCommandSpec.swift +++ b/MasKitTests/Commands/UninstallCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class UninstallCommandSpec: QuickSpec { override func spec() { describe("uninstall command") { @@ -32,9 +33,11 @@ class UninstallCommandSpec: QuickSpec { } it("can't remove a missing app") { let result = uninstall.run(options) - expect(result).to(beFailure { error in - expect(error) == .notInstalled - }) + expect(result) + .to( + beFailure { error in + expect(error) == .notInstalled + }) } it("finds an app") { mockLibrary.installedApps.append(app) @@ -51,9 +54,11 @@ class UninstallCommandSpec: QuickSpec { } it("can't remove a missing app") { let result = uninstall.run(options) - expect(result).to(beFailure { error in - expect(error) == .notInstalled - }) + expect(result) + .to( + beFailure { error in + expect(error) == .notInstalled + }) } it("removes an app") { mockLibrary.installedApps.append(app) @@ -62,14 +67,16 @@ class UninstallCommandSpec: QuickSpec { expect(result).to(beSuccess()) } it("fails if there is a problem with the trash command") { - var brokenUninstall = app // make mutable copy + var brokenUninstall = app // make mutable copy brokenUninstall.bundlePath = "/dev/null" mockLibrary.installedApps.append(brokenUninstall) let result = uninstall.run(options) - expect(result).to(beFailure { error in - expect(error) == .uninstallFailed - }) + expect(result) + .to( + beFailure { error in + expect(error) == .uninstallFailed + }) } } } diff --git a/MasKitTests/Commands/UpgradeCommandSpec.swift b/MasKitTests/Commands/UpgradeCommandSpec.swift index 8c801cd8e..3a863da95 100644 --- a/MasKitTests/Commands/UpgradeCommandSpec.swift +++ b/MasKitTests/Commands/UpgradeCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class UpgradeCommandSpec: QuickSpec { override func spec() { describe("upgrade command") { diff --git a/MasKitTests/Commands/VendorCommandSpec.swift b/MasKitTests/Commands/VendorCommandSpec.swift index 7f1e7bb6e..f1bfb1dd2 100644 --- a/MasKitTests/Commands/VendorCommandSpec.swift +++ b/MasKitTests/Commands/VendorCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2019 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class VendorCommandSpec: QuickSpec { override func spec() { let result = SearchResult( @@ -27,15 +28,19 @@ class VendorCommandSpec: QuickSpec { } it("fails to open app with invalid ID") { let result = cmd.run(VendorCommand.Options(appId: -999)) - expect(result).to(beFailure { error in - expect(error) == .searchFailed - }) + expect(result) + .to( + beFailure { error in + expect(error) == .searchFailed + }) } it("can't find app with unknown ID") { let result = cmd.run(VendorCommand.Options(appId: 999)) - expect(result).to(beFailure { error in - expect(error) == .noSearchResultsFound - }) + expect(result) + .to( + beFailure { error in + expect(error) == .noSearchResultsFound + }) } it("opens vendor app page in browser") { storeSearch.apps[result.trackId] = result diff --git a/MasKitTests/Commands/VersionCommandSpec.swift b/MasKitTests/Commands/VersionCommandSpec.swift index c889ddf93..a81072485 100644 --- a/MasKitTests/Commands/VersionCommandSpec.swift +++ b/MasKitTests/Commands/VersionCommandSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class VersionCommandSpec: QuickSpec { override func spec() { describe("version command") { diff --git a/MasKitTests/Controllers/AppLibraryMock.swift b/MasKitTests/Controllers/AppLibraryMock.swift index b591754ad..61d70c470 100644 --- a/MasKitTests/Controllers/AppLibraryMock.swift +++ b/MasKitTests/Controllers/AppLibraryMock.swift @@ -16,13 +16,15 @@ class AppLibraryMock: AppLibrary { /// - Parameter bundleId: Bundle identifier of app. /// - Returns: Software Product of app if found; nil otherwise. public func installedApp(forBundleId _: String) -> SoftwareProduct? { - return nil + nil } func uninstallApp(app: SoftwareProduct) throws { if !installedApps.contains(where: { (product) -> Bool in app.itemIdentifier == product.itemIdentifier - }) { throw MASError.notInstalled } + }) { + throw MASError.notInstalled + } // Special case for testing where we pretend the trash command failed if app.bundlePath == "/dev/null" { diff --git a/MasKitTests/Controllers/MasAppLibrarySpec.swift b/MasKitTests/Controllers/MasAppLibrarySpec.swift index eb4260de3..50921f868 100644 --- a/MasKitTests/Controllers/MasAppLibrarySpec.swift +++ b/MasKitTests/Controllers/MasAppLibrarySpec.swift @@ -6,10 +6,11 @@ // Copyright © 2020 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class MasAppLibrarySpec: QuickSpec { override func spec() { let library = MasAppLibrary(softwareMap: SoftwareMapMock(products: apps)) @@ -33,18 +34,17 @@ let myApp = SoftwareProductMock( bundleIdentifier: "com.example", bundlePath: "", bundleVersion: "", - itemIdentifier: 1234) + itemIdentifier: 1234 +) -var apps: [SoftwareProduct] = [ - myApp -] +var apps: [SoftwareProduct] = [myApp] // MARK: - SoftwareMapMock struct SoftwareMapMock: SoftwareMap { var products: [SoftwareProduct] = [] func allSoftwareProducts() -> [SoftwareProduct] { - return products + products } func product(for bundleIdentifier: String) -> SoftwareProduct? { diff --git a/MasKitTests/Controllers/MasStoreSearchSpec.swift b/MasKitTests/Controllers/MasStoreSearchSpec.swift index 8b896ce62..176182b84 100644 --- a/MasKitTests/Controllers/MasStoreSearchSpec.swift +++ b/MasKitTests/Controllers/MasStoreSearchSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2019 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class MasStoreSearchSpec: QuickSpec { override func spec() { describe("store") { diff --git a/MasKitTests/Controllers/StoreSearchMock.swift b/MasKitTests/Controllers/StoreSearchMock.swift index bcb996c51..e899e65e5 100644 --- a/MasKitTests/Controllers/StoreSearchMock.swift +++ b/MasKitTests/Controllers/StoreSearchMock.swift @@ -23,7 +23,7 @@ class StoreSearchMock: StoreSearch { } guard let result = apps[appId] - else { throw MASError.noSearchResultsFound } + else { throw MASError.noSearchResultsFound } return result } diff --git a/MasKitTests/Controllers/StoreSearchSpec.swift b/MasKitTests/Controllers/StoreSearchSpec.swift index 9b67f60d4..87b219e62 100644 --- a/MasKitTests/Controllers/StoreSearchSpec.swift +++ b/MasKitTests/Controllers/StoreSearchSpec.swift @@ -5,14 +5,15 @@ // Created by Ben Chatelain on 1/11/19. // Copyright © 2019 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + /// Protocol minimal implementation struct StoreSearchForTesting: StoreSearch { - func lookup(app _: Int) throws -> SearchResult? { return nil } - func search(for _: String) throws -> SearchResultList { return SearchResultList(resultCount: 0, results: []) } + func lookup(app _: Int) throws -> SearchResult? { nil } + func search(for _: String) throws -> SearchResultList { SearchResultList(resultCount: 0, results: []) } } class StoreSearchSpec: QuickSpec { @@ -23,15 +24,14 @@ class StoreSearchSpec: QuickSpec { it("contains the app name") { let appName = "myapp" let urlString = storeSearch.searchURLString(forApp: appName) - expect(urlString) == - "https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appName)" + expect(urlString) == "https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appName)" } it("contains the encoded app name") { let appName = "My App" let appNameEncoded = "My%20App" let urlString = storeSearch.searchURLString(forApp: appName) - expect(urlString) == - "https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appNameEncoded)" + expect(urlString) + == "https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appNameEncoded)" } // Find a character that causes addingPercentEncoding(withAllowedCharacters to return nil xit("is nil when app name cannot be url encoded") { diff --git a/MasKitTests/Errors/MASErrorTestCase.swift b/MasKitTests/Errors/MASErrorTestCase.swift index 3ec8e2f86..4130f7a12 100644 --- a/MasKitTests/Errors/MASErrorTestCase.swift +++ b/MasKitTests/Errors/MASErrorTestCase.swift @@ -6,10 +6,11 @@ // Copyright © 2018 Andrew Naylor. All rights reserved. // -@testable import MasKit import Foundation import XCTest +@testable import MasKit + class MASErrorTestCase: XCTestCase { private let errorDomain = "MAS" var error: MASError! @@ -19,7 +20,7 @@ class MASErrorTestCase: XCTestCase { /// 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 { return "dummy value" } + get { "dummy value" } set { NSError.setUserInfoValueProvider(forDomain: errorDomain) { (_: Error, _: String) -> Any? in newValue diff --git a/MasKitTests/Extensions/Bundle+JSON.swift b/MasKitTests/Extensions/Bundle+JSON.swift index e5288969c..b05bb558a 100644 --- a/MasKitTests/Extensions/Bundle+JSON.swift +++ b/MasKitTests/Extensions/Bundle+JSON.swift @@ -24,7 +24,7 @@ extension Bundle { /// - Parameter fileName: Name of file to locate. /// - Returns: URL to file. static func url(for fileName: String) -> URL? { - return Bundle(for: NetworkSessionMock.self).url(for: fileName) + Bundle(for: NetworkSessionMock.self).url(for: fileName) } /// Builds a URL for a file in the JSON directory of the current bundle. @@ -32,10 +32,13 @@ extension Bundle { /// - Parameter fileName: Name of file to locate. /// - Returns: URL to file. func url(for fileName: String) -> URL? { - guard let path = self.path(forResource: fileName.fileNameWithoutExtension, - ofType: fileName.fileExtension, - inDirectory: "JSON") - else { fatalError("Unable to load file \(fileName)") } + guard + let path = self.path( + forResource: fileName.fileNameWithoutExtension, + ofType: fileName.fileExtension, + inDirectory: "JSON" + ) + else { fatalError("Unable to load file \(fileName)") } return URL(fileURLWithPath: path) } diff --git a/MasKitTests/Extensions/String+FileExtension.swift b/MasKitTests/Extensions/String+FileExtension.swift index 8fa2cff5d..1c08969ea 100644 --- a/MasKitTests/Extensions/String+FileExtension.swift +++ b/MasKitTests/Extensions/String+FileExtension.swift @@ -11,11 +11,11 @@ import Foundation extension String { /// Returns the file name before the extension. var fileNameWithoutExtension: String { - return (self as NSString).deletingPathExtension + (self as NSString).deletingPathExtension } /// Returns the file extension. var fileExtension: String { - return (self as NSString).pathExtension + (self as NSString).pathExtension } } diff --git a/MasKitTests/ExternalCommands/OpenSystemCommandSpec.swift b/MasKitTests/ExternalCommands/OpenSystemCommandSpec.swift index 79ededa1b..c8a24dc8c 100644 --- a/MasKitTests/ExternalCommands/OpenSystemCommandSpec.swift +++ b/MasKitTests/ExternalCommands/OpenSystemCommandSpec.swift @@ -6,14 +6,14 @@ // Copyright © 2020 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class OpenSystemCommandSpec: QuickSpec { override func spec() { describe("open system command") { - context("binary path") { it("defaults to the macOS open command") { let cmd = OpenSystemCommand() diff --git a/MasKitTests/Formatters/AppListFormatterSpec.swift b/MasKitTests/Formatters/AppListFormatterSpec.swift index 1431db223..de0a9c1d1 100644 --- a/MasKitTests/Formatters/AppListFormatterSpec.swift +++ b/MasKitTests/Formatters/AppListFormatterSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2020 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class AppListsFormatterSpec: QuickSpec { override func spec() { // static func reference @@ -25,14 +26,14 @@ class AppListsFormatterSpec: QuickSpec { expect(output) == "" } it("can format a single product") { - products = [SoftwareProductMock( + let product = SoftwareProductMock( appName: "Awesome App", bundleIdentifier: "", bundlePath: "", bundleVersion: "19.2.1", itemIdentifier: 12345 - )] - let output = format(products) + ) + let output = format([product]) expect(output) == "12345 Awesome App (19.2.1)" } it("can format two products") { @@ -50,11 +51,10 @@ class AppListsFormatterSpec: QuickSpec { bundlePath: "", bundleVersion: "1.2.0", itemIdentifier: 67890 - ) + ), ] let output = format(products) - expect(output) == - "12345 Awesome App (19.2.1)\n67890 Even Better App (1.2.0)" + expect(output) == "12345 Awesome App (19.2.1)\n67890 Even Better App (1.2.0)" } } } diff --git a/MasKitTests/Formatters/SearchResultFormatterSpec.swift b/MasKitTests/Formatters/SearchResultFormatterSpec.swift index ac010cdfc..c83fc6613 100644 --- a/MasKitTests/Formatters/SearchResultFormatterSpec.swift +++ b/MasKitTests/Formatters/SearchResultFormatterSpec.swift @@ -6,10 +6,11 @@ // Copyright © 2019 mas-cli. All rights reserved. // -@testable import MasKit import Nimble import Quick +@testable import MasKit + class SearchResultsFormatterSpec: QuickSpec { override func spec() { // static func reference @@ -25,23 +26,23 @@ class SearchResultsFormatterSpec: QuickSpec { expect(output) == "" } it("can format a single result") { - results = [SearchResult( + let result = SearchResult( price: 9.87, trackId: 12345, trackName: "Awesome App", version: "19.2.1" - )] - let output = format(results, false) + ) + let output = format([result], false) expect(output) == " 12345 Awesome App (19.2.1)" } it("can format a single result with price") { - results = [SearchResult( + let result = SearchResult( price: 9.87, trackId: 12345, trackName: "Awesome App", version: "19.2.1" - )] - let output = format(results, true) + ) + let output = format([result], true) expect(output) == " 12345 Awesome App $ 9.87 (19.2.1)" } it("can format a two results") { @@ -57,11 +58,10 @@ class SearchResultsFormatterSpec: QuickSpec { trackId: 67890, trackName: "Even Better App", version: "1.2.0" - ) + ), ] let output = format(results, false) - expect(output) == - " 12345 Awesome App (19.2.1)\n 67890 Even Better App (1.2.0)" + expect(output) == " 12345 Awesome App (19.2.1)\n 67890 Even Better App (1.2.0)" } it("can format a two results with prices") { results = [ @@ -76,11 +76,11 @@ class SearchResultsFormatterSpec: QuickSpec { trackId: 67890, trackName: "Even Better App", version: "1.2.0" - ) + ), ] let output = format(results, true) - expect(output) == - " 12345 Awesome App $ 9.87 (19.2.1)\n 67890 Even Better App $ 0.01 (1.2.0)" + expect(output) + == " 12345 Awesome App $ 9.87 (19.2.1)\n 67890 Even Better App $ 0.01 (1.2.0)" } } } diff --git a/MasKitTests/Models/SearchResultListSpec.swift b/MasKitTests/Models/SearchResultListSpec.swift index 315d92a9f..8df863ea2 100644 --- a/MasKitTests/Models/SearchResultListSpec.swift +++ b/MasKitTests/Models/SearchResultListSpec.swift @@ -6,9 +6,10 @@ // Copyright © 2020 mas-cli. All rights reserved. // -@testable import MasKit -import Quick import Nimble +import Quick + +@testable import MasKit class SearchResultListSpec: QuickSpec { override func spec() { diff --git a/MasKitTests/Models/SearchResultSpec.swift b/MasKitTests/Models/SearchResultSpec.swift index 26d5056e1..c9933d917 100644 --- a/MasKitTests/Models/SearchResultSpec.swift +++ b/MasKitTests/Models/SearchResultSpec.swift @@ -6,9 +6,10 @@ // Copyright © 2020 mas-cli. All rights reserved. // -@testable import MasKit -import Quick import Nimble +import Quick + +@testable import MasKit class SearchResultSpec: QuickSpec { override func spec() { diff --git a/MasKitTests/Network/NetworkManagerTests.swift b/MasKitTests/Network/NetworkManagerTests.swift index a43ebd9ee..9f5c4fcb8 100644 --- a/MasKitTests/Network/NetworkManagerTests.swift +++ b/MasKitTests/Network/NetworkManagerTests.swift @@ -6,9 +6,10 @@ // Copyright © 2019 mas-cli. All rights reserved. // -@testable import MasKit import XCTest +@testable import MasKit + class NetworkManagerTests: XCTestCase { func testSuccessfulAsyncResponse() { // Setup our objects diff --git a/MasKitTests/Network/NetworkSessionMockFromFile.swift b/MasKitTests/Network/NetworkSessionMockFromFile.swift index 0e4f2c000..0835c15ef 100644 --- a/MasKitTests/Network/NetworkSessionMockFromFile.swift +++ b/MasKitTests/Network/NetworkSessionMockFromFile.swift @@ -27,7 +27,7 @@ class NetworkSessionMockFromFile: NetworkSessionMock { /// - completionHandler: Closure which is delivered either data or an error. @objc override func loadData(from _: URL, completionHandler: @escaping (Data?, Error?) -> Void) { guard let fileURL = Bundle.url(for: responseFile) - else { fatalError("Unable to load file \(responseFile)") } + else { fatalError("Unable to load file \(responseFile)") } do { let data = try Data(contentsOf: fileURL, options: .mappedIfSafe) diff --git a/MasKitTests/Network/TestURLSessionDelegate.swift b/MasKitTests/Network/TestURLSessionDelegate.swift index 16a096497..872d82037 100644 --- a/MasKitTests/Network/TestURLSessionDelegate.swift +++ b/MasKitTests/Network/TestURLSessionDelegate.swift @@ -10,14 +10,17 @@ import Foundation /// Delegate for network requests initiated from tests. class TestURLSessionDelegate: NSObject, URLSessionDelegate { - func urlSession(_: URLSession, - didReceive challenge: URLAuthenticationChallenge, - completionHandler: (URLSession.AuthChallengeDisposition, - URLCredential?) -> Void) { - + func urlSession( + _: URLSession, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: ( + URLSession.AuthChallengeDisposition, + URLCredential? + ) -> Void + ) { // For example, you may want to override this to accept some self-signed certs here. - if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, - Constants.selfSignedHosts.contains(challenge.protectionSpace.host) { + let methodIsServerTrust = challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust + if methodIsServerTrust, Constants.selfSignedHosts.contains(challenge.protectionSpace.host) { // Allow the self-signed cert. let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!) completionHandler(.useCredential, credential) @@ -28,7 +31,7 @@ class TestURLSessionDelegate: NSObject, URLSessionDelegate { } } - struct Constants { + enum Constants { // A list of hosts you allow self-signed certificates on. // You'd likely have your dev/test servers here. // Please don't put your production server here! diff --git a/MasKitTests/Nimble/ResultPredicates.swift b/MasKitTests/Nimble/ResultPredicates.swift index 5944de65c..c6c343ce2 100644 --- a/MasKitTests/Nimble/ResultPredicates.swift +++ b/MasKitTests/Nimble/ResultPredicates.swift @@ -6,14 +6,14 @@ // Copyright © 2018 mas-cli. All rights reserved. // -@testable import MasKit import Nimble +@testable import MasKit + /// Nimble predicate for result enum success case, no associated value -func beSuccess() -> Predicate> { - return Predicate.define("be ") { expression, message in - if let actual = try expression.evaluate(), - case .success = actual { +func beSuccess() -> Predicate> { + Predicate.define("be ") { expression, message in + if case .success = try expression.evaluate() { return PredicateResult(status: .matches, message: message) } return PredicateResult(status: .fail, message: message) @@ -21,10 +21,9 @@ func beSuccess() -> Predicate> { } /// Nimble predicate for result enum failure with associated error -func beFailure(test: @escaping (MASError) -> Void = { _ in }) -> Predicate> { - return Predicate.define("be ") { expression, message in - if let actual = try expression.evaluate(), - case let .failure(error) = actual { +func beFailure(test: @escaping (MASError) -> Void = { _ in }) -> Predicate> { + Predicate.define("be ") { expression, message in + if case let .failure(error) = try expression.evaluate() { test(error) return PredicateResult(status: .matches, message: message) } diff --git a/MasKitTests/OutputListener.swift b/MasKitTests/OutputListener.swift index 33784ee3b..7f4b8c894 100644 --- a/MasKitTests/OutputListener.swift +++ b/MasKitTests/OutputListener.swift @@ -65,20 +65,21 @@ extension OutputListener { // Restore stdout freopen("/dev/stdout", "a", stdout) - [inputPipe.fileHandleForReading, outputPipe.fileHandleForWriting].forEach { file in - file.closeFile() - } + [inputPipe.fileHandleForReading, outputPipe.fileHandleForWriting] + .forEach { file in + file.closeFile() + } } } extension OutputListener { /// File descriptor for stdout (aka STDOUT_FILENO) var stdoutFileDescriptor: Int32 { - return FileHandle.standardOutput.fileDescriptor + FileHandle.standardOutput.fileDescriptor } /// File descriptor for stderr (aka STDERR_FILENO) var stderrFileDescriptor: Int32 { - return FileHandle.standardError.fileDescriptor + FileHandle.standardError.fileDescriptor } } diff --git a/MasKitTests/OutputListenerSpec.swift b/MasKitTests/OutputListenerSpec.swift index 5fc73832b..dc00b24e7 100644 --- a/MasKitTests/OutputListenerSpec.swift +++ b/MasKitTests/OutputListenerSpec.swift @@ -31,9 +31,9 @@ class OutputListenerSpec: QuickSpec { output.openConsolePipe() let expectedOutput = """ - hi there + hi there - """ + """ print("hi there") diff --git a/MasKitTests/Strongify.swift b/MasKitTests/Strongify.swift index 07fd646a8..3e58d8fd6 100644 --- a/MasKitTests/Strongify.swift +++ b/MasKitTests/Strongify.swift @@ -8,12 +8,16 @@ // https://medium.com/@merowing_/stop-weak-strong-dance-in-swift-3aec6d3563d4 -func strongify(_ context: Context?, - closure: @escaping (Context, Arguments) -> Void) -> (Arguments) -> Void { - return { [weak context] arguments in +func strongify( + _ context: Context?, + closure: @escaping (Context, Arguments) -> Void +) -> (Arguments) -> Void { + let strongified = { [weak context] (arguments: Arguments) in guard let strongContext = context else { return } closure(strongContext, arguments) } + + return strongified } func strongify(_ context: Context?, closure: @escaping (Context) -> Void) { diff --git a/contrib/completion/mas-completion.bash b/contrib/completion/mas-completion.bash index 64750e506..d431b2990 100644 --- a/contrib/completion/mas-completion.bash +++ b/contrib/completion/mas-completion.bash @@ -1,18 +1,17 @@ #!/usr/bin/env bash -_mas() -{ - local cur prev words cword; - if declare -F _init_completions >/dev/null 2>&1; then - _init_completion - else - COMPREPLY=() - _get_comp_words_by_ref cur prev words cword - fi - if [[ $cword -eq 1 ]]; then - COMPREPLY=($( compgen -W "$(mas help | tail +3 | awk '{print $1}')" -- "$cur" )); - return 0 - fi +_mas() { + local cur prev words cword + if declare -F _init_completions >/dev/null 2>&1; then + _init_completion + else + COMPREPLY=() + _get_comp_words_by_ref cur prev words cword + fi + if [[ $cword -eq 1 ]]; then + COMPREPLY=($(compgen -W "$(mas help | tail +3 | awk '{print $1}')" -- "$cur")) + return 0 + fi } complete -F _mas mas diff --git a/docs/style.md b/docs/style.md index c94161747..6de3332a8 100644 --- a/docs/style.md +++ b/docs/style.md @@ -1,7 +1,8 @@ # All Files +- Use `script/format` to automatically fix a number of style violations. - Remove unnecessary whitespace from the end of lines. - - Use `git diff --check` to look for these before committing. + - 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). diff --git a/mas-cli.xcodeproj/project.pbxproj b/mas-cli.xcodeproj/project.pbxproj index 791a8003d..970968806 100644 --- a/mas-cli.xcodeproj/project.pbxproj +++ b/mas-cli.xcodeproj/project.pbxproj @@ -795,7 +795,7 @@ buildPhases = ( F8FB714D20F2B41400F56FDC /* Headers */, F8FB714E20F2B41400F56FDC /* Sources */, - B576FE3121E96E6C0016B39D /* 🚨 SwiftLint */, + B576FE3121E96E6C0016B39D /* 🚨 Lint */, F8FB714F20F2B41400F56FDC /* Frameworks */, F83213A72173F58B008BA8A0 /* Copy Frameworks */, F8FB715020F2B41400F56FDC /* Resources */, @@ -902,7 +902,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - B576FE3121E96E6C0016B39D /* 🚨 SwiftLint */ = { + B576FE3121E96E6C0016B39D /* 🚨 Lint */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -911,14 +911,14 @@ ); inputPaths = ( ); - name = "🚨 SwiftLint"; + name = "🚨 Lint"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "script/swiftlint_runscript\n"; + shellPath = /bin/zsh; + shellScript = "script/lint\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/script/archive b/script/archive index 49babcda4..4e079290f 100755 --- a/script/archive +++ b/script/archive @@ -14,9 +14,9 @@ zip -r mas.xcarchive.zip mas.xcarchive echo "==> ️🗜️ Compressing binary and framework" pushd mas.xcarchive/Products zip -r \ - mas-cli.zip \ - bin/mas \ - Frameworks/MasKit.framework + mas-cli.zip \ + bin/mas \ + Frameworks/MasKit.framework mv mas-cli.zip ../../ popd diff --git a/script/bootstrap b/script/bootstrap index 111b2815b..e14dd6227 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -3,8 +3,7 @@ # script/bootstrap # mas # -# Installs gem tools, installs Swift dependencies via CocoaPods and sorts -# the Xcode project file references. +# Installs development dependencies and builds project dependencies. # main() { @@ -12,10 +11,11 @@ main() { echo "==> 👢 Bootstrapping" - # Install ruby tools + # Install Ruby tools bundle install - # Install Mint and shellcheck + # Install Homebrew tools + rm -f Brewfile.lock.json brew bundle install --no-upgrade --verbose # Download and build project dependencies diff --git a/script/bottle b/script/bottle index 471a52449..e208e7a74 100755 --- a/script/bottle +++ b/script/bottle @@ -35,13 +35,13 @@ OLD_FILENAME="mas--${VERSION}.${OS_VERSIONS[0]}.bottle.tar.gz" brew tap --list-pinned | grep mas-cli/tap && brew tap-unpin mas-cli/tap # Uninstall if necessary -if brew ls --versions mas > /dev/null; then - brew uninstall mas +if brew ls --versions mas >/dev/null; then + brew uninstall mas fi # Uninstall if still found on path -if command -v mas > /dev/null; then - script/uninstall || true # ignore failure +if command -v mas >/dev/null; then + script/uninstall || true # ignore failure fi # Purge the Carthage cache to avoid this error from Homebrew sandboxing: @@ -64,7 +64,8 @@ SHA256=$(shasum --algorithm 256 "${OLD_FILENAME}" | cut -f 1 -d ' ' -) mkdir -p "$BOTTLE_DIR" # Start of bottle block -BOTTLE_BLOCK=$(cat <<-EOF +BOTTLE_BLOCK=$( + cat <<-EOF bottle do root_url "$ROOT_URL" EOF @@ -77,20 +78,22 @@ EOF # Fix filename for os in ${OS_VERSIONS[*]}; do - new_filename="mas-${VERSION}.${os}.bottle.tar.gz" - cp -v "${OLD_FILENAME}" "${BOTTLE_DIR}/${new_filename}" + new_filename="mas-${VERSION}.${os}.bottle.tar.gz" + cp -v "${OLD_FILENAME}" "${BOTTLE_DIR}/${new_filename}" - # Append each os - # BOTTLE_BLOCK="$(printf "${BOTTLE_BLOCK}\n sha256 cellar: :any, %-15s %s" "${os}:" "${SHA256}")" - BOTTLE_BLOCK="$BOTTLE_BLOCK"$(cat <<-EOF + # Append each os + # BOTTLE_BLOCK="$(printf "${BOTTLE_BLOCK}\n sha256 cellar: :any, %-15s %s" "${os}:" "${SHA256}")" + BOTTLE_BLOCK="$BOTTLE_BLOCK"$( + cat <<-EOF sha256 cellar: :any, $os: "$SHA256" EOF -) + ) done # End of bottle block -BOTTLE_BLOCK=$(cat <<-EOF +BOTTLE_BLOCK=$( + cat <<-EOF end EOF diff --git a/script/brew_formula_update b/script/brew_formula_update index d76516b19..2c3d56954 100755 --- a/script/brew_formula_update +++ b/script/brew_formula_update @@ -12,24 +12,24 @@ BREW_CORE_PATH="$(brew --prefix)/Homebrew/Library/Taps/homebrew/homebrew-core/Formula" function usage { - echo "Usage: brew_formula_bump [v1.0] [sha1_hash]" - echo "- version will be inferred using version script if not provided" - echo "- sha will be inferred from the current commit if not provided" - exit 1 + echo "Usage: brew_formula_bump [v1.0] [sha1_hash]" + echo "- version will be inferred using version script if not provided" + echo "- sha will be inferred from the current commit if not provided" + exit 1 } # arg 1 - version tag if test -n "$1"; then - VERSION="$1" + VERSION="$1" else - VERSION="v$(script/version)" + VERSION="v$(script/version)" fi # arg 2 - revision (commit hash) if test -n "$2"; then - REVISION="$2" + REVISION="$2" else - REVISION=$(git rev-parse "$VERSION") + REVISION=$(git rev-parse "$VERSION") fi # Purge the Carthage cache to avoid this error from Homebrew sandboxing: @@ -44,39 +44,39 @@ diff "Homebrew/mas.rb" "$BREW_CORE_PATH/mas.rb" status=$? formula_revisions=0 if test $status -ne 0; then - echo "There are changes in the local formula (Homebrew/mas.rb) that haven't been released yet." - #exit $status - formula_revisions=1 + echo "There are changes in the local formula (Homebrew/mas.rb) that haven't been released yet." + #exit $status + formula_revisions=1 fi echo "==> 🧪 Updating homebrew-core formula mas ($VERSION, $REVISION)" echo "Validating formula" brew bump-formula-pr \ - --tag="$VERSION" \ - --revision="$REVISION" \ - --strict \ - mas + --tag="$VERSION" \ + --revision="$REVISION" \ + --strict \ + mas # brew exit status status=$? if test $status -ne 0; then - echo "Formula did not validate using 'brew bump-formula-pr'" - exit $status + echo "Formula did not validate using 'brew bump-formula-pr'" + exit $status fi pushd "$BREW_CORE_PATH" || exit 2 echo "Updating formula" if test $formula_revisions -eq 1; then - # Options to - dry_run="--dry-run --write" + # Options to + dry_run="--dry-run --write" fi brew bump-formula-pr \ - --tag="$VERSION" \ - --revision="$REVISION" \ - --strict \ - --verbose \ - "$dry_run" \ - mas + --tag="$VERSION" \ + --revision="$REVISION" \ + --strict \ + --verbose \ + "$dry_run" \ + mas diff --git a/script/build b/script/build index 188c69fa0..a0b3cba41 100755 --- a/script/build +++ b/script/build @@ -42,29 +42,29 @@ main() { build() { echo "==> 🏗️ Building mas ($VERSION)" - set -o pipefail && \ - xcodebuild \ - -workspace "$WORKSPACE" \ - -scheme "$SCHEME" \ - -configuration "$CONFIG" \ - OBJROOT="$BUILD_DIR" \ - SYMROOT="$BUILD_DIR" \ - build \ - | bundle exec xcpretty --color + set -o pipefail && + xcodebuild \ + -workspace "$WORKSPACE" \ + -scheme "$SCHEME" \ + -configuration "$CONFIG" \ + OBJROOT="$BUILD_DIR" \ + SYMROOT="$BUILD_DIR" \ + build | + bundle exec xcpretty --color } archive() { echo "==> 📦 Archiving mas ($VERSION)" - set -o pipefail && \ - xcodebuild \ - -workspace "$WORKSPACE" \ - -scheme "$SCHEME" \ - -configuration "$CONFIG" \ - -archivePath "$BUILD_DIR/mas.xcarchive" \ - OBJROOT="$BUILD_DIR" \ - SYMROOT="$BUILD_DIR" \ - archive \ - | bundle exec xcpretty --color + set -o pipefail && + xcodebuild \ + -workspace "$WORKSPACE" \ + -scheme "$SCHEME" \ + -configuration "$CONFIG" \ + -archivePath "$BUILD_DIR/mas.xcarchive" \ + OBJROOT="$BUILD_DIR" \ + SYMROOT="$BUILD_DIR" \ + archive | + bundle exec xcpretty --color } main diff --git a/script/format b/script/format new file mode 100755 index 000000000..02969fd24 --- /dev/null +++ b/script/format @@ -0,0 +1,35 @@ +#!/bin/bash -e +# +# script/format +# mas +# +# Linting checks for development and CI. +# +# Automatically formats and fixes style violations using various tools. +# Additionally runs `lint` to report any remaining style violations. +# +# Please keep in sync with script/lint. +# + +echo "==> 🚨 Formatting mas" + +for LINTER in shfmt swift-format swiftformat swiftlint; do + if [[ ! -x "$(command -v ${LINTER})" ]]; then + echo "error: ${LINTER} is not installed. Run 'script/bootstrap' or 'brew install ${LINTER}'." + exit 1 + fi +done + +echo +echo "--> 🕊️ Swift" +swiftformat . +for SOURCE in mas MasKit MasKitTests; do + swift-format format --in-place --configuration .swift-format --recursive ${SOURCE} +done +swiftlint lint --fix --strict + +echo +echo "--> 📜 Bash" +shfmt -i 2 -l -w contrib/ script/ + +script/lint diff --git a/script/install b/script/install index f425ad6bd..3623c4e8b 100755 --- a/script/install +++ b/script/install @@ -26,30 +26,30 @@ INSTALL_TEMPORARY_FOLDER=${DSTROOT:-build/distributions} # Final destination. # Override default prefix path with optional 1st arg if test -n "$1"; then - PREFIX="$1" + PREFIX="$1" elif [[ $(uname -m) == 'arm64' ]]; then - PREFIX=/opt/homebrew + PREFIX=/opt/homebrew else - PREFIX=/usr/local + PREFIX=/usr/local fi echo "==> 📲 Installing mas ($VERSION) to $PREFIX" xcodebuild \ - -project "$PROJECT" \ - -scheme "$SCHEME" \ - -configuration "$CONFIG" \ - OBJROOT="$BUILD_DIR" \ - SYMROOT="$BUILD_DIR" \ - DSTROOT="$INSTALL_TEMPORARY_FOLDER" \ - install + -project "$PROJECT" \ + -scheme "$SCHEME" \ + -configuration "$CONFIG" \ + OBJROOT="$BUILD_DIR" \ + SYMROOT="$BUILD_DIR" \ + DSTROOT="$INSTALL_TEMPORARY_FOLDER" \ + install # Deep copy MasKit.framework ditto -v \ - "$INSTALL_TEMPORARY_FOLDER/Frameworks" \ - "$PREFIX/Frameworks" + "$INSTALL_TEMPORARY_FOLDER/Frameworks" \ + "$PREFIX/Frameworks" # Copy mas binary ditto -v \ - "$INSTALL_TEMPORARY_FOLDER/bin" \ - "$PREFIX/bin" + "$INSTALL_TEMPORARY_FOLDER/bin" \ + "$PREFIX/bin" diff --git a/script/lint b/script/lint index c3026d68e..82357e095 100755 --- a/script/lint +++ b/script/lint @@ -3,18 +3,33 @@ # script/lint # mas # -# Linting checks for CI "Lint" stage. +# Linting checks for development and CI. +# +# Reports style violations without making any modifications to the code. +# +# Please keep in sync with script/format. # echo "==> 🚨 Linting mas" +for LINTER in git swift-format swiftformat swiftlint; do + if [[ ! -x "$(command -v ${LINTER})" ]]; then + echo "error: ${LINTER} is not installed. Run 'script/bootstrap' or 'brew install ${LINTER}'." + exit 1 + fi +done + +echo "--> 🌳 Git" +git diff --check + echo echo "--> 🕊️ Swift" +swiftformat --lint . +for SOURCE in mas MasKit MasKitTests; do + swift-format lint --configuration .swift-format --recursive ${SOURCE} +done swiftlint lint --strict echo echo "--> 📜 Bash" -shopt -s extglob -# Only lint files with no extension (skipping .pl) -shellcheck --shell=bash script/!(*.*) -shopt -u extglob +shfmt -d -i 2 -l -w contrib/ script/ diff --git a/script/package b/script/package index 43a19f48c..b98961463 100755 --- a/script/package +++ b/script/package @@ -33,19 +33,19 @@ echo "==> 📦 Assemble an installer package" # - installer: The install failed (The Installer encountered an error that caused the installation to fail. Contact the software manufacturer for assistance.) # - GUI warning: This package is incompatible with this version of macOS and may fail to install. (may damage your system... ) pkgbuild \ - --identifier "$IDENTIFIER" \ - --install-location "/usr/local" \ - --version "$VERSION" \ - --root "$INSTALL_TEMPORARY_FOLDER" \ - --component-plist "$COMPONENTS_PLIST" \ - "$COMPONENT_PACKAGE" + --identifier "$IDENTIFIER" \ + --install-location "/usr/local" \ + --version "$VERSION" \ + --root "$INSTALL_TEMPORARY_FOLDER" \ + --component-plist "$COMPONENTS_PLIST" \ + "$COMPONENT_PACKAGE" # Build distribution package (aka "product archive"). Not sure why, but this is how Carthage does it. # https://github.com/Carthage/Carthage/blob/master/Makefile#L87 productbuild \ - --distribution "$DISTRIBUTION_PLIST" \ - --package-path "$BUILD_DIR" \ - "$DISTRIBUTION_PACKAGE" + --distribution "$DISTRIBUTION_PLIST" \ + --package-path "$BUILD_DIR" \ + "$DISTRIBUTION_PACKAGE" echo "==> 🔢 Files Hashes" shasum -a 256 "$DISTRIBUTION_PACKAGE" diff --git a/script/package_install b/script/package_install index 1d6909669..61def1ae6 100755 --- a/script/package_install +++ b/script/package_install @@ -11,8 +11,8 @@ IDENTIFIER=com.mphys.mas-cli echo "==> 📲 Installing mas" sudo installer \ - -pkg build/mas.pkg \ - -target / + -pkg build/mas.pkg \ + -target / pkgutil --pkg-info "$IDENTIFIER" diff --git a/script/release b/script/release index cc1245e1e..ea5ca79bf 100755 --- a/script/release +++ b/script/release @@ -56,7 +56,8 @@ prompt() { write_version_number() { local value - value=$1; shift + value=$1 + shift write_plist "CFBundleShortVersionString" "${value}" } @@ -66,15 +67,18 @@ read_version_number() { read_plist() { local key - key=$1; shift + key=$1 + shift /usr/libexec/PlistBuddy -c "Print :${key}" "${PLIST_FILE}" } write_plist() { local key local value - key=$1; shift - value=$1; shift + key=$1 + shift + value=$1 + shift echo "Setting ${key} to ${value}" >&2 /usr/libexec/PlistBuddy -c "Set :${key} ${value}" "${PLIST_FILE}" } diff --git a/script/sort b/script/sort deleted file mode 100755 index 1b07acee4..000000000 --- a/script/sort +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# -# script/sort -# mas -# -# Invokes the sort.pl script on the Xcode project to organize project references. -# - -main() { - echo "==> 📶 Sorting project files" - sort_project -} - -sort_project() { - perl script/sort.pl mas-cli.xcodeproj/project.pbxproj -} - -main diff --git a/script/sort.pl b/script/sort.pl deleted file mode 100644 index 58473c269..000000000 --- a/script/sort.pl +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/perl -w -# -# script/sort.pl -# mas -# -# Sorts "children" and "files" sections in Xcode project.pbxproj files. -# - -# Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of -# its contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# Script to sort "children" and "files" sections in Xcode project.pbxproj files - -use strict; - -use File::Basename; -use File::Spec; -use File::Temp qw(tempfile); -use Getopt::Long; - -sub sortChildrenByFileName($$); -sub sortFilesByFileName($$); - -# Files (or products) without extensions -my %isFile = map { $_ => 1 } qw( - create_hash_table - jsc - minidom - testapi - testjsglue -); - -my $printWarnings = 1; -my $showHelp; - -my $getOptionsResult = GetOptions( - 'h|help' => \$showHelp, - 'w|warnings!' => \$printWarnings, -); - -if (scalar(@ARGV) == 0 && !$showHelp) { - print STDERR "ERROR: No Xcode project files (project.pbxproj) listed on command-line.\n"; - undef $getOptionsResult; -} - -if (!$getOptionsResult || $showHelp) { - print STDERR <<__END__; -Usage: @{[ basename($0) ]} [options] path/to/project.pbxproj [path/to/project.pbxproj ...] - -h|--help show this help message - -w|--[no-]warnings show or suppress warnings (default: show warnings) -__END__ - exit 1; -} - -for my $projectFile (@ARGV) { - if (basename($projectFile) =~ /\.xcodeproj$/) { - $projectFile = File::Spec->catfile($projectFile, "project.pbxproj"); - } - - if (basename($projectFile) ne "project.pbxproj") { - print STDERR "WARNING: Not an Xcode project file: $projectFile\n" if $printWarnings; - next; - } - - # Grab the mainGroup for the project file - my $mainGroup = ""; - open(IN, "< $projectFile") || die "Could not open $projectFile: $!"; - while (my $line = ) { - $mainGroup = $2 if $line =~ m#^(\s*)mainGroup = ([0-9A-F]{24} /\* .+ \*/);$#; - } - close(IN); - - my ($OUT, $tempFileName) = tempfile( - basename($projectFile) . "-XXXXXXXX", - DIR => dirname($projectFile), - UNLINK => 0, - ); - - # Clean up temp file in case of die() - $SIG{__DIE__} = sub { - close(IN); - close($OUT); - unlink($tempFileName); - }; - - my @lastTwo = (); - open(IN, "< $projectFile") || die "Could not open $projectFile: $!"; - while (my $line = ) { - if ($line =~ /^(\s*)files = \(\s*$/) { - print $OUT $line; - my $endMarker = $1 . ");"; - my @files; - while (my $fileLine = ) { - if ($fileLine =~ /^\Q$endMarker\E\s*$/) { - $endMarker = $fileLine; - last; - } - push @files, $fileLine; - } - print $OUT sort sortFilesByFileName @files; - print $OUT $endMarker; - } elsif ($line =~ /^(\s*)children = \(\s*$/) { - print $OUT $line; - my $endMarker = $1 . ");"; - my @children; - while (my $childLine = ) { - if ($childLine =~ /^\Q$endMarker\E\s*$/) { - $endMarker = $childLine; - last; - } - push @children, $childLine; - } - if ($lastTwo[0] =~ m#^\s+\Q$mainGroup\E = \{$#) { - # Don't sort mainGroup - print $OUT @children; - } else { - print $OUT sort sortChildrenByFileName @children; - } - print $OUT $endMarker; - } else { - print $OUT $line; - } - - push @lastTwo, $line; - shift @lastTwo if scalar(@lastTwo) > 2; - } - close(IN); - close($OUT); - - unlink($projectFile) || die "Could not delete $projectFile: $!"; - rename($tempFileName, $projectFile) || die "Could not rename $tempFileName to $projectFile: $!"; -} - -exit 0; - -sub sortChildrenByFileName($$) -{ - my ($a, $b) = @_; - my $aFileName = $1 if $a =~ /^\s*[A-Z0-9]{24} \/\* (.+) \*\/,$/; - my $bFileName = $1 if $b =~ /^\s*[A-Z0-9]{24} \/\* (.+) \*\/,$/; - my $aSuffix = $1 if $aFileName =~ m/\.([^.]+)$/; - my $bSuffix = $1 if $bFileName =~ m/\.([^.]+)$/; - if ((!$aSuffix && !$isFile{$aFileName} && $bSuffix) || ($aSuffix && !$bSuffix && !$isFile{$bFileName})) { - return !$aSuffix ? -1 : 1; - } - return lc($aFileName) cmp lc($bFileName); -} - -sub sortFilesByFileName($$) -{ - my ($a, $b) = @_; - my $aFileName = $1 if $a =~ /^\s*[A-Z0-9]{24} \/\* (.+) in /; - my $bFileName = $1 if $b =~ /^\s*[A-Z0-9]{24} \/\* (.+) in /; - return lc($aFileName) cmp lc($bFileName); -} diff --git a/script/swiftlint_runscript b/script/swiftlint_runscript deleted file mode 100755 index 502874c6b..000000000 --- a/script/swiftlint_runscript +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -e -# -# script/swiftlint_runscript -# mas-cli -# -# Invokes SwiftLint from inside an Xcode run script build phase. Minimal output. -# https://github.com/realm/SwiftLint -# - -if test -n "$JENKINS_URL" -then - echo "Skipping SwiftLint run script on CI, will run in Lint stage." - exit -fi - -# 😕 When run from Xcode, the command command doesn't support these options -# command --quiet --search swiftlint -# : command: --: invalid option -# : command: usage: command [-pVv] command [arg ...] - -if command -v swiftlint > /dev/null; then - swiftlint autocorrect - swiftlint lint --quiet -else - echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" -fi diff --git a/script/test b/script/test index e5777ef71..971de12a6 100755 --- a/script/test +++ b/script/test @@ -26,11 +26,11 @@ CONFIG="Debug" echo "==> ✅ Testing" -set -o pipefail && \ - xcodebuild -project "$PROJECT" \ - -scheme "$SCHEME" \ - -configuration "$CONFIG" \ - OBJROOT="$BUILD_DIR" \ - SYMROOT="$BUILD_DIR" \ - test \ - | bundle exec xcpretty --color +set -o pipefail && + xcodebuild -project "$PROJECT" \ + -scheme "$SCHEME" \ + -configuration "$CONFIG" \ + OBJROOT="$BUILD_DIR" \ + SYMROOT="$BUILD_DIR" \ + test | + bundle exec xcpretty --color diff --git a/script/uninstall b/script/uninstall index d7cb5c7b0..4d2dbc821 100755 --- a/script/uninstall +++ b/script/uninstall @@ -8,11 +8,11 @@ # Override default prefix path with optional 1st arg if test -n "$1"; then - PREFIX="$1" + PREFIX="$1" elif [[ $(uname -m) == 'arm64' ]]; then - PREFIX=/opt/homebrew + PREFIX=/opt/homebrew else - PREFIX=/usr/local + PREFIX=/usr/local fi echo "==> 🔥 Uninstalling mas from $PREFIX" diff --git a/script/update_headers b/script/update_headers index 19b0661e1..9fa816848 100755 --- a/script/update_headers +++ b/script/update_headers @@ -22,7 +22,8 @@ check_class_dump() { } extract_private_framework_headers() { - local framework_name="$1"; shift + local framework_name="$1" + shift local directory="PrivateFrameworks/${framework_name}" mkdir -p "$directory" class-dump -Ho "$directory" "/System/Library/PrivateFrameworks/${framework_name}.framework"