diff options
Diffstat (limited to 'lib/irc/parser')
-rw-r--r-- | lib/irc/parser/isupport.ex | 32 | ||||
-rw-r--r-- | lib/irc/parser/line.ex | 161 | ||||
-rw-r--r-- | lib/irc/parser/numeric.ex | 5 | ||||
-rw-r--r-- | lib/irc/parser/prefix.ex | 47 |
4 files changed, 202 insertions, 43 deletions
diff --git a/lib/irc/parser/isupport.ex b/lib/irc/parser/isupport.ex index 9dd7f30..dda4220 100644 --- a/lib/irc/parser/isupport.ex +++ b/lib/irc/parser/isupport.ex @@ -1,8 +1,17 @@ defmodule Irc.Parser.Isupport do @moduledoc """ - ISUPPORT list parser + ISUPPORT list parser. """ + @typedoc "Server ISUPPORT." + @type t :: %{key => value} + @type key :: String.t + @type value :: true | Integer.t() | Map.t() | prefix_map + + @type prefix :: String.t() + @type mode :: String.t() + @type prefix_map :: %{prefix() => mode()} + def parse(list, acc \\ %{}) do list = List.flatten(list) Enum.reduce(list, acc, fn(entry, acc) -> @@ -13,25 +22,36 @@ defmodule Irc.Parser.Isupport do else Map.put(acc, entry, true) end - [entry, value] -> Map.put(acc, entry, parse_value(value)) + [entry, value] -> Map.put(acc, entry, parse_value(entry, value)) end end) end - defp parse_value(value) do + defp parse_value(entry, value) do if String.contains?(value, ":") do Enum.reduce(String.split(value, ","), %{}, fn(key_and_value, acc) -> case String.split(key_and_value, ":", parts: 2) do [key] -> Map.put(acc, key, nil) - [key, value] -> Map.put(acc, key, format_value(value)) + [key, value] -> Map.put(acc, key, format_value(key, value)) end end) else - format_value(value) + format_value(entry, value) + end + end + + def format_value("PREFIX", value) do + case String.split(value, ")", parts: 2) do + ["("<>modes, prefixes] -> + modes = String.split(modes, "", trim: true) + prefixes = String.split(prefixes, "", trim: true) + Enum.reduce(Enum.with_index(modes), %{}, fn({mode, position}, acc) -> + Map.put(acc, Enum.at(prefixes, position), mode) + end) end end - def format_value(value) do + def format_value(key, value) do case Integer.parse(value) do {i, ""} -> i _ -> value 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 diff --git a/lib/irc/parser/numeric.ex b/lib/irc/parser/numeric.ex index 3a6e27f..413f13c 100644 --- a/lib/irc/parser/numeric.ex +++ b/lib/irc/parser/numeric.ex @@ -13,8 +13,9 @@ defmodule Irc.Parser.Numeric do defmacro __using__(_) do quote do - require Irc.Numeric - import Irc.Numeric + alias Irc.Parser.Numeric + require Irc.Parser.Numeric + import Irc.Parser.Numeric end end diff --git a/lib/irc/parser/prefix.ex b/lib/irc/parser/prefix.ex new file mode 100644 index 0000000..e446d7c --- /dev/null +++ b/lib/irc/parser/prefix.ex @@ -0,0 +1,47 @@ +defmodule Irc.Parser.Prefix do + + @moduledoc """ + Parser for prefixes. + + Supports multi-prefix. + """ + + @spec parse(String.t, Irc.Connection.t) :: {modes :: [String.t], rest :: nil | String.t} + @doc """ + If `lax` is set to true, the parser will ignore chars until it meets a known prefix. + """ + def parse(string, conn, lax \\ false) do + map = Map.get(conn.isupport, "PREFIX") || %{} + {prefixes, rest} = parse_prefix(string, Map.keys(map), lax) + modes = Enum.map(prefixes, fn(p) -> Map.get(map, p) end) + |> Enum.filter(fn(p) -> p end) + {Enum.reverse(modes), to_string(Enum.reverse(rest))} + end + + defp parse_prefix(string, all_prefixes, lax) do + parse_prefix(to_charlist(string), to_charlist(all_prefixes), [], [], lax) + end + + defp parse_prefix(all = [prefix | rest], all_prefixes, acc, ign, lax) do + prefix_str = to_string([prefix]) + cond do + Enum.member?(all_prefixes, prefix_str) -> parse_prefix(rest, all_prefixes, [prefix_str | acc], ign, lax) + lax && acc == [] -> parse_prefix(rest, all_prefixes, acc, [prefix | ign], lax) + true -> parse_lax(all, acc, []) + end + end + + defp parse_prefix([], _, acc, ign, _) do + {acc, ign} + end + + defp parse_lax([a | rest], prefixes, acc) do + parse_lax(rest, prefixes, [a | acc]) + end + + defp parse_lax([], prefixes, acc) do + {prefixes, acc} + end + +end + |