diff options
Diffstat (limited to 'lib/irc/parser/line.ex')
-rw-r--r-- | lib/irc/parser/line.ex | 161 |
1 files changed, 126 insertions, 35 deletions
diff --git a/lib/irc/parser/line.ex b/lib/irc/parser/line.ex index 3ba2016..17c381c 100644 --- a/lib/irc/parser/line.ex +++ b/lib/irc/parser/line.ex @@ -1,24 +1,87 @@ defmodule Irc.Parser.Line do @moduledoc """ - IRC line parser + IRC line parser/encoder """ - defstruct tags: %{}, source: nil, command: nil, args: [] + @type t :: %__MODULE__{ + tags: Map.t(), + source: Irc.Mask.t() | String.t() | nil, + command: String.t(), + args: list() + } + defstruct __private__: %{}, tags: %{}, source: nil, command: nil, args: [] + @spec parse(string) :: t() @doc "Parse a server-to-client line." def parse(line) do {tags, rest} = parse_tags(line) {source, rest} = parse_source(rest) {command, rest} = parse_cmd(rest) args = parse_args(rest) - %__MODULE__{tags: tags, source: source, command: command, args: args} + private = %{ + at: DateTime.utc_now() + } + %__MODULE__{__private__: private, tags: tags, source: source, command: command, args: args} + end + + @spec encode(t()) :: String.t + @doc "Encode a line" + def encode(line = %__MODULE__{}, conn \\ %Irc.Connection{}) do + [line.source, line.tags, line.command, line.args] + src = if line.source, do: ":"<>to_string(line.source) + tags = Enum.reduce(line.tags, [], fn({k,v}, acc) -> ["#{k}=#{v}" | acc] end) + |> Enum.join(";") + tags = if Enum.member?(conn.capabs || [], "message-tags") && tags != "", do: "@"<>tags + [tags, src, line.command, encode_args(line.args)] + |> Enum.filter(fn(x) -> x end) + |> Enum.join(" ") + end + + def new(command, args \\ [], tags \\ %{}) do + %__MODULE__{command: command, args: args, tags: tags, __private__: %{at: DateTime.utc_now()}} + end + + @doc "Returns the line date (server time if sent, otherwise, parse time)" + @spec at(t()) :: DateTime.t() + def at(%__MODULE__{__private__: %{at: at}, tags: %{"time" => server_time}}) do + case DateTime.from_iso8601(server_time) do + {:ok, date} -> date + _ -> at + end + end + def at(%__MODULE__{__private__: %{at: at}}), do: at + def at(_), do: nil + + @spec to?(t(), Irc.Connection.t() | Irc.Mask.t() | Irc.User.t()) :: boolean + @doc "Returns true if the line is adressed to the connection." + def to?(%__MODULE__{args: [nick | _]}, %{__struct__: s, nick: nick}) when s in [Irc.Connection, Irc.Mask, Irc.User] do + true end + def to?(%__MODULE__{}, %{__struct__: s}) when s in [Irc.Connection, Irc.Mask, Irc.User], do: false + + @spec self?(t(), Irc.Connection.t() | Irc.Mask.t() | Irc.User.t()) :: boolean + @doc "Returns true if the line source is the from the given connection/mask." + def self?(%__MODULE__{source: %Irc.Mask{nick: nick}}, %Irc.Connection{nick: nick}) do + true + end + def self?(%__MODULE__{source: mask = %Irc.Mask{}}, mask = %Irc.Mask{}) do + true + end + def self?(line = %__MODULE__{source: mask = %Irc.Mask{}}, user = %Irc.User{}) do + self?(line, Irc.User.to_mask(user)) + end + def self?(%__MODULE__{source: nick}, %Irc.Connection{nick: nick}) do + true + end + def self?(%__MODULE__{}, %Irc.Connection{}), do: false + def self?(%__MODULE__{}, %Irc.User{}), do: false + def self?(%__MODULE__{}, %Irc.Mask{}), do: false # ARGS - def parse_args(input), do: parse_args(input, []) + defp parse_args(input), do: parse_args(input, []) - def parse_args(input, acc) do + defp parse_args(input, acc) do case parse_arg(input, []) do {:continue, new, rest} -> parse_args(rest, [new | acc]) {:finished, new} -> @@ -28,37 +91,37 @@ defmodule Irc.Parser.Line do end # Final argument is the only argument. - def parse_arg([?: | rest], acc) when length(acc) == 0 do + defp parse_arg([?: | rest], acc) when length(acc) == 0 do final = parse_final_arg(rest, []) {:finished, [final]} end - def parse_arg([0x20, ?: | rest], acc) do + defp parse_arg([0x20, ?: | rest], acc) do final = parse_final_arg(rest, []) {:finished, [final, acc]} end - def parse_arg([0x20 | rest], acc) do + defp parse_arg([0x20 | rest], acc) do {:continue, acc, rest} end - def parse_arg([char | rest], acc) do + defp parse_arg([char | rest], acc) do parse_arg(rest, [char | acc]) end - def parse_arg([], acc) do + defp parse_arg([], acc) do {:finished, [acc]} end - def parse_final_arg([char | rest], acc) do + defp parse_final_arg([char | rest], acc) do parse_final_arg(rest, [char | acc]) end - def parse_final_arg([], acc) do + defp parse_final_arg([], acc) do acc end - def format_args(list) when is_list(list) do + defp format_args(list) when is_list(list) do list |> Enum.map(fn(arg) -> String.trim(acc_to_str(arg)) end) |> Enum.reverse() @@ -66,64 +129,69 @@ defmodule Irc.Parser.Line do # COMMAND - def parse_cmd(input), do: parse_cmd(input, []) + defp parse_cmd(input), do: parse_cmd(input, []) - def parse_cmd([0x20 | rest], acc) do + defp parse_cmd([0x20 | rest], acc) do {acc_to_str(acc), rest} end - def parse_cmd([char | rest], acc) do + defp parse_cmd([char | rest], acc) do parse_cmd(rest, [char | acc]) end # SOURCE - def parse_source([?: | input]), do: parse_source(input, []) - def parse_source(input), do: {nil, input} + defp parse_source([?: | input]), do: parse_source(input, []) + defp parse_source(input), do: {nil, input} - def parse_source([0x20 | rest], acc) do - {acc_to_str(acc), rest} + defp parse_source([0x20 | rest], acc) do + string = acc_to_str(acc) + result = case Irc.Mask.parse(string) do + {:error, _} -> string + {:ok, mask} -> mask + end + {result, rest} end - def parse_source([char | rest], acc), do: parse_source(rest, [char | acc]) + defp parse_source([char | rest], acc), do: parse_source(rest, [char | acc]) # TAGS - def parse_tags([?@ | input]), do: parse_tags(input, []) - def parse_tags(input), do: {%{}, input} + defp parse_tags([?@ | input]), do: parse_tags(input, []) + defp parse_tags(input), do: {%{}, input} - def parse_tags(input, acc) do + defp parse_tags(input, acc) do case parse_tag(input) do {:continue, new, rest} -> parse_tags([new | acc], rest) {:finished, new, rest} -> {format_tags([new | acc]), rest} end end - def parse_tag(input) do + defp parse_tag(input) do parse_tag({[], nil}, input) end - def parse_tag({acc, nil}, [?= | rest]) do + defp parse_tag({acc, nil}, [?= | rest]) do parse_tag({acc, []}, rest) end - def parse_tag({acc, nil}, [?; | rest]) do + defp parse_tag({acc, nil}, [?; | rest]) do {:continue, {acc, true}, rest} end - def parse_tag({acc, nil}, [char | rest]) do + defp parse_tag({acc, nil}, [char | rest]) do parse_tag({[char | acc], nil}, rest) end - def parse_tag({key, acc}, [?; | rest]) do + defp parse_tag({key, acc}, [?; | rest]) do {:continue, {key, acc}, rest} end - def parse_tag({key, acc}, [0x20 | rest]) do + defp parse_tag({key, acc}, [0x20 | rest]) do {:finished, {key, acc}, rest} end - def parse_tag({key, acc}, [char | rest]) do + defp parse_tag({key, acc}, [char | rest]) do parse_tag({key, [char | acc]}, rest) end - def format_tags(list) do + defp format_tags(list) do list |> Enum.map(fn({k,v}) -> {acc_to_str(k), acc_to_str(v)} @@ -131,13 +199,36 @@ defmodule Irc.Parser.Line do |> Enum.into(Map.new) end - def acc_to_str([]), do: nil - def acc_to_str(list) when is_list(list) do + defp acc_to_str([]), do: nil + defp acc_to_str(list) when is_list(list) do list |> Enum.reverse() |> to_string() end - def acc_to_str(other), do: other + defp acc_to_str(other), do: other + + + defp encode_args(args) do + encode_args(args, []) + |> Enum.reverse() + |> Enum.join(" ") + end + + defp encode_args([arg], acc) do + [":"<>arg | acc] + end + + defp encode_args([arg | rest], acc) do + encode_args(rest, [arg | acc]) + end + + defp encode_args([], acc), do: acc + + defimpl String.Chars, for: __MODULE__ do + def to_string(line) do + Irc.Parser.Line.encode(line) + end + end end |