Packages
A simple Elixir package for parsing user agent strings with the help of BrowserScope's UA database
Current section
Files
Jump to
Current section
Files
lib/ua/parser.ex
defmodule UA.Parser do
use GenServer
def start_link() do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def parse(user_agent) do
GenServer.call(__MODULE__, {:parse, user_agent})
end
def detect_browser(user_agent) do
GenServer.call(__MODULE__, {:detect_browser, user_agent})
end
def detect_os(user_agent) do
GenServer.call(__MODULE__, {:detect_os, user_agent})
end
def detect_device(user_agent) do
GenServer.call(__MODULE__, {:detect_device, user_agent})
end
@data UA.Data.preload
defp data do
@data
end
def handle_call({:parse, ua}, _from, state) do
browser = do_detect_browser(ua)
os = do_detect_os(ua)
device = do_detect_device(ua)
{:reply, {browser, os, device}, state}
end
def handle_call({:detect_browser, ua}, _from, state) do
{:reply, do_detect_browser(ua), state}
end
def handle_call({:detect_os, ua}, _from, state) do
{:reply, do_detect_os(ua), state}
end
def handle_call({:detect_device, ua}, _from, state) do
{:reply, do_detect_device(ua), state}
end
defp do_detect_browser(ua) do
data[:user_agent_parsers]
|> Enum.find(fn(%{regex: regex}) -> Regex.run(regex, ua) end)
|> parse_browser(ua)
end
defp parse_browser(nil, _), do: :unknown
defp parse_browser(parser, ua) do
family_repl = parser[:family_replacement]
v1_repl = parser[:v1_replacement]
v2_repl = parser[:v2_replacement]
version = [v1_repl, v2_repl] |> Enum.filter(&(&1 != nil)) |> version_from_parts
case Regex.run(parser[:regex], ua) do
[_, family | ver_parts] ->
%UA.Browser{
family: family_repl || family,
version: version || version_from_parts(ver_parts)
}
[_] ->
%UA.Browser{
family: family_repl,
version: version || :unknown
}
end
end
def do_detect_os(ua) do
data[:os_parsers]
|> Enum.find(fn(%{regex: regex}) -> Regex.run(regex, ua) end)
|> parse_os(ua)
end
defp parse_os(nil, _), do: :unknown
defp parse_os(parser, ua) do
os_repl = parser[:os_replacement]
v1_repl = parser[:os_v1_replacement]
v2_repl = parser[:os_v2_replacement]
v3_repl = parser[:os_v3_replacement]
version = [v1_repl, v2_repl, v3_repl] |> Enum.filter(&(&1 != nil)) |> version_from_parts
case Regex.run(parser[:regex], ua) do
[_, os | ver_parts] ->
%UA.OS{
family: os_repl || os,
version: version || version_from_parts(ver_parts)
}
[_] ->
%UA.OS{ family: os_repl, version: version || :unknown }
end
end
def do_detect_device(ua) do
data[:device_parsers]
|> Enum.find(fn(%{regex: regex}) -> Regex.run(regex, ua) end)
|> parse_device(ua)
end
defp parse_device(nil, _), do: :unknown
defp parse_device(parser, ua) do
substitutions = case Regex.run(parser[:regex], ua) do
[_, device, brand, model] -> [{"$1", device}, {"$2", brand}, {"$3", model}]
[_, device, brand] -> [{"$1", device}, {"$2", brand}]
[_, device] -> [{"$1", device}]
[_] -> []
end
%UA.Device{
name: make_substitutions(parser[:device_replacement], substitutions),
brand: make_substitutions(parser[:brand_replacement], substitutions),
model: make_substitutions(parser[:model_replacement], substitutions)
}
end
defp version_from_parts([]), do: nil
defp version_from_parts(parts), do: Enum.join(parts, ".")
defp make_substitutions(:unknown, _), do: :unknown
defp make_substitutions(string, substitutions) do
substitutions |> Enum.reduce(string, fn({k, v}, string) ->
String.replace(string, k, v)
end)
end
end