Packages

A library for communicating with an IMAP server

Current section

Files

Jump to
ex_imap_client lib ex_imap_client response_parser transformer.ex
Raw

lib/ex_imap_client/response_parser/transformer.ex

defmodule ExImapClient.ResponseParser.Transformer do
@moduledoc false
@result_callback_fun :result_callback_fun
@stack_callback_fun :stack_callback_fun
def transform_ast(ast) do
iterate(ast)
end
defp iterate(results \\ [], ast)
defp iterate(results, []) do
results
|> Enum.reverse()
end
defp iterate(results, [status]) when status in [:OK, :NO, :BAD] do
final_results =
results
|> Enum.reverse()
{status, final_results}
end
defp iterate(results, [{@result_callback_fun, fun} | rest]) do
iterate(fun.(results), rest)
end
defp iterate(results, [{@stack_callback_fun, fun} | rest]) do
{new_results, new_stack} = fun.(results, rest)
iterate(new_results, new_stack)
end
defp iterate(results, [{:xx_number, kids} | rest]) do
callback =
fn [digits] ->
case Integer.parse(digits) do
{integer, ""} ->
[integer | results]
_ ->
{float, ""} = Float.parse(digits)
[float | results]
end
end
|> wrap_result_callback()
iterate(kids ++ [callback | rest])
end
defp iterate(results, [{:xx_key_value_map, kids} | rest]) do
callback =
fn key_value_pairs ->
map =
key_value_pairs
|> Enum.flat_map(fn
{k, v} ->
[{k, v}]
v ->
[{:untagged, v}]
end)
|> Enum.reduce(Map.new(), fn {k, v}, acc -> acc |> add_key(k, v) end)
[map | results]
end
|> wrap_result_callback()
iterate(kids ++ [callback | rest])
end
defp iterate(results, [{:xx_key_value_pair, kids} | rest]) do
callback =
fn [%{key: [key], value: value}] ->
[{key, value} | results]
end
|> wrap_result_callback()
iterate(kids ++ [callback | rest])
end
defp iterate(results, [{:xx_date, kids} | rest]) do
callback =
fn
[%{year: [year], month: [month], day: [day]}] ->
{:ok, date} = Date.new(year, month, day)
[date | results]
end
|> wrap_result_callback()
iterate(kids ++ [callback | rest])
end
defp iterate(results, [{:xx_date_time_value, kids} | rest]) do
callback =
fn
[%{date: [date], time: [time], offset: [offset]}], stack ->
{:ok, dt} = DateTime.new(date, time)
dt = add_offset(dt, offset, :minute)
{[dt | results], stack}
[%{date: [date], time: [time], timezone: [timezone]}], stack ->
case make_date_time(date, time, timezone) do
{:ok, dt} ->
{[dt | results], stack}
{:error, _reason} = error ->
{[error], []}
end
[%{date: [date], time: [time]}], stack ->
case NaiveDateTime.new(date, time) do
{:ok, dt} ->
{[dt | results], stack}
{:error, _reason} = error ->
{[error], []}
end
end
|> wrap_stack_callback()
iterate(kids ++ [callback | rest])
end
defp iterate(results, [{:xx_microsecond, kids} | rest]) do
callback =
fn [float] ->
ms = (float * 1_000_000) |> trunc()
[ms | results]
end
|> wrap_result_callback()
iterate(kids ++ [callback | rest])
end
defp iterate(results, [{:xx_time_of_day, kids} | rest]) do
callback =
fn [map] ->
%{
hour: [hour],
minute: [minute],
second: [second],
microsecond: [microsecond]
} = make_time_defaults(map)
{:ok, tod} = make_time(hour, minute, second, microsecond)
[tod | results]
end
|> wrap_result_callback()
iterate(kids ++ [callback | rest])
end
defp iterate(results, [{:xx_hours_to_minutes, kids} | rest]) do
callback =
fn [hours] ->
[hours * 60 | results]
end
|> wrap_result_callback()
iterate(kids ++ [callback | rest])
end
defp iterate(results, [{:xx_negate, kids} | rest]) do
callback =
fn [number] ->
[number * -1 | results]
end
|> wrap_result_callback()
iterate(kids ++ [callback | rest])
end
defp iterate(results, [{:xx_add, kids} | rest]) do
callback =
fn numbers ->
[Enum.sum(numbers) | results]
end
|> wrap_result_callback()
iterate(kids ++ [callback | rest])
end
defp iterate(results, [{:xx_nil, _kids} | rest]) do
iterate([nil | results], rest)
end
defp iterate(results, [{:xx_symbol, kids} | rest]) do
callback =
fn [string] ->
[string |> String.to_atom() | results]
end
|> wrap_result_callback()
iterate(kids ++ [callback | rest])
end
defp iterate(results, [{:xx_response_ok, []} | rest]) do
callback =
fn new_results, new_stack ->
{new_results, new_stack ++ [:OK]}
end
|> wrap_stack_callback()
iterate(results, [callback | rest])
end
defp iterate(results, [{:xx_response_no, []} | rest]) do
callback =
fn new_results, new_stack ->
{new_results, new_stack ++ [:NO]}
end
|> wrap_stack_callback()
iterate(results, [callback | rest])
end
defp iterate(results, [{:xx_response_bad, []} | rest]) do
callback =
fn new_results, new_stack ->
{new_results, new_stack ++ [:BAD]}
end
|> wrap_stack_callback()
iterate(results, [callback | rest])
end
# TODO implement
defp iterate(results, [{:xx_offset_map_to_minutes, kids} | rest]) do
callback =
fn [offset_map] ->
%{hour: hour, minute: minute} = make_offset_defaults(offset_map)
offset = hour * 60 + minute
[offset | results]
end
|> wrap_result_callback()
iterate(kids ++ [callback | rest])
end
defp iterate(old_results, [{tag, kids} | rest]) do
callback =
fn new_results ->
new_results =
new_results
|> Enum.reverse()
[{tag, new_results} | old_results]
end
|> wrap_result_callback()
iterate(kids ++ [callback | rest])
end
defp iterate(old_results, [list | rest]) when is_list(list) do
callback =
fn new_results ->
new_results =
new_results
|> Enum.reverse()
[new_results | old_results]
end
|> wrap_result_callback()
iterate(list ++ [callback | rest])
end
defp iterate(results, [scalar | rest]) do
iterate([scalar | results], rest)
end
defp wrap_result_callback(fun) do
{@result_callback_fun, fun}
end
defp wrap_stack_callback(fun) do
{@stack_callback_fun, fun}
end
defp make_time(hour, minute, second, 0) do
Time.new(hour, minute, second)
end
defp make_time(hour, minute, second, microsecond) do
Time.new(hour, minute, second, microsecond)
end
defp make_date_time(date, time, timezone) do
DateTime.new(date, time, timezone, Tzdata.TimeZoneDatabase)
end
defp make_time_defaults(time) do
%{
hour: [0],
minute: [0],
second: [0],
microsecond: [0]
}
|> Map.merge(time)
end
defp make_offset_defaults(offset) do
%{hour: 0, minute: 0}
|> Map.merge(offset)
end
defp add_key(map, key, values) when is_list(values) do
Map.merge(map, %{key => values}, fn _k, v2, v1 -> v1 ++ v2 end)
end
defp add_key(map, key, value) do
add_key(map, key, [value])
end
defp add_offset(date_time, offset, :minute) do
DateTime.add(date_time, offset * 60, :second)
end
end