Add —availability filter to list-devices

This commit is contained in:
Simon Kågedal Reimer 2019-05-05 13:50:39 +02:00
parent 9a982c41e4
commit 60ea7523a1
5 changed files with 153 additions and 26 deletions

View File

@ -2,11 +2,23 @@ import Foundation
import Basic
import SPMUtility
struct FilteringOptions {
enum Availability: String, StringEnumArgument {
case yes
case no
case all
public static var completion: ShellCompletion = .none
}
var availability: Availability = .yes
}
struct CommandLineOptions {
enum SubCommand {
case noCommand
case version
case listDevices
case command(Command)
}
struct Error: Swift.Error {
@ -22,10 +34,13 @@ struct CommandLineOptions {
argumentParser.printUsage(on: stream)
}
}
struct NoCommandError: Swift.Error { }
private let parser: ArgumentParser
private(set) var subCommand: SubCommand
private static let allCommands: [Command] = [ListDevicesCommand()]
static func parse(commandName: String, arguments: [String]) throws -> CommandLineOptions {
let parser = ArgumentParser(
commandName: commandName,
@ -36,29 +51,53 @@ struct CommandLineOptions {
let binder = ArgumentBinder<CommandLineOptions>()
binder.bind(option: parser.add(
option: "--version",
shortName: "-v",
kind: Bool.self,
usage: "Prints the version and exits"
), to: { options, _ in
options.subCommand = .version
})
parser.add(
subparser: "list-devices",
overview: "List available Xcode Simulator devices"
binder.bind(
option: parser.add(
option: "--version",
kind: Bool.self,
usage: "Prints the version and exits"
), to: { options, _ in
options.subCommand = .version
}
)
var options = CommandLineOptions(parser: parser, subCommand: .noCommand)
do {
let result = try parser.parse(arguments)
switch result.subparser(parser) {
case "list-devices":
options.subCommand = .listDevices
default:
try binder.fill(parseResult: result, into: &options)
binder.bind(
parser: parser,
to: { options, subcommand in
print("Parsed subcommand: \(subcommand)")
}
)
for command in allCommands {
let subparser = parser.add(
subparser: command.name,
overview: command.overview
)
command.addOptions(to: subparser)
}
var options = CommandLineOptions(parser: parser, subCommand: .noCommand)
checkresult: do {
let result = try parser.parse(arguments)
try binder.fill(parseResult: result, into: &options)
// In some cases like --version, we already have a subcommand and are thus already done
guard case SubCommand.noCommand = options.subCommand else {
break checkresult
}
guard
let subcommandName = result.subparser(parser),
var command = allCommands.first(where: { $0.name == subcommandName })
else {
throw Error(
underlyingError: NoCommandError(),
argumentParser: parser
)
}
try command.fillParseResult(result)
options.subCommand = SubCommand.command(command)
} catch {
throw Error(underlyingError: error, argumentParser: parser)
}

View File

@ -0,0 +1,14 @@
import Foundation
import SPMUtility
protocol Command {
/// Subcommand as it will be written on command-line
var name: String { get }
/// Overview for help
var overview: String { get }
func addOptions(to parser: ArgumentParser)
mutating func fillParseResult(_ parseResult: ArgumentParser.Result) throws
func run() throws
}

View File

@ -1,14 +1,57 @@
import Foundation
import SPMUtility
struct ListDevicesCommand {
struct ListDevicesCommand: Command {
let name = "list-devices"
let overview = "List available Xcode Simulator devices"
private let binder = ArgumentBinder<ListDevicesCommand>()
private var filteringOptions = FilteringOptions()
func addOptions(to parser: ArgumentParser) {
binder.bind(option: parser.add(
option: "--availability",
kind: FilteringOptions.Availability.self,
usage: "Only list available devices? yes|no|all, defaults to all"
), to: { command, availability in
command.filteringOptions.availability = availability
})
}
mutating func fillParseResult(_ parseResult: ArgumentParser.Result) throws {
try binder.fill(parseResult: parseResult, into: &self)
}
func run() throws {
let allDevices = try Simctl.listDevices()
for (runtime, devices) in allDevices.devices {
let filteredDevices = devices.filter(using: filteringOptions)
guard !filteredDevices.isEmpty else {
continue
}
print("\(runtime):")
for device in devices {
for device in filteredDevices {
print(" - \(device.name)")
}
}
}
}
extension FilteringOptions.Availability {
func matches(_ availability: Bool) -> Bool {
switch (self, availability) {
case (.yes, true), (.no, false), (.all, _):
return true
case (.yes, false), (.no, true):
return false
}
}
}
extension Sequence where Element == Simctl.Device {
func filter(using filteringOptions: FilteringOptions) -> [Simctl.Device] {
return filter { device in
filteringOptions.availability.matches(device.isAvailable)
}
}
}

View File

@ -35,8 +35,8 @@ public class XcodeSimulatorTool {
options.printUsage(on: stdoutStream)
case .version:
print("xcode-simulator-tool version 0.1")
case .listDevices:
try ListDevicesCommand().run()
case .command(let command):
try command.run()
}
}
}

View File

@ -1,12 +1,16 @@
import XCTest
import Basic
import SPMUtility
@testable import XcodeSimulatorKit
class CommandLineOptionsTests: XCTestCase {
func testListDevices() throws {
let options = try CommandLineOptions.parse(commandName: "command", arguments: ["list-devices"])
XCTAssertEqual(options.subCommand, .listDevices)
guard case let CommandLineOptions.SubCommand.command(command) = options.subCommand else {
return XCTFail("Should be parsed as a command")
}
XCTAssertEqual(command.name, "list-devices")
}
func testArgumentParser() throws {
@ -18,4 +22,31 @@ class CommandLineOptionsTests: XCTestCase {
let result = try parser.parse(["a"])
XCTAssertEqual(result.subparser(parser), "a")
}
func testSubParserWithOptions() throws {
let parser = ArgumentParser(commandName: "SomeBinary", usage: "sample parser", overview: "Sample overview")
let verboseOption = parser.add(
option: "--verbose",
shortName: "-v",
kind: Bool.self,
usage: "Print more information"
)
let fillCupParser = parser.add(subparser: "fill-cup", overview: "Fill cup")
let contentOption = fillCupParser.add(
option: "--content",
kind: String.self,
usage: "Specify content to fill cup with"
)
let result = try parser.parse(["--verbose", "fill-cup", "--content", "milk"])
guard let foundSubparser = result.subparser(parser) else {
return XCTFail("Expected to find a sub parser")
}
XCTAssertEqual(foundSubparser, "fill-cup")
XCTAssertEqual(result.get(contentOption), "milk")
parser.printUsage(on: stdoutStream)
fillCupParser.printUsage(on: stdoutStream)
}
}