Simulator/App Finder
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