Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F115512
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
44 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/auto_linker.ex b/lib/auto_linker.ex
index 8bc43be..222cd79 100644
--- a/lib/auto_linker.ex
+++ b/lib/auto_linker.ex
@@ -1,68 +1,68 @@
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>"`
+ `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>"
+ ~s(<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>"
+ ~s(<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>"
+ ~s(<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>"
+ ~s(<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>"
+ ~s(<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
* `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/`)
* `mention_handler: nil` - a custom handler to validate and formart a mention
* `hashtag: false` - link #hashtags (when `true`, requires `hashtag_prefix` or `hashtag_handler` options to be set)
* `hashtag_prefix: nil` - a prefix to build a link for a hashtag (example: `https://example.com/tag/`)
* `hashtag_handler: nil` - a custom handler to validate and formart a hashtag
* `extra: false` - link urls with rarely used schemes (magnet, ipfs, irc, etc.)
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)
end
def link_map(text, acc, opts \\ []) do
parse({text, acc}, opts)
end
end
diff --git a/lib/auto_linker/builder.ex b/lib/auto_linker/builder.ex
index 22d1758..185357f 100644
--- a/lib/auto_linker/builder.ex
+++ b/lib/auto_linker/builder.ex
@@ -1,178 +1,178 @@
defmodule AutoLinker.Builder do
@moduledoc """
Module for building the auto generated link.
"""
@doc """
Create a link.
"""
def create_link(url, opts) do
[]
|> build_attrs(url, opts, :rel)
|> build_attrs(url, opts, :target)
|> build_attrs(url, opts, :class)
|> build_attrs(url, opts, :scheme)
|> format_url(url, opts)
end
def create_markdown_links(text, opts) do
[]
|> build_attrs(text, opts, :rel)
|> build_attrs(text, opts, :target)
|> build_attrs(text, opts, :class)
|> format_markdown(text, opts)
end
defp build_attrs(attrs, _, opts, :rel) do
if rel = Map.get(opts, :rel, "noopener noreferrer"), do: [{:rel, rel} | attrs], else: attrs
end
defp build_attrs(attrs, _, opts, :target) do
if Map.get(opts, :new_window, true), do: [{:target, :_blank} | attrs], else: attrs
end
defp build_attrs(attrs, _, opts, :class) do
if cls = Map.get(opts, :class, "auto-linker"), do: [{:class, cls} | attrs], else: attrs
end
defp build_attrs(attrs, url, _opts, :scheme) do
if String.starts_with?(url, ["http://", "https://"]),
do: [{:href, url} | attrs],
else: [{:href, "http://" <> url} | attrs]
end
defp format_url(attrs, url, opts) do
url =
url
|> strip_prefix(Map.get(opts, :strip_prefix, true))
|> truncate(Map.get(opts, :truncate, false))
attrs = format_attrs(attrs)
"<a #{attrs}>" <> url <> "</a>"
end
defp format_attrs(attrs) do
attrs
- |> Enum.map(fn {key, value} -> ~s(#{key}='#{value}') end)
+ |> Enum.map(fn {key, value} -> ~s(#{key}="#{value}") end)
|> Enum.join(" ")
end
defp format_markdown(attrs, text, _opts) do
attrs =
case format_attrs(attrs) do
"" -> ""
attrs -> " " <> attrs
end
Regex.replace(~r/\[(.+?)\]\((.+?)\)/, text, "<a href='\\2'#{attrs}>\\1</a>")
end
defp truncate(url, false), do: url
defp truncate(url, len) when len < 3, do: url
defp truncate(url, len) do
if String.length(url) > len, do: String.slice(url, 0, len - 2) <> "..", else: url
end
defp strip_prefix(url, true) do
url
|> String.replace(~r/^https?:\/\//, "")
|> String.replace(~r/^www\./, "")
end
defp strip_prefix(url, _), do: url
def create_phone_link([], buffer, _) do
buffer
end
def create_phone_link([h | t], buffer, opts) do
create_phone_link(t, format_phone_link(h, buffer, opts), opts)
end
def format_phone_link([h | _], buffer, opts) do
val =
h
|> String.replace(~r/[\.\+\- x\(\)]+/, "")
|> format_phone_link(h, opts)
# val = ~s'<a href="#" class="phone-number" data-phone="#{number}">#{h}</a>'
String.replace(buffer, h, val)
end
def format_phone_link(number, original, opts) do
tag = opts[:tag] || "a"
class = opts[:class] || "phone-number"
data_phone = opts[:data_phone] || "data-phone"
attrs = format_attributes(opts[:attributes] || [])
href = opts[:href] || "#"
~s'<#{tag} href="#{href}" class="#{class}" #{data_phone}="#{number}"#{attrs}>#{original}</#{
tag
}>'
end
def create_mention_link("@" <> name, _buffer, opts) do
mention_prefix = opts[:mention_prefix]
url = mention_prefix <> name
[]
|> build_attrs(url, opts, :rel)
|> build_attrs(url, opts, :target)
|> build_attrs(url, opts, :class)
|> build_attrs(url, opts, :scheme)
|> format_mention(name, opts)
end
def create_hashtag_link(tag, _buffer, opts) do
hashtag_prefix = opts[:hashtag_prefix]
url = hashtag_prefix <> tag
[]
|> build_attrs(url, opts, :rel)
|> build_attrs(url, opts, :target)
|> build_attrs(url, opts, :class)
|> build_attrs(url, opts, :scheme)
|> format_hashtag(tag, opts)
end
def create_email_link(email, opts) do
[]
|> build_attrs(email, opts, :class)
|> format_email(email, opts)
end
def create_extra_link(uri, opts) do
[]
|> build_attrs(uri, opts, :class)
|> format_extra(uri, opts)
end
def format_mention(attrs, name, _opts) do
attrs = format_attrs(attrs)
"<a #{attrs}>@" <> name <> "</a>"
end
def format_hashtag(attrs, tag, _opts) do
attrs = format_attrs(attrs)
"<a #{attrs}>#" <> tag <> "</a>"
end
def format_email(attrs, email, _opts) do
attrs = format_attrs(attrs)
- "<a href='mailto:#{email}' #{attrs}>#{email}</a>"
+ ~s(<a href="mailto:#{email}" #{attrs}>#{email}</a>)
end
def format_extra(attrs, uri, _opts) do
- attrs = format_attrs(attrs)
- "<a href='#{uri}' #{attrs}>#{uri}</a>"
+ attrs = format_attributes(attrs)
+ ~s(<a href="#{uri}"#{attrs}>#{uri}</a>)
end
defp format_attributes(attrs) do
Enum.reduce(attrs, "", fn {name, value}, acc ->
acc <> ~s' #{name}="#{value}"'
end)
end
end
diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex
index be32b59..b00fc67 100644
--- a/lib/auto_linker/parser.ex
+++ b/lib/auto_linker/parser.ex
@@ -1,430 +1,430 @@
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>"
+ ~s{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.split("\n", trim: true)
@default_opts ~w(url)a
def parse(input, opts \\ %{})
def parse(input, opts) when is_binary(input), do: {input, nil} |> parse(opts) |> elem(0)
def parse(input, list) when is_list(list), do: parse(input, Enum.into(list, %{}))
def parse(input, 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(input, Map.merge(config, opts))
end
defp do_parse(input, %{phone: false} = opts), do: do_parse(input, Map.delete(opts, :phone))
defp do_parse(input, %{url: false} = opts), do: do_parse(input, Map.delete(opts, :url))
defp do_parse(input, %{phone: _} = opts) do
input
|> do_parse(opts, {"", "", :parsing}, &check_and_link_phone/3)
|> do_parse(Map.delete(opts, :phone))
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, %{extra: true} = opts) do
input
|> do_parse(opts, {"", "", :parsing}, &check_and_link_extra/3)
|> do_parse(Map.delete(opts, :extra))
end
defp do_parse({text, user_acc}, %{markdown: true} = opts) do
text
|> Builder.create_markdown_links(opts)
|> (&{&1, user_acc}).()
|> do_parse(Map.delete(opts, :markdown))
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, %{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, _), do: input
defp do_parse({"", user_acc}, _opts, {"", acc, _}, _handler),
do: {acc, user_acc}
defp do_parse({"", user_acc}, opts, {buffer, acc, _}, handler) do
{buffer, user_acc} = run_handler(handler, buffer, opts, user_acc)
{acc <> buffer, user_acc}
end
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, :skip}, handler),
do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "</a>", :parsing}, handler)
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, {buffer, acc, {:attrs, level}}, handler),
do:
do_parse(
{text, user_acc},
opts,
{"", acc <> buffer <> ">", {:html, level}},
handler
)
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)
end
defp do_parse({"</" <> text, user_acc}, opts, {buffer, acc, {:html, level}}, handler) do
{buffer, user_acc} = run_handler(handler, buffer, opts, user_acc)
do_parse(
{text, user_acc},
opts,
{"", acc <> buffer <> "</", {:close, level}},
handler
)
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, level}}, handler),
do:
do_parse(
{text, user_acc},
opts,
{"", acc <> buffer <> ">", {:html, level - 1}},
handler
)
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({"\n" <> text, user_acc}, opts, {buffer, acc, {:open, level}}, handler),
do:
do_parse(
{text, user_acc},
opts,
{"", acc <> buffer <> "\n", {:attrs, level}},
handler
)
# default cases where state is not important
defp do_parse(
{" " <> text, user_acc},
%{phone: _} = 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}, handler) do
{buffer, user_acc} = run_handler(handler, buffer, opts, user_acc)
do_parse(
{text, user_acc},
opts,
{"", acc <> buffer <> " ", state},
handler
)
end
defp do_parse({"\n" <> text, user_acc}, opts, {buffer, acc, state}, handler) do
{buffer, user_acc} = run_handler(handler, buffer, opts, user_acc)
do_parse(
{text, user_acc},
opts,
{"", acc <> buffer <> "\n", state},
handler
)
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)
do_parse(
{"", user_acc},
opts,
{"", acc <> buffer, state},
handler
)
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)
def check_and_link(buffer, opts, _user_acc) do
buffer
|> is_url?(opts[:scheme])
|> link_url(buffer, opts)
end
def check_and_link_email(buffer, opts, _user_acc) do
buffer
|> is_email?
|> link_email(buffer, opts)
end
def check_and_link_phone(buffer, opts, _user_acc) do
buffer
|> match_phone
|> link_phone(buffer, opts)
end
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
buffer
|> match_hashtag
|> link_hashtag(buffer, opts, user_acc)
end
def check_and_link_extra("xmpp:" <> handle, opts, _user_acc) do
handle
|> is_email?
|> link_extra("xmpp:" <> handle, opts)
end
def check_and_link_extra(buffer, opts, _user_acc) 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
@match_scheme |> Regex.match?(buffer) |> is_valid_tld?(buffer)
end
end
def is_url?(buffer, _) do
if Regex.match?(@invalid_url, buffer) do
false
else
@match_url |> Regex.match?(buffer) |> is_valid_tld?(buffer)
end
end
def is_email?(buffer) do
if Regex.match?(@invalid_url, buffer) do
false
else
@match_email |> Regex.match?(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, _, _user_acc), do: buffer
def link_hashtag(hashtag, buffer, %{hashtag_handler: hashtag_handler} = opts, user_acc) do
hashtag_handler.(hashtag, buffer, opts, user_acc)
end
def link_hashtag(hashtag, buffer, opts, _user_acc) do
Builder.create_hashtag_link(hashtag, buffer, opts)
end
def link_mention(nil, buffer, _, user_acc), do: {buffer, user_acc}
def link_mention(mention, buffer, %{mention_handler: mention_handler} = opts, user_acc) do
mention_handler.(mention, buffer, opts, user_acc)
end
def link_mention(mention, buffer, opts, _user_acc) 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, opts, user_acc) do
case handler.(buffer, opts, user_acc) do
{buffer, user_acc} -> {buffer, user_acc}
buffer -> {buffer, user_acc}
end
end
end
diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs
index 968c847..c906206 100644
--- a/test/auto_linker_test.exs
+++ b/test/auto_linker_test.exs
@@ -1,342 +1,345 @@
defmodule AutoLinkerTest do
use ExUnit.Case, async: true
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>"
+ "<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>"
+ "<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>"
+ " <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>"
+ "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, acc ->
if Enum.member?(valid_users, user) do
link = ~s(<a href="https://example.com/user/#{user}" data-user="#{user}">#{mention}</a>)
{link, %{acc | mentions: MapSet.put(acc.mentions, {mention, user})}}
else
{buffer, acc}
end
end
{result_text, %{mentions: mentions}} =
AutoLinker.link_map(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"
+ "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, acc ->
link = AutoLinker.Builder.create_hashtag_link(hashtag, buffer, opts)
{link, %{acc | tags: MapSet.put(acc.tags, hashtag)}}
end
{result_text, %{tags: tags}} =
AutoLinker.link_map(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>"
+ "<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>}
+ ~s{hello <a class="auto-linker" target="_blank" rel="noopener noreferrer" href="https://example.com/user/user">@user</a> and <a class="auto-linker" target="_blank" rel="noopener noreferrer" href="https://example.com/user/anotherUser">@anotherUser</a>.}
- assert AutoLinker.link("hello @user and @anotherUser",
+ 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>"
+ "hey <a class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://example.com/user/user@example.com\">@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>"
+ " one <a class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://example.com/tag/2two\">#2two</a> three <a class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://example.com/tag/four\">#four</a>."
- assert AutoLinker.link("one #two three #four",
+ assert AutoLinker.link(" one #2two 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>"
+ "<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>"
+ "<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> ."
+ "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>"
+ "<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>"
+ "<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>"
+ "<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>"
+ "<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>"
+ "<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>"
+ "<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>"
+ "<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>"
+
+ 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>"
+ 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>"
+ "<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>"
+ "<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>"
+ "<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"
+ "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"
+ "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"
+ "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"
+ "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
diff --git a/test/parser_test.exs b/test/parser_test.exs
index f15c600..998b8f4 100644
--- a/test/parser_test.exs
+++ b/test/parser_test.exs
@@ -1,150 +1,153 @@
defmodule AutoLinker.ParserTest do
use ExUnit.Case, async: true
doctest AutoLinker.Parser
import AutoLinker.Parser
describe "is_url" do
test "valid scheme true" do
valid_scheme_urls()
|> Enum.each(fn url ->
assert is_url?(url, true)
end)
end
test "invalid scheme true" do
invalid_scheme_urls()
|> Enum.each(fn url ->
refute is_url?(url, true)
end)
end
test "valid scheme false" do
valid_non_scheme_urls()
|> Enum.each(fn url ->
assert is_url?(url, false)
end)
end
test "invalid scheme false" do
invalid_non_scheme_urls()
|> Enum.each(fn url ->
refute is_url?(url, false)
end)
end
end
describe "match_phone" do
test "valid" do
valid_phone_nunbers()
|> Enum.each(fn number ->
assert number |> match_phone() |> valid_number?(number)
end)
end
test "invalid" do
invalid_phone_numbers()
|> Enum.each(fn number ->
assert number |> match_phone() |> is_nil
end)
end
end
describe "parse" do
test "does not link attributes" do
text = "Check out <a href='google.com'>google</a>"
assert parse(text) == text
text = "Check out <img src='google.com' alt='google.com'/>"
assert parse(text) == text
text = "Check out <span><img src='google.com' alt='google.com'/></span>"
assert parse(text) == text
end
test "links url inside html" do
text = "Check out <div class='section'>google.com</div>"
- expected = "Check out <div class='section'><a href='http://google.com'>google.com</a></div>"
+
+ expected =
+ "Check out <div class='section'><a href=\"http://google.com\">google.com</a></div>"
+
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
end
def valid_number?([list], number) do
assert List.last(list) == number
end
def valid_number?(_, _), do: false
def valid_scheme_urls,
do: [
"https://www.example.com",
"http://www2.example.com",
"http://home.example-site.com",
"http://blog.example.com",
"http://www.example.com/product",
"http://www.example.com/products?id=1&page=2",
"http://www.example.com#up",
"http://255.255.255.255",
"http://www.site.com:8008"
]
def invalid_scheme_urls,
do: [
"http://invalid.com/perl.cgi?key= | http://web-site.com/cgi-bin/perl.cgi?key1=value1&key2"
]
def valid_non_scheme_urls,
do: [
"www.example.com",
"www2.example.com",
"www.example.com:2000",
"www.example.com?abc=1",
"example.example-site.com",
"example.com",
"example.ca",
"example.tv",
"example.com:999?one=one",
"255.255.255.255",
"255.255.255.255:3000?one=1&two=2"
]
def invalid_non_scheme_urls,
do: [
"invalid.com/perl.cgi?key= | web-site.com/cgi-bin/perl.cgi?key1=value1&key2",
"invalid.",
"hi..there",
"555.555.5555"
]
def valid_phone_nunbers,
do: [
"x55",
"x555",
"x5555",
"x12345",
"+1 555 555-5555",
"555 555-5555",
"555.555.5555",
"613-555-5555",
"1 (555) 555-5555",
"(555) 555-5555",
"1.555.555.5555",
"800 555-5555",
"1.800.555.5555",
"1 (800) 555-5555",
"888 555-5555",
"887 555-5555",
"1-877-555-5555",
"1 800 710-5515"
]
def invalid_phone_numbers,
do: [
"5555",
"x5",
"(555) 555-55"
]
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Nov 28, 5:51 AM (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40911
Default Alt Text
(44 KB)
Attached To
Mode
R19 linkify
Attached
Detach File
Event Timeline
Log In to Comment