Add —availability filter to list-devices
This commit is contained in:
parent
9a982c41e4
commit
60ea7523a1
|
@ -2,11 +2,23 @@ import Foundation
|
||||||
import Basic
|
import Basic
|
||||||
import SPMUtility
|
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 {
|
struct CommandLineOptions {
|
||||||
enum SubCommand {
|
enum SubCommand {
|
||||||
case noCommand
|
case noCommand
|
||||||
case version
|
case version
|
||||||
case listDevices
|
case command(Command)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Error: Swift.Error {
|
struct Error: Swift.Error {
|
||||||
|
@ -22,10 +34,13 @@ struct CommandLineOptions {
|
||||||
argumentParser.printUsage(on: stream)
|
argumentParser.printUsage(on: stream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
struct NoCommandError: Swift.Error { }
|
||||||
|
|
||||||
private let parser: ArgumentParser
|
private let parser: ArgumentParser
|
||||||
private(set) var subCommand: SubCommand
|
private(set) var subCommand: SubCommand
|
||||||
|
|
||||||
|
private static let allCommands: [Command] = [ListDevicesCommand()]
|
||||||
|
|
||||||
static func parse(commandName: String, arguments: [String]) throws -> CommandLineOptions {
|
static func parse(commandName: String, arguments: [String]) throws -> CommandLineOptions {
|
||||||
let parser = ArgumentParser(
|
let parser = ArgumentParser(
|
||||||
commandName: commandName,
|
commandName: commandName,
|
||||||
|
@ -36,29 +51,53 @@ struct CommandLineOptions {
|
||||||
|
|
||||||
let binder = ArgumentBinder<CommandLineOptions>()
|
let binder = ArgumentBinder<CommandLineOptions>()
|
||||||
|
|
||||||
binder.bind(option: parser.add(
|
binder.bind(
|
||||||
|
option: parser.add(
|
||||||
option: "--version",
|
option: "--version",
|
||||||
shortName: "-v",
|
|
||||||
kind: Bool.self,
|
kind: Bool.self,
|
||||||
usage: "Prints the version and exits"
|
usage: "Prints the version and exits"
|
||||||
), to: { options, _ in
|
), to: { options, _ in
|
||||||
options.subCommand = .version
|
options.subCommand = .version
|
||||||
})
|
}
|
||||||
|
|
||||||
parser.add(
|
|
||||||
subparser: "list-devices",
|
|
||||||
overview: "List available Xcode Simulator devices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var options = CommandLineOptions(parser: parser, subCommand: .noCommand)
|
binder.bind(
|
||||||
do {
|
parser: parser,
|
||||||
let result = try parser.parse(arguments)
|
to: { options, subcommand in
|
||||||
switch result.subparser(parser) {
|
print("Parsed subcommand: \(subcommand)")
|
||||||
case "list-devices":
|
|
||||||
options.subCommand = .listDevices
|
|
||||||
default:
|
|
||||||
try binder.fill(parseResult: result, into: &options)
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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 {
|
} catch {
|
||||||
throw Error(underlyingError: error, argumentParser: parser)
|
throw Error(underlyingError: error, argumentParser: parser)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -1,14 +1,57 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import SPMUtility
|
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 {
|
func run() throws {
|
||||||
let allDevices = try Simctl.listDevices()
|
let allDevices = try Simctl.listDevices()
|
||||||
for (runtime, devices) in allDevices.devices {
|
for (runtime, devices) in allDevices.devices {
|
||||||
|
let filteredDevices = devices.filter(using: filteringOptions)
|
||||||
|
guard !filteredDevices.isEmpty else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
print("\(runtime):")
|
print("\(runtime):")
|
||||||
for device in devices {
|
for device in filteredDevices {
|
||||||
print(" - \(device.name)")
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -35,8 +35,8 @@ public class XcodeSimulatorTool {
|
||||||
options.printUsage(on: stdoutStream)
|
options.printUsage(on: stdoutStream)
|
||||||
case .version:
|
case .version:
|
||||||
print("xcode-simulator-tool version 0.1")
|
print("xcode-simulator-tool version 0.1")
|
||||||
case .listDevices:
|
case .command(let command):
|
||||||
try ListDevicesCommand().run()
|
try command.run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
|
import Basic
|
||||||
import SPMUtility
|
import SPMUtility
|
||||||
@testable import XcodeSimulatorKit
|
@testable import XcodeSimulatorKit
|
||||||
|
|
||||||
class CommandLineOptionsTests: XCTestCase {
|
class CommandLineOptionsTests: XCTestCase {
|
||||||
func testListDevices() throws {
|
func testListDevices() throws {
|
||||||
let options = try CommandLineOptions.parse(commandName: "command", arguments: ["list-devices"])
|
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 {
|
func testArgumentParser() throws {
|
||||||
|
@ -18,4 +22,31 @@ class CommandLineOptionsTests: XCTestCase {
|
||||||
let result = try parser.parse(["a"])
|
let result = try parser.parse(["a"])
|
||||||
XCTAssertEqual(result.subparser(parser), "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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue