Last active
August 29, 2015 14:07
-
-
Save rob-brown/68498740903fffab0126 to your computer and use it in GitHub Desktop.
Simulator/App Finder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Simulator do | |
defstruct name: "", version: "", directory: "" | |
# Uses the home directory to find the devices directory. | |
case :init.get_argument(:home) do | |
{:ok, [[dir]]} -> | |
@simulator_dir :filename.join [dir, "Library", "Developer", "CoreSimulator", "Devices"] | |
_ -> | |
IO.puts "Unable to find the home directory." | |
exit :no_home_directory | |
end | |
## Public | |
def help, do: Helper.help | |
def all() do | |
@simulator_dir | |
|> Helper.list_directory | |
|> Enum.filter(&(File.dir? &1)) | |
|> Enum.map(&(:filename.join &1, "device.plist")) | |
|> Enum.map(&(parse_device_plist &1)) | |
end | |
def list() do | |
all |> Enum.each(&(IO.inspect &1)) | |
end | |
## Helpers | |
defp parse_device_plist(file) do | |
data = File.read! file | |
%Simulator{name: extract_device_name(data), version: extract_version_number(data), directory: :filename.dirname(file)} | |
end | |
defp extract_device_name(data) do | |
# I know regexes aren't good for parsing XML, but they keep the logic simple. | |
~r"<key>name</key>.*<string>(.*)</string>"irms | |
|> Regex.run(data) | |
|> Enum.at(1) | |
end | |
defp extract_version_number(data) do | |
~r"<key>runtime</key>.*<string>(.*)</string>"irms | |
|> Regex.run(data) | |
|> Enum.at(1) | |
|> parse_version_number | |
end | |
defp parse_version_number(version) do | |
~r"iOS-(\d+)-?(\d+)?-?(\d+)?"i | |
|> Regex.run(version) | |
|> _parse_version_number | |
end | |
defp _parse_version_number([_, major]), do: "iOS #{major}" | |
defp _parse_version_number([_, major, minor]), do: "iOS #{major}.#{minor}" | |
defp _parse_version_number([_, major, minor, patch]), do: "iOS #{major}.#{minor}.#{patch}" | |
end | |
defmodule App do | |
defstruct name: "", location: "", simulator: nil | |
def help, do: Helper.help | |
def all() do | |
Simulator.all | |
|> Enum.map(&(apps_for_simulator &1)) | |
|> List.flatten | |
end | |
def find(name) do | |
all |> Enum.filter(&(app_names_match? &1.name, name)) | |
end | |
def list(name \\ nil) | |
def list(nil) do | |
all |> Enum.each(&(IO.inspect &1)) | |
end | |
def list(name) do | |
name |> find |> Enum.each(&(IO.inspect &1)) | |
end | |
def open(name) do | |
name |> find |> Enum.map(&(&1.location)) |> _open(name) | |
end | |
defp _open([], name), do: IO.puts "No app found with name: #{name}." | |
defp _open(dirs, _name), do: Enum.each(dirs, &(open_directory &1)) | |
## Helpers | |
defp app_names_match?(name1, name2) do | |
normalize(name1) == normalize(name2) | |
end | |
defp normalize(app_name) do | |
app_name | |
|> String.downcase | |
|> String.replace " ", "" | |
end | |
defp apps_for_simulator(simulator) do | |
dir = simulator.directory | |
apps = apps_from_old_directory(dir) ++ apps_from_new_directory(dir) | |
Enum.map(apps, &(%App{&1 | simulator: simulator})) | |
end | |
defp apps_from_old_directory(directory) do | |
app_dir = :filename.join [directory, "data", "Applications"] | |
_apps_from_old_directory(app_dir, File.exists?(app_dir)) | |
end | |
defp _apps_from_old_directory(_directory, false), do: [] | |
defp _apps_from_old_directory(directory, true) do | |
directory | |
|> Helper.list_directory | |
|> Enum.map(&(apps_in_directory &1)) | |
|> List.flatten | |
end | |
defp apps_in_directory(directory) do | |
directory | |
|> File.ls! | |
|> Enum.filter(&(&1 =~ ~r".*\.app")) | |
|> Enum.map(&(String.replace(&1, ".app", ""))) | |
|> Enum.map(&(%App{name: &1, location: directory})) | |
end | |
defp apps_from_new_directory(directory) do | |
app_dir = :filename.join [directory, "data", "Containers", "Data", "Application"] | |
_apps_from_new_directory(app_dir, File.exists?(app_dir)) | |
end | |
defp _apps_from_new_directory(_directory, false), do: [] | |
defp _apps_from_new_directory(directory, true) do | |
directory | |
|> Helper.list_directory | |
|> Enum.map(&(apps_inferred_from_cache &1)) | |
|> List.flatten | |
end | |
defp apps_inferred_from_cache(directory) do | |
:filename.join([directory, "Library", "Caches"]) | |
|> Helper.list_directory | |
|> Enum.filter(&(cache_directory? &1)) | |
|> Enum.map(&(app_name_from_cache &1)) | |
|> Enum.map(&(%App{name: &1, location: directory})) | |
end | |
defp cache_directory?(directory) do | |
:filename.basename(directory) =~ ~r".*\..*" | |
end | |
defp app_name_from_cache(directory) do | |
directory | |
|> String.split(".") | |
|> List.last | |
|> String.replace("-", " ") | |
end | |
defp open_directory(directory) do | |
System.cmd "open", [directory] | |
end | |
end | |
# Contains code common to Simulator and App. | |
defmodule Helper do | |
def help do | |
IO.puts """ | |
Start script from shell: | |
iex simulator.exs | |
List simulators: | |
Simulator.list | |
List apps: | |
App.list | |
App.list "<app name>" | |
Open app in Finder (may open more than one directory): | |
App.open "<app name>" | |
Show this help again: | |
App.help | |
Simulator.help | |
""" | |
end | |
def list_directory(directory) do | |
directory | |
|> File.ls! | |
|> Enum.filter(&(not hidden_file? &1)) | |
|> Enum.map(&(:filename.join directory, &1)) | |
end | |
def hidden_file?(filename) do | |
filename | |
|> :filename.basename | |
|> String.starts_with?(".") | |
end | |
end | |
## Inspect implementations | |
defimpl Inspect, for: Simulator do | |
def inspect(sim, _opts) do | |
"#{sim.name} #{sim.version}" | |
end | |
end | |
defimpl Inspect, for: App do | |
def inspect(app, _opt) do | |
"#{app.name} | #{inspect app.simulator}" | |
end | |
end | |
# Shows the help when this file is run as a script. | |
Simulator.help |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment