summaryrefslogtreecommitdiff
path: root/lib/irc/parser
diff options
context:
space:
mode:
Diffstat (limited to 'lib/irc/parser')
-rw-r--r--lib/irc/parser/isupport.ex32
-rw-r--r--lib/irc/parser/line.ex161
-rw-r--r--lib/irc/parser/numeric.ex5
-rw-r--r--lib/irc/parser/prefix.ex47
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
+