From 093d2344d2570527c5d6a57df1f535e740ac58e3 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 21 Jun 2019 20:09:48 +0700 Subject: [PATCH] Refactor parser Refactor parser --- lib/auto_linker.ex | 1 - lib/auto_linker/builder.ex | 10 ++- lib/auto_linker/parser.ex | 160 ++++++++++++++----------------------- test/parser_test.exs | 5 -- 4 files changed, 68 insertions(+), 108 deletions(-) diff --git a/lib/auto_linker.ex b/lib/auto_linker.ex index b843a8d..0568068 100644 --- 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 index 2cf714b..560a3b2 100644 --- 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 index a427888..68dfa36 100644 --- 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 google.com} """ + @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({" text, user_acc}, opts, {buffer, acc, :parsing}, handler), - do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> " text, user_acc}, opts, {buffer, acc, :parsing}, type), + do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> " text, user_acc}, opts, {buffer, acc, :parsing}, handler), - do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> " text, user_acc}, opts, {buffer, acc, :parsing}, type), + do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> " text, user_acc}, opts, {buffer, acc, :parsing}, handler), - do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> " text, user_acc}, opts, {buffer, acc, :parsing}, type), + do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "" <> text, user_acc}, opts, {buffer, acc, :skip}, handler), - do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "", :parsing}, handler) + defp do_parse({"" <> text, user_acc}, opts, {buffer, acc, :skip}, type), + do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "", :parsing}, type) - defp do_parse({"" <> text, user_acc}, opts, {buffer, acc, :skip}, handler), - do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "", :parsing}, handler) + defp do_parse({"" <> text, user_acc}, opts, {buffer, acc, :skip}, type), + do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "", :parsing}, type) - defp do_parse({"" <> text, user_acc}, opts, {buffer, acc, :skip}, handler), - do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "", :parsing}, handler) + defp do_parse({"" <> text, user_acc}, opts, {buffer, acc, :skip}, type), + do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "", :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({<> <> text, user_acc}, opts, {"", acc, {:attrs, level}}, handler) do - do_parse({text, user_acc}, opts, {"", acc <> <>, {:attrs, level}}, handler) + defp do_parse({<> <> text, user_acc}, opts, {"", acc, {:attrs, level}}, type) do + do_parse({text, user_acc}, opts, {"", acc <> <>, {: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 <> "" <> 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( {<>, 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({<>, user_acc}, opts, {buffer, acc, state}, handler) do - {buffer, user_acc} = run_handler(handler, buffer <> <>, opts, user_acc) + defp do_parse({<>, user_acc}, opts, {buffer, acc, state}, type) do + {buffer, user_acc} = link(type, buffer <> <>, opts, user_acc) do_parse( {"", user_acc}, opts, {"", acc <> buffer, state}, - handler + type ) end - defp do_parse({<> <> text, user_acc}, opts, {buffer, acc, state}, handler), - do: do_parse({text, user_acc}, opts, {buffer <> <>, acc, state}, handler) + defp do_parse({<> <> text, user_acc}, opts, {buffer, acc, state}, type), + do: do_parse({text, user_acc}, opts, {buffer <> <>, 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 index 7abeb6f..ca0dfe9 100644 --- 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
google.com
```" - assert parse(text, exclude_patterns: ["```"]) == text - end - test "do not link parens" do text = " foo (https://example.com/path/folder/), bar"