Browse Source

Refactor parser

Refactor parser
merge-requests/18/head
Egor Kislitsyn 4 years ago
parent
commit
093d2344d2
4 changed files with 68 additions and 108 deletions
  1. +0
    -1
      lib/auto_linker.ex
  2. +8
    -2
      lib/auto_linker/builder.ex
  3. +60
    -100
      lib/auto_linker/parser.ex
  4. +0
    -5
      test/parser_test.exs

+ 0
- 1
lib/auto_linker.ex View File

@ -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/`)


+ 8
- 2
lib/auto_linker/builder.ex View File

@ -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


+ 60
- 100
lib/auto_linker/parser.ex View File

@ -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))
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 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),
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 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
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 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( 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),
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:
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
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 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
{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( 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),
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) 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
~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 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
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, user_acc} -> {buffer, user_acc}
buffer -> {buffer, user_acc} buffer -> {buffer, user_acc}
end end


+ 0
- 5
test/parser_test.exs View File

@ -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"


Loading…
Cancel
Save