logo

auto_linker

AutoLinker-shim, based on https://git.pleroma.social/pleroma/auto_linker git clone https://hacktivis.me/git/auto_linker.git
commit: 093d2344d2570527c5d6a57df1f535e740ac58e3
parent 76cfb574a3ecfd80f43ef9abe2017a081a6314a4
Author: Egor Kislitsyn <egor@kislitsyn.com>
Date:   Fri, 21 Jun 2019 20:09:48 +0700

Refactor parser

Refactor parser

Diffstat:

Mlib/auto_linker.ex1-
Mlib/auto_linker/builder.ex10++++++++--
Mlib/auto_linker/parser.ex160++++++++++++++++++++++++++++++-------------------------------------------------
Mtest/parser_test.exs5-----
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"