mirror of
https://github.com/mas-cli/mas
synced 2024-11-21 19:23:01 +00:00
Merge branch 'master' into patch-2
This commit is contained in:
commit
ae913cb747
32 changed files with 709 additions and 257 deletions
6
.bundle/config
Normal file
6
.bundle/config
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
BUNDLE_CLEAN: "true"
|
||||
BUNDLE_BIN: "bin"
|
||||
BUNDLE_JOBS: "8"
|
||||
BUNDLE_DISABLE_SHARED_GEMS: "true"
|
||||
BUNDLE_PATH: ".rubygems"
|
29
.gitignore
vendored
29
.gitignore
vendored
|
@ -28,6 +28,10 @@ xcuserdata
|
|||
*.hmap
|
||||
*.ipa
|
||||
|
||||
# Bundler
|
||||
/.rubygems/
|
||||
/bin/
|
||||
|
||||
# CocoaPods
|
||||
#
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
|
@ -43,6 +47,25 @@ xcuserdata
|
|||
|
||||
Carthage/Build
|
||||
|
||||
# CocoaSeeds
|
||||
!Seeds/Seedfile.lock
|
||||
Seeds/**/.git*
|
||||
Seeds/**/.swift-version
|
||||
Seeds/**/.travis.yml
|
||||
Seeds/**/*.md
|
||||
Seeds/**/Cartfile*
|
||||
Seeds/**/Carthage/
|
||||
Seeds/**/LICENSE*
|
||||
Seeds/**/Package.*
|
||||
Seeds/**/*Info.plist
|
||||
Seeds/**/*.podspec
|
||||
Seeds/**/*.xc*
|
||||
Seeds/**/Tests/
|
||||
# Commandant
|
||||
!Seeds/Commandant/Source/*.swift
|
||||
# Result
|
||||
!Seeds/Result/Result/*.swift
|
||||
|
||||
## https://github.com/github/gitignore/blob/master/Global/OSX.gitignore
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
|
@ -70,7 +93,5 @@ Network Trash Folder
|
|||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
mas-cli.zip
|
||||
mas-cli.dSYM.zip
|
||||
mas.xcarchive.zip
|
||||
|
||||
# Build artifacts
|
||||
*.zip
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
language: objective-c
|
||||
xcode_sdk: macosx10.11
|
||||
osx_image: xcode8.2
|
||||
xcode_sdk: macosx10.13
|
||||
osx_image: xcode9.2
|
||||
|
||||
env:
|
||||
global:
|
||||
|
@ -8,6 +8,9 @@ env:
|
|||
- LC_ALL=en_US.UTF-8
|
||||
- LANGUAGE=en_US.UTF-8
|
||||
|
||||
install:
|
||||
- bundle install
|
||||
|
||||
script:
|
||||
- script/build
|
||||
|
||||
|
|
71
CHANGELOG.md
Normal file
71
CHANGELOG.md
Normal file
|
@ -0,0 +1,71 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.3.1] Better Errors - 2016-09-25
|
||||
- Descriptive error messages instead of exit codes
|
||||
- Fixed nullability issue with `list` command
|
||||
- Simpler upgrade checking
|
||||
|
||||
## [v1.3.0] Multiple app install - 2016-09-14
|
||||
- Fix install of Free apps (#19)
|
||||
- Install / Upgrade multiple apps at once
|
||||
- Skip Install if the app is already installed
|
||||
|
||||
## [v1.2.2] Secure Password entry - 2016-09-14
|
||||
- Support reading password from STDIN
|
||||
- Fix building with Swift 2.3/Xcode 8
|
||||
|
||||
## [v1.2.1] - 2016-09-13
|
||||
- Support reading password from STDIN
|
||||
- Fix building with Swift 2.3/Xcode 8
|
||||
|
||||
## [v1.2.0] Search - 2016-04-16
|
||||
- `search` command
|
||||
- Fix `mas list` illegal instruction (#16)
|
||||
|
||||
## [v1.1.3] - 2016-02-21
|
||||
- Fix Illegal Instruction: 4 error (#10)
|
||||
|
||||
## [v1.1.2] Upload dSYM correctly - 2016-02-21
|
||||
- Move the dSYM to the xcarchive
|
||||
|
||||
## [v1.1.1] Upload dSYM - 2016-02-21
|
||||
- Upload dSYM from Travis release
|
||||
|
||||
## [v1.1.0] Sign In - 2016-02-13
|
||||
- Added `signin` command (#3)
|
||||
- Added `signout` command
|
||||
|
||||
## [v1.0.2] Upgrade all - 2015-12-30
|
||||
### Features
|
||||
- Added `upgrade` command (#1)
|
||||
|
||||
### Fixes
|
||||
- Updated to latest version of Commandant
|
||||
- Broken `install` command after updating Commandant
|
||||
|
||||
## [v1.0.1] - 2015-12-30
|
||||
- Bump version to 1.0.1
|
||||
|
||||
## [v1.0.0] - 2015-09-20
|
||||
- Initial Release
|
||||
|
||||
[Unreleased]: https://github.com/mas-cli/mas/compare/v1.3.1...HEAD
|
||||
[v1.3.1]: https://github.com/mas-cli/mas/compare/v1.3.0...v1.3.1
|
||||
[v1.3.0]: https://github.com/mas-cli/mas/compare/v1.2.2...v1.3.0
|
||||
[v1.2.2]: https://github.com/mas-cli/mas/compare/v1.2.1...v1.2.2
|
||||
[v1.2.1]: https://github.com/mas-cli/mas/compare/v1.2.0...v1.2.1
|
||||
[v1.2.0]: https://github.com/mas-cli/mas/compare/v1.1.2...v1.2.0
|
||||
[v1.1.3]: https://github.com/mas-cli/mas/compare/v1.1.2...v1.1.3
|
||||
[v1.1.2]: https://github.com/mas-cli/mas/compare/v1.1.1...v1.1.2
|
||||
[v1.1.1]: https://github.com/mas-cli/mas/compare/v1.1.0...v1.1.1
|
||||
[v1.1.0]: https://github.com/mas-cli/mas/compare/v1.0.2...v1.1.0
|
||||
[v1.0.2]: https://github.com/mas-cli/mas/compare/v1.0.1...v1.0.2
|
||||
[v1.0.1]: https://github.com/mas-cli/mas/compare/v1.0.0...v1.0.1
|
||||
[v1.0.0]: https://github.com/mas-cli/mas/compare/7e0e18d8335cf5eee6a162ea7981ad02ca4294b2...v1.0.0
|
39
Gemfile.lock
39
Gemfile.lock
|
@ -1,30 +1,21 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (5.0.0.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
claide (1.0.0)
|
||||
cocoaseeds (0.8.0)
|
||||
colorize (~> 0.7.0)
|
||||
CFPropertyList (2.3.6)
|
||||
claide (1.0.2)
|
||||
cocoaseeds (0.8.3)
|
||||
colored2 (~> 3.1)
|
||||
xcodeproj (>= 0.28)
|
||||
colored (1.2)
|
||||
colorize (0.7.7)
|
||||
concurrent-ruby (1.0.2)
|
||||
i18n (0.7.0)
|
||||
minitest (5.9.0)
|
||||
rouge (1.11.1)
|
||||
thread_safe (0.3.5)
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
xcodeproj (1.3.1)
|
||||
activesupport (>= 3)
|
||||
claide (>= 1.0.0, < 2.0)
|
||||
colored (~> 1.2)
|
||||
xcpretty (0.2.2)
|
||||
rouge (~> 1.8)
|
||||
colored2 (3.1.2)
|
||||
nanaimo (0.2.3)
|
||||
rouge (2.0.7)
|
||||
xcodeproj (1.5.4)
|
||||
CFPropertyList (~> 2.3.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.2.3)
|
||||
xcpretty (0.2.8)
|
||||
rouge (~> 2.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
@ -34,4 +25,4 @@ DEPENDENCIES
|
|||
xcpretty
|
||||
|
||||
BUNDLED WITH
|
||||
1.12.5
|
||||
1.16.1
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
# mas-cli
|
||||
|
||||
A simple command line interface for the Mac App Store. Designed for scripting
|
||||
and automation.
|
||||
A simple command line interface for the Mac App Store. Designed for scripting and automation.
|
||||
|
||||
[![Build Status](https://travis-ci.org/mas-cli/mas.svg?branch=master)](https://travis-ci.org/mas-cli/mas)
|
||||
|
||||
## Install
|
||||
|
||||
|
|
5
Seedfile
5
Seedfile
|
@ -1,2 +1,3 @@
|
|||
github "carthage/Commandant", "0.11.1", :files => "Sources/Commandant/*.swift"
|
||||
github "antitypical/Result", "3.0.0", :files => "Result/*.swift"
|
||||
# Commandant 0.12.0 doesn't have Swift 4 fixes yet
|
||||
github "carthage/Commandant", "master", files: "Sources/Commandant/*.swift"
|
||||
github "antitypical/Result", "3.2.4", files: "Result/*.swift"
|
||||
|
|
|
@ -30,66 +30,70 @@ public struct Argument<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Evaluates the given argument in the given mode.
|
||||
///
|
||||
/// If parsing command line arguments, and no value was specified on the command
|
||||
/// line, the argument's `defaultValue` is used.
|
||||
public func <| <T: ArgumentProtocol, ClientError>(mode: CommandMode, argument: Argument<T>) -> Result<T, CommandantError<ClientError>> {
|
||||
switch mode {
|
||||
case let .arguments(arguments):
|
||||
guard let stringValue = arguments.consumePositionalArgument() else {
|
||||
if let defaultValue = argument.defaultValue {
|
||||
return .success(defaultValue)
|
||||
} else {
|
||||
return .failure(missingArgumentError(argument.usage))
|
||||
// MARK: - Operators
|
||||
|
||||
extension CommandMode {
|
||||
/// Evaluates the given argument in the given mode.
|
||||
///
|
||||
/// If parsing command line arguments, and no value was specified on the command
|
||||
/// line, the argument's `defaultValue` is used.
|
||||
public static func <| <T: ArgumentProtocol, ClientError>(mode: CommandMode, argument: Argument<T>) -> Result<T, CommandantError<ClientError>> {
|
||||
switch mode {
|
||||
case let .arguments(arguments):
|
||||
guard let stringValue = arguments.consumePositionalArgument() else {
|
||||
if let defaultValue = argument.defaultValue {
|
||||
return .success(defaultValue)
|
||||
} else {
|
||||
return .failure(missingArgumentError(argument.usage))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let value = T.from(string: stringValue) {
|
||||
return .success(value)
|
||||
} else {
|
||||
return .failure(argument.invalidUsageError(stringValue))
|
||||
}
|
||||
if let value = T.from(string: stringValue) {
|
||||
return .success(value)
|
||||
} else {
|
||||
return .failure(argument.invalidUsageError(stringValue))
|
||||
}
|
||||
|
||||
case .usage:
|
||||
return .failure(informativeUsageError(argument))
|
||||
case .usage:
|
||||
return .failure(informativeUsageError(argument))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluates the given argument list in the given mode.
|
||||
///
|
||||
/// If parsing command line arguments, and no value was specified on the command
|
||||
/// line, the argument's `defaultValue` is used.
|
||||
public func <| <T: ArgumentProtocol, ClientError>(mode: CommandMode, argument: Argument<[T]>) -> Result<[T], CommandantError<ClientError>> {
|
||||
switch mode {
|
||||
case let .arguments(arguments):
|
||||
guard let firstValue = arguments.consumePositionalArgument() else {
|
||||
if let defaultValue = argument.defaultValue {
|
||||
return .success(defaultValue)
|
||||
} else {
|
||||
return .failure(missingArgumentError(argument.usage))
|
||||
/// Evaluates the given argument list in the given mode.
|
||||
///
|
||||
/// If parsing command line arguments, and no value was specified on the command
|
||||
/// line, the argument's `defaultValue` is used.
|
||||
public static func <| <T: ArgumentProtocol, ClientError>(mode: CommandMode, argument: Argument<[T]>) -> Result<[T], CommandantError<ClientError>> {
|
||||
switch mode {
|
||||
case let .arguments(arguments):
|
||||
guard let firstValue = arguments.consumePositionalArgument() else {
|
||||
if let defaultValue = argument.defaultValue {
|
||||
return .success(defaultValue)
|
||||
} else {
|
||||
return .failure(missingArgumentError(argument.usage))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var values = [T]()
|
||||
var values = [T]()
|
||||
|
||||
guard let value = T.from(string: firstValue) else {
|
||||
return .failure(argument.invalidUsageError(firstValue))
|
||||
}
|
||||
|
||||
values.append(value)
|
||||
|
||||
while let nextValue = arguments.consumePositionalArgument() {
|
||||
guard let value = T.from(string: nextValue) else {
|
||||
return .failure(argument.invalidUsageError(nextValue))
|
||||
guard let value = T.from(string: firstValue) else {
|
||||
return .failure(argument.invalidUsageError(firstValue))
|
||||
}
|
||||
|
||||
values.append(value)
|
||||
|
||||
while let nextValue = arguments.consumePositionalArgument() {
|
||||
guard let value = T.from(string: nextValue) else {
|
||||
return .failure(argument.invalidUsageError(nextValue))
|
||||
}
|
||||
|
||||
values.append(value)
|
||||
}
|
||||
|
||||
return .success(values)
|
||||
|
||||
case .usage:
|
||||
return .failure(informativeUsageError(argument))
|
||||
}
|
||||
|
||||
return .success(values)
|
||||
|
||||
case .usage:
|
||||
return .failure(informativeUsageError(argument))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ private enum RawArgument: Equatable {
|
|||
case value(String)
|
||||
|
||||
/// One or more flag arguments (e.g 'r' and 'f' for `-rf`)
|
||||
case flag(Set<Character>)
|
||||
case flag(OrderedSet<Character>)
|
||||
}
|
||||
|
||||
private func ==(lhs: RawArgument, rhs: RawArgument) -> Bool {
|
||||
|
@ -67,11 +67,11 @@ public final class ArgumentParser {
|
|||
rawArguments.append(contentsOf: options.map { arg in
|
||||
if arg.hasPrefix("-") {
|
||||
// Do we have `--{key}` or `-{flags}`.
|
||||
let opt = arg.characters.dropFirst()
|
||||
let opt = arg.dropFirst()
|
||||
if opt.first == "-" {
|
||||
return .key(String(opt.dropFirst()))
|
||||
} else {
|
||||
return .flag(Set(opt))
|
||||
return .flag(OrderedSet(opt))
|
||||
}
|
||||
} else {
|
||||
return .value(arg)
|
||||
|
|
|
@ -31,11 +31,14 @@ extension String: ArgumentProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - migration support
|
||||
@available(*, unavailable, renamed: "ArgumentProtocol")
|
||||
public typealias ArgumentType = ArgumentProtocol
|
||||
|
||||
extension ArgumentProtocol {
|
||||
@available(*, unavailable, renamed: "from(string:)")
|
||||
static func fromString(_ string: String) -> Self? { return nil }
|
||||
extension RawRepresentable where RawValue: StringProtocol, Self: ArgumentProtocol {
|
||||
public static func from(string: String) -> Self? {
|
||||
return RawValue(string).flatMap(Self.init(rawValue:))
|
||||
}
|
||||
}
|
||||
|
||||
extension RawRepresentable where RawValue: FixedWidthInteger, Self: ArgumentProtocol {
|
||||
public static func from(string: String) -> Self? {
|
||||
return RawValue(string).flatMap(Self.init(rawValue:))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,12 +85,19 @@ public final class CommandRegistry<ClientError: Error> {
|
|||
|
||||
public init() {}
|
||||
|
||||
/// Registers the given command, making it available to run.
|
||||
/// Registers the given commands, making those available to run.
|
||||
///
|
||||
/// If another command was already registered with the same `verb`, it will
|
||||
/// be overwritten.
|
||||
public func register<C: CommandProtocol>(_ command: C) where C.ClientError == ClientError, C.Options.ClientError == ClientError {
|
||||
commandsByVerb[command.verb] = CommandWrapper(command)
|
||||
/// If another commands were already registered with the same `verb`s, those
|
||||
/// will be overwritten.
|
||||
@discardableResult
|
||||
public func register<C: CommandProtocol>(_ commands: C...)
|
||||
-> CommandRegistry
|
||||
where C.ClientError == ClientError, C.Options.ClientError == ClientError
|
||||
{
|
||||
for command in commands {
|
||||
commandsByVerb[command.verb] = CommandWrapper(command)
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
/// Runs the command corresponding to the given verb, passing it the given
|
||||
|
@ -155,12 +162,14 @@ extension CommandRegistry {
|
|||
// Extract the executable name.
|
||||
let executableName = arguments.remove(at: 0)
|
||||
|
||||
let verb = arguments.first ?? defaultVerb
|
||||
if arguments.count > 0 {
|
||||
// use the default verb even if we have other arguments
|
||||
var verb = defaultVerb
|
||||
if let argument = arguments.first, !argument.hasPrefix("-") {
|
||||
verb = argument
|
||||
// Remove the command name.
|
||||
arguments.remove(at: 0)
|
||||
}
|
||||
|
||||
|
||||
switch run(command: verb, arguments: arguments) {
|
||||
case .success?:
|
||||
exit(EXIT_SUCCESS)
|
||||
|
@ -211,14 +220,3 @@ extension CommandRegistry {
|
|||
return launchTask("/usr/bin/env", arguments: [ subcommand ] + arguments)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - migration support
|
||||
@available(*, unavailable, renamed: "CommandProtocol")
|
||||
public typealias CommandType = CommandProtocol
|
||||
|
||||
extension CommandRegistry {
|
||||
@available(*, unavailable, renamed: "run(command:arguments:)")
|
||||
public func runCommand(_ verb: String, arguments: [String]) -> Result<(), CommandantError<ClientError>>? {
|
||||
return run(command: verb, arguments: arguments)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -136,12 +136,18 @@ internal func informativeUsageError<T: ArgumentProtocol, ClientError>(_ option:
|
|||
return informativeUsageError("--\(option.key) (\(T.name))", option: option)
|
||||
}
|
||||
|
||||
/// Constructs an error that describes how to use the option.
|
||||
internal func informativeUsageError<T: ArgumentProtocol, ClientError>(_ option: Option<[T]>) -> CommandantError<ClientError> {
|
||||
return informativeUsageError("--\(option.key) (\(option.defaultValue))", option: option)
|
||||
}
|
||||
|
||||
/// Constructs an error that describes how to use the option.
|
||||
internal func informativeUsageError<T: ArgumentProtocol, ClientError>(_ option: Option<[T]?>) -> CommandantError<ClientError> {
|
||||
return informativeUsageError("--\(option.key) (\(T.name))", option: option)
|
||||
}
|
||||
|
||||
/// Constructs an error that describes how to use the given boolean option.
|
||||
internal func informativeUsageError<ClientError>(_ option: Option<Bool>) -> CommandantError<ClientError> {
|
||||
let key = option.key
|
||||
return informativeUsageError((option.defaultValue ? "--no-\(key)" : "--\(key)"), option: option)
|
||||
}
|
||||
|
||||
// MARK: - migration support
|
||||
@available(*, unavailable, message: "Use ErrorProtocol instead of ClientErrorType")
|
||||
public typealias ClientErrorType = Error
|
||||
|
|
|
@ -46,10 +46,10 @@ public struct HelpCommand<ClientError: Error>: CommandProtocol {
|
|||
|
||||
print("Available commands:\n")
|
||||
|
||||
let maxVerbLength = self.registry.commands.map { $0.verb.characters.count }.max() ?? 0
|
||||
let maxVerbLength = self.registry.commands.map { $0.verb.count }.max() ?? 0
|
||||
|
||||
for command in self.registry.commands {
|
||||
let padding = repeatElement(Character(" "), count: maxVerbLength - command.verb.characters.count)
|
||||
let padding = repeatElement(Character(" "), count: maxVerbLength - command.verb.count)
|
||||
print(" \(command.verb)\(String(padding)) \(command.function)")
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.11.1</string>
|
||||
<string>0.12.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// LinuxSupport.swift
|
||||
// Commandant
|
||||
//
|
||||
// Created by Norio Nomura on 3/26/16.
|
||||
// Copyright © 2016 Carthage. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// swift-corelibs-foundation is still written in Swift 2 API.
|
||||
#if os(Linux)
|
||||
typealias Process = Task
|
||||
#endif
|
|
@ -19,18 +19,19 @@ import Foundation
|
|||
/// struct LogOptions: OptionsProtocol {
|
||||
/// let verbosity: Int
|
||||
/// let outputFilename: String
|
||||
/// let shouldDelete: Bool
|
||||
/// let logName: String
|
||||
///
|
||||
/// static func create(verbosity: Int)(outputFilename: String)(logName: String) -> LogOptions {
|
||||
/// return LogOptions(verbosity: verbosity, outputFilename: outputFilename, logName: logName)
|
||||
/// static func create(_ verbosity: Int) -> (String) -> (Bool) -> (String) -> LogOptions {
|
||||
/// return { outputFilename in { shouldDelete in { logName in LogOptions(verbosity: verbosity, outputFilename: outputFilename, shouldDelete: shouldDelete, logName: logName) } } }
|
||||
/// }
|
||||
///
|
||||
/// static func evaluate(m: CommandMode) -> Result<LogOptions, CommandantError<YourErrorType>> {
|
||||
/// static func evaluate(_ m: CommandMode) -> Result<LogOptions, CommandantError<YourErrorType>> {
|
||||
/// return create
|
||||
/// <*> m <| Option(key: "verbose", defaultValue: 0, usage: "the verbosity level with which to read the logs")
|
||||
/// <*> m <| Option(key: "outputFilename", defaultValue: "", usage: "a file to print output to, instead of stdout")
|
||||
/// <*> m <| Switch(flag: "d", key: "delete", defaultValue: false, usage: "delete the logs when finished")
|
||||
/// <*> m <| Option(usage: "the log to read")
|
||||
/// <*> m <| Switch(flag: "d", key: "delete", usage: "delete the logs when finished")
|
||||
/// <*> m <| Argument(usage: "the log to read")
|
||||
/// }
|
||||
/// }
|
||||
public protocol OptionsProtocol {
|
||||
|
@ -82,6 +83,8 @@ extension Option: CustomStringConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Operators
|
||||
|
||||
// Inspired by the Argo library:
|
||||
// https://github.com/thoughtbot/Argo
|
||||
/*
|
||||
|
@ -141,74 +144,130 @@ public func <*> <T, U, ClientError>(f: Result<((T) -> U), CommandantError<Client
|
|||
}
|
||||
}
|
||||
|
||||
/// Evaluates the given option in the given mode.
|
||||
///
|
||||
/// If parsing command line arguments, and no value was specified on the command
|
||||
/// line, the option's `defaultValue` is used.
|
||||
public func <| <T: ArgumentProtocol, ClientError>(mode: CommandMode, option: Option<T>) -> Result<T, CommandantError<ClientError>> {
|
||||
let wrapped = Option<T?>(key: option.key, defaultValue: option.defaultValue, usage: option.usage)
|
||||
// Since we are passing a non-nil default value, we can safely unwrap the
|
||||
// result.
|
||||
return (mode <| wrapped).map { $0! }
|
||||
}
|
||||
extension CommandMode {
|
||||
/// Evaluates the given option in the given mode.
|
||||
///
|
||||
/// If parsing command line arguments, and no value was specified on the command
|
||||
/// line, the option's `defaultValue` is used.
|
||||
public static func <| <T: ArgumentProtocol, ClientError>(mode: CommandMode, option: Option<T>) -> Result<T, CommandantError<ClientError>> {
|
||||
let wrapped = Option<T?>(key: option.key, defaultValue: option.defaultValue, usage: option.usage)
|
||||
// Since we are passing a non-nil default value, we can safely unwrap the
|
||||
// result.
|
||||
return (mode <| wrapped).map { $0! }
|
||||
}
|
||||
|
||||
/// Evaluates the given option in the given mode.
|
||||
///
|
||||
/// If parsing command line arguments, and no value was specified on the command
|
||||
/// line, `nil` is used.
|
||||
public func <| <T: ArgumentProtocol, ClientError>(mode: CommandMode, option: Option<T?>) -> Result<T?, CommandantError<ClientError>> {
|
||||
let key = option.key
|
||||
switch mode {
|
||||
case let .arguments(arguments):
|
||||
var stringValue: String?
|
||||
switch arguments.consumeValue(forKey: key) {
|
||||
case let .success(value):
|
||||
stringValue = value
|
||||
/// Evaluates the given option in the given mode.
|
||||
///
|
||||
/// If parsing command line arguments, and no value was specified on the command
|
||||
/// line, `nil` is used.
|
||||
public static func <| <T: ArgumentProtocol, ClientError>(mode: CommandMode, option: Option<T?>) -> Result<T?, CommandantError<ClientError>> {
|
||||
let key = option.key
|
||||
switch mode {
|
||||
case let .arguments(arguments):
|
||||
var stringValue: String?
|
||||
switch arguments.consumeValue(forKey: key) {
|
||||
case let .success(value):
|
||||
stringValue = value
|
||||
|
||||
case let .failure(error):
|
||||
switch error {
|
||||
case let .usageError(description):
|
||||
case let .failure(error):
|
||||
switch error {
|
||||
case let .usageError(description):
|
||||
return .failure(.usageError(description: description))
|
||||
|
||||
case .commandError:
|
||||
fatalError("CommandError should be impossible when parameterized over NoError")
|
||||
}
|
||||
}
|
||||
|
||||
if let stringValue = stringValue {
|
||||
if let value = T.from(string: stringValue) {
|
||||
return .success(value)
|
||||
}
|
||||
|
||||
let description = "Invalid value for '--\(key)': \(stringValue)"
|
||||
return .failure(.usageError(description: description))
|
||||
|
||||
case .commandError:
|
||||
fatalError("CommandError should be impossible when parameterized over NoError")
|
||||
} else {
|
||||
return .success(option.defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
if let stringValue = stringValue {
|
||||
if let value = T.from(string: stringValue) {
|
||||
case .usage:
|
||||
return .failure(informativeUsageError(option))
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluates the given option in the given mode.
|
||||
///
|
||||
/// If parsing command line arguments, and no value was specified on the command
|
||||
/// line, the option's `defaultValue` is used.
|
||||
public static func <| <T: ArgumentProtocol, ClientError>(mode: CommandMode, option: Option<[T]>) -> Result<[T], CommandantError<ClientError>> {
|
||||
let wrapped = Option<[T]?>(key: option.key, defaultValue: option.defaultValue, usage: option.usage)
|
||||
// Since we are passing a non-nil default value, we can safely unwrap the
|
||||
// result.
|
||||
return (mode <| wrapped).map { $0! }
|
||||
}
|
||||
|
||||
/// Evaluates the given option in the given mode.
|
||||
///
|
||||
/// If parsing command line arguments, and no value was specified on the command
|
||||
/// line, `nil` is used.
|
||||
public static func <| <T: ArgumentProtocol, ClientError>(mode: CommandMode, option: Option<[T]?>) -> Result<[T]?, CommandantError<ClientError>> {
|
||||
let key = option.key
|
||||
|
||||
switch mode {
|
||||
case let .arguments(arguments):
|
||||
let stringValue: String?
|
||||
switch arguments.consumeValue(forKey: key) {
|
||||
case let .success(value):
|
||||
stringValue = value
|
||||
|
||||
case let .failure(error):
|
||||
switch error {
|
||||
case let .usageError(description):
|
||||
return .failure(.usageError(description: description))
|
||||
|
||||
case .commandError:
|
||||
fatalError("CommandError should be impossible when parameterized over NoError")
|
||||
}
|
||||
}
|
||||
|
||||
guard let unwrappedStringValue = stringValue else {
|
||||
return .success(option.defaultValue)
|
||||
}
|
||||
|
||||
let components = unwrappedStringValue.split(
|
||||
omittingEmptySubsequences: true,
|
||||
whereSeparator: [",", " "].contains
|
||||
)
|
||||
var resultValues: [T] = []
|
||||
for component in components {
|
||||
guard let value = T.from(string: String(component)) else {
|
||||
let description = "Invalid value for '--\(key)': \(unwrappedStringValue)"
|
||||
return .failure(.usageError(description: description))
|
||||
}
|
||||
resultValues.append(value)
|
||||
}
|
||||
return .success(resultValues)
|
||||
|
||||
case .usage:
|
||||
return .failure(informativeUsageError(option))
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluates the given boolean option in the given mode.
|
||||
///
|
||||
/// If parsing command line arguments, and no value was specified on the command
|
||||
/// line, the option's `defaultValue` is used.
|
||||
public static func <| <ClientError>(mode: CommandMode, option: Option<Bool>) -> Result<Bool, CommandantError<ClientError>> {
|
||||
switch mode {
|
||||
case let .arguments(arguments):
|
||||
if let value = arguments.consumeBoolean(forKey: option.key) {
|
||||
return .success(value)
|
||||
} else {
|
||||
return .success(option.defaultValue)
|
||||
}
|
||||
|
||||
let description = "Invalid value for '--\(key)': \(stringValue)"
|
||||
return .failure(.usageError(description: description))
|
||||
} else {
|
||||
return .success(option.defaultValue)
|
||||
}
|
||||
|
||||
case .usage:
|
||||
return .failure(informativeUsageError(option))
|
||||
case .usage:
|
||||
return .failure(informativeUsageError(option))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluates the given boolean option in the given mode.
|
||||
///
|
||||
/// If parsing command line arguments, and no value was specified on the command
|
||||
/// line, the option's `defaultValue` is used.
|
||||
public func <| <ClientError>(mode: CommandMode, option: Option<Bool>) -> Result<Bool, CommandantError<ClientError>> {
|
||||
switch mode {
|
||||
case let .arguments(arguments):
|
||||
if let value = arguments.consumeBoolean(forKey: option.key) {
|
||||
return .success(value)
|
||||
} else {
|
||||
return .success(option.defaultValue)
|
||||
}
|
||||
|
||||
case .usage:
|
||||
return .failure(informativeUsageError(option))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - migration support
|
||||
@available(*, unavailable, renamed: "OptionsProtocol")
|
||||
public typealias OptionsType = OptionsProtocol
|
||||
|
|
51
Seeds/Commandant/Sources/Commandant/OrderedSet.swift
Normal file
51
Seeds/Commandant/Sources/Commandant/OrderedSet.swift
Normal file
|
@ -0,0 +1,51 @@
|
|||
/// A poor man's ordered set.
|
||||
internal struct OrderedSet<T: Hashable> {
|
||||
fileprivate var values: [T] = []
|
||||
|
||||
init<S: Sequence>(_ sequence: S) where S.Element == T {
|
||||
for e in sequence where !values.contains(e) {
|
||||
values.append(e)
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
mutating func remove(_ member: T) -> T? {
|
||||
if let index = values.index(of: member) {
|
||||
return values.remove(at: index)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension OrderedSet: Equatable {
|
||||
static func == (_ lhs: OrderedSet, rhs: OrderedSet) -> Bool {
|
||||
return lhs.values == rhs.values
|
||||
}
|
||||
}
|
||||
|
||||
extension OrderedSet: Collection {
|
||||
subscript(position: Int) -> T {
|
||||
return values[position]
|
||||
}
|
||||
|
||||
var count: Int {
|
||||
return values.count
|
||||
}
|
||||
|
||||
var isEmpty: Bool {
|
||||
return values.isEmpty
|
||||
}
|
||||
|
||||
var startIndex: Int {
|
||||
return values.startIndex
|
||||
}
|
||||
|
||||
var endIndex: Int {
|
||||
return values.endIndex
|
||||
}
|
||||
|
||||
func index(after i: Int) -> Int {
|
||||
return values.index(after: i)
|
||||
}
|
||||
}
|
|
@ -44,20 +44,25 @@ extension Switch: CustomStringConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
/// Evaluates the given boolean switch in the given mode.
|
||||
///
|
||||
/// If parsing command line arguments, and no value was specified on the command
|
||||
/// line, the option's `defaultValue` is used.
|
||||
public func <| <ClientError> (mode: CommandMode, option: Switch) -> Result<Bool, CommandantError<ClientError>> {
|
||||
switch mode {
|
||||
case let .arguments(arguments):
|
||||
var enabled = arguments.consume(key: option.key)
|
||||
if let flag = option.flag {
|
||||
enabled = arguments.consumeBoolean(flag: flag)
|
||||
}
|
||||
return .success(enabled)
|
||||
// MARK: - Operators
|
||||
|
||||
case .usage:
|
||||
return .failure(informativeUsageError(option.description, usage: option.usage))
|
||||
extension CommandMode {
|
||||
/// Evaluates the given boolean switch in the given mode.
|
||||
///
|
||||
/// If parsing command line arguments, and no value was specified on the command
|
||||
/// line, the option's `defaultValue` is used.
|
||||
public static func <| <ClientError> (mode: CommandMode, option: Switch) -> Result<Bool, CommandantError<ClientError>> {
|
||||
switch mode {
|
||||
case let .arguments(arguments):
|
||||
var enabled = arguments.consume(key: option.key)
|
||||
|
||||
if let flag = option.flag, !enabled {
|
||||
enabled = arguments.consumeBoolean(flag: flag)
|
||||
}
|
||||
return .success(enabled)
|
||||
|
||||
case .usage:
|
||||
return .failure(informativeUsageError(option.description, usage: option.usage))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ public enum Result<T, Error: Swift.Error>: ResultProtocol, CustomStringConvertib
|
|||
self = .failure(error)
|
||||
}
|
||||
|
||||
/// Constructs a result from an Optional, failing with `Error` if `nil`.
|
||||
/// Constructs a result from an `Optional`, failing with `Error` if `nil`.
|
||||
public init(_ value: T?, failWith: @autoclosure () -> Error) {
|
||||
self = value.map(Result.success) ?? .failure(failWith())
|
||||
}
|
||||
|
@ -31,14 +31,17 @@ public enum Result<T, Error: Swift.Error>: ResultProtocol, CustomStringConvertib
|
|||
public init(attempt f: () throws -> T) {
|
||||
do {
|
||||
self = .success(try f())
|
||||
} catch {
|
||||
} catch var error {
|
||||
if Error.self == AnyError.self {
|
||||
error = AnyError(error)
|
||||
}
|
||||
self = .failure(error as! Error)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Deconstruction
|
||||
|
||||
/// Returns the value from `Success` Results or `throw`s the error.
|
||||
/// Returns the value from `success` Results or `throw`s the error.
|
||||
public func dematerialize() throws -> T {
|
||||
switch self {
|
||||
case let .success(value):
|
||||
|
@ -50,7 +53,7 @@ public enum Result<T, Error: Swift.Error>: ResultProtocol, CustomStringConvertib
|
|||
|
||||
/// Case analysis for Result.
|
||||
///
|
||||
/// Returns the value produced by applying `ifFailure` to `Failure` Results, or `ifSuccess` to `Success` Results.
|
||||
/// Returns the value produced by applying `ifFailure` to `failure` Results, or `ifSuccess` to `success` Results.
|
||||
public func analysis<Result>(ifSuccess: (T) -> Result, ifFailure: (Error) -> Result) -> Result {
|
||||
switch self {
|
||||
case let .success(value):
|
||||
|
@ -108,15 +111,38 @@ public enum Result<T, Error: Swift.Error>: ResultProtocol, CustomStringConvertib
|
|||
|
||||
// MARK: - Derive result from failable closure
|
||||
|
||||
public func materialize<T>(_ f: () throws -> T) -> Result<T, AnyError> {
|
||||
return materialize(try f())
|
||||
}
|
||||
|
||||
public func materialize<T>(_ f: @autoclosure () throws -> T) -> Result<T, AnyError> {
|
||||
do {
|
||||
return .success(try f())
|
||||
} catch {
|
||||
return .failure(AnyError(error))
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "Use the overload which returns `Result<T, AnyError>` instead")
|
||||
public func materialize<T>(_ f: () throws -> T) -> Result<T, NSError> {
|
||||
return materialize(try f())
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "Use the overload which returns `Result<T, AnyError>` instead")
|
||||
public func materialize<T>(_ f: @autoclosure () throws -> T) -> Result<T, NSError> {
|
||||
do {
|
||||
return .success(try f())
|
||||
} catch let error as NSError {
|
||||
return .failure(error)
|
||||
} catch {
|
||||
// This isn't great, but it lets us maintain compatibility until this deprecated
|
||||
// method can be removed.
|
||||
#if _runtime(_ObjC)
|
||||
return .failure(error as NSError)
|
||||
#else
|
||||
// https://github.com/apple/swift-corelibs-foundation/blob/swift-3.0.2-RELEASE/Foundation/NSError.swift#L314
|
||||
let userInfo = _swift_Foundation_getErrorDefaultUserInfo(error) as? [String: Any]
|
||||
let nsError = NSError(domain: error._domain, code: error._code, userInfo: userInfo)
|
||||
return .failure(nsError)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,21 +150,23 @@ public func materialize<T>(_ f: @autoclosure () throws -> T) -> Result<T, NSErro
|
|||
|
||||
#if !os(Linux)
|
||||
|
||||
/// Constructs a Result with the result of calling `try` with an error pointer.
|
||||
/// Constructs a `Result` with the result of calling `try` with an error pointer.
|
||||
///
|
||||
/// This is convenient for wrapping Cocoa API which returns an object or `nil` + an error, by reference. e.g.:
|
||||
///
|
||||
/// Result.try { NSData(contentsOfURL: URL, options: .DataReadingMapped, error: $0) }
|
||||
/// Result.try { NSData(contentsOfURL: URL, options: .dataReadingMapped, error: $0) }
|
||||
@available(*, deprecated, message: "This will be removed in Result 4.0. Use `Result.init(attempt:)` instead. See https://github.com/antitypical/Result/issues/85 for the details.")
|
||||
public func `try`<T>(_ function: String = #function, file: String = #file, line: Int = #line, `try`: (NSErrorPointer) -> T?) -> Result<T, NSError> {
|
||||
var error: NSError?
|
||||
return `try`(&error).map(Result.success) ?? .failure(error ?? Result<T, NSError>.error(function: function, file: file, line: line))
|
||||
}
|
||||
|
||||
/// Constructs a Result with the result of calling `try` with an error pointer.
|
||||
/// Constructs a `Result` with the result of calling `try` with an error pointer.
|
||||
///
|
||||
/// This is convenient for wrapping Cocoa API which returns a `Bool` + an error, by reference. e.g.:
|
||||
///
|
||||
/// Result.try { NSFileManager.defaultManager().removeItemAtURL(URL, error: $0) }
|
||||
@available(*, deprecated, message: "This will be removed in Result 4.0. Use `Result.init(attempt:)` instead. See https://github.com/antitypical/Result/issues/85 for the details.")
|
||||
public func `try`(_ function: String = #function, file: String = #file, line: Int = #line, `try`: (NSErrorPointer) -> Bool) -> Result<(), NSError> {
|
||||
var error: NSError?
|
||||
return `try`(&error) ?
|
||||
|
@ -148,9 +176,9 @@ public func `try`(_ function: String = #function, file: String = #file, line: In
|
|||
|
||||
#endif
|
||||
|
||||
// MARK: - ErrorProtocolConvertible conformance
|
||||
// MARK: - ErrorConvertible conformance
|
||||
|
||||
extension NSError: ErrorProtocolConvertible {
|
||||
extension NSError: ErrorConvertible {
|
||||
public static func error(from error: Swift.Error) -> Self {
|
||||
func cast<T: NSError>(_ error: Swift.Error) -> T {
|
||||
return error as! T
|
||||
|
@ -160,14 +188,70 @@ extension NSError: ErrorProtocolConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
// MARK: - Errors
|
||||
|
||||
/// An “error” that is impossible to construct.
|
||||
///
|
||||
/// This can be used to describe `Result`s where failures will never
|
||||
/// be generated. For example, `Result<Int, NoError>` describes a result that
|
||||
/// contains an `Int`eger and is guaranteed never to be a `Failure`.
|
||||
public enum NoError: Swift.Error { }
|
||||
/// contains an `Int`eger and is guaranteed never to be a `failure`.
|
||||
public enum NoError: Swift.Error, Equatable {
|
||||
public static func ==(lhs: NoError, rhs: NoError) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erased error which wraps an arbitrary error instance. This should be
|
||||
/// useful for generic contexts.
|
||||
public struct AnyError: Swift.Error {
|
||||
/// The underlying error.
|
||||
public let error: Swift.Error
|
||||
|
||||
public init(_ error: Swift.Error) {
|
||||
if let anyError = error as? AnyError {
|
||||
self = anyError
|
||||
} else {
|
||||
self.error = error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AnyError: ErrorConvertible {
|
||||
public static func error(from error: Error) -> AnyError {
|
||||
return AnyError(error)
|
||||
}
|
||||
}
|
||||
|
||||
extension AnyError: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return String(describing: error)
|
||||
}
|
||||
}
|
||||
|
||||
// There appears to be a bug in Foundation on Linux which prevents this from working:
|
||||
// https://bugs.swift.org/browse/SR-3565
|
||||
// Don't forget to comment the tests back in when removing this check when it's fixed!
|
||||
#if !os(Linux)
|
||||
|
||||
extension AnyError: LocalizedError {
|
||||
public var errorDescription: String? {
|
||||
return error.localizedDescription
|
||||
}
|
||||
|
||||
public var failureReason: String? {
|
||||
return (error as? LocalizedError)?.failureReason
|
||||
}
|
||||
|
||||
public var helpAnchor: String? {
|
||||
return (error as? LocalizedError)?.helpAnchor
|
||||
}
|
||||
|
||||
public var recoverySuggestion: String? {
|
||||
return (error as? LocalizedError)?.recoverySuggestion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// MARK: - migration support
|
||||
extension Result {
|
||||
|
|
|
@ -51,6 +51,14 @@ public extension ResultProtocol {
|
|||
ifFailure: Result<U, Error>.failure)
|
||||
}
|
||||
|
||||
/// Returns a Result with a tuple of the receiver and `other` values if both
|
||||
/// are `Success`es, or re-wrapping the error of the earlier `Failure`.
|
||||
public func fanout<R: ResultProtocol>(_ other: @autoclosure () -> R) -> Result<(Value, R.Value), Error>
|
||||
where Error == R.Error
|
||||
{
|
||||
return self.flatMap { left in other().map { right in (left, right) } }
|
||||
}
|
||||
|
||||
/// Returns a new Result by mapping `Failure`'s values using `transform`, or re-wrapping `Success`es’ values.
|
||||
public func mapError<Error2>(_ transform: (Error) -> Error2) -> Result<Value, Error2> {
|
||||
return flatMapError { .failure(transform($0)) }
|
||||
|
@ -62,6 +70,14 @@ public extension ResultProtocol {
|
|||
ifSuccess: Result<Value, Error2>.success,
|
||||
ifFailure: transform)
|
||||
}
|
||||
|
||||
/// Returns a new Result by mapping `Success`es’ values using `success`, and by mapping `Failure`'s values using `failure`.
|
||||
public func bimap<U, Error2>(success: (Value) -> U, failure: (Error) -> Error2) -> Result<U, Error2> {
|
||||
return analysis(
|
||||
ifSuccess: { .success(success($0)) },
|
||||
ifFailure: { .failure(failure($0)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public extension ResultProtocol {
|
||||
|
@ -82,11 +98,11 @@ public extension ResultProtocol {
|
|||
}
|
||||
|
||||
/// Protocol used to constrain `tryMap` to `Result`s with compatible `Error`s.
|
||||
public protocol ErrorProtocolConvertible: Swift.Error {
|
||||
public protocol ErrorConvertible: Swift.Error {
|
||||
static func error(from error: Swift.Error) -> Self
|
||||
}
|
||||
|
||||
public extension ResultProtocol where Error: ErrorProtocolConvertible {
|
||||
public extension ResultProtocol where Error: ErrorConvertible {
|
||||
|
||||
/// Returns the result of applying `transform` to `Success`es’ values, or wrapping thrown errors.
|
||||
public func tryMap<U>(_ transform: (Value) throws -> U) -> Result<U, Error> {
|
||||
|
@ -108,10 +124,11 @@ public extension ResultProtocol where Error: ErrorProtocolConvertible {
|
|||
infix operator &&& : LogicalConjunctionPrecedence
|
||||
|
||||
/// Returns a Result with a tuple of `left` and `right` values if both are `Success`es, or re-wrapping the error of the earlier `Failure`.
|
||||
@available(*, deprecated, renamed: "ResultProtocol.fanout(self:_:)")
|
||||
public func &&& <L: ResultProtocol, R: ResultProtocol> (left: L, right: @autoclosure () -> R) -> Result<(L.Value, R.Value), L.Error>
|
||||
where L.Error == R.Error
|
||||
{
|
||||
return left.flatMap { left in right().map { right in (left, right) } }
|
||||
return left.fanout(right)
|
||||
}
|
||||
|
||||
precedencegroup ChainingPrecedence {
|
||||
|
@ -124,6 +141,7 @@ infix operator >>- : ChainingPrecedence
|
|||
/// Returns the result of applying `transform` to `Success`es’ values, or re-wrapping `Failure`’s errors.
|
||||
///
|
||||
/// This is a synonym for `flatMap`.
|
||||
@available(*, deprecated, renamed: "ResultProtocol.flatMap(self:_:)")
|
||||
public func >>- <T: ResultProtocol, U> (result: T, transform: (T.Value) -> Result<U, T.Error>) -> Result<U, T.Error> {
|
||||
return result.flatMap(transform)
|
||||
}
|
||||
|
@ -164,8 +182,11 @@ public typealias ResultType = ResultProtocol
|
|||
@available(*, unavailable, renamed: "Error")
|
||||
public typealias ResultErrorType = Swift.Error
|
||||
|
||||
@available(*, unavailable, renamed: "ErrorProtocolConvertible")
|
||||
public typealias ErrorTypeConvertible = ErrorProtocolConvertible
|
||||
@available(*, unavailable, renamed: "ErrorConvertible")
|
||||
public typealias ErrorTypeConvertible = ErrorConvertible
|
||||
|
||||
@available(*, deprecated, renamed: "ErrorConvertible")
|
||||
public protocol ErrorProtocolConvertible: ErrorConvertible {}
|
||||
|
||||
extension ResultProtocol {
|
||||
@available(*, unavailable, renamed: "recover(with:)")
|
||||
|
@ -174,7 +195,7 @@ extension ResultProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
extension ErrorProtocolConvertible {
|
||||
extension ErrorConvertible {
|
||||
@available(*, unavailable, renamed: "error(from:)")
|
||||
public static func errorFromErrorType(_ error: Swift.Error) -> Self {
|
||||
fatalError()
|
||||
|
|
4
Seeds/Seedfile.lock
Normal file
4
Seeds/Seedfile.lock
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
SEEDS:
|
||||
- Commandant (master)
|
||||
- Result (3.2.4)
|
|
@ -3,7 +3,7 @@
|
|||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 47;
|
||||
objectVersion = 48;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
|
@ -11,11 +11,12 @@
|
|||
0C47E694564FCB59996690DD /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E4D331CCD66ADFE19CE39 /* Command.swift */; };
|
||||
0EBF5CDD379D7462C3389536 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9257C5FABA335E5F060CB7F7 /* Result.swift */; };
|
||||
15E27926A580EABEB1B218EF /* Switch.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1B6BEDF32AF3F8A575FB1F /* Switch.swift */; };
|
||||
25209791ED0F49CF5BAF7348 /* LinuxSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E3BFBE58DFCE19A53A23D7 /* LinuxSupport.swift */; };
|
||||
3053D11E74A22A4C5A6BE833 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = F547B3DC473CFB1BE0AEB70A /* Errors.swift */; };
|
||||
30EA893640B02CCF679F9C57 /* Option.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD7FE171F643805F7BC38A7 /* Option.swift */; };
|
||||
693A98991CBFFA760004D3B4 /* Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693A98981CBFFA760004D3B4 /* Search.swift */; };
|
||||
693A989B1CBFFAAA0004D3B4 /* NSURLSession+Synchronous.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693A989A1CBFFAAA0004D3B4 /* NSURLSession+Synchronous.swift */; };
|
||||
900A1E811DBAC8CB0069B1A8 /* Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = 900A1E801DBAC8CB0069B1A8 /* Info.swift */; };
|
||||
92AE0FD7BE06D64692E6C1E6 /* OrderedSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9871C2273F4D762A1F19B07 /* OrderedSet.swift */; };
|
||||
AD0785BC0EC6BBF4ED560DCC /* ArgumentParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9D96DDBBCCCC5944160ABE /* ArgumentParser.swift */; };
|
||||
ADE553C828AF4EAFF39ED3E1 /* ArgumentProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4237E5AA1A289D03D2A2FB8 /* ArgumentProtocol.swift */; };
|
||||
EBD6B44FDF65E0253153629F /* HelpCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FDC2B8063EC231E28353D23 /* HelpCommand.swift */; };
|
||||
|
@ -56,13 +57,14 @@
|
|||
2AD7FE171F643805F7BC38A7 /* Option.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Option.swift; path = Seeds/Commandant/Sources/Commandant/Option.swift; sourceTree = "<group>"; };
|
||||
326E4D331CCD66ADFE19CE39 /* Command.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Command.swift; path = Seeds/Commandant/Sources/Commandant/Command.swift; sourceTree = "<group>"; };
|
||||
5150F7FB7CF2A77F675D8E92 /* ResultProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ResultProtocol.swift; path = Seeds/Result/Result/ResultProtocol.swift; sourceTree = "<group>"; };
|
||||
55E3BFBE58DFCE19A53A23D7 /* LinuxSupport.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LinuxSupport.swift; path = Seeds/Commandant/Sources/Commandant/LinuxSupport.swift; sourceTree = "<group>"; };
|
||||
693A98981CBFFA760004D3B4 /* Search.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Search.swift; sourceTree = "<group>"; };
|
||||
693A989A1CBFFAAA0004D3B4 /* NSURLSession+Synchronous.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSURLSession+Synchronous.swift"; sourceTree = "<group>"; };
|
||||
8FDC2B8063EC231E28353D23 /* HelpCommand.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HelpCommand.swift; path = Seeds/Commandant/Sources/Commandant/HelpCommand.swift; sourceTree = "<group>"; };
|
||||
900A1E801DBAC8CB0069B1A8 /* Info.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Info.swift; sourceTree = "<group>"; };
|
||||
9257C5FABA335E5F060CB7F7 /* Result.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Result.swift; path = Seeds/Result/Result/Result.swift; sourceTree = "<group>"; };
|
||||
AF1B6BEDF32AF3F8A575FB1F /* Switch.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Switch.swift; path = Seeds/Commandant/Sources/Commandant/Switch.swift; sourceTree = "<group>"; };
|
||||
B4237E5AA1A289D03D2A2FB8 /* ArgumentProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ArgumentProtocol.swift; path = Seeds/Commandant/Sources/Commandant/ArgumentProtocol.swift; sourceTree = "<group>"; };
|
||||
D9871C2273F4D762A1F19B07 /* OrderedSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OrderedSet.swift; path = Seeds/Commandant/Sources/Commandant/OrderedSet.swift; sourceTree = "<group>"; };
|
||||
EA9D96DDBBCCCC5944160ABE /* ArgumentParser.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ArgumentParser.swift; path = Seeds/Commandant/Sources/Commandant/ArgumentParser.swift; sourceTree = "<group>"; };
|
||||
ED031A781B5127C00097692E /* mas */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = mas; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
ED031A7B1B5127C00097692E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||
|
@ -152,8 +154,8 @@
|
|||
326E4D331CCD66ADFE19CE39 /* Command.swift */,
|
||||
F547B3DC473CFB1BE0AEB70A /* Errors.swift */,
|
||||
8FDC2B8063EC231E28353D23 /* HelpCommand.swift */,
|
||||
55E3BFBE58DFCE19A53A23D7 /* LinuxSupport.swift */,
|
||||
2AD7FE171F643805F7BC38A7 /* Option.swift */,
|
||||
D9871C2273F4D762A1F19B07 /* OrderedSet.swift */,
|
||||
AF1B6BEDF32AF3F8A575FB1F /* Switch.swift */,
|
||||
);
|
||||
name = Commandant;
|
||||
|
@ -206,6 +208,7 @@
|
|||
EDE296521C700F4300554778 /* SignOut.swift */,
|
||||
EDD3B3621C34709400B56B88 /* Upgrade.swift */,
|
||||
EDB6CE8B1BAEC3D400648B4D /* Version.swift */,
|
||||
900A1E801DBAC8CB0069B1A8 /* Info.swift */,
|
||||
);
|
||||
name = Commands;
|
||||
path = "mas-cli/Commands";
|
||||
|
@ -307,17 +310,17 @@
|
|||
attributes = {
|
||||
LastSwiftMigration = 0730;
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 0800;
|
||||
LastUpgradeCheck = 0920;
|
||||
ORGANIZATIONNAME = "Andrew Naylor";
|
||||
TargetAttributes = {
|
||||
ED031A771B5127C00097692E = {
|
||||
CreatedOnToolsVersion = 7.0;
|
||||
LastSwiftMigration = 0800;
|
||||
LastSwiftMigration = 0920;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = ED031A731B5127C00097692E /* Build configuration list for PBXProject "mas-cli" */;
|
||||
compatibilityVersion = "Xcode 6.3";
|
||||
compatibilityVersion = "Xcode 8.0";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
|
@ -351,6 +354,7 @@
|
|||
ED0F23871B87537200AE40CD /* Account.swift in Sources */,
|
||||
F184B6B7CD9C013CACDED0FB /* Argument.swift in Sources */,
|
||||
AD0785BC0EC6BBF4ED560DCC /* ArgumentParser.swift in Sources */,
|
||||
900A1E811DBAC8CB0069B1A8 /* Info.swift in Sources */,
|
||||
ADE553C828AF4EAFF39ED3E1 /* ArgumentProtocol.swift in Sources */,
|
||||
0C47E694564FCB59996690DD /* Command.swift in Sources */,
|
||||
ED0F238B1B87569C00AE40CD /* Downloader.swift in Sources */,
|
||||
|
@ -359,11 +363,11 @@
|
|||
EBD6B44FDF65E0253153629F /* HelpCommand.swift in Sources */,
|
||||
ED0F237F1B87522400AE40CD /* Install.swift in Sources */,
|
||||
ED0F23901B87A56F00AE40CD /* ISStoreAccount.swift in Sources */,
|
||||
25209791ED0F49CF5BAF7348 /* LinuxSupport.swift in Sources */,
|
||||
ED0F23831B87533A00AE40CD /* List.swift in Sources */,
|
||||
ED031A7C1B5127C00097692E /* main.swift in Sources */,
|
||||
693A989B1CBFFAAA0004D3B4 /* NSURLSession+Synchronous.swift in Sources */,
|
||||
30EA893640B02CCF679F9C57 /* Option.swift in Sources */,
|
||||
92AE0FD7BE06D64692E6C1E6 /* OrderedSet.swift in Sources */,
|
||||
ED0F23851B87536A00AE40CD /* Outdated.swift in Sources */,
|
||||
ED0F23891B87543D00AE40CD /* PurchaseDownloadObserver.swift in Sources */,
|
||||
EDCBF9531D89AC6F000039C6 /* Reset.swift in Sources */,
|
||||
|
@ -391,14 +395,20 @@
|
|||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
|
@ -438,14 +448,20 @@
|
|||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
|
@ -483,6 +499,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "com.mphys.mas-cli";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "mas-cli/mas-cli-Bridging-Header.h";
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
@ -501,7 +518,8 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "com.mphys.mas-cli";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "mas-cli/mas-cli-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
LastUpgradeVersion = "0920"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -26,6 +26,7 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
|
@ -45,6 +46,7 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "1"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
|
105
mas-cli/Commands/Info.swift
Normal file
105
mas-cli/Commands/Info.swift
Normal file
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// Info.swift
|
||||
// mas-cli
|
||||
//
|
||||
// Created by Denis Lebedev on 21/10/2016.
|
||||
// Copyright © 2016 Andrew Naylor. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct InfoCommand: CommandProtocol {
|
||||
let verb = "info"
|
||||
let function = "Display app information from the Mac App Store"
|
||||
|
||||
func run(_ options: InfoOptions) -> Result<(), MASError> {
|
||||
guard let infoURLString = infoURLString(options.appId),
|
||||
let searchJson = URLSession.requestSynchronousJSONWithURLString(infoURLString) as? [String: Any] else {
|
||||
return .failure(.searchFailed)
|
||||
}
|
||||
|
||||
guard let resultCount = searchJson[ResultKeys.ResultCount] as? Int, resultCount > 0,
|
||||
let results = searchJson[ResultKeys.Results] as? [[String: Any]],
|
||||
let result = results.first else {
|
||||
print("No results found")
|
||||
return .failure(.noSearchResultsFound)
|
||||
}
|
||||
|
||||
print(AppInfoFormatter.format(appInfo: result))
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
private func infoURLString(_ appId: String) -> String? {
|
||||
if let urlEncodedAppId = appId.URLEncodedString {
|
||||
return "https://itunes.apple.com/lookup?id=\(urlEncodedAppId)"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
struct InfoOptions: OptionsProtocol {
|
||||
let appId: String
|
||||
|
||||
static func create(_ appId: String) -> InfoOptions {
|
||||
return InfoOptions(appId: appId)
|
||||
}
|
||||
|
||||
static func evaluate(_ m: CommandMode) -> Result<InfoOptions, CommandantError<MASError>> {
|
||||
return create
|
||||
<*> m <| Argument(usage: "the app id to show info")
|
||||
}
|
||||
}
|
||||
|
||||
private struct AppInfoFormatter {
|
||||
|
||||
private enum Keys {
|
||||
static let Name = "trackCensoredName"
|
||||
static let Version = "version"
|
||||
static let Price = "formattedPrice"
|
||||
static let Seller = "sellerName"
|
||||
static let VersionReleaseDate = "currentVersionReleaseDate"
|
||||
static let MinimumOS = "minimumOsVersion"
|
||||
static let FileSize = "fileSizeBytes"
|
||||
static let AppStoreUrl = "trackViewUrl"
|
||||
}
|
||||
|
||||
static func format(appInfo: [String: Any]) -> String {
|
||||
let headline = [
|
||||
"\(appInfo.stringOrEmpty(key: Keys.Name))",
|
||||
"\(appInfo.stringOrEmpty(key: Keys.Version))",
|
||||
"[\(appInfo.stringOrEmpty(key: Keys.Price))]",
|
||||
].joined(separator: " ")
|
||||
|
||||
return [
|
||||
headline,
|
||||
"By: \(appInfo.stringOrEmpty(key: Keys.Seller))",
|
||||
"Released: \(humaReadableDate(appInfo.stringOrEmpty(key: Keys.VersionReleaseDate)))",
|
||||
"Minimum OS: \(appInfo.stringOrEmpty(key: Keys.MinimumOS))",
|
||||
"Size: \(humanReadableSize(appInfo.stringOrEmpty(key: Keys.FileSize)))",
|
||||
"From: \(appInfo.stringOrEmpty(key: Keys.AppStoreUrl))",
|
||||
].joined(separator: "\n")
|
||||
}
|
||||
|
||||
private static func humanReadableSize(_ size: String) -> String {
|
||||
let bytesSize = Int64(size) ?? 0
|
||||
return ByteCountFormatter.string(fromByteCount: bytesSize, countStyle: .file)
|
||||
}
|
||||
|
||||
private static func humaReadableDate(_ serverDate: String) -> String {
|
||||
let serverDateFormatter = DateFormatter()
|
||||
serverDateFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
serverDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
|
||||
|
||||
let humanDateFormatter = DateFormatter()
|
||||
humanDateFormatter.timeStyle = .none
|
||||
humanDateFormatter.dateStyle = .medium
|
||||
return serverDateFormatter.date(from: serverDate).flatMap(humanDateFormatter.string(from:)) ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
extension Dictionary {
|
||||
fileprivate func stringOrEmpty(key: Key) -> String {
|
||||
return self[key] as? String ?? ""
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ struct InstallCommand: CommandProtocol {
|
|||
|
||||
switch downloadResults.count {
|
||||
case 0:
|
||||
return .success()
|
||||
return .success(())
|
||||
case 1:
|
||||
return .failure(downloadResults[0])
|
||||
default:
|
||||
|
|
|
@ -58,12 +58,13 @@ struct ResetCommand: CommandProtocol {
|
|||
}
|
||||
|
||||
// Wipe Download Directory
|
||||
let directory = CKDownloadDirectory(nil)
|
||||
do {
|
||||
try FileManager.default.removeItem(atPath: directory!)
|
||||
} catch {
|
||||
if options.debug {
|
||||
printError("removeItemAtPath:\"\(directory)\" failed, \(error)")
|
||||
if let directory = CKDownloadDirectory(nil) {
|
||||
do {
|
||||
try FileManager.default.removeItem(atPath: directory)
|
||||
} catch {
|
||||
if options.debug {
|
||||
printError("removeItemAtPath:\"\(directory)\" failed, \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ struct SearchCommand: CommandProtocol {
|
|||
}
|
||||
|
||||
func searchURLString(_ appName: String) -> String? {
|
||||
if let urlEncodedAppName = appName.URLEncodedString() {
|
||||
if let urlEncodedAppName = appName.URLEncodedString {
|
||||
return "https://itunes.apple.com/search?entity=macSoftware&term=\(urlEncodedAppName)&attribute=allTrackTerm"
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -42,7 +42,7 @@ struct UpgradeCommand: CommandProtocol {
|
|||
|
||||
switch updateResults.count {
|
||||
case 0:
|
||||
return .success()
|
||||
return .success(())
|
||||
case 1:
|
||||
return .failure(updateResults[0])
|
||||
default:
|
||||
|
|
|
@ -55,9 +55,8 @@ public extension URLSession {
|
|||
public extension String {
|
||||
|
||||
/// Return an URL encoded string
|
||||
func URLEncodedString() -> String? {
|
||||
let customAllowedSet = CharacterSet.urlQueryAllowed
|
||||
return addingPercentEncoding(withAllowedCharacters: customAllowedSet)
|
||||
var URLEncodedString: String? {
|
||||
return addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ public struct StderrOutputStream: TextOutputStream {
|
|||
let registry = CommandRegistry<MASError>()
|
||||
let helpCommand = HelpCommand(registry: registry)
|
||||
registry.register(AccountCommand())
|
||||
registry.register(InfoCommand())
|
||||
registry.register(InstallCommand())
|
||||
registry.register(ListCommand())
|
||||
registry.register(OutdatedCommand())
|
||||
|
|
14
script/build
14
script/build
|
@ -11,11 +11,21 @@ main() {
|
|||
}
|
||||
|
||||
build() {
|
||||
set -o pipefail && xcodebuild -project "mas-cli.xcodeproj" -scheme mas-cli -configuration Release clean build | xcpretty -c
|
||||
set -o pipefail && \
|
||||
xcodebuild -project "mas-cli.xcodeproj" \
|
||||
-scheme mas-cli \
|
||||
-configuration Release \
|
||||
clean build \
|
||||
| bundle exec xcpretty --color
|
||||
}
|
||||
|
||||
archive() {
|
||||
set -o pipefail && xcodebuild -project "mas-cli.xcodeproj" -scheme mas-cli -archivePath mas.xcarchive archive | xcpretty -c
|
||||
set -o pipefail && \
|
||||
xcodebuild -project "mas-cli.xcodeproj" \
|
||||
-scheme mas-cli \
|
||||
-archivePath mas.xcarchive \
|
||||
archive \
|
||||
| bundle exec xcpretty --color
|
||||
}
|
||||
|
||||
main
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
echo "==> Moving dSYM to xcarchive"
|
||||
mkdir -p mas.xcarchive/dSYMs/
|
||||
mv build/mas.dSYM mas.xcarchive/dSYMs/
|
||||
|
||||
echo "==> Compressing mas.xcarchive"
|
||||
|
|
Loading…
Reference in a new issue