commit: 093d2344d2570527c5d6a57df1f535e740ac58e3
parent 76cfb574a3ecfd80f43ef9abe2017a081a6314a4
Author: Egor Kislitsyn <egor@kislitsyn.com>
Date: Fri, 21 Jun 2019 20:09:48 +0700
Refactor parser
Refactor parser
Diffstat:
4 files changed, 68 insertions(+), 108 deletions(-)
diff --git a/lib/auto_linker.ex b/lib/auto_linker.ex
@@ -31,7 +31,6 @@ defmodule AutoLinker do
* `strip_prefix: true` - Strip the scheme prefix
* `exclude_class: false` - Set to a class name when you don't want urls auto linked in the html of the give class
* `exclude_id: false` - Set to an element id when you don't want urls auto linked in the html of the give element
- * `exclude_patterns: ["```"]` - Don't link anything between the the pattern
* `email: false` - link email links
* `mention: false` - link @mentions (when `true`, requires `mention_prefix` or `mention_handler` options to be set)
* `mention_prefix: nil` - a prefix to build a link for a mention (example: `https://example.com/user/`)
diff --git a/lib/auto_linker/builder.ex b/lib/auto_linker/builder.ex
@@ -25,7 +25,10 @@ defmodule AutoLinker.Builder do
end
defp build_attrs(attrs, _, opts, :rel) do
- if rel = Map.get(opts, :rel, "noopener noreferrer"), do: [{:rel, rel} | attrs], else: attrs
+ case Map.get(opts, :rel, "noopener noreferrer") do
+ rel when is_binary(rel) -> [{:rel, rel} | attrs]
+ _ -> attrs
+ end
end
defp build_attrs(attrs, _, opts, :target) do
@@ -33,7 +36,10 @@ defmodule AutoLinker.Builder do
end
defp build_attrs(attrs, _, opts, :class) do
- if cls = Map.get(opts, :class, "auto-linker"), do: [{:class, cls} | attrs], else: attrs
+ case Map.get(opts, :class, "auto-linker") do
+ cls when is_binary(cls) -> [{:class, cls} | attrs]
+ _ -> attrs
+ end
end
defp build_attrs(attrs, url, _opts, :href) do
diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex
@@ -46,7 +46,7 @@ defmodule AutoLinker.Parser do
@doc """
Parse the given string, identifying items to link.
- Parses the string, replacing the matching urls and phone numbers with an html link.
+ Parses the string, replacing the matching urls with an html link.
## Examples
@@ -54,6 +54,8 @@ defmodule AutoLinker.Parser do
~s{Check out <a href="http://google.com" class="auto-linker" target="_blank" rel="noopener noreferrer">google.com</a>}
"""
+ @types [:url, :email, :hashtag, :mention, :extra]
+
def parse(input, opts \\ %{})
def parse(input, opts) when is_binary(input), do: {input, %{}} |> parse(opts) |> elem(0)
def parse(input, list) when is_list(list), do: parse(input, Enum.into(list, %{}))
@@ -61,157 +63,115 @@ defmodule AutoLinker.Parser do
def parse(input, opts) do
opts = Map.merge(@default_opts, opts)
+ Enum.reduce(opts, input, fn
+ {type, true}, input when type in @types ->
+ do_parse(input, opts, {"", "", :parsing}, type)
- do_parse(input, Map.merge(config, opts))
- end
-
- defp do_parse(input, %{url: false} = opts), do: do_parse(input, Map.delete(opts, :url))
-
- defp do_parse(input, %{hashtag: true} = opts) do
- input
- |> do_parse(opts, {"", "", :parsing}, &check_and_link_hashtag/3)
- |> do_parse(Map.delete(opts, :hashtag))
- end
-
- defp do_parse(input, %{extra: true} = opts) do
- input
- |> do_parse(opts, {"", "", :parsing}, &check_and_link_extra/3)
- |> do_parse(Map.delete(opts, :extra))
- end
-
- defp do_parse(input, %{email: true} = opts) do
- input
- |> do_parse(opts, {"", "", :parsing}, &check_and_link_email/3)
- |> do_parse(Map.delete(opts, :email))
- end
-
- defp do_parse({text, user_acc}, %{url: _} = opts) do
- input =
- with exclude <- Map.get(opts, :exclude_patterns),
- true <- is_list(exclude),
- true <- String.starts_with?(text, exclude) do
- {text, user_acc}
- else
- _ ->
- do_parse(
- {text, user_acc},
- opts,
- {"", "", :parsing},
- &check_and_link/3
- )
- end
-
- do_parse(input, Map.delete(opts, :url))
- end
-
- defp do_parse(input, %{mention: true} = opts) do
- input
- |> do_parse(opts, {"", "", :parsing}, &check_and_link_mention/3)
- |> do_parse(Map.delete(opts, :mention))
+ _, input ->
+ input
+ end)
end
- defp do_parse(input, _), do: input
-
defp do_parse({"", user_acc}, _opts, {"", acc, _}, _handler),
do: {acc, user_acc}
- defp do_parse({"<a" <> text, user_acc}, opts, {buffer, acc, :parsing}, handler),
- do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "<a", :skip}, handler)
+ defp do_parse({"<a" <> text, user_acc}, opts, {buffer, acc, :parsing}, type),
+ do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "<a", :skip}, type)
- defp do_parse({"<pre" <> text, user_acc}, opts, {buffer, acc, :parsing}, handler),
- do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "<pre", :skip}, handler)
+ defp do_parse({"<pre" <> text, user_acc}, opts, {buffer, acc, :parsing}, type),
+ do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "<pre", :skip}, type)
- defp do_parse({"<code" <> text, user_acc}, opts, {buffer, acc, :parsing}, handler),
- do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "<code", :skip}, handler)
+ defp do_parse({"<code" <> text, user_acc}, opts, {buffer, acc, :parsing}, type),
+ do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "<code", :skip}, type)
- defp do_parse({"</a>" <> text, user_acc}, opts, {buffer, acc, :skip}, handler),
- do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "</a>", :parsing}, handler)
+ defp do_parse({"</a>" <> text, user_acc}, opts, {buffer, acc, :skip}, type),
+ do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "</a>", :parsing}, type)
- defp do_parse({"</pre>" <> text, user_acc}, opts, {buffer, acc, :skip}, handler),
- do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "</pre>", :parsing}, handler)
+ defp do_parse({"</pre>" <> text, user_acc}, opts, {buffer, acc, :skip}, type),
+ do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "</pre>", :parsing}, type)
- defp do_parse({"</code>" <> text, user_acc}, opts, {buffer, acc, :skip}, handler),
- do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "</code>", :parsing}, handler)
+ defp do_parse({"</code>" <> text, user_acc}, opts, {buffer, acc, :skip}, type),
+ do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "</code>", :parsing}, type)
- defp do_parse({"<" <> text, user_acc}, opts, {"", acc, :parsing}, handler),
- do: do_parse({text, user_acc}, opts, {"<", acc, {:open, 1}}, handler)
+ defp do_parse({"<" <> text, user_acc}, opts, {"", acc, :parsing}, type),
+ do: do_parse({text, user_acc}, opts, {"<", acc, {:open, 1}}, type)
- defp do_parse({"<" <> text, user_acc}, opts, {"", acc, {:html, level}}, handler) do
- do_parse({text, user_acc}, opts, {"<", acc, {:open, level + 1}}, handler)
+ defp do_parse({"<" <> text, user_acc}, opts, {"", acc, {:html, level}}, type) do
+ do_parse({text, user_acc}, opts, {"<", acc, {:open, level + 1}}, type)
end
- defp do_parse({">" <> text, user_acc}, opts, {buffer, acc, {:attrs, level}}, handler),
+ defp do_parse({">" <> text, user_acc}, opts, {buffer, acc, {:attrs, level}}, type),
do:
do_parse(
{text, user_acc},
opts,
{"", acc <> buffer <> ">", {:html, level}},
- handler
+ type
)
- defp do_parse({<<ch::8>> <> text, user_acc}, opts, {"", acc, {:attrs, level}}, handler) do
- do_parse({text, user_acc}, opts, {"", acc <> <<ch::8>>, {:attrs, level}}, handler)
+ defp do_parse({<<ch::8>> <> text, user_acc}, opts, {"", acc, {:attrs, level}}, type) do
+ do_parse({text, user_acc}, opts, {"", acc <> <<ch::8>>, {:attrs, level}}, type)
end
- defp do_parse({"</" <> text, user_acc}, opts, {buffer, acc, {:html, level}}, handler) do
- {buffer, user_acc} = run_handler(handler, buffer, opts, user_acc)
+ defp do_parse({"</" <> text, user_acc}, opts, {buffer, acc, {:html, level}}, type) do
+ {buffer, user_acc} = link(type, buffer, opts, user_acc)
do_parse(
{text, user_acc},
opts,
{"", acc <> buffer <> "</", {:close, level}},
- handler
+ type
)
end
- defp do_parse({">" <> text, user_acc}, opts, {buffer, acc, {:close, 1}}, handler),
- do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> ">", :parsing}, handler)
+ defp do_parse({">" <> text, user_acc}, opts, {buffer, acc, {:close, 1}}, type),
+ do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> ">", :parsing}, type)
- defp do_parse({">" <> text, user_acc}, opts, {buffer, acc, {:close, level}}, handler),
+ defp do_parse({">" <> text, user_acc}, opts, {buffer, acc, {:close, level}}, type),
do:
do_parse(
{text, user_acc},
opts,
{"", acc <> buffer <> ">", {:html, level - 1}},
- handler
+ type
)
- defp do_parse({text, user_acc}, opts, {buffer, acc, {:open, level}}, handler) do
- do_parse({text, user_acc}, opts, {"", acc <> buffer, {:attrs, level}}, handler)
+ defp do_parse({text, user_acc}, opts, {buffer, acc, {:open, level}}, type) do
+ do_parse({text, user_acc}, opts, {"", acc <> buffer, {:attrs, level}}, type)
end
defp do_parse(
{<<char::bytes-size(1), text::binary>>, user_acc},
opts,
{buffer, acc, state},
- handler
+ type
)
when char in [" ", "\r", "\n"] do
- {buffer, user_acc} = run_handler(handler, buffer, opts, user_acc)
+ {buffer, user_acc} = link(type, buffer, opts, user_acc)
do_parse(
{text, user_acc},
opts,
{"", acc <> buffer <> char, state},
- handler
+ type
)
end
- defp do_parse({<<ch::8>>, user_acc}, opts, {buffer, acc, state}, handler) do
- {buffer, user_acc} = run_handler(handler, buffer <> <<ch::8>>, opts, user_acc)
+ defp do_parse({<<ch::8>>, user_acc}, opts, {buffer, acc, state}, type) do
+ {buffer, user_acc} = link(type, buffer <> <<ch::8>>, opts, user_acc)
do_parse(
{"", user_acc},
opts,
{"", acc <> buffer, state},
- handler
+ type
)
end
- defp do_parse({<<ch::8>> <> text, user_acc}, opts, {buffer, acc, state}, handler),
- do: do_parse({text, user_acc}, opts, {buffer <> <<ch::8>>, acc, state}, handler)
+ defp do_parse({<<ch::8>> <> text, user_acc}, opts, {buffer, acc, state}, type),
+ do: do_parse({text, user_acc}, opts, {buffer <> <<ch::8>>, acc, state}, type)
- def check_and_link(buffer, opts, _user_acc) do
+ def check_and_link(:url, buffer, opts, _user_acc) do
str = strip_parens(buffer)
if url?(str, opts) do
@@ -224,36 +184,36 @@ defmodule AutoLinker.Parser do
end
end
- defp strip_parens("(" <> buffer) do
- ~r/[^\)]*/ |> Regex.run(buffer) |> hd()
- end
-
- defp strip_parens(buffer), do: buffer
-
- def check_and_link_email(buffer, opts, _user_acc) do
+ def check_and_link(:email, buffer, opts, _user_acc) do
if email?(buffer, opts), do: link_email(buffer, opts), else: buffer
end
- def check_and_link_mention(buffer, opts, user_acc) do
+ def check_and_link(:mention, buffer, opts, user_acc) do
buffer
|> match_mention
|> link_mention(buffer, opts, user_acc)
end
- def check_and_link_hashtag(buffer, opts, user_acc) do
+ def check_and_link(:hashtag, buffer, opts, user_acc) do
buffer
|> match_hashtag
|> link_hashtag(buffer, opts, user_acc)
end
- def check_and_link_extra("xmpp:" <> handle, opts, _user_acc) do
+ def check_and_link(:extra, "xmpp:" <> handle, opts, _user_acc) do
if email?(handle, opts), do: link_extra("xmpp:" <> handle, opts), else: handle
end
- def check_and_link_extra(buffer, opts, _user_acc) do
+ def check_and_link(:extra, buffer, opts, _user_acc) do
if String.starts_with?(buffer, @prefix_extra), do: link_extra(buffer, opts), else: buffer
end
+ defp strip_parens("(" <> buffer) do
+ ~r/[^\)]*/ |> Regex.run(buffer) |> hd()
+ end
+
+ defp strip_parens(buffer), do: buffer
+
# @doc false
def url?(buffer, opts) do
@@ -363,8 +323,8 @@ defmodule AutoLinker.Parser do
Builder.create_extra_link(buffer, opts)
end
- defp run_handler(handler, buffer, opts, user_acc) do
- case handler.(buffer, opts, user_acc) do
+ defp link(type, buffer, opts, user_acc) do
+ case check_and_link(type, buffer, opts, user_acc) do
{buffer, user_acc} -> {buffer, user_acc}
buffer -> {buffer, user_acc}
end
diff --git a/test/parser_test.exs b/test/parser_test.exs
@@ -157,11 +157,6 @@ defmodule AutoLinker.ParserTest do
assert parse(text, class: false, rel: false, new_window: false) == expected
end
- test "excludes html with specified class" do
- text = "```Check out <div class='section'>google.com</div>```"
- assert parse(text, exclude_patterns: ["```"]) == text
- end
-
test "do not link parens" do
text = " foo (https://example.com/path/folder/), bar"