Skip to content

Instantly share code, notes, and snippets.

@rob-brown
Last active August 29, 2015 14:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rob-brown/68498740903fffab0126 to your computer and use it in GitHub Desktop.
Save rob-brown/68498740903fffab0126 to your computer and use it in GitHub Desktop.
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