diff options
author | href <href@random.sh> | 2019-12-30 11:30:01 +0100 |
---|---|---|
committer | href <href@random.sh> | 2019-12-30 11:30:01 +0100 |
commit | bc506bcd62cfb1bc62a81600c214fd849b54248d (patch) | |
tree | 8c7a2f474c38389591610c4ad294664ebdfe5bdd /lib/irc/parser |
Initial commit...
Diffstat (limited to 'lib/irc/parser')
-rw-r--r-- | lib/irc/parser/isupport.ex | 41 | ||||
-rw-r--r-- | lib/irc/parser/line.ex | 143 | ||||
-rw-r--r-- | lib/irc/parser/mode.ex | 32 | ||||
-rw-r--r-- | lib/irc/parser/numeric.ex | 72 |
4 files changed, 288 insertions, 0 deletions
diff --git a/lib/irc/parser/isupport.ex b/lib/irc/parser/isupport.ex new file mode 100644 index 0000000..9dd7f30 --- /dev/null +++ b/lib/irc/parser/isupport.ex @@ -0,0 +1,41 @@ +defmodule Irc.Parser.Isupport do + @moduledoc """ + ISUPPORT list parser + """ + + def parse(list, acc \\ %{}) do + list = List.flatten(list) + Enum.reduce(list, acc, fn(entry, acc) -> + case String.split(entry, "=", parts: 2) do + [entry] -> + if String.contains?(entry, " ") do + acc + else + Map.put(acc, entry, true) + end + [entry, value] -> Map.put(acc, entry, parse_value(value)) + end + end) + end + + defp parse_value(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)) + end + end) + else + format_value(value) + end + end + + def format_value(value) do + case Integer.parse(value) do + {i, ""} -> i + _ -> value + end + end + +end diff --git a/lib/irc/parser/line.ex b/lib/irc/parser/line.ex new file mode 100644 index 0000000..3ba2016 --- /dev/null +++ b/lib/irc/parser/line.ex @@ -0,0 +1,143 @@ +defmodule Irc.Parser.Line do + @moduledoc """ + IRC line parser + """ + + defstruct tags: %{}, source: nil, command: nil, args: [] + + @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} + end + + # ARGS + + def parse_args(input), do: parse_args(input, []) + + def parse_args(input, acc) do + case parse_arg(input, []) do + {:continue, new, rest} -> parse_args(rest, [new | acc]) + {:finished, new} -> + list = new ++ acc + format_args(list) + end + end + + # Final argument is the only argument. + def parse_arg([?: | rest], acc) when length(acc) == 0 do + final = parse_final_arg(rest, []) + {:finished, [final]} + end + + def parse_arg([0x20, ?: | rest], acc) do + final = parse_final_arg(rest, []) + {:finished, [final, acc]} + end + + def parse_arg([0x20 | rest], acc) do + {:continue, acc, rest} + end + + def parse_arg([char | rest], acc) do + parse_arg(rest, [char | acc]) + end + + def parse_arg([], acc) do + {:finished, [acc]} + end + + def parse_final_arg([char | rest], acc) do + parse_final_arg(rest, [char | acc]) + end + + def parse_final_arg([], acc) do + acc + end + + def format_args(list) when is_list(list) do + list + |> Enum.map(fn(arg) -> String.trim(acc_to_str(arg)) end) + |> Enum.reverse() + end + + # COMMAND + + def parse_cmd(input), do: parse_cmd(input, []) + + def parse_cmd([0x20 | rest], acc) do + {acc_to_str(acc), rest} + end + + def 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} + + def parse_source([0x20 | rest], acc) do + {acc_to_str(acc), rest} + end + def 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} + + def 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 + parse_tag({[], nil}, input) + end + + def parse_tag({acc, nil}, [?= | rest]) do + parse_tag({acc, []}, rest) + end + def parse_tag({acc, nil}, [?; | rest]) do + {:continue, {acc, true}, rest} + end + def parse_tag({acc, nil}, [char | rest]) do + parse_tag({[char | acc], nil}, rest) + end + + def parse_tag({key, acc}, [?; | rest]) do + {:continue, {key, acc}, rest} + end + + def parse_tag({key, acc}, [0x20 | rest]) do + {:finished, {key, acc}, rest} + end + def parse_tag({key, acc}, [char | rest]) do + parse_tag({key, [char | acc]}, rest) + end + + def format_tags(list) do + list + |> Enum.map(fn({k,v}) -> + {acc_to_str(k), acc_to_str(v)} + end) + |> Enum.into(Map.new) + end + + def acc_to_str([]), do: nil + def acc_to_str(list) when is_list(list) do + list + |> Enum.reverse() + |> to_string() + end + def acc_to_str(other), do: other + +end + diff --git a/lib/irc/parser/mode.ex b/lib/irc/parser/mode.ex new file mode 100644 index 0000000..d4c0a16 --- /dev/null +++ b/lib/irc/parser/mode.ex @@ -0,0 +1,32 @@ +defmodule Irc.Parser.Mode do + @moduledoc """ + Mode change parser + """ + + def changes(string, previous) do + list = to_charlist(string) + acc_changes(list, nil, previous) + end + + defp acc_changes([?+ | rest], _, acc) do + acc_changes(rest, true, acc) + end + + defp acc_changes([?- | rest], _, acc) do + acc_changes(rest, false, acc) + end + + defp acc_changes([0x20 | _], _, acc), do: acc + + defp acc_changes([char | rest], action, acc) do + acc = if action do + [char | acc] + else + List.delete(acc, char) + end + acc_changes(rest, action, acc) + end + + defp acc_changes([], _, acc), do: acc + +end diff --git a/lib/irc/parser/numeric.ex b/lib/irc/parser/numeric.ex new file mode 100644 index 0000000..3a6e27f --- /dev/null +++ b/lib/irc/parser/numeric.ex @@ -0,0 +1,72 @@ +defmodule Irc.Parser.Numeric do + require Logger + + @moduledoc """ + Helpers for using IRC Numerics. + + * Use macros to translate from `rpl_MOTDSTART` (a macro is generated for every numeric name). + * Convert a numeric to its name (name_numeric()) + * Convert a name to its numeric (to_numeric()) + * Check if a numeric is an error (error_numeric?()) + + """ + + defmacro __using__(_) do + quote do + require Irc.Numeric + import Irc.Numeric + end + end + + @numerics File.read!(Path.join(:code.priv_dir(:irc), "numerics.txt")) + |> String.split("\n") + |> Enum.map(fn(line) -> + case String.split(String.trim(line), " ", parts: 2) do + ["#"<>_, _] -> nil + ["#"<>_] -> nil + [numeric, full_name = "ERR_" <> name] -> {numeric, name, true, full_name} + [numeric, full_name = "RPL_" <> name] -> {numeric, name, false, full_name} + line -> + Logger.debug "Invalid line in numeric.txt: #{inspect line}" + end + end) + |> Enum.filter(fn(n) -> n end) + + for {num, name, error, full_name} <- @numerics do + prefix = if error, do: "err_", else: "rpl_" + fname = "#{prefix}#{name}" |> String.to_atom() + + defmacro unquote(fname)() do + unquote(num) + end + + def error_numeric?(unquote(num)) do + unquote(error) + end + def error_numeric?(unquote(name)) do + unquote(error) + end + def error_numeric?(unquote(full_name)) do + unquote(error) + end + + def name_numeric(unquote(num)) do + unquote(full_name) + end + + def to_numeric(unquote(num)) do + unquote(num) + end + def to_numeric(unquote(name)) do + unquote(num) + end + def to_numeric(unquote(fname)) do + unquote(num) + end + def to_numeric(unquote(full_name)) do + unquote(num) + end + + end + +end |