Browse Source

Merge branch 'linkify' into 'master'

Linkify

See merge request pleroma/linkify!18
merge-requests/19/head
Egor 4 years ago
parent
commit
74c1986da5
13 changed files with 615 additions and 977 deletions
  1. +1
    -1
      .gitlab-ci.yml
  2. +18
    -40
      README.md
  3. +0
    -30
      config/config.exs
  4. +0
    -69
      lib/auto_linker.ex
  5. +51
    -0
      lib/linkify.ex
  6. +10
    -56
      lib/linkify/builder.ex
  7. +90
    -205
      lib/linkify/parser.ex
  8. +10
    -12
      mix.exs
  9. +3
    -5
      mix.lock
  10. +0
    -448
      test/auto_linker_test.exs
  11. +18
    -49
      test/builder_test.exs
  12. +404
    -0
      test/linkify_test.exs
  13. +10
    -62
      test/parser_test.exs

+ 1
- 1
.gitlab-ci.yml View File

@ -1,4 +1,4 @@
image: elixir:1.7.2
image: elixir:1.8.1
cache:
key: ${CI_COMMIT_REF_SLUG}


+ 18
- 40
README.md View File

@ -1,25 +1,16 @@
# AutoLinker
# Linkify
[![Build Status](https://travis-ci.org/smpallen99/auto_linker.png?branch=master)](https://travis-ci.org/smpallen99/auto_linker) [![Hex Version][hex-img]][hex] [![License][license-img]][license]
[hex-img]: https://img.shields.io/hexpm/v/auto_linker.svg
[hex]: https://hex.pm/packages/auto_linker
[license-img]: http://img.shields.io/badge/license-MIT-brightgreen.svg
[license]: http://opensource.org/licenses/MIT
AutoLinker is a basic package for turning website names, and phone numbers into links.
Linkify is a basic package for turning website names into links.
Use this package in your web view to convert web references into click-able links.
This is a very early version. Some of the described options are not yet functional.
## Installation
The package can be installed by adding `auto_linker` to your list of dependencies in `mix.exs`:
The package can be installed by adding `linkify` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[{:auto_linker, "~> 0.2"}]
[{:linkify, "~> 0.1"}]
end
```
@ -27,44 +18,31 @@ end
The following examples illustrate some examples on how to use the auto linker.
```iex
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>"
```elixir
iex> AutoLinker.link("call me at x9999", phone: true)
"call me at <a href=\"#\" class=\"phone-number\" data-phone=\"9999\">x9999</a>"
iex> Linkify.link("google.com")
"<a href=\"http://google.com\">google.com</a>"
iex> AutoLinker.link("or at home on 555.555.5555", phone: true)
"or at home on <a href=\"#\" class=\"phone-number\" data-phone=\"5555555555\">555.555.5555</a>"
iex> Linkify.link("google.com", class: "linkified")
"<a href=\"http://google.com\" class=\"linkified\">google.com</a>"
iex> AutoLinker.link(", work (555) 555-5555", phone: true)
", work <a href=\"#\" class=\"phone-number\" data-phone=\"5555555555\">(555) 555-5555</a>"
iex> Linkify.link("google.com", new_window: true)
"<a href=\"http://google.com\" target=\"_blank\">google.com</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>"
iex> Linkify.link("google.com", new_window: true, rel: "noopener noreferrer")
"<a href=\"http://google.com\" target=\"_blank\" rel=\"noopener noreferrer\">google.com</a>"
```
See the [Docs](https://hexdocs.pm/auto_linker/) for more examples
## Configuration
By default, link parsing is enabled and phone parsing is disabled.
See the [Docs](https://hexdocs.pm/linkify/) for more examples
```elixir
# enable phone parsing, and disable link parsing
config :auto_linker, opts: [phone: true, url: false]
```
## Acknowledgements
This is a fork of [auto_linker](https://github.com/smpallen99/auto_linker) by [Steve Pallen](https://github.com/smpallen99).
## License
`auto_linker` is Copyright (c) 2017 E-MetroTel
Copyright © 2017 E-MetroTel
Copyright © 2019 Pleroma Authors
The source is released under the MIT License.


+ 0
- 30
config/config.exs View File

@ -1,30 +0,0 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.
# You can configure for your application as:
#
# config :auto_linker, key: :value
#
# And access this configuration in your application as:
#
# Application.get_env(:auto_linker, :key)
#
# Or configure a 3rd-party app:
#
# config :logger, level: :info
#
# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"

+ 0
- 69
lib/auto_linker.ex View File

@ -1,69 +0,0 @@
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")
~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)
~s(<a href="http://google.com" class="auto-linker">google.com</a>)
iex> AutoLinker.link("google.com", new_window: false, rel: false, class: false)
~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)
~s(<a href='http://google.com'>Google</a>)
iex> AutoLinker.link("[Google Search](http://google.com)", markdown: true)
~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.)
* `validate_tld: true` - Set to false to disable TLD validation for urls/emails, also can be set to :no_scheme to validate TLDs only for urls without a scheme (e.g `example.com` will be validated, but `http://example.loki` won't)
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

+ 51
- 0
lib/linkify.ex View File

@ -0,0 +1,51 @@
defmodule Linkify 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\">google.com</a>"`
## Examples
iex> Linkify.link("google.com")
~s(<a href="http://google.com">google.com</a>)
iex> Linkify.link("google.com", new_window: true, rel: "noopener noreferrer")
~s(<a href="http://google.com" target="_blank" rel="noopener noreferrer">google.com</a>)
iex> Linkify.link("google.com", class: "linkified")
~s(<a href="http://google.com" class="linkified">google.com</a>)
"""
import Linkify.Parser
@doc """
Finds links and turns them into HTML `<a>` tag.
Options:
* `class` - specify the class to be added to the generated link.
* `rel` - specify the rel attribute.
* `new_window` - set to `true` to add `target="_blank"` attribute
* `truncate` - Set to a number to truncate urls longer then the number. Truncated urls will end in `...`
* `strip_prefix` - Strip the scheme prefix (default: `false`)
* `exclude_class` - Set to a class name when you don't want urls auto linked in the html of the give class (default: `false`)
* `exclude_id` - Set to an element id when you don't want urls auto linked in the html of the give element (default: `false`)
* `email` - link email links (default: `false`)
* `mention` - link @mentions (when `true`, requires `mention_prefix` or `mention_handler` options to be set) (default: `false`)
* `mention_prefix` - a prefix to build a link for a mention (example: `https://example.com/user/`, default: `nil`)
* `mention_handler` - a custom handler to validate and formart a mention (default: `nil`)
* `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.)
* `validate_tld: true` - Set to false to disable TLD validation for urls/emails, also can be set to :no_scheme to validate TLDs only for urls without a scheme (e.g `example.com` will be validated, but `http://example.loki` won't)
"""
def link(text, opts \\ []) do
parse(text, opts)
end
def link_map(text, acc, opts \\ []) do
parse({text, acc}, opts)
end
end

lib/auto_linker/builder.ex → lib/linkify/builder.ex View File

@ -1,4 +1,4 @@
defmodule AutoLinker.Builder do
defmodule Linkify.Builder do
@moduledoc """
Module for building the auto generated link.
"""
@ -17,14 +17,6 @@ defmodule AutoLinker.Builder do
|> format_url(text, 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, uri, %{rel: get_rel}, :rel) when is_function(get_rel, 1) do
case get_rel.(uri) do
nil -> attrs
@ -33,15 +25,21 @@ 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) do
rel when is_binary(rel) -> [{:rel, rel} | attrs]
_ -> attrs
end
end
defp build_attrs(attrs, _, opts, :target) do
if Map.get(opts, :new_window, true), do: [{:target, :_blank} | attrs], else: attrs
if Map.get(opts, :new_window), 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
case Map.get(opts, :class) do
cls when is_binary(cls) -> [{:class, cls} | attrs]
_ -> attrs
end
end
defp build_attrs(attrs, url, _opts, :href) do
@ -68,16 +66,6 @@ defmodule AutoLinker.Builder do
|> 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
@ -93,34 +81,6 @@ defmodule AutoLinker.Builder do
defp strip_prefix(url, _), do: url
def create_phone_link([], buffer, _), do: buffer
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]
@ -182,10 +142,4 @@ defmodule AutoLinker.Builder do
attrs = format_attrs(attrs)
~s(<a #{attrs}>#{uri}</a>)
end
defp format_attributes(attrs) do
Enum.reduce(attrs, "", fn {name, value}, acc ->
acc <> ~s' #{name}="#{value}"'
end)
end
end

lib/auto_linker/parser.ex → lib/linkify/parser.ex View File

@ -1,19 +1,15 @@
defmodule AutoLinker.Parser do
defmodule Linkify.Parser do
@moduledoc """
Module to handle parsing the the input string.
"""
alias AutoLinker.Builder
alias Linkify.Builder
@invalid_url ~r/(\.\.+)|(^(\d+\.){1,2}\d+$)/
@match_url ~r{^[\w\.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+$}
@match_url ~r{^(?:\W*)?(?<url>(?:https?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:\/?#[\]@!\$&'\(\)\*\+,;=.]+$)}u
@match_scheme ~r{^(?:\W*)?(?<url>(?:https?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:\/?#[\]@!\$&'\(\)\*\+,;=.]+$)}u
@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{^(?:\W*https?:\/\/)?(?:[^@\n]+\\w@)?(?<host>[^:#~\/\n?]+)}u
@match_hostname ~r{^\W*(?<scheme>https?:\/\/)?(?:[^@\n]+\\w@)?(?<host>[^:#~\/\n?]+)}u
@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])$"
@ -42,228 +38,144 @@ defmodule AutoLinker.Parser do
@tlds "./priv/tlds.txt" |> File.read!() |> String.split("\n", trim: true) |> MapSet.new()
@default_opts ~w(url validate_tld)a
@default_opts %{
url: true,
validate_tld: true
}
@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
iex> AutoLinker.Parser.parse("Check out google.com")
~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>}
iex> Linkify.Parser.parse("Check out google.com")
~s{Check out <a href="http://google.com">google.com</a>}
"""
@types [:url, :email, :hashtag, :mention, :extra]
def parse(input, opts \\ %{})
def parse(input, opts) when is_binary(input), do: {input, nil} |> parse(opts) |> elem(0)
def parse(input, opts) when is_binary(input), do: {input, %{}} |> parse(opts) |> elem(0)
def parse(input, list) when is_list(list), do: parse(input, Enum.into(list, %{}))
def parse(input, 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)
opts = Map.merge(@default_opts, opts)
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, %{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
Enum.reduce(opts, input, fn
{type, true}, input when type in @types ->
do_parse(input, opts, {"", "", :parsing}, type)
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))
_, input ->
input
end)
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))
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
# 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(
{<<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
case parse_link(str, opts) do
case @match_url |> Regex.run(str, capture: [:url]) |> hd() do
^buffer -> link_url(buffer, opts)
url -> String.replace(buffer, url, link_url(url, opts))
end
@ -272,56 +184,38 @@ defmodule AutoLinker.Parser do
end
end
defp parse_link(str, %{scheme: true}) do
@match_scheme |> Regex.run(str, capture: [:url]) |> hd()
end
defp parse_link(str, _), do: str
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_phone(buffer, opts, _user_acc) do
buffer
|> match_phone
|> link_phone(buffer, opts)
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
# @doc false
defp strip_parens("(" <> buffer) do
~r/[^\)]*/ |> Regex.run(buffer) |> hd()
end
defp strip_parens(buffer), do: buffer
def url?(buffer, opts) do
if opts[:scheme] do
valid_url?(buffer) && Regex.match?(@match_scheme, buffer) && valid_tld?(buffer, opts)
else
valid_url?(buffer) && Regex.match?(@match_url, buffer) && valid_tld?(buffer, opts)
end
valid_url?(buffer) && Regex.match?(@match_url, buffer) && valid_tld?(buffer, opts)
end
def email?(buffer, opts) do
@ -330,38 +224,35 @@ defmodule AutoLinker.Parser do
defp valid_url?(url), do: !Regex.match?(@invalid_url, url)
def valid_tld?(buffer, opts) do
@doc """
Validates a URL's TLD. Returns a boolean.
Will return `true` if `:validate_tld` option set to `false`.
Will skip validation and return `true` if `:validate_tld` set to `:no_scheme` and the url has a scheme.
"""
def valid_tld?(url, opts) do
[scheme, host] = Regex.run(@match_hostname, url, capture: [:scheme, :host])
cond do
opts[:validate_tld] == false ->
true
opts[:validate_tld] == :no_scheme && opts[:scheme] ->
ip?(host) ->
true
# don't validate if scheme is present
opts[:validate_tld] == :no_scheme and scheme != "" ->
true
true ->
with [host] <- Regex.run(@match_hostname, buffer, capture: [:host]) do
if ip?(host) do
true
else
tld = host |> String.split(".") |> List.last()
MapSet.member?(@tlds, tld)
end
else
_ -> false
end
tld = host |> String.split(".") |> List.last()
MapSet.member?(@tlds, tld)
end
end
def ip?(buffer), do: Regex.match?(@match_ip, buffer)
@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
@ -416,12 +307,6 @@ defmodule AutoLinker.Parser do
defp maybe_update_buffer(out, _match, _buffer), do: out
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(buffer, opts) do
Builder.create_link(buffer, opts)
@ -436,8 +321,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

+ 10
- 12
mix.exs View File

@ -1,21 +1,21 @@
defmodule AutoLinker.Mixfile do
defmodule Linkify.Mixfile do
use Mix.Project
@version "0.2.2"
@version "0.1.0"
def project do
[
app: :auto_linker,
app: :linkify,
version: @version,
elixir: "~> 1.4",
elixir: "~> 1.8",
build_embedded: Mix.env() == :prod,
start_permanent: Mix.env() == :prod,
deps: deps(),
docs: [extras: ["README.md"]],
package: package(),
name: "AutoLinker",
name: "Linkify",
description: """
AutoLinker is a basic package for turning website names into links.
Linkify is a basic package for turning website names into links.
"""
]
end
@ -29,18 +29,16 @@ defmodule AutoLinker.Mixfile do
# Dependencies can be Hex packages:
defp deps do
[
{:ex_doc, "~> 0.19", only: :dev, runtime: false},
{:earmark, "~> 1.2", only: :dev, override: true},
{:credo, "~> 1.0.0", only: [:dev, :test], runtime: false}
{:ex_doc, "~> 0.20", only: :dev, runtime: false},
{:credo, "~> 1.1.0", only: [:dev, :test], runtime: false}
]
end
defp package do
[
maintainers: ["Stephen Pallen"],
licenses: ["MIT"],
links: %{"Github" => "https://github.com/smpallen99/auto_linker"},
files: ~w(lib README.md mix.exs LICENSE)
links: %{"GitLab" => "https://git.pleroma.social/pleroma/linkify"},
files: ~w(lib priv README.md mix.exs LICENSE)
]
end
end

+ 3
- 5
mix.lock View File

@ -1,12 +1,10 @@
%{
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
"credo": {:hex, :credo, "1.0.2", "88bc918f215168bf6ce7070610a6173c45c82f32baa08bdfc80bf58df2d103b6", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.19.3", "3c7b0f02851f5fc13b040e8e925051452e41248f685e40250d7e40b07b9f8c10", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"file_system": {:hex, :file_system, "0.2.6", "fd4dc3af89b9ab1dc8ccbcc214a0e60c41f34be251d9307920748a14bf41f1d3", [:mix], [], "hexpm"},
"credo": {:hex, :credo, "1.1.0", "e0c07b2fd7e2109495f582430a1bc96b2c71b7d94c59dfad120529f65f19872f", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"mix_test_watch": {:hex, :mix_test_watch, "0.9.0", "c72132a6071261893518fa08e121e911c9358713f62794a90c95db59042af375", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
}

+ 0
- 448
test/auto_linker_test.exs View File

@ -1,448 +0,0 @@
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>"
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) ==
~s(<a href="#" class="phone-number" data-phone="8888888888">888 888-8888</a>) <>
~s( <a href='a.com' class="auto-linker" target="_blank" rel="noopener noreferrer">ab</a>)
end
test "all kinds of links" do
text =
"hello google.com https://ddg.com 888 888-8888 user@email.com [google.com](http://google.com) irc:///mIRC"
expected =
"hello <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=\"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,
scheme: true,
extra: true,
class: false,
new_window: false,
rel: false
) == expected
end
test "rel as function" do
text = "google.com"
expected = "<a href=\"http://google.com\" rel=\"com\">google.com</a>"
custom_rel = fn url ->
url |> String.split(".") |> List.last()
end
assert AutoLinker.link(text,
class: false,
new_window: false,
rel: custom_rel
) == expected
text = "google.com"
expected = "<a href=\"http://google.com\">google.com</a>"
custom_rel = fn _ -> nil end
assert AutoLinker.link(text,
class: false,
new_window: false,
rel: custom_rel
) == expected
end
test "link_map/2" do
assert AutoLinker.link_map("google.com", []) ==
{"<a href=\"http://google.com\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">google.com</a>",
[]}
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"
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>"
assert MapSet.to_list(tags) == ["#hello", "#world"]
end
test "mention handler and hashtag prefix" do
text =
"Hello again, @user.&lt;script&gt;&lt;/script&gt;\nThis is on another :moominmamma: line. #2hu #epic #phantasmagoric"
handler = fn "@" <> user = mention, _, _, _ ->
~s(<span class='h-card'><a href='#/user/#{user}'>@<span>#{mention}</span></a></span>)
end
expected =
"Hello again, <span class='h-card'><a href='#/user/user'>@<span>@user</span></a></span>.&lt;script&gt;&lt;/script&gt;\nThis is on another :moominmamma: line. <a href=\"/tag/2hu\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">#2hu</a> <a href=\"/tag/epic\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">#epic</a> <a href=\"/tag/phantasmagoric\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">#phantasmagoric</a>"
assert AutoLinker.link(text,
mention: true,
mention_handler: handler,
hashtag: true,
hashtag_prefix: "/tag/"
) == expected
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 "mentions inside html tags" do
text =
"<p><strong>hello world</strong></p>\n<p><`em>another @user__test and @user__test google.com paragraph</em></p>\n"
expected =
"<p><strong>hello world</strong></p>\n<p><`em>another <a href=\"u/user__test\">@user__test</a> and <a href=\"u/user__test\">@user__test</a> <a href=\"http://google.com\">google.com</a> paragraph</em></p>\n"
assert AutoLinker.link(text,
mention: true,
mention_prefix: "u/",
class: false,
rel: false,
new_window: false
) == 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
end
describe "hashtag links" do
test "hashtag" do
expected =
" one <a href=\"https://example.com/tag/2two\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">#2two</a> three <a href=\"https://example.com/tag/four\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">#four</a>."
assert AutoLinker.link(" one #2two three #four.",
hashtag: true,
hashtag_prefix: "https://example.com/tag/"
) == expected
end
test "must have non-numbers" do
expected = "<a href=\"/t/1ok\">#1ok</a> #42 #7"
assert AutoLinker.link("#1ok #42 #7",
hashtag: true,
hashtag_prefix: "/t/",
class: false,
rel: false,
new_window: false
) == expected
end
test "support French" do
text = "#administrateur·rice·s #ingénieur·e·s"
expected =
"<a href=\"/t/administrateur·rice·s\">#administrateur·rice·s</a> <a href=\"/t/ingénieur·e·s\">#ingénieur·e·s</a>"
assert AutoLinker.link(text,
hashtag: true,
hashtag_prefix: "/t/",
class: false,
rel: false,
new_window: false
) == expected
end
test "support Telugu" do
text = "#చక్రం #కకకకక్ #కకకకాక #కకకక్రకకకక"
expected =
"<a href=\"/t/చక్రం\">#చక్రం</a> <a href=\"/t/కకకకక్\">#కకకకక్</a> <a href=\"/t/కకకకాక\">#కకకకాక</a> <a href=\"/t/కకకక్రకకకక\">#కకకక్రకకకక</a>"
assert AutoLinker.link(text,
hashtag: true,
hashtag_prefix: "/t/",
class: false,
rel: false,
new_window: false
) == 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 "turn urls with schema into urls" do
text = "📌https://google.com"
expected = "📌<a href=\"https://google.com\">google.com</a>"
assert AutoLinker.link(text, scheme: true, class: false, new_window: false, rel: false) ==
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, new_window: false, rel: false) == 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, new_window: false, rel: false) == 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, new_window: false, rel: false) == 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

+ 18
- 49
test/builder_test.exs View File

@ -1,39 +1,34 @@
defmodule AutoLinker.BuilderTest do
defmodule Linkify.BuilderTest do
use ExUnit.Case, async: true
doctest AutoLinker.Builder
doctest Linkify.Builder
import AutoLinker.Builder
import Linkify.Builder
test "create_link/2" do
expected =
"<a href=\"http://text\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">text</a>"
expected = "<a href=\"http://text\">text</a>"
assert create_link("text", %{}) == expected
expected = "<a href=\"http://text\" class=\"auto-linker\" target=\"_blank\">text</a>"
assert create_link("text", %{rel: nil}) == expected
expected = "<a href=\"http://text\" target=\"_blank\">text</a>"
expected =
"<a href=\"http://text\" class=\"auto-linker\" target=\"_blank\" rel=\"me\">text</a>"
assert create_link("text", %{new_window: true}) == expected
assert create_link("text", %{rel: "me"}) == expected
expected = "<a href=\"http://text\" class=\"linkified\">text</a>"
assert create_link("text", %{class: "linkified"}) == expected
expected = "<a href=\"http://text\" class=\"auto-linker\" target=\"_blank\">t...</a>"
expected = "<a href=\"http://text\" rel=\"me\">text</a>"
assert create_link("text", %{truncate: 3, rel: false}) == expected
assert create_link("text", %{rel: "me"}) == expected
expected = "<a href=\"http://text\" class=\"auto-linker\" target=\"_blank\">text</a>"
assert create_link("text", %{truncate: 2, rel: false}) == expected
expected = "<a href=\"http://text\">t...</a>"
expected = "<a href=\"http://text\" class=\"auto-linker\" target=\"_blank\">http://text</a>"
assert create_link("http://text", %{rel: false, strip_prefix: false}) == expected
end
assert create_link("text", %{truncate: 3}) == expected
test "create_markdown_links/2" do
expected =
"<a href='url' class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">text</a>"
expected = "<a href=\"http://text\">text</a>"
assert create_link("text", %{truncate: 2}) == expected
assert create_markdown_links("[text](url)", %{}) == expected
expected = "<a href=\"http://text\">http://text</a>"
assert create_link("http://text", %{strip_prefix: false}) == expected
end
test "format_hashtag/3" do
@ -53,40 +48,14 @@ defmodule AutoLinker.BuilderTest do
assert format_mention(%{href: "url"}, "user@host", nil) == expected
end
describe "create_phone_link" do
test "finishes" do
assert create_phone_link([], "", []) == ""
end
test "handles one link" do
phrase = "my exten is x888. Call me."
expected =
~s'my exten is <a href="#" class="phone-number" data-phone="888" test=\"test\">x888</a>. Call me.'
assert create_phone_link([["x888", ""]], phrase, attributes: [test: "test"]) == expected
end
test "handles multiple links" do
phrase = "555.555.5555 or (555) 888-8888"
expected =
~s'<a href="#" class="phone-number" data-phone="5555555555">555.555.5555</a> or ' <>
~s'<a href="#" class="phone-number" data-phone="5558888888">(555) 888-8888</a>'
assert create_phone_link([["555.555.5555", ""], ["(555) 888-8888"]], phrase, []) == expected
end
end
test "create_mention_link/3" do
expected =
"<a href=\"/u/navi\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">@navi</a>"
expected = "<a href=\"/u/navi\">@navi</a>"
assert create_mention_link("@navi", "hello @navi", %{mention_prefix: "/u/"}) == expected
end
test "create_email_link/3" do
expected = "<a href=\"mailto:user@example.org\" class=\"auto-linker\">user@example.org</a>"
expected = "<a href=\"mailto:user@example.org\">user@example.org</a>"
assert create_email_link("user@example.org", %{}) == expected
assert create_email_link("user@example.org", %{href: "mailto:user@example.org"}) == expected
end


+ 404
- 0
test/linkify_test.exs View File

@ -0,0 +1,404 @@
defmodule LinkifyTest do
use ExUnit.Case, async: true
doctest Linkify
test "default link" do
assert Linkify.link("google.com") ==
"<a href=\"http://google.com\">google.com</a>"
end
test "does on link existing links" do
text = ~s(<a href="http://google.com">google.com</a>)
assert Linkify.link(text) == text
end
test "all kinds of links" do
text = "hello google.com https://ddg.com user@email.com irc:///mIRC"
expected =
"hello <a href=\"http://google.com\">google.com</a> <a href=\"https://ddg.com\">ddg.com</a> <a href=\"mailto:user@email.com\">user@email.com</a> <a href=\"irc:///mIRC\">irc:///mIRC</a>"
assert Linkify.link(text,
email: true,
extra: true
) == expected
end
test "class attribute" do
assert Linkify.link("google.com", class: "linkified") ==
"<a href=\"http://google.com\" class=\"linkified\">google.com</a>"
end
test "rel attribute" do
assert Linkify.link("google.com", rel: "noopener noreferrer") ==
"<a href=\"http://google.com\" rel=\"noopener noreferrer\">google.com</a>"
end
test "rel as function" do
text = "google.com"
expected = "<a href=\"http://google.com\" rel=\"com\">google.com</a>"
custom_rel = fn url ->
url |> String.split(".") |> List.last()
end
assert Linkify.link(text, rel: custom_rel) == expected
text = "google.com"
expected = "<a href=\"http://google.com\">google.com</a>"
custom_rel = fn _ -> nil end
assert Linkify.link(text, rel: custom_rel) == expected
end
test "link_map/2" do
assert Linkify.link_map("google.com", []) ==
{"<a href=\"http://google.com\">google.com</a>", []}
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}} =
Linkify.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"
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 = Linkify.Builder.create_hashtag_link(hashtag, buffer, opts)
{link, %{acc | tags: MapSet.put(acc.tags, hashtag)}}
end
{result_text, %{tags: tags}} =
Linkify.link_map(text, %{tags: MapSet.new()},
hashtag: true,
hashtag_handler: handler,
hashtag_prefix: "https://example.com/user/",
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
test "mention handler and hashtag prefix" do
text =
"Hello again, @user.&lt;script&gt;&lt;/script&gt;\nThis is on another :moominmamma: line. #2hu #epic #phantasmagoric"
handler = fn "@" <> user = mention, _, _, _ ->
~s(<span class="h-card"><a href="#/user/#{user}">@<span>#{mention}</span></a></span>)
end
expected =
~s(Hello again, <span class="h-card"><a href="#/user/user">@<span>@user</span></a></span>.&lt;script&gt;&lt;/script&gt;\nThis is on another :moominmamma: line. <a href="/tag/2hu" target="_blank">#2hu</a> <a href="/tag/epic" target="_blank">#epic</a> <a href="/tag/phantasmagoric" target="_blank">#phantasmagoric</a>)
assert Linkify.link(text,
mention: true,
mention_handler: handler,
hashtag: true,
hashtag_prefix: "/tag/",
new_window: true
) == expected
end
end
describe "mentions" do
test "simple mentions" do
expected =
~s{hello <a href="https://example.com/user/user" target="_blank">@user</a> and <a href="https://example.com/user/anotherUser" target="_blank">@anotherUser</a>.}
assert Linkify.link("hello @user and @anotherUser.",
mention: true,
mention_prefix: "https://example.com/user/",
new_window: true
) == expected
end
test "mentions inside html tags" do
text =
"<p><strong>hello world</strong></p>\n<p><`em>another @user__test and @user__test google.com paragraph</em></p>\n"
expected =
"<p><strong>hello world</strong></p>\n<p><`em>another <a href=\"u/user__test\">@user__test</a> and <a href=\"u/user__test\">@user__test</a> <a href=\"http://google.com\">google.com</a> paragraph</em></p>\n"
assert Linkify.link(text, mention: true, mention_prefix: "u/") == expected
end
test "metion @user@example.com" do
text = "hey @user@example.com"
expected =
"hey <a href=\"https://example.com/user/user@example.com\" target=\"_blank\">@user@example.com</a>"
assert Linkify.link(text,
mention: true,
mention_prefix: "https://example.com/user/",
new_window: true
) == expected
end
end
describe "hashtag links" do
test "hashtag" do
expected =
" one <a href=\"https://example.com/tag/2two\" target=\"_blank\">#2two</a> three <a href=\"https://example.com/tag/four\" target=\"_blank\">#four</a>."
assert Linkify.link(" one #2two three #four.",
hashtag: true,
hashtag_prefix: "https://example.com/tag/",
new_window: true
) == expected
end
test "must have non-numbers" do
expected = "<a href=\"/t/1ok\">#1ok</a> #42 #7"
assert Linkify.link("#1ok #42 #7",
hashtag: true,
hashtag_prefix: "/t/",
rel: false
) == expected
end
test "support French" do
text = "#administrateur·rice·s #ingénieur·e·s"
expected =
"<a href=\"/t/administrateur·rice·s\">#administrateur·rice·s</a> <a href=\"/t/ingénieur·e·s\">#ingénieur·e·s</a>"
assert Linkify.link(text,
hashtag: true,
hashtag_prefix: "/t/",
rel: false
) == expected
end
test "support Telugu" do
text = "#చక్రం #కకకకక్ #కకకకాక #కకకక్రకకకక"
expected =
"<a href=\"/t/చక్రం\">#చక్రం</a> <a href=\"/t/కకకకక్\">#కకకకక్</a> <a href=\"/t/కకకకాక\">#కకకకాక</a> <a href=\"/t/కకకక్రకకకక\">#కకకక్రకకకక</a>"
assert Linkify.link(text,
hashtag: true,
hashtag_prefix: "/t/",
rel: false
) == 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 Linkify.link(text,
hashtag: true,
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 Linkify.link(text,
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\" target=\"_blank\">youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla</a> ."
assert Linkify.link(text, new_window: true) == expected
# no scheme
text = "Hey, check out www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ."
assert Linkify.link(text, new_window: true) == expected
end
test "turn urls with schema into urls" do
text = "📌https://google.com"
expected = "📌<a href=\"https://google.com\">google.com</a>"
assert Linkify.link(text, rel: false) == expected
end
test "hostname/@user" do
text = "https://example.com/@user"
expected = "<a href=\"https://example.com/@user\" target=\"_blank\">example.com/@user</a>"
assert Linkify.link(text, new_window: true) == expected
text = "https://example.com:4000/@user"
expected =
"<a href=\"https://example.com:4000/@user\" target=\"_blank\">example.com:4000/@user</a>"
assert Linkify.link(text, new_window: true) == expected
text = "https://example.com:4000/@user"
expected =
"<a href=\"https://example.com:4000/@user\" target=\"_blank\">example.com:4000/@user</a>"
assert Linkify.link(text, new_window: true) == expected
text = "@username"
expected = "@username"
assert Linkify.link(text, new_window: true) == expected
text = "http://www.cs.vu.nl/~ast/intel/"
expected = "<a href=\"http://www.cs.vu.nl/~ast/intel/\">cs.vu.nl/~ast/intel/</a>"
assert Linkify.link(text) == 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\">forum.zdoom.org/viewtopic.php?f=44&t=57087</a>"
assert Linkify.link(text) == 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\">en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul</a>"
assert Linkify.link(text) == expected
text = "https://en.wikipedia.org/wiki/Duff's_device"
expected =
"<a href=\"https://en.wikipedia.org/wiki/Duff's_device\">en.wikipedia.org/wiki/Duff's_device</a>"
assert Linkify.link(text) == expected
end
end
describe "non http links" do
test "xmpp" do
text = "xmpp:user@example.com"
expected = "<a href=\"xmpp:user@example.com\">xmpp:user@example.com</a>"
assert Linkify.link(text, extra: true) == expected
end
test "email" do
text = "user@example.com"
expected = "<a href=\"mailto:user@example.com\">user@example.com</a>"
assert Linkify.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\">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 Linkify.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\">dweb://584faa05d394190ab1a3f0240607f9bf2b7e2bd9968830a11cf77db0cea36a21+v1.0.0/path/to/file.txt</a>"
assert Linkify.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\">google.com</a>"
assert Linkify.link(text) == 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 Linkify.link(text) == expected
text = "this url https://google.foobar.com/ has valid TLD"
expected =
"this url <a href=\"https://google.foobar.com/\">google.foobar.com/</a> has valid TLD"
assert Linkify.link(text) == expected
end
test "only existing TLDs without scheme" do
text = "this url google.foobar.blah11blah/ has invalid TLD"
assert Linkify.link(text) == text
text = "this url google.foobar.com/ has valid TLD"
expected =
"this url <a href=\"http://google.foobar.com/\">google.foobar.com/</a> has valid TLD"
assert Linkify.link(text) == 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/\">google.foobar.com/</a> has valid TLD"
assert Linkify.link(text) == expected
text = "this url google.foobar.com/ has valid TLD"
expected =
"this url <a href=\"http://google.foobar.com/\">google.foobar.com/</a> has valid TLD"
assert Linkify.link(text) == expected
end
end
end

+ 10
- 62
test/parser_test.exs View File

@ -1,8 +1,8 @@
defmodule AutoLinker.ParserTest do
defmodule Linkify.ParserTest do
use ExUnit.Case, async: true
doctest AutoLinker.Parser
doctest Linkify.Parser
import AutoLinker.Parser
import Linkify.Parser
describe "url?/2" do
test "valid scheme true" do
@ -106,28 +106,10 @@ defmodule AutoLinker.ParserTest do
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 "handle line breakes" do
text = "google.com\r\nssss"
expected =
"<a href=\"http://google.com\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">google.com</a>\r\nssss"
expected = "<a href=\"http://google.com\">google.com</a>\r\nssss"
assert parse(text) == expected
end
@ -157,25 +139,20 @@ defmodule AutoLinker.ParserTest do
expected = "<div><a href=\"http://google.com\">google.com</a></div>"
assert parse(text, class: false, rel: false, new_window: false, phone: false) == expected
assert parse(text, class: false, rel: false) == expected
text = "Check out <div class='section'>google.com</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
assert parse(text, class: false, rel: false) == expected
end
test "links url inside nested html" do
text = "<p><strong>google.com</strong></p>"
expected = "<p><strong><a href=\"http://google.com\">google.com</a></strong></p>"
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
assert parse(text, class: false, rel: false) == expected
end
test "do not link parens" do
@ -184,19 +161,19 @@ defmodule AutoLinker.ParserTest do
expected =
" foo (<a href=\"https://example.com/path/folder/\">example.com/path/folder/</a>), bar"
assert parse(text, class: false, rel: false, new_window: false, scheme: true) == expected
assert parse(text, class: false, rel: false, scheme: true) == expected
text = " foo (example.com/path/folder/), bar"
expected =
" foo (<a href=\"http://example.com/path/folder/\">example.com/path/folder/</a>), bar"
assert parse(text, class: false, rel: false, new_window: false) == expected
assert parse(text, class: false, rel: false) == expected
end
test "do not link urls" do
text = "google.com"
assert parse(text, url: false, phone: true) == text
assert parse(text, url: false) == text
end
test "do not link `:test.test`" do
@ -260,35 +237,6 @@ defmodule AutoLinker.ParserTest do
"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"
]
def custom_tld_scheme_urls,
do: [
"http://whatever.null/",


Loading…
Cancel
Save