Refactor parser
Refactor parser
This commit is contained in:
parent
76cfb574a3
commit
093d2344d2
4 changed files with 68 additions and 108 deletions
|
@ -31,7 +31,6 @@ defmodule AutoLinker do
|
||||||
* `strip_prefix: true` - Strip the scheme prefix
|
* `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_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_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
|
* `email: false` - link email links
|
||||||
* `mention: false` - link @mentions (when `true`, requires `mention_prefix` or `mention_handler` options to be set)
|
* `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/`)
|
* `mention_prefix: nil` - a prefix to build a link for a mention (example: `https://example.com/user/`)
|
||||||
|
|
|
@ -25,7 +25,10 @@ defmodule AutoLinker.Builder do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_attrs(attrs, _, opts, :rel) do
|
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
|
end
|
||||||
|
|
||||||
defp build_attrs(attrs, _, opts, :target) do
|
defp build_attrs(attrs, _, opts, :target) do
|
||||||
|
@ -33,7 +36,10 @@ defmodule AutoLinker.Builder do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_attrs(attrs, _, opts, :class) do
|
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
|
end
|
||||||
|
|
||||||
defp build_attrs(attrs, url, _opts, :href) do
|
defp build_attrs(attrs, url, _opts, :href) do
|
||||||
|
|
|
@ -46,7 +46,7 @@ defmodule AutoLinker.Parser do
|
||||||
@doc """
|
@doc """
|
||||||
Parse the given string, identifying items to link.
|
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
|
## 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>}
|
~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 \\ %{})
|
||||||
def parse(input, opts) when is_binary(input), do: {input, %{}} |> parse(opts) |> elem(0)
|
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, %{}))
|
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
|
def parse(input, opts) do
|
||||||
opts = Map.merge(@default_opts, opts)
|
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))
|
_, input ->
|
||||||
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
|
input
|
||||||
|> do_parse(opts, {"", "", :parsing}, &check_and_link_hashtag/3)
|
end)
|
||||||
|> do_parse(Map.delete(opts, :hashtag))
|
|
||||||
end
|
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))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_parse(input, _), do: input
|
|
||||||
|
|
||||||
defp do_parse({"", user_acc}, _opts, {"", acc, _}, _handler),
|
defp do_parse({"", user_acc}, _opts, {"", acc, _}, _handler),
|
||||||
do: {acc, user_acc}
|
do: {acc, user_acc}
|
||||||
|
|
||||||
defp do_parse({"<a" <> text, user_acc}, opts, {buffer, acc, :parsing}, handler),
|
defp do_parse({"<a" <> text, user_acc}, opts, {buffer, acc, :parsing}, type),
|
||||||
do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "<a", :skip}, handler)
|
do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "<a", :skip}, type)
|
||||||
|
|
||||||
defp do_parse({"<pre" <> text, user_acc}, opts, {buffer, acc, :parsing}, handler),
|
defp do_parse({"<pre" <> text, user_acc}, opts, {buffer, acc, :parsing}, type),
|
||||||
do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "<pre", :skip}, handler)
|
do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "<pre", :skip}, type)
|
||||||
|
|
||||||
defp do_parse({"<code" <> text, user_acc}, opts, {buffer, acc, :parsing}, handler),
|
defp do_parse({"<code" <> text, user_acc}, opts, {buffer, acc, :parsing}, type),
|
||||||
do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "<code", :skip}, handler)
|
do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "<code", :skip}, type)
|
||||||
|
|
||||||
defp do_parse({"</a>" <> text, user_acc}, opts, {buffer, acc, :skip}, handler),
|
defp do_parse({"</a>" <> text, user_acc}, opts, {buffer, acc, :skip}, type),
|
||||||
do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "</a>", :parsing}, handler)
|
do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "</a>", :parsing}, type)
|
||||||
|
|
||||||
defp do_parse({"</pre>" <> text, user_acc}, opts, {buffer, acc, :skip}, handler),
|
defp do_parse({"</pre>" <> text, user_acc}, opts, {buffer, acc, :skip}, type),
|
||||||
do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "</pre>", :parsing}, handler)
|
do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "</pre>", :parsing}, type)
|
||||||
|
|
||||||
defp do_parse({"</code>" <> text, user_acc}, opts, {buffer, acc, :skip}, handler),
|
defp do_parse({"</code>" <> text, user_acc}, opts, {buffer, acc, :skip}, type),
|
||||||
do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "</code>", :parsing}, handler)
|
do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "</code>", :parsing}, type)
|
||||||
|
|
||||||
defp do_parse({"<" <> text, user_acc}, opts, {"", acc, :parsing}, handler),
|
defp do_parse({"<" <> text, user_acc}, opts, {"", acc, :parsing}, type),
|
||||||
do: do_parse({text, user_acc}, opts, {"<", acc, {:open, 1}}, handler)
|
do: do_parse({text, user_acc}, opts, {"<", acc, {:open, 1}}, type)
|
||||||
|
|
||||||
defp do_parse({"<" <> text, user_acc}, opts, {"", acc, {:html, level}}, handler) do
|
defp do_parse({"<" <> text, user_acc}, opts, {"", acc, {:html, level}}, type) do
|
||||||
do_parse({text, user_acc}, opts, {"<", acc, {:open, level + 1}}, handler)
|
do_parse({text, user_acc}, opts, {"<", acc, {:open, level + 1}}, type)
|
||||||
end
|
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:
|
||||||
do_parse(
|
do_parse(
|
||||||
{text, user_acc},
|
{text, user_acc},
|
||||||
opts,
|
opts,
|
||||||
{"", acc <> buffer <> ">", {:html, level}},
|
{"", acc <> buffer <> ">", {:html, level}},
|
||||||
handler
|
type
|
||||||
)
|
)
|
||||||
|
|
||||||
defp do_parse({<<ch::8>> <> text, user_acc}, opts, {"", acc, {:attrs, level}}, handler) do
|
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}}, handler)
|
do_parse({text, user_acc}, opts, {"", acc <> <<ch::8>>, {:attrs, level}}, type)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_parse({"</" <> text, user_acc}, opts, {buffer, acc, {:html, level}}, handler) do
|
defp do_parse({"</" <> text, user_acc}, opts, {buffer, acc, {:html, level}}, type) do
|
||||||
{buffer, user_acc} = run_handler(handler, buffer, opts, user_acc)
|
{buffer, user_acc} = link(type, buffer, opts, user_acc)
|
||||||
|
|
||||||
do_parse(
|
do_parse(
|
||||||
{text, user_acc},
|
{text, user_acc},
|
||||||
opts,
|
opts,
|
||||||
{"", acc <> buffer <> "</", {:close, level}},
|
{"", acc <> buffer <> "</", {:close, level}},
|
||||||
handler
|
type
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_parse({">" <> text, user_acc}, opts, {buffer, acc, {:close, 1}}, handler),
|
defp do_parse({">" <> text, user_acc}, opts, {buffer, acc, {:close, 1}}, type),
|
||||||
do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> ">", :parsing}, handler)
|
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:
|
||||||
do_parse(
|
do_parse(
|
||||||
{text, user_acc},
|
{text, user_acc},
|
||||||
opts,
|
opts,
|
||||||
{"", acc <> buffer <> ">", {:html, level - 1}},
|
{"", acc <> buffer <> ">", {:html, level - 1}},
|
||||||
handler
|
type
|
||||||
)
|
)
|
||||||
|
|
||||||
defp do_parse({text, user_acc}, opts, {buffer, acc, {:open, level}}, handler) do
|
defp do_parse({text, user_acc}, opts, {buffer, acc, {:open, level}}, type) do
|
||||||
do_parse({text, user_acc}, opts, {"", acc <> buffer, {:attrs, level}}, handler)
|
do_parse({text, user_acc}, opts, {"", acc <> buffer, {:attrs, level}}, type)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_parse(
|
defp do_parse(
|
||||||
{<<char::bytes-size(1), text::binary>>, user_acc},
|
{<<char::bytes-size(1), text::binary>>, user_acc},
|
||||||
opts,
|
opts,
|
||||||
{buffer, acc, state},
|
{buffer, acc, state},
|
||||||
handler
|
type
|
||||||
)
|
)
|
||||||
when char in [" ", "\r", "\n"] do
|
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(
|
do_parse(
|
||||||
{text, user_acc},
|
{text, user_acc},
|
||||||
opts,
|
opts,
|
||||||
{"", acc <> buffer <> char, state},
|
{"", acc <> buffer <> char, state},
|
||||||
handler
|
type
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_parse({<<ch::8>>, user_acc}, opts, {buffer, acc, state}, handler) do
|
defp do_parse({<<ch::8>>, user_acc}, opts, {buffer, acc, state}, type) do
|
||||||
{buffer, user_acc} = run_handler(handler, buffer <> <<ch::8>>, opts, user_acc)
|
{buffer, user_acc} = link(type, buffer <> <<ch::8>>, opts, user_acc)
|
||||||
|
|
||||||
do_parse(
|
do_parse(
|
||||||
{"", user_acc},
|
{"", user_acc},
|
||||||
opts,
|
opts,
|
||||||
{"", acc <> buffer, state},
|
{"", acc <> buffer, state},
|
||||||
handler
|
type
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_parse({<<ch::8>> <> text, user_acc}, opts, {buffer, 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}, handler)
|
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)
|
str = strip_parens(buffer)
|
||||||
|
|
||||||
if url?(str, opts) do
|
if url?(str, opts) do
|
||||||
|
@ -224,36 +184,36 @@ defmodule AutoLinker.Parser do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp strip_parens("(" <> buffer) do
|
def check_and_link(:email, buffer, opts, _user_acc) do
|
||||||
~r/[^\)]*/ |> Regex.run(buffer) |> hd()
|
|
||||||
end
|
|
||||||
|
|
||||||
defp strip_parens(buffer), do: buffer
|
|
||||||
|
|
||||||
def check_and_link_email(buffer, opts, _user_acc) do
|
|
||||||
if email?(buffer, opts), do: link_email(buffer, opts), else: buffer
|
if email?(buffer, opts), do: link_email(buffer, opts), else: buffer
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_and_link_mention(buffer, opts, user_acc) do
|
def check_and_link(:mention, buffer, opts, user_acc) do
|
||||||
buffer
|
buffer
|
||||||
|> match_mention
|
|> match_mention
|
||||||
|> link_mention(buffer, opts, user_acc)
|
|> link_mention(buffer, opts, user_acc)
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_and_link_hashtag(buffer, opts, user_acc) do
|
def check_and_link(:hashtag, buffer, opts, user_acc) do
|
||||||
buffer
|
buffer
|
||||||
|> match_hashtag
|
|> match_hashtag
|
||||||
|> link_hashtag(buffer, opts, user_acc)
|
|> link_hashtag(buffer, opts, user_acc)
|
||||||
end
|
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
|
if email?(handle, opts), do: link_extra("xmpp:" <> handle, opts), else: handle
|
||||||
end
|
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
|
if String.starts_with?(buffer, @prefix_extra), do: link_extra(buffer, opts), else: buffer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp strip_parens("(" <> buffer) do
|
||||||
|
~r/[^\)]*/ |> Regex.run(buffer) |> hd()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp strip_parens(buffer), do: buffer
|
||||||
|
|
||||||
# @doc false
|
# @doc false
|
||||||
|
|
||||||
def url?(buffer, opts) do
|
def url?(buffer, opts) do
|
||||||
|
@ -363,8 +323,8 @@ defmodule AutoLinker.Parser do
|
||||||
Builder.create_extra_link(buffer, opts)
|
Builder.create_extra_link(buffer, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp run_handler(handler, buffer, opts, user_acc) do
|
defp link(type, buffer, opts, user_acc) do
|
||||||
case handler.(buffer, opts, user_acc) do
|
case check_and_link(type, buffer, opts, user_acc) do
|
||||||
{buffer, user_acc} -> {buffer, user_acc}
|
{buffer, user_acc} -> {buffer, user_acc}
|
||||||
buffer -> {buffer, user_acc}
|
buffer -> {buffer, user_acc}
|
||||||
end
|
end
|
||||||
|
|
|
@ -157,11 +157,6 @@ defmodule AutoLinker.ParserTest do
|
||||||
assert parse(text, class: false, rel: false, new_window: false) == expected
|
assert parse(text, class: false, rel: false, new_window: false) == expected
|
||||||
end
|
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
|
test "do not link parens" do
|
||||||
text = " foo (https://example.com/path/folder/), bar"
|
text = " foo (https://example.com/path/folder/), bar"
|
||||||
|
|
||||||
|
|
Reference in a new issue