Skip to content

Commit

Permalink
Allow changing of a single simulator's location (#7)
Browse files Browse the repository at this point in the history
This allows you to pass a `-s` argument followed by the simulator's
display name in order to set the location of a single simulator, instead
of all booted simulators.
  • Loading branch information
Keith Smiley authored Dec 28, 2017
1 parent a52b6bf commit a431dd9
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 15 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.DS_Store
set-simulator-location
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ Or using place search:
$ set-simulator-location -q Lyft HQ San Francisco
```

By default the location is set on all booted simulators. If you'd like
to change it for only one of the booted simulators you can pass `-s`
followed by the simulator's display name:

```sh
$ set-simulator-location -q Lyft HQ San Francisco -s iPhone X
```

NOTE: If you have multiple booted simulators with the same name, the
location will be set on all of them.

## Installation

With [`homebrew`](http://brew.sh/):
Expand Down
19 changes: 19 additions & 0 deletions sources/errors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
enum SimulatorFetchError: Error, CustomStringConvertible {
case simctlFailed
case failedToReadOutput
case noBootedSimulators
case noMatchingSimulators(name: String)

var description: String {
switch self {
case .simctlFailed:
return "Running `simctl list` failed"
case .failedToReadOutput:
return "Failed to read output from simctl"
case .noBootedSimulators:
return "No simulators are currently booted"
case .noMatchingSimulators(let name):
return "No booted simulators named '\(name)'"
}
}
}
20 changes: 18 additions & 2 deletions sources/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@ import CoreLocation
import Foundation

var arguments = CommandLine.arguments.dropFirst()

var deviceName: String?
if let argumentIndex = arguments.index(of: "-s") {
let device = arguments.suffix(from: argumentIndex).dropFirst().joined(separator: " ")
if device.isEmpty {
exitWithUsage()
} else {
deviceName = device
}

arguments = arguments.prefix(upTo: argumentIndex)
}

guard let flag = arguments.popFirst() else {
exitWithUsage()
}
Expand All @@ -18,10 +31,13 @@ guard let command = commands[flag] else {
switch command(Array(arguments)) {
case .success(let coordinate) where coordinate.isValid:
do {
postNotification(for: coordinate, to: try getBootedSimulators())
let bootedSimulators = try getBootedSimulators()
let simulators = try deviceName.map { try getSimulators(named: $0, from: bootedSimulators) }
?? bootedSimulators
postNotification(for: coordinate, to: simulators.map { $0.udid })
print("Setting location to \(coordinate.latitude) \(coordinate.longitude)")
} catch let error as SimulatorFetchError {
exitWithUsage(error: error.rawValue)
exitWithUsage(error: error.description)
}
case .success(let coordinate):
exitWithUsage(error: "Coordinate: \(coordinate) is invalid")
Expand Down
2 changes: 1 addition & 1 deletion sources/query.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func findLocation(from arguments: [String]) -> Result<CLLocationCoordinate2D> {
}

guard let coordinate = placemark.location?.coordinate else {
return .failure("No coordinate found for '\(placemark.name)'")
return .failure("No coordinate found for '\(placemark.name ?? "")'")
}

return .success(coordinate)
Expand Down
47 changes: 36 additions & 11 deletions sources/simulators.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
import Foundation

enum SimulatorFetchError: String, Error {
case simctlFailed = "Running `simctl list` failed"
case failedToReadOutput = "Failed to read output from simctl"
case noBootedSimulators = "No simulators are currently booted"
struct Simulator {
fileprivate enum State: String {
case shutdown = "Shutdown"
case booted = "Booted"
}

fileprivate var state: State
let name: String
let udid: String

fileprivate init?(dictionary: [String: String]) {
guard let state = dictionary["state"].flatMap(State.init), let udid = dictionary["udid"],
let name = dictionary["name"] else
{
return nil
}

self.name = name
self.state = state
self.udid = udid
}
}

func getSimulators(named name: String, from simulators: [Simulator]) throws -> [Simulator] {
let matchingSimulators = simulators.filter { $0.name.lowercased() == name.lowercased() }
if matchingSimulators.isEmpty {
throw SimulatorFetchError.noMatchingSimulators(name: name)
}

return matchingSimulators
}

func getBootedSimulators() throws -> [String] {
func getBootedSimulators() throws -> [Simulator] {
let task = Process()
task.launchPath = "/usr/bin/xcrun"
task.arguments = ["simctl", "list", "-j", "devices"]
Expand All @@ -29,14 +55,13 @@ func getBootedSimulators() throws -> [String] {
}

let devices = json["devices"] as? [String: [[String: String]]] ?? [:]
let bootedIDs = devices
.flatMap { $1 }
.filter { $0["state"] == "Booted" }
.flatMap { $0["udid"] }
let bootedSimulators = devices.flatMap { $1 }
.flatMap(Simulator.init)
.filter { $0.state == .booted }

if bootedIDs.isEmpty {
if bootedSimulators.isEmpty {
throw SimulatorFetchError.noBootedSimulators
}

return bootedIDs
return bootedSimulators
}
2 changes: 1 addition & 1 deletion sources/stderr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ func exitWithUsage(error: String? = nil) -> Never {
print(error, terminator: "\n\n", to: &stderrStream)
}

print("Usage set-simulator-location [-c 0 0|-q San Francisco]", to: &stderrStream)
print("Usage: set-simulator-location [-c 0 0|-q San Francisco] [-s Simulator Name]", to: &stderrStream)
exit(EXIT_FAILURE)
}

0 comments on commit a431dd9

Please sign in to comment.