logo

auto_linker

AutoLinker-shim, based on https://git.pleroma.social/pleroma/auto_linker
commit: 34e4e2f95377dfd920def5dd5646053e54c5492d
parent: 0d72ff37a4c28c1a80b4b0f198c082cd8990ad73
Author: Egor Kislitsyn <egor@kislitsyn.com>
Date:   Tue,  5 Feb 2019 18:22:51 +0700

add formatter

Diffstat:

A.formatter.exs4++++
Mlib/auto_linker.ex3+--
Mlib/auto_linker/builder.ex27++++++++++++++++++---------
Mlib/auto_linker/parser.ex70++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mmix.exs16+++++++++-------
Mtest/auto_linker_test.exs14++++++--------
Mtest/builder_test.exs15+++++++++++----
Mtest/parser_test.exs140+++++++++++++++++++++++++++++++++++++++++--------------------------------------
8 files changed, 170 insertions(+), 119 deletions(-)

diff --git a/.formatter.exs b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/lib/auto_linker.ex b/lib/auto_linker.ex @@ -51,7 +51,6 @@ defmodule AutoLinker do Note that passing opts to `link/2` will override the configuration settings. """ def link(text, opts \\ []) do - parse text, opts + parse(text, opts) end - end diff --git a/lib/auto_linker/builder.ex b/lib/auto_linker/builder.ex @@ -24,20 +24,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 + 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 + 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 + 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] + do: [{:href, url} | attrs], + else: [{:href, "http://" <> url} | attrs] end defp format_url(attrs, url, opts) do @@ -45,6 +46,7 @@ defmodule AutoLinker.Builder do url |> strip_prefix(Map.get(opts, :strip_prefix, true)) |> truncate(Map.get(opts, :truncate, false)) + attrs = format_attrs(attrs) "<a #{attrs}>" <> url <> "</a>" end @@ -61,11 +63,13 @@ defmodule AutoLinker.Builder 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 @@ -75,13 +79,15 @@ defmodule AutoLinker.Builder do |> 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 + create_phone_link(t, format_phone_link(h, buffer, opts), opts) end def format_phone_link([h | _], buffer, opts) do @@ -89,6 +95,7 @@ defmodule AutoLinker.Builder do 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 @@ -100,7 +107,9 @@ defmodule AutoLinker.Builder do attrs = format_attributes(opts[:attributes] || []) href = opts[:href] || "#" - ~s'<#{tag} href="#{href}" class="#{class}" #{data_phone}="#{number}"#{attrs}>#{original}</#{tag}>' + ~s'<#{tag} href="#{href}" class="#{class}" #{data_phone}="#{number}"#{attrs}>#{original}</#{ + tag + }>' end defp format_attributes(attrs) do diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex @@ -43,20 +43,21 @@ defmodule AutoLinker.Parser do :auto_linker |> Application.get_env(:opts, []) |> Enum.into(%{}) - |> Map.put(:attributes, + |> Map.put( + :attributes, Application.get_env(:auto_linker, :attributes, []) ) opts = - Enum.reduce @default_opts, opts, fn opt, acc -> + Enum.reduce(@default_opts, opts, fn opt, acc -> if is_nil(opts[opt]) and is_nil(config[opt]) do - Map.put acc, opt, true + Map.put(acc, opt, true) else acc end - end + end) - do_parse text, Map.merge(config, opts) + do_parse(text, Map.merge(config, opts)) end defp do_parse(text, %{phone: false} = opts), do: do_parse(text, Map.delete(opts, :phone)) @@ -85,11 +86,12 @@ defmodule AutoLinker.Parser do defp do_parse(text, _), do: text - defp do_parse("", _scheme, _opts ,{"", acc, _}, _handler), + defp do_parse("", _scheme, _opts, {"", acc, _}, _handler), do: acc - defp do_parse("", scheme, opts ,{buffer, acc, _}, handler), + defp do_parse("", scheme, opts, {buffer, acc, _}, handler), do: acc <> handler.(buffer, scheme, opts) + defp do_parse("<a" <> text, scheme, opts, {buffer, acc, :parsing}, handler), do: do_parse(text, scheme, opts, {"", acc <> buffer <> "<a", :skip}, handler) @@ -106,8 +108,14 @@ defmodule AutoLinker.Parser do do: do_parse(text, scheme, opts, {"", acc <> <<ch::8>>, {:attrs, level}}, handler) defp do_parse("</" <> text, scheme, opts, {buffer, acc, {:html, level}}, handler), - do: do_parse(text, scheme, opts, - {"", acc <> handler.(buffer, scheme, opts) <> "</", {:close, level}}, handler) + do: + do_parse( + text, + scheme, + opts, + {"", acc <> handler.(buffer, scheme, opts) <> "</", {:close, level}}, + handler + ) defp do_parse(">" <> text, scheme, opts, {buffer, acc, {:close, 1}}, handler), do: do_parse(text, scheme, opts, {"", acc <> buffer <> ">", :parsing}, handler) @@ -126,16 +134,34 @@ defmodule AutoLinker.Parser do do: do_parse(text, scheme, opts, {buffer <> " ", acc, state}, handler) defp do_parse(" " <> text, scheme, opts, {buffer, acc, state}, handler), - do: do_parse(text, scheme, opts, - {"", acc <> handler.(buffer, scheme, opts) <> " ", state}, handler) + do: + do_parse( + text, + scheme, + opts, + {"", acc <> handler.(buffer, scheme, opts) <> " ", state}, + handler + ) defp do_parse("\n" <> text, scheme, opts, {buffer, acc, state}, handler), - do: do_parse(text, scheme, opts, - {"", acc <> handler.(buffer, scheme, opts) <> "\n", state}, handler) + do: + do_parse( + text, + scheme, + opts, + {"", acc <> handler.(buffer, scheme, opts) <> "\n", state}, + handler + ) defp do_parse(<<ch::8>>, scheme, opts, {buffer, acc, state}, handler), - do: do_parse("", scheme, opts, - {"", acc <> handler.(buffer <> <<ch::8>>, scheme, opts), state}, handler) + do: + do_parse( + "", + scheme, + opts, + {"", acc <> handler.(buffer <> <<ch::8>>, scheme, opts), state}, + handler + ) defp do_parse(<<ch::8>> <> text, scheme, opts, {buffer, acc, state}, handler), do: do_parse(text, scheme, opts, {buffer <> <<ch::8>>, acc, state}, handler) @@ -154,24 +180,24 @@ defmodule AutoLinker.Parser do @doc false def is_url?(buffer, true) do - if Regex.match? @invalid_url, buffer do + if Regex.match?(@invalid_url, buffer) do false else - Regex.match? @match_scheme, buffer + Regex.match?(@match_scheme, buffer) end end def is_url?(buffer, _) do - if Regex.match? @invalid_url, buffer do + if Regex.match?(@invalid_url, buffer) do false else - Regex.match? @match_url, buffer + Regex.match?(@match_url, buffer) end end @doc false def match_phone(buffer) do - case Regex.scan @match_phone, buffer do + case Regex.scan(@match_phone, buffer) do [] -> nil other -> other end @@ -180,13 +206,13 @@ defmodule AutoLinker.Parser do def link_phone(nil, buffer, _), do: buffer def link_phone(list, buffer, opts) do - Builder.create_phone_link list, buffer, opts + 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 + def link_url(_, buffer, _opts), do: buffer end diff --git a/mix.exs b/mix.exs @@ -8,8 +8,8 @@ defmodule AutoLinker.Mixfile do app: :auto_linker, version: @version, elixir: "~> 1.4", - build_embedded: Mix.env == :prod, - start_permanent: Mix.env == :prod, + build_embedded: Mix.env() == :prod, + start_permanent: Mix.env() == :prod, deps: deps(), docs: [extras: ["README.md"]], package: package(), @@ -17,7 +17,7 @@ defmodule AutoLinker.Mixfile do description: """ AutoLinker is a basic package for turning website names into links. """ - ] + ] end # Configuration for the OTP application @@ -30,14 +30,16 @@ defmodule AutoLinker.Mixfile do defp deps do [ {:ex_doc, "~> 0.18", only: :dev}, - {:earmark, "~> 1.2", only: :dev, override: true}, + {:earmark, "~> 1.2", only: :dev, override: true} ] end defp package do - [ maintainers: ["Stephen Pallen"], + [ + maintainers: ["Stephen Pallen"], licenses: ["MIT"], - links: %{ "Github" => "https://github.com/smpallen99/auto_linker" }, - files: ~w(lib README.md mix.exs LICENSE)] + links: %{"Github" => "https://github.com/smpallen99/auto_linker"}, + files: ~w(lib README.md mix.exs LICENSE) + ] end end diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs @@ -2,31 +2,29 @@ defmodule AutoLinkerTest do use ExUnit.Case doctest AutoLinker - test "phone number" do assert AutoLinker.link(", work (555) 555-5555", phone: true) == - ~s{, work <a href="#" class="phone-number" data-phone="5555555555">(555) 555-5555</a>} + ~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>" + "<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=\"#\" class=\"phone-number\" data-phone=\"8888888888\">888 888-8888</a>" <> + " <a href='a.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>ab</a>" end - end diff --git a/test/builder_test.exs b/test/builder_test.exs @@ -8,17 +8,24 @@ defmodule AutoLinker.BuilderTest 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">x888</a>. Call me.' + + expected = + ~s'my exten is <a href="#" class="phone-number" data-phone="888">x888</a>. Call me.' + assert create_phone_link([["x888", ""]], phrase, []) == 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>' + + 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 end - diff --git a/test/parser_test.exs b/test/parser_test.exs @@ -11,18 +11,21 @@ defmodule AutoLinker.ParserTest do 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 -> @@ -47,7 +50,6 @@ defmodule AutoLinker.ParserTest do end end - describe "parse" do test "does not link attributes" do text = "Check out <a href='google.com'>google</a>" @@ -61,7 +63,7 @@ defmodule AutoLinker.ParserTest do 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>" - assert parse(text, class: false, rel: false, new_window: false) == expected + assert parse(text, class: false, rel: false, new_window: false) == expected end test "excludes html with specified class" do @@ -76,69 +78,73 @@ defmodule AutoLinker.ParserTest do 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", - ] - + 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 -