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