Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F115851
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
32 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/auto_linker.ex b/lib/auto_linker.ex
index 296ef67..ace5b51 100644
--- a/lib/auto_linker.ex
+++ b/lib/auto_linker.ex
@@ -1,56 +1,60 @@
defmodule AutoLinker do
@moduledoc """
Create url links from text containing urls.
Turns an input string like `"Check out google.com"` into
`Check out "<a href='http://google.com' target='_blank' rel='noopener noreferrer'>google.com</a>"`
## Examples
iex> AutoLinker.link("google.com")
"<a href='http://google.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>google.com</a>"
iex> AutoLinker.link("google.com", new_window: false, rel: false)
"<a href='http://google.com' class='auto-linker'>google.com</a>"
iex> AutoLinker.link("google.com", new_window: false, rel: false, class: false)
"<a href='http://google.com'>google.com</a>"
iex> AutoLinker.link("[Google](http://google.com)", markdown: true, new_window: false, rel: false, class: false)
"<a href='http://google.com'>Google</a>"
iex> AutoLinker.link("[Google Search](http://google.com)", markdown: true)
"<a href='http://google.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>Google Search</a>"
"""
import AutoLinker.Parser
@doc """
Auto link a string.
Options:
* `class: "auto-linker"` - specify the class to be added to the generated link. false to clear
* `rel: "noopener noreferrer"` - override the rel attribute. false to clear
* `new_window: true` - set to false to remove `target='_blank'` attribute
* `scheme: false` - Set to true to link urls with schema `http://google`
* `truncate: false` - Set to a number to truncate urls longer then the number. Truncated urls will end in `..`
* `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
* `markdown: false` - link markdown style links
Each of the above options can be specified when calling `link(text, opts)`
or can be set in the `:auto_linker`'s configuration. For example:
config :auto_linker,
class: false,
new_window: false
Note that passing opts to `link/2` will override the configuration settings.
"""
def link(text, opts \\ []) do
- parse(text, opts)
+ parse({text, nil}, opts) |> elem(0)
+ end
+
+ def link_and_parse(text, parsed, opts \\ []) do
+ parse({text, parsed}, opts)
end
end
diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex
index 0fbf86a..22e34a5 100644
--- a/lib/auto_linker/parser.ex
+++ b/lib/auto_linker/parser.ex
@@ -1,373 +1,395 @@
defmodule AutoLinker.Parser do
@moduledoc """
Module to handle parsing the the input string.
"""
alias AutoLinker.Builder
@doc """
Parse the given string, identifying items to link.
Parses the string, replacing the matching urls and phone numbers with an html link.
## Examples
iex> AutoLinker.Parser.parse("Check out google.com")
"Check out <a href='http://google.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>google.com</a>"
iex> AutoLinker.Parser.parse("call me at x9999", phone: true)
~s{call me at <a href="#" class="phone-number" data-phone="9999">x9999</a>}
iex> AutoLinker.Parser.parse("or at home on 555.555.5555", phone: true)
~s{or at home on <a href="#" class="phone-number" data-phone="5555555555">555.555.5555</a>}
iex> AutoLinker.Parser.parse(", work (555) 555-5555", phone: true)
~s{, work <a href="#" class="phone-number" data-phone="5555555555">(555) 555-5555</a>}
"""
# @invalid_url ~r/\.\.+/
@invalid_url ~r/(\.\.+)|(^(\d+\.){1,2}\d+$)/
@match_url ~r{^[\w\.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+$}
@match_scheme ~r{^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+$}
@match_phone ~r"((?:x\d{2,7})|(?:(?:\+?1\s?(?:[.-]\s?)?)?(?:\(\s?(?:[2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s?\)|(?:[2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s?(?:[.-]\s?)?)(?:[2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s?(?:[.-]\s?)?(?:[0-9]{4}))"
@match_hostname ~r{^(?:https?:\/\/)?(?:[^@\n]+\\w@)?(?<host>[^:#~\/\n?]+)}
@match_ip ~r"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
# @user
# @user@example.com
@match_mention ~r/^@[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@?[a-zA-Z0-9_-](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/u
# https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
@match_email ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/u
@match_hashtag ~r/^\#(?<tag>\w+)/u
@prefix_extra [
"magnet:?",
"dweb://",
"dat://",
"gopher://",
"ipfs://",
"ipns://",
"irc://",
"ircs://",
"irc6://",
"mumble://",
"ssb://"
]
@tlds "./priv/tlds.txt" |> File.read!() |> String.trim() |> String.split("\n")
@default_opts ~w(url)a
def parse(text, opts \\ %{})
def parse(text, list) when is_list(list), do: parse(text, Enum.into(list, %{}))
def parse(text, opts) do
config =
:auto_linker
|> Application.get_env(:opts, [])
|> Enum.into(%{})
|> Map.put(
:attributes,
Application.get_env(:auto_linker, :attributes, [])
)
opts =
Enum.reduce(@default_opts, opts, fn opt, acc ->
if is_nil(opts[opt]) and is_nil(config[opt]) do
Map.put(acc, opt, true)
else
acc
end
end)
do_parse(text, Map.merge(config, opts))
end
defp do_parse(text, %{phone: false} = opts), do: do_parse(text, Map.delete(opts, :phone))
defp do_parse(text, %{url: false} = opts), do: do_parse(text, Map.delete(opts, :url))
defp do_parse(text, %{phone: _} = opts) do
text
- |> do_parse(false, opts, {"", "", :parsing}, &check_and_link_phone/3)
+ |> do_parse(false, opts, {"", "", :parsing}, &check_and_link_phone/4)
|> do_parse(Map.delete(opts, :phone))
end
defp do_parse(text, %{mention: true} = opts) do
text
- |> do_parse(false, opts, {"", "", :parsing}, &check_and_link_mention/3)
+ |> do_parse(false, opts, {"", "", :parsing}, &check_and_link_mention/4)
|> do_parse(Map.delete(opts, :mention))
end
defp do_parse(text, %{extra: true} = opts) do
text
- |> do_parse(false, opts, {"", "", :parsing}, &check_and_link_extra/3)
+ |> do_parse(false, opts, {"", "", :parsing}, &check_and_link_extra/4)
|> do_parse(Map.delete(opts, :extra))
end
- defp do_parse(text, %{markdown: true} = opts) do
+ defp do_parse({text, out}, %{markdown: true} = opts) do
text
|> Builder.create_markdown_links(opts)
+ |> (&{&1, out}).()
|> do_parse(Map.delete(opts, :markdown))
end
defp do_parse(text, %{email: true} = opts) do
text
- |> do_parse(false, opts, {"", "", :parsing}, &check_and_link_email/3)
+ |> do_parse(false, opts, {"", "", :parsing}, &check_and_link_email/4)
|> do_parse(Map.delete(opts, :email))
end
defp do_parse(text, %{url: _} = opts) do
if (exclude = Map.get(opts, :exclude_pattern, false)) && String.starts_with?(text, exclude) do
text
else
- do_parse(text, Map.get(opts, :scheme, false), opts, {"", "", :parsing}, &check_and_link/3)
+ do_parse(text, Map.get(opts, :scheme, false), opts, {"", "", :parsing}, &check_and_link/4)
end
|> do_parse(Map.delete(opts, :url))
end
defp do_parse(text, %{hashtag: true} = opts) do
text
- |> do_parse(false, opts, {"", "", :parsing}, &check_and_link_hashtag/3)
+ |> do_parse(false, opts, {"", "", :parsing}, &check_and_link_hashtag/4)
|> do_parse(Map.delete(opts, :hashtag))
end
defp do_parse(text, _), do: text
- defp do_parse("", _scheme, _opts, {"", acc, _}, _handler),
- do: acc
+ defp do_parse({"", out}, _scheme, _opts, {"", acc, _}, _handler),
+ do: {acc, out}
- defp do_parse("", scheme, opts, {buffer, acc, _}, handler),
- do: acc <> handler.(buffer, scheme, opts)
+ defp do_parse({"", out}, scheme, opts, {buffer, acc, _}, handler) do
+ {buffer, out} = run_handler(handler, buffer, scheme, opts, out)
+ {acc <> buffer, out}
+ end
- defp do_parse("<a" <> text, scheme, opts, {buffer, acc, :parsing}, handler),
- do: do_parse(text, scheme, opts, {"", acc <> buffer <> "<a", :skip}, handler)
+ defp do_parse({"<a" <> text, out}, scheme, opts, {buffer, acc, :parsing}, handler),
+ do: do_parse({text, out}, scheme, opts, {"", acc <> buffer <> "<a", :skip}, handler)
- defp do_parse("</a>" <> text, scheme, opts, {buffer, acc, :skip}, handler),
- do: do_parse(text, scheme, opts, {"", acc <> buffer <> "</a>", :parsing}, handler)
+ defp do_parse({"</a>" <> text, out}, scheme, opts, {buffer, acc, :skip}, handler),
+ do: do_parse({text, out}, scheme, opts, {"", acc <> buffer <> "</a>", :parsing}, handler)
- defp do_parse("<" <> text, scheme, opts, {"", acc, :parsing}, handler),
- do: do_parse(text, scheme, opts, {"<", acc, {:open, 1}}, handler)
+ defp do_parse({"<" <> text, out}, scheme, opts, {"", acc, :parsing}, handler),
+ do: do_parse({text, out}, scheme, opts, {"<", acc, {:open, 1}}, handler)
- defp do_parse(">" <> text, scheme, opts, {buffer, acc, {:attrs, level}}, handler),
- do: do_parse(text, scheme, opts, {"", acc <> buffer <> ">", {:html, level}}, handler)
+ defp do_parse({">" <> text, out}, scheme, opts, {buffer, acc, {:attrs, level}}, handler),
+ do: do_parse({text, out}, scheme, opts, {"", acc <> buffer <> ">", {:html, level}}, handler)
- defp do_parse(<<ch::8>> <> text, scheme, opts, {"", acc, {:attrs, level}}, handler) do
- do_parse(text, scheme, opts, {"", acc <> <<ch::8>>, {:attrs, level}}, handler)
+ defp do_parse({<<ch::8>> <> text, out}, scheme, opts, {"", acc, {:attrs, level}}, handler) do
+ do_parse({text, out}, scheme, opts, {"", acc <> <<ch::8>>, {:attrs, level}}, handler)
end
- defp do_parse("</" <> text, scheme, opts, {buffer, acc, {:html, level}}, handler),
- do:
- do_parse(
- text,
- scheme,
- opts,
- {"", acc <> handler.(buffer, scheme, opts) <> "</", {:close, level}},
- handler
- )
+ defp do_parse({"</" <> text, out}, scheme, opts, {buffer, acc, {:html, level}}, handler) do
+ {buffer, out} = run_handler(handler, buffer, scheme, opts, out)
- defp do_parse(">" <> text, scheme, opts, {buffer, acc, {:close, 1}}, handler),
- do: do_parse(text, scheme, opts, {"", acc <> buffer <> ">", :parsing}, handler)
+ do_parse(
+ {text, out},
+ scheme,
+ opts,
+ {"", acc <> buffer <> "</", {:close, level}},
+ handler
+ )
+ end
+
+ defp do_parse({">" <> text, out}, scheme, opts, {buffer, acc, {:close, 1}}, handler),
+ do: do_parse({text, out}, scheme, opts, {"", acc <> buffer <> ">", :parsing}, handler)
- defp do_parse(">" <> text, scheme, opts, {buffer, acc, {:close, level}}, handler),
- do: do_parse(text, scheme, opts, {"", acc <> buffer <> ">", {:html, level - 1}}, handler)
+ defp do_parse({">" <> text, out}, scheme, opts, {buffer, acc, {:close, level}}, handler),
+ do:
+ do_parse({text, out}, scheme, opts, {"", acc <> buffer <> ">", {:html, level - 1}}, handler)
- defp do_parse(" " <> text, scheme, opts, {buffer, acc, {:open, level}}, handler),
- do: do_parse(text, scheme, opts, {"", acc <> buffer <> " ", {:attrs, level}}, handler)
+ defp do_parse({" " <> text, out}, scheme, opts, {buffer, acc, {:open, level}}, handler),
+ do: do_parse({text, out}, scheme, opts, {"", acc <> buffer <> " ", {:attrs, level}}, handler)
- defp do_parse("\n" <> text, scheme, opts, {buffer, acc, {:open, level}}, handler),
- do: do_parse(text, scheme, opts, {"", acc <> buffer <> "\n", {:attrs, level}}, handler)
+ defp do_parse({"\n" <> text, out}, scheme, opts, {buffer, acc, {:open, level}}, handler),
+ do: do_parse({text, out}, scheme, opts, {"", acc <> buffer <> "\n", {:attrs, level}}, handler)
# default cases where state is not important
- defp do_parse(" " <> text, scheme, %{phone: _} = opts, {buffer, acc, state}, handler),
- do: do_parse(text, scheme, opts, {buffer <> " ", acc, state}, handler)
+ defp do_parse({" " <> text, out}, scheme, %{phone: _} = opts, {buffer, acc, state}, handler),
+ do: do_parse({text, out}, scheme, opts, {buffer <> " ", acc, state}, handler)
+
+ defp do_parse({" " <> text, out}, scheme, opts, {buffer, acc, state}, handler) do
+ {buffer, out} = run_handler(handler, buffer, scheme, opts, out)
+
+ do_parse(
+ {text, out},
+ scheme,
+ opts,
+ {"", acc <> buffer <> " ", state},
+ handler
+ )
+ end
- defp do_parse(" " <> text, scheme, opts, {buffer, acc, state}, handler),
- do:
- do_parse(
- text,
- scheme,
- opts,
- {"", acc <> handler.(buffer, scheme, opts) <> " ", state},
- handler
- )
+ defp do_parse({"\n" <> text, out}, scheme, opts, {buffer, acc, state}, handler) do
+ {buffer, out} = run_handler(handler, buffer, scheme, opts, out)
- defp do_parse("\n" <> text, scheme, opts, {buffer, acc, state}, handler),
- do:
- do_parse(
- text,
- scheme,
- opts,
- {"", acc <> handler.(buffer, scheme, opts) <> "\n", state},
- handler
- )
+ do_parse(
+ {text, out},
+ scheme,
+ opts,
+ {"", acc <> buffer <> "\n", state},
+ handler
+ )
+ end
- defp do_parse(<<ch::8>>, scheme, opts, {buffer, acc, state}, handler),
- do:
- do_parse(
- "",
- scheme,
- opts,
- {"", acc <> handler.(buffer <> <<ch::8>>, scheme, opts), state},
- handler
- )
+ defp do_parse({<<ch::8>>, out}, scheme, opts, {buffer, acc, state}, handler) do
+ {buffer, out} = run_handler(handler, buffer <> <<ch::8>>, scheme, opts, out)
- defp do_parse(<<ch::8>> <> text, scheme, opts, {buffer, acc, state}, handler),
- do: do_parse(text, scheme, opts, {buffer <> <<ch::8>>, acc, state}, handler)
+ do_parse(
+ {"", out},
+ scheme,
+ opts,
+ {"", acc <> buffer, state},
+ handler
+ )
+ end
+
+ defp do_parse({<<ch::8>> <> text, out}, scheme, opts, {buffer, acc, state}, handler),
+ do: do_parse({text, out}, scheme, opts, {buffer <> <<ch::8>>, acc, state}, handler)
- def check_and_link(buffer, scheme, opts) do
+ def check_and_link(buffer, scheme, opts, _out) do
buffer
|> is_url?(scheme)
|> link_url(buffer, opts)
end
- def check_and_link_email(buffer, _, opts) do
+ def check_and_link_email(buffer, _, opts, _out) do
buffer
|> is_email?
|> link_email(buffer, opts)
end
- def check_and_link_phone(buffer, _, opts) do
+ def check_and_link_phone(buffer, _, opts, _out) do
buffer
|> match_phone
|> link_phone(buffer, opts)
end
- def check_and_link_mention(buffer, _, opts) do
+ def check_and_link_mention(buffer, _, opts, out) do
buffer
|> match_mention
- |> link_mention(buffer, opts)
+ |> link_mention(buffer, opts, out)
end
- def check_and_link_hashtag(buffer, _, opts) do
+ def check_and_link_hashtag(buffer, _, opts, out) do
buffer
|> match_hashtag
- |> link_hashtag(buffer, opts)
+ |> link_hashtag(buffer, opts, out)
end
- def check_and_link_extra("xmpp:" <> handle, _, opts) do
+ def check_and_link_extra("xmpp:" <> handle, _, opts, _out) do
handle
|> is_email?
|> link_extra("xmpp:" <> handle, opts)
end
- def check_and_link_extra(buffer, _, opts) do
+ def check_and_link_extra(buffer, _, opts, _out) do
buffer
|> String.starts_with?(@prefix_extra)
|> link_extra(buffer, opts)
end
# @doc false
def is_url?(buffer, true) do
if Regex.match?(@invalid_url, buffer) do
false
else
Regex.match?(@match_scheme, buffer) |> is_valid_tld?(buffer)
end
end
def is_url?(buffer, _) do
if Regex.match?(@invalid_url, buffer) do
false
else
Regex.match?(@match_url, buffer) |> is_valid_tld?(buffer)
end
end
def is_email?(buffer) do
if Regex.match?(@invalid_url, buffer) do
false
else
Regex.match?(@match_email, buffer) |> is_valid_tld?(buffer)
end
end
def is_valid_tld?(true, buffer) do
[host] = Regex.run(@match_hostname, buffer, capture: [:host])
if is_ip?(host) do
true
else
tld = host |> String.split(".") |> List.last()
Enum.member?(@tlds, tld)
end
end
def is_valid_tld?(false, _), do: false
def is_ip?(buffer) do
Regex.match?(@match_ip, buffer)
end
@doc false
def match_phone(buffer) do
case Regex.scan(@match_phone, buffer) do
[] -> nil
other -> other
end
end
def match_mention(buffer) do
case Regex.run(@match_mention, buffer) do
[mention] -> mention
_ -> nil
end
end
def match_hashtag(buffer) do
case Regex.run(@match_hashtag, buffer, capture: [:tag]) do
[hashtag] -> hashtag
_ -> nil
end
end
- def link_hashtag(nil, buffer, _), do: buffer
+ def link_hashtag(nil, buffer, _, _out), do: buffer
- def link_hashtag(hashtag, buffer, opts) do
+ def link_hashtag(hashtag, buffer, %{hashtag_handler: hashtag_handler} = opts, out) do
+ hashtag_handler.(hashtag, buffer, opts, out)
+ end
+
+ def link_hashtag(hashtag, buffer, opts, _out) do
Builder.create_hashtag_link(hashtag, buffer, opts)
end
- def link_mention(nil, buffer, _), do: buffer
+ def link_mention(nil, buffer, _, out), do: {buffer, out}
- def link_mention(mention, _buffer, %{mention_formatter: mention_formatter} = opts) do
- {buffer, _} = mention_formatter.(mention, opts)
- buffer
+ def link_mention(mention, buffer, %{mention_handler: mention_handler} = opts, out) do
+ mention_handler.(mention, buffer, opts, out)
end
- def link_mention(mention, buffer, opts) do
+ def link_mention(mention, buffer, opts, _out) do
Builder.create_mention_link(mention, buffer, opts)
end
def link_phone(nil, buffer, _), do: buffer
def link_phone(list, buffer, opts) do
Builder.create_phone_link(list, buffer, opts)
end
@doc false
def link_url(true, buffer, opts) do
Builder.create_link(buffer, opts)
end
def link_url(_, buffer, _opts), do: buffer
@doc false
def link_email(true, buffer, opts) do
Builder.create_email_link(buffer, opts)
end
def link_email(_, buffer, _opts), do: buffer
def link_extra(true, buffer, opts) do
Builder.create_extra_link(buffer, opts)
end
def link_extra(_, buffer, _opts), do: buffer
+
+ defp run_handler(handler, buffer, scheme, opts, out) do
+ case handler.(buffer, scheme, opts, out) do
+ {buffer, out} -> {buffer, out}
+ buffer -> {buffer, out}
+ end
+ end
end
diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs
index 880e838..c9835a7 100644
--- a/test/auto_linker_test.exs
+++ b/test/auto_linker_test.exs
@@ -1,291 +1,342 @@
defmodule AutoLinkerTest do
use ExUnit.Case
doctest AutoLinker
test "phone number" do
assert AutoLinker.link(", work (555) 555-5555", phone: true) ==
~s{, work <a href="#" class="phone-number" data-phone="5555555555">(555) 555-5555</a>}
end
test "default link" do
assert AutoLinker.link("google.com") ==
"<a href='http://google.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>google.com</a>"
end
test "markdown" do
assert AutoLinker.link("[google.com](http://google.com)", markdown: true) ==
"<a href='http://google.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>google.com</a>"
end
test "does on link existing links" do
assert AutoLinker.link("<a href='http://google.com'>google.com</a>") ==
"<a href='http://google.com'>google.com</a>"
end
test "phone number and markdown link" do
assert AutoLinker.link("888 888-8888 [ab](a.com)", phone: true, markdown: true) ==
"<a href=\"#\" class=\"phone-number\" data-phone=\"8888888888\">888 888-8888</a>" <>
" <a href='a.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>ab</a>"
end
test "all kinds of links" do
text =
"hello @user google.com https://ddg.com 888 888-8888 #tag user@email.com [google.com](http://google.com) irc:///mIRC"
expected =
"hello <a href='https://example.com/user/user'>@user</a> <a href='http://google.com'>google.com</a> <a href='https://ddg.com'>ddg.com</a> <a href=\"#\" class=\"phone-number\" data-phone=\"8888888888\">888 888-8888</a> <a href='https://example.com/tag/tag'>#tag</a> <a href='mailto:user@email.com' >user@email.com</a> <a href='http://google.com'>google.com</a> <a href='irc:///mIRC' >irc:///mIRC</a>"
assert AutoLinker.link(text,
phone: true,
markdown: true,
email: true,
mention: true,
mention_prefix: "https://example.com/user/",
hashtag: true,
hashtag_prefix: "https://example.com/tag/",
scheme: true,
extra: true,
class: false,
new_window: false,
rel: false
) == expected
end
+ describe "custom handlers" do
+ test "mentions handler" do
+ text = "hello @user, @valid_user and @invalid_user"
+ valid_users = ["user", "valid_user"]
+
+ handler = fn "@" <> user = mention, buffer, _opts, parsed ->
+ if Enum.member?(valid_users, user) do
+ link = ~s(<a href="https://example.com/user/#{user}" data-user="#{user}">#{mention}</a>)
+ {link, %{parsed | mentions: MapSet.put(parsed.mentions, {mention, user})}}
+ else
+ {buffer, parsed}
+ end
+ end
+
+ {result_text, %{mentions: mentions}} =
+ AutoLinker.link_and_parse(text, %{mentions: MapSet.new()},
+ mention: true,
+ mention_handler: handler
+ )
+
+ assert result_text ==
+ "hello <a href=\"https://example.com/user/user\" data-user=\"user\">@user</a> <a href=\"https://example.com/user/valid_user\" data-user=\"valid_user\">@valid_user</a> and @invalid_user"
+
+ assert mentions |> MapSet.to_list() |> Enum.map(&elem(&1, 1)) == valid_users
+ end
+
+ test "hashtags handler" do
+ text = "#hello #world"
+
+ handler = fn hashtag, buffer, opts, parsed ->
+ link = AutoLinker.Builder.create_hashtag_link(hashtag, buffer, opts)
+ {link, %{parsed | tags: MapSet.put(parsed.tags, hashtag)}}
+ end
+
+ {result_text, %{tags: tags}} =
+ AutoLinker.link_and_parse(text, %{tags: MapSet.new()},
+ hashtag: true,
+ hashtag_handler: handler,
+ hashtag_prefix: "https://example.com/user/",
+ class: false,
+ new_window: false,
+ rel: false
+ )
+
+ assert result_text ==
+ "<a href='https://example.com/user/hello'>#hello</a> <a href='https://example.com/user/world'>#world</a>"
+
+ assert MapSet.to_list(tags) == ["hello", "world"]
+ end
+ end
+
describe "mentions" do
test "simple mentions" do
expected =
~s{hello <a href='https://example.com/user/user' class='auto-linker' target='_blank' rel='noopener noreferrer'>@user</a> and <a href='https://example.com/user/anotherUser' class='auto-linker' target='_blank' rel='noopener noreferrer'>@anotherUser</a>}
assert AutoLinker.link("hello @user and @anotherUser",
mention: true,
mention_prefix: "https://example.com/user/"
) == expected
end
test "metion @user@example.com" do
text = "hey @user@example.com"
expected =
"hey <a href='https://example.com/user/user@example.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>@user@example.com</a>"
assert AutoLinker.link(text,
mention: true,
mention_prefix: "https://example.com/user/"
) == expected
end
test "skip if starts with @@" do
text = "hello @@user and @anotherUser"
expected =
"hello @@user and <a href='https://example.com/user/anotherUser' class='auto-linker' target='_blank' rel='noopener noreferrer'>@anotherUser</a>"
assert AutoLinker.link(text,
mention: true,
mention_prefix: "https://example.com/user/"
) == expected
end
end
describe "hashtag links" do
test "hashtag" do
expected =
"one <a href='https://example.com/tag/two' class='auto-linker' target='_blank' rel='noopener noreferrer'>#two</a> three <a href='https://example.com/tag/four' class='auto-linker' target='_blank' rel='noopener noreferrer'>#four</a>"
assert AutoLinker.link("one #two three #four",
hashtag: true,
hashtag_prefix: "https://example.com/tag/"
) == expected
end
test "do not turn urls with hashes into hashtags" do
text = "google.com#test #test google.com/#test #tag"
expected =
"<a href='http://google.com#test'>google.com#test</a> <a href='https://example.com/tag/test'>#test</a> <a href='http://google.com/#test'>google.com/#test</a> <a href='https://example.com/tag/tag'>#tag</a>"
assert AutoLinker.link(text,
scheme: true,
hashtag: true,
class: false,
new_window: false,
rel: false,
hashtag_prefix: "https://example.com/tag/"
) == expected
end
test "works with non-latin characters" do
text = "#漢字 #は #тест #ทดสอบ"
expected =
"<a href='https://example.com/tag/漢字'>#漢字</a> <a href='https://example.com/tag/は'>#は</a> <a href='https://example.com/tag/тест'>#тест</a> <a href='https://example.com/tag/ทดสอบ'>#ทดสอบ</a>"
assert AutoLinker.link(text,
scheme: true,
class: false,
new_window: false,
rel: false,
hashtag: true,
hashtag_prefix: "https://example.com/tag/"
) == expected
end
end
describe "links" do
test "turning urls into links" do
text = "Hey, check out http://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ."
expected =
"Hey, check out <a href='http://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla' class='auto-linker' target='_blank' rel='noopener noreferrer'>youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla</a> ."
assert AutoLinker.link(text, scheme: true) == expected
# no scheme
text = "Hey, check out www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ."
assert AutoLinker.link(text, scheme: true) == expected
end
test "hostname/@user" do
text = "https://example.com/@user"
expected =
"<a href='https://example.com/@user' class='auto-linker' target='_blank' rel='noopener noreferrer'>example.com/@user</a>"
assert AutoLinker.link(text, scheme: true) == expected
text = "https://example.com:4000/@user"
expected =
"<a href='https://example.com:4000/@user' class='auto-linker' target='_blank' rel='noopener noreferrer'>example.com:4000/@user</a>"
assert AutoLinker.link(text, scheme: true) == expected
text = "https://example.com:4000/@user"
expected =
"<a href='https://example.com:4000/@user' class='auto-linker' target='_blank' rel='noopener noreferrer'>example.com:4000/@user</a>"
assert AutoLinker.link(text, scheme: true) == expected
text = "@username"
expected = "@username"
assert AutoLinker.link(text, scheme: true) == expected
text = "http://www.cs.vu.nl/~ast/intel/"
expected =
"<a href='http://www.cs.vu.nl/~ast/intel/' class='auto-linker' target='_blank' rel='noopener noreferrer'>cs.vu.nl/~ast/intel/</a>"
assert AutoLinker.link(text, scheme: true) == expected
text = "https://forum.zdoom.org/viewtopic.php?f=44&t=57087"
expected =
"<a href='https://forum.zdoom.org/viewtopic.php?f=44&t=57087' class='auto-linker' target='_blank' rel='noopener noreferrer'>forum.zdoom.org/viewtopic.php?f=44&t=57087</a>"
assert AutoLinker.link(text, scheme: true) == expected
text = "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul"
expected =
"<a href='https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul' class='auto-linker' target='_blank' rel='noopener noreferrer'>en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul</a>"
assert AutoLinker.link(text, scheme: true) == expected
text = "https://en.wikipedia.org/wiki/Duff's_device"
expected =
"<a href='https://en.wikipedia.org/wiki/Duff's_device' class='auto-linker' target='_blank' rel='noopener noreferrer'>en.wikipedia.org/wiki/Duff's_device</a>"
assert AutoLinker.link(text, scheme: true) == expected
end
end
describe "non http links" do
test "xmpp" do
text = "xmpp:user@example.com"
expected = "<a href='xmpp:user@example.com' class='auto-linker'>xmpp:user@example.com</a>"
assert AutoLinker.link(text, extra: true) == expected
end
test "email" do
text = "user@example.com"
expected = "<a href='mailto:user@example.com' class='auto-linker'>user@example.com</a>"
assert AutoLinker.link(text, email: true) == expected
end
test "magnet" do
text =
"magnet:?xt=urn:btih:a4104a9d2f5615601c429fe8bab8177c47c05c84&dn=ubuntu-18.04.1.0-live-server-amd64.iso&tr=http%3A%2F%2Ftorrent.ubuntu.com%3A6969%2Fannounce&tr=http%3A%2F%2Fipv6.torrent.ubuntu.com%3A6969%2Fannounce"
expected =
"<a href='magnet:?xt=urn:btih:a4104a9d2f5615601c429fe8bab8177c47c05c84&dn=ubuntu-18.04.1.0-live-server-amd64.iso&tr=http%3A%2F%2Ftorrent.ubuntu.com%3A6969%2Fannounce&tr=http%3A%2F%2Fipv6.torrent.ubuntu.com%3A6969%2Fannounce' class='auto-linker'>magnet:?xt=urn:btih:a4104a9d2f5615601c429fe8bab8177c47c05c84&dn=ubuntu-18.04.1.0-live-server-amd64.iso&tr=http%3A%2F%2Ftorrent.ubuntu.com%3A6969%2Fannounce&tr=http%3A%2F%2Fipv6.torrent.ubuntu.com%3A6969%2Fannounce</a>"
assert AutoLinker.link(text, extra: true) == expected
end
test "dweb" do
text =
"dweb://584faa05d394190ab1a3f0240607f9bf2b7e2bd9968830a11cf77db0cea36a21+v1.0.0/path/to/file.txt"
expected =
"<a href='dweb://584faa05d394190ab1a3f0240607f9bf2b7e2bd9968830a11cf77db0cea36a21+v1.0.0/path/to/file.txt' class='auto-linker'>dweb://584faa05d394190ab1a3f0240607f9bf2b7e2bd9968830a11cf77db0cea36a21+v1.0.0/path/to/file.txt</a>"
assert AutoLinker.link(text, extra: true) == expected
end
end
describe "TLDs" do
test "parse with scheme" do
text = "https://google.com"
expected =
"<a href='https://google.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>google.com</a>"
assert AutoLinker.link(text, scheme: true) == expected
end
test "only existing TLDs with scheme" do
text = "this url https://google.foobar.blah11blah/ has invalid TLD"
expected = "this url https://google.foobar.blah11blah/ has invalid TLD"
assert AutoLinker.link(text, scheme: true) == expected
text = "this url https://google.foobar.com/ has valid TLD"
expected =
"this url <a href='https://google.foobar.com/' class='auto-linker' target='_blank' rel='noopener noreferrer'>google.foobar.com/</a> has valid TLD"
assert AutoLinker.link(text, scheme: true) == expected
end
test "only existing TLDs without scheme" do
text = "this url google.foobar.blah11blah/ has invalid TLD"
expected = "this url google.foobar.blah11blah/ has invalid TLD"
assert AutoLinker.link(text, scheme: false) == expected
text = "this url google.foobar.com/ has valid TLD"
expected =
"this url <a href='http://google.foobar.com/' class='auto-linker' target='_blank' rel='noopener noreferrer'>google.foobar.com/</a> has valid TLD"
assert AutoLinker.link(text, scheme: false) == expected
end
test "only existing TLDs with and without scheme" do
text = "this url http://google.foobar.com/ has valid TLD"
expected =
"this url <a href='http://google.foobar.com/' class='auto-linker' target='_blank' rel='noopener noreferrer'>google.foobar.com/</a> has valid TLD"
assert AutoLinker.link(text, scheme: true) == expected
text = "this url google.foobar.com/ has valid TLD"
expected =
"this url <a href='http://google.foobar.com/' class='auto-linker' target='_blank' rel='noopener noreferrer'>google.foobar.com/</a> has valid TLD"
assert AutoLinker.link(text, scheme: true) == expected
end
end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Nov 29, 8:50 AM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41185
Default Alt Text
(32 KB)
Attached To
Mode
R19 linkify
Attached
Detach File
Event Timeline
Log In to Comment