From 6c7a0c13636d6d2125de30ea638e63cb4350abd0 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 18 Feb 2019 18:34:38 +0700 Subject: [PATCH 01/25] use quotemarks in html --- lib/auto_linker.ex | 12 ++++---- lib/auto_linker/builder.ex | 8 ++--- lib/auto_linker/parser.ex | 2 +- test/auto_linker_test.exs | 63 ++++++++++++++++++++------------------ test/parser_test.exs | 5 ++- 5 files changed, 48 insertions(+), 42 deletions(-) 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 @@ -3,24 +3,24 @@ defmodule AutoLinker do Create url links from text containing urls. Turns an input string like `"Check out google.com"` into - `Check out "google.com"` + `Check out "google.com"` ## Examples iex> AutoLinker.link("google.com") - "google.com" + ~s(google.com) iex> AutoLinker.link("google.com", new_window: false, rel: false) - "google.com" + ~s(google.com) iex> AutoLinker.link("google.com", new_window: false, rel: false, class: false) - "google.com" + ~s(google.com) iex> AutoLinker.link("[Google](http://google.com)", markdown: true, new_window: false, rel: false, class: false) - "Google" + ~s(Google) iex> AutoLinker.link("[Google Search](http://google.com)", markdown: true) - "Google Search" + ~s(Google Search) """ import AutoLinker.Parser 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 @@ -53,7 +53,7 @@ defmodule AutoLinker.Builder do 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 @@ -162,12 +162,12 @@ defmodule AutoLinker.Builder do def format_email(attrs, email, _opts) do attrs = format_attrs(attrs) - "#{email}" + ~s(#{email}) end def format_extra(attrs, uri, _opts) do - attrs = format_attrs(attrs) - "#{uri}" + attrs = format_attributes(attrs) + ~s(#{uri}) end defp format_attributes(attrs) do 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 @@ -13,7 +13,7 @@ defmodule AutoLinker.Parser do ## Examples iex> AutoLinker.Parser.parse("Check out google.com") - "Check out google.com" + ~s{Check out google.com} iex> AutoLinker.Parser.parse("call me at x9999", phone: true) ~s{call me at x9999} 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 @@ -9,12 +9,12 @@ defmodule AutoLinkerTest do test "default link" do assert AutoLinker.link("google.com") == - "google.com" + "google.com" end test "markdown" do assert AutoLinker.link("[google.com](http://google.com)", markdown: true) == - "google.com" + "google.com" end test "does on link existing links" do @@ -25,7 +25,7 @@ defmodule AutoLinkerTest do test "phone number and markdown link" do assert AutoLinker.link("888 888-8888 [ab](a.com)", phone: true, markdown: true) == "888 888-8888" <> - " ab" + " ab" end test "all kinds of links" do @@ -33,7 +33,7 @@ defmodule AutoLinkerTest do "hello @user google.com https://ddg.com 888 888-8888 #tag user@email.com [google.com](http://google.com) irc:///mIRC" expected = - "hello @user google.com ddg.com 888 888-8888 #tag user@email.com google.com irc:///mIRC" + "hello @user google.com ddg.com 888 888-8888 #tag user@email.com google.com irc:///mIRC" assert AutoLinker.link(text, phone: true, @@ -72,7 +72,7 @@ defmodule AutoLinkerTest do ) assert result_text == - "hello @user @valid_user and @invalid_user" + "hello @user, @valid_user and @invalid_user" assert mentions |> MapSet.to_list() |> Enum.map(&elem(&1, 1)) == valid_users end @@ -96,7 +96,7 @@ defmodule AutoLinkerTest do ) assert result_text == - "#hello #world" + "#hello #world" assert MapSet.to_list(tags) == ["hello", "world"] end @@ -105,9 +105,9 @@ defmodule AutoLinkerTest do describe "mentions" do test "simple mentions" do expected = - ~s{hello @user and @anotherUser} + ~s{hello @user and @anotherUser.} - assert AutoLinker.link("hello @user and @anotherUser", + assert AutoLinker.link("hello @user and @anotherUser.", mention: true, mention_prefix: "https://example.com/user/" ) == expected @@ -117,7 +117,7 @@ defmodule AutoLinkerTest do text = "hey @user@example.com" expected = - "hey @user@example.com" + "hey @user@example.com" assert AutoLinker.link(text, mention: true, @@ -141,9 +141,9 @@ defmodule AutoLinkerTest do describe "hashtag links" do test "hashtag" do expected = - "one #two three #four" + " one #2two three #four." - assert AutoLinker.link("one #two three #four", + assert AutoLinker.link(" one #2two three #four.", hashtag: true, hashtag_prefix: "https://example.com/tag/" ) == expected @@ -153,7 +153,7 @@ defmodule AutoLinkerTest do text = "google.com#test #test google.com/#test #tag" expected = - "google.com#test #test google.com/#test #tag" + "google.com#test #test google.com/#test #tag" assert AutoLinker.link(text, scheme: true, @@ -169,7 +169,7 @@ defmodule AutoLinkerTest do text = "#漢字 #は #тест #ทดสอบ" expected = - "#漢字 #は #тест #ทดสอบ" + "#漢字 #は #тест #ทดสอบ" assert AutoLinker.link(text, scheme: true, @@ -187,7 +187,7 @@ defmodule AutoLinkerTest do text = "Hey, check out http://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ." expected = - "Hey, check out youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ." + "Hey, check out youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ." assert AutoLinker.link(text, scheme: true) == expected @@ -200,21 +200,21 @@ defmodule AutoLinkerTest do text = "https://example.com/@user" expected = - "example.com/@user" + "example.com/@user" assert AutoLinker.link(text, scheme: true) == expected text = "https://example.com:4000/@user" expected = - "example.com:4000/@user" + "example.com:4000/@user" assert AutoLinker.link(text, scheme: true) == expected text = "https://example.com:4000/@user" expected = - "example.com:4000/@user" + "example.com:4000/@user" assert AutoLinker.link(text, scheme: true) == expected @@ -225,28 +225,28 @@ defmodule AutoLinkerTest do text = "http://www.cs.vu.nl/~ast/intel/" expected = - "cs.vu.nl/~ast/intel/" + "cs.vu.nl/~ast/intel/" assert AutoLinker.link(text, scheme: true) == expected text = "https://forum.zdoom.org/viewtopic.php?f=44&t=57087" expected = - "forum.zdoom.org/viewtopic.php?f=44&t=57087" + "forum.zdoom.org/viewtopic.php?f=44&t=57087" assert AutoLinker.link(text, scheme: true) == expected text = "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul" expected = - "en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul" + "en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul" assert AutoLinker.link(text, scheme: true) == expected text = "https://en.wikipedia.org/wiki/Duff's_device" expected = - "en.wikipedia.org/wiki/Duff's_device" + "en.wikipedia.org/wiki/Duff's_device" assert AutoLinker.link(text, scheme: true) == expected end @@ -255,13 +255,16 @@ defmodule AutoLinkerTest do describe "non http links" do test "xmpp" do text = "xmpp:user@example.com" - expected = "xmpp:user@example.com" + + expected = + "xmpp:user@example.com" + assert AutoLinker.link(text, extra: true) == expected end test "email" do text = "user@example.com" - expected = "user@example.com" + expected = "user@example.com" assert AutoLinker.link(text, email: true) == expected end @@ -270,7 +273,7 @@ defmodule AutoLinkerTest do "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 = - "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" assert AutoLinker.link(text, extra: true) == expected end @@ -280,7 +283,7 @@ defmodule AutoLinkerTest do "dweb://584faa05d394190ab1a3f0240607f9bf2b7e2bd9968830a11cf77db0cea36a21+v1.0.0/path/to/file.txt" expected = - "dweb://584faa05d394190ab1a3f0240607f9bf2b7e2bd9968830a11cf77db0cea36a21+v1.0.0/path/to/file.txt" + "dweb://584faa05d394190ab1a3f0240607f9bf2b7e2bd9968830a11cf77db0cea36a21+v1.0.0/path/to/file.txt" assert AutoLinker.link(text, extra: true) == expected end @@ -291,7 +294,7 @@ defmodule AutoLinkerTest do text = "https://google.com" expected = - "google.com" + "google.com" assert AutoLinker.link(text, scheme: true) == expected end @@ -305,7 +308,7 @@ defmodule AutoLinkerTest do text = "this url https://google.foobar.com/ has valid TLD" expected = - "this url google.foobar.com/ has valid TLD" + "this url google.foobar.com/ has valid TLD" assert AutoLinker.link(text, scheme: true) == expected end @@ -318,7 +321,7 @@ defmodule AutoLinkerTest do text = "this url google.foobar.com/ has valid TLD" expected = - "this url google.foobar.com/ has valid TLD" + "this url google.foobar.com/ has valid TLD" assert AutoLinker.link(text, scheme: false) == expected end @@ -327,14 +330,14 @@ defmodule AutoLinkerTest do text = "this url http://google.foobar.com/ has valid TLD" expected = - "this url google.foobar.com/ has valid TLD" + "this url google.foobar.com/ has valid TLD" assert AutoLinker.link(text, scheme: true) == expected text = "this url google.foobar.com/ has valid TLD" expected = - "this url google.foobar.com/ has valid TLD" + "this url google.foobar.com/ has valid TLD" assert AutoLinker.link(text, scheme: true) == expected 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 @@ -62,7 +62,10 @@ defmodule AutoLinker.ParserTest do test "links url inside html" do text = "Check out
google.com
" - expected = "Check out
google.com
" + + expected = + "Check out
google.com
" + assert parse(text, class: false, rel: false, new_window: false) == expected end From 558a7e9f9a185fcdef5809740d12265ae91e8940 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 18 Feb 2019 18:39:56 +0700 Subject: [PATCH 02/25] improve hashtags --- lib/auto_linker/builder.ex | 5 ++--- lib/auto_linker/parser.ex | 2 +- test/auto_linker_test.exs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/auto_linker/builder.ex b/lib/auto_linker/builder.ex index 185357f..be0ed26 100644 --- a/lib/auto_linker/builder.ex +++ b/lib/auto_linker/builder.ex @@ -125,16 +125,15 @@ defmodule AutoLinker.Builder do |> format_mention(name, opts) end - def create_hashtag_link(tag, _buffer, opts) do + def create_hashtag_link("#" <> tag, _buffer, opts) do hashtag_prefix = opts[:hashtag_prefix] url = hashtag_prefix <> tag - [] + [href: url] |> 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 diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex index b00fc67..7374df0 100644 --- a/lib/auto_linker/parser.ex +++ b/lib/auto_linker/parser.ex @@ -45,7 +45,7 @@ defmodule AutoLinker.Parser do # 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/^\#(?\w+)/u + @match_hashtag ~r/^(?\#\w+)/u @prefix_extra [ "magnet:?", diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs index c906206..fc6f64d 100644 --- a/test/auto_linker_test.exs +++ b/test/auto_linker_test.exs @@ -98,7 +98,7 @@ defmodule AutoLinkerTest do assert result_text == "#hello #world" - assert MapSet.to_list(tags) == ["hello", "world"] + assert MapSet.to_list(tags) == ["#hello", "#world"] end end From 3607fe603c89129e8e5f9e6b2584b16b5d9e2493 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 18 Feb 2019 18:40:14 +0700 Subject: [PATCH 03/25] update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b6012c7..a0ad33a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ erl_crash.dump # Also ignore archive artifacts (built via "mix archive.build"). *.ez +.elixir_ls From 65f9d43dbe035542c91086b868f586f249c6f1d6 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 18 Feb 2019 18:40:49 +0700 Subject: [PATCH 04/25] update exdoc and add credo --- mix.exs | 5 +++-- mix.lock | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/mix.exs b/mix.exs index 572d1c9..d73175c 100644 --- a/mix.exs +++ b/mix.exs @@ -29,8 +29,9 @@ defmodule AutoLinker.Mixfile do # Dependencies can be Hex packages: defp deps do [ - {:ex_doc, "~> 0.18", only: :dev}, - {:earmark, "~> 1.2", only: :dev, override: true} + {:ex_doc, "~> 0.19", only: :dev, runtime: false}, + {:earmark, "~> 1.2", only: :dev, override: true}, + {:credo, "~> 1.0.0", only: [:dev, :test], runtime: false} ] end diff --git a/mix.lock b/mix.lock index da8bc52..a265551 100644 --- a/mix.lock +++ b/mix.lock @@ -1,2 +1,12 @@ -%{"earmark": {:hex, :earmark, "1.2.3", "206eb2e2ac1a794aa5256f3982de7a76bf4579ff91cb28d0e17ea2c9491e46a4", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}} +%{ + "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"}, + "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"}, +} From 33c6754592063c94b4e8598675f1f2d46fc9a49e Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 18 Feb 2019 18:45:29 +0700 Subject: [PATCH 05/25] fix parser --- lib/auto_linker/builder.ex | 3 +-- lib/auto_linker/parser.ex | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/auto_linker/builder.ex b/lib/auto_linker/builder.ex index be0ed26..a46ccf4 100644 --- a/lib/auto_linker/builder.ex +++ b/lib/auto_linker/builder.ex @@ -117,11 +117,10 @@ defmodule AutoLinker.Builder do url = mention_prefix <> name - [] + [href: url] |> 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 diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex index 7374df0..14cb785 100644 --- a/lib/auto_linker/parser.ex +++ b/lib/auto_linker/parser.ex @@ -378,23 +378,43 @@ defmodule AutoLinker.Parser do 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) + hashtag + |> hashtag_handler.(buffer, opts, user_acc) + |> maybe_update_buffer(hashtag, buffer) end def link_hashtag(hashtag, buffer, opts, _user_acc) do - Builder.create_hashtag_link(hashtag, buffer, opts) + hashtag + |> Builder.create_hashtag_link(buffer, opts) + |> maybe_update_buffer(hashtag, buffer) 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) + mention + |> mention_handler.(buffer, opts, user_acc) + |> maybe_update_buffer(mention, buffer) end def link_mention(mention, buffer, opts, _user_acc) do - Builder.create_mention_link(mention, buffer, opts) + mention + |> Builder.create_mention_link(buffer, opts) + |> maybe_update_buffer(mention, buffer) end + defp maybe_update_buffer(out, match, buffer) when is_binary(out) do + maybe_update_buffer({out, nil}, match, buffer) + end + + defp maybe_update_buffer({out, user_acc}, match, buffer) + when match != buffer and out != buffer do + out = String.replace(buffer, match, out) + {out, user_acc} + end + + defp maybe_update_buffer(out, _match, _buffer), do: out + def link_phone(nil, buffer, _), do: buffer def link_phone(list, buffer, opts) do From 8f7496b1d3c291b7be563ffce5abba35c1d09406 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 18 Feb 2019 18:55:17 +0700 Subject: [PATCH 06/25] fix mentions and hashtags --- lib/auto_linker/parser.ex | 12 ++++++------ test/auto_linker_test.exs | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex index 14cb785..2e8a97b 100644 --- a/lib/auto_linker/parser.ex +++ b/lib/auto_linker/parser.ex @@ -100,10 +100,10 @@ defmodule AutoLinker.Parser do |> do_parse(Map.delete(opts, :phone)) end - defp do_parse(input, %{mention: true} = opts) do + defp do_parse(input, %{hashtag: true} = opts) do input - |> do_parse(opts, {"", "", :parsing}, &check_and_link_mention/3) - |> do_parse(Map.delete(opts, :mention)) + |> do_parse(opts, {"", "", :parsing}, &check_and_link_hashtag/3) + |> do_parse(Map.delete(opts, :hashtag)) end defp do_parse(input, %{extra: true} = opts) do @@ -144,10 +144,10 @@ defmodule AutoLinker.Parser do do_parse(input, Map.delete(opts, :url)) end - defp do_parse(input, %{hashtag: true} = opts) do + defp do_parse(input, %{mention: true} = opts) do input - |> do_parse(opts, {"", "", :parsing}, &check_and_link_hashtag/3) - |> do_parse(Map.delete(opts, :hashtag)) + |> do_parse(opts, {"", "", :parsing}, &check_and_link_mention/3) + |> do_parse(Map.delete(opts, :mention)) end defp do_parse(input, _), do: input diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs index fc6f64d..7ac672e 100644 --- a/test/auto_linker_test.exs +++ b/test/auto_linker_test.exs @@ -100,6 +100,25 @@ defmodule AutoLinkerTest do assert MapSet.to_list(tags) == ["#hello", "#world"] end + + test "mention handler and hashtag prefix" do + text = + "Hello again, @user.<script></script>\nThis is on another :moominmamma: line. #2hu #epic #phantasmagoric" + + handler = fn "@" <> user = mention, _, _, _ -> + ~s(@#{mention}) + end + + expected = + "Hello again, @@user.<script></script>\nThis is on another :moominmamma: line. #2hu #epic #phantasmagoric" + + assert AutoLinker.link(text, + mention: true, + mention_handler: handler, + hashtag: true, + hashtag_prefix: "/tag/" + ) == expected + end end describe "mentions" do From 056d7f2f3708863bb7ddcb29e8be9f14c53b29f5 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 18 Feb 2019 19:06:06 +0700 Subject: [PATCH 07/25] improve mentions matching --- lib/auto_linker/parser.ex | 2 +- test/auto_linker_test.exs | 20 ++------------------ 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex index 2e8a97b..0448cdd 100644 --- a/lib/auto_linker/parser.ex +++ b/lib/auto_linker/parser.ex @@ -40,7 +40,7 @@ defmodule AutoLinker.Parser do # @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 + @match_mention ~r/^@[a-zA-Z\d_-]+@[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])?)*|@[a-zA-Z\d_-]+/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 diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs index 7ac672e..ad26d81 100644 --- a/test/auto_linker_test.exs +++ b/test/auto_linker_test.exs @@ -30,19 +30,15 @@ defmodule AutoLinkerTest do 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" + "hello google.com https://ddg.com 888 888-8888 user@email.com [google.com](http://google.com) irc:///mIRC" expected = - "hello @user google.com ddg.com 888 888-8888 #tag user@email.com google.com irc:///mIRC" + "hello google.com ddg.com 888 888-8888 user@email.com google.com irc:///mIRC" 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, @@ -143,18 +139,6 @@ defmodule AutoLinkerTest do mention_prefix: "https://example.com/user/" ) == expected end - - test "skip if starts with @@" do - text = "hello @@user and @anotherUser" - - expected = - "hello @@user and @anotherUser" - - assert AutoLinker.link(text, - mention: true, - mention_prefix: "https://example.com/user/" - ) == expected - end end describe "hashtag links" do From 9fb01942dfca76b10c07d47b48f3515128395b2d Mon Sep 17 00:00:00 2001 From: Egor Date: Wed, 20 Feb 2019 10:16:51 +0000 Subject: [PATCH 08/25] fix carriage return --- lib/auto_linker/parser.ex | 52 +++++++++++++++++---------------------- test/parser_test.exs | 9 +++++++ 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex index 0448cdd..c675bb1 100644 --- a/lib/auto_linker/parser.ex +++ b/lib/auto_linker/parser.ex @@ -205,23 +205,20 @@ defmodule AutoLinker.Parser do 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 - ) + defp do_parse( + {<>, user_acc}, + opts, + {buffer, acc, {:open, level}}, + handler + ) + when char in [" ", "\r", "\n"] do + do_parse( + {text, user_acc}, + opts, + {"", acc <> buffer <> char, {:attrs, level}}, + handler + ) + end # default cases where state is not important defp do_parse( @@ -232,24 +229,19 @@ defmodule AutoLinker.Parser do ), 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 + defp do_parse( + {<>, user_acc}, + opts, + {buffer, acc, state}, + handler + ) + when char in [" ", "\r", "\n"] do {buffer, user_acc} = run_handler(handler, buffer, opts, user_acc) do_parse( {text, user_acc}, opts, - {"", acc <> buffer <> "\n", state}, + {"", acc <> buffer <> char, state}, handler ) end diff --git a/test/parser_test.exs b/test/parser_test.exs index 998b8f4..66bfb97 100644 --- a/test/parser_test.exs +++ b/test/parser_test.exs @@ -51,6 +51,15 @@ defmodule AutoLinker.ParserTest do end describe "parse" do + test "handle line breakes" do + text = "google.com\r\nssss" + + expected = + "google.com\r\nssss" + + assert parse(text) == expected + end + test "does not link attributes" do text = "Check out google" assert parse(text) == text From ab58d24c038e4de51dfb9913b88dbca1fc67d53d Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 21 Feb 2019 14:30:59 +0700 Subject: [PATCH 09/25] do not match hashtags consisting entirely of numbers --- lib/auto_linker/parser.ex | 7 +++++-- test/auto_linker_test.exs | 12 ++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex index c675bb1..74d50ee 100644 --- a/lib/auto_linker/parser.ex +++ b/lib/auto_linker/parser.ex @@ -362,8 +362,11 @@ defmodule AutoLinker.Parser do def match_hashtag(buffer) do case Regex.run(@match_hashtag, buffer, capture: [:tag]) do - [hashtag] -> hashtag - _ -> nil + [hashtag] -> + if Regex.match?(~r/#\d+$/, hashtag), do: nil, else: hashtag + + _ -> + nil end end diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs index ad26d81..49a592b 100644 --- a/test/auto_linker_test.exs +++ b/test/auto_linker_test.exs @@ -152,6 +152,18 @@ defmodule AutoLinkerTest do ) == expected end + test "must have non-numbers" do + expected = "#1ok #42 #7" + + assert AutoLinker.link("#1ok #42 #7", + 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" From 1bd56a552a157a9d170e9653b9a3f8e3a5802ae4 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 21 Feb 2019 17:25:12 +0700 Subject: [PATCH 10/25] update hashtag regex --- lib/auto_linker/parser.ex | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex index 74d50ee..435ba83 100644 --- a/lib/auto_linker/parser.ex +++ b/lib/auto_linker/parser.ex @@ -25,7 +25,6 @@ defmodule AutoLinker.Parser do ~s{, work (555) 555-5555} """ - # @invalid_url ~r/\.\.+/ @invalid_url ~r/(\.\.+)|(^(\d+\.){1,2}\d+$)/ @match_url ~r{^[\w\.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+$} @@ -45,7 +44,7 @@ defmodule AutoLinker.Parser do # 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/^(?\#\w+)/u + @match_hashtag ~r/^(?\#[[:word:]_]*[[:alpha:]_·][[:word:]_]*)/u @prefix_extra [ "magnet:?", @@ -362,11 +361,8 @@ defmodule AutoLinker.Parser do def match_hashtag(buffer) do case Regex.run(@match_hashtag, buffer, capture: [:tag]) do - [hashtag] -> - if Regex.match?(~r/#\d+$/, hashtag), do: nil, else: hashtag - - _ -> - nil + [hashtag] -> hashtag + _ -> nil end end From 3ceaf266dacf825406ebb8011c52cbc352d5c53f Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 22 Feb 2019 14:39:06 +0700 Subject: [PATCH 11/25] support French --- lib/auto_linker/parser.ex | 2 +- test/auto_linker_test.exs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex index 435ba83..c37f56c 100644 --- a/lib/auto_linker/parser.ex +++ b/lib/auto_linker/parser.ex @@ -44,7 +44,7 @@ defmodule AutoLinker.Parser do # 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/^(?\#[[:word:]_]*[[:alpha:]_·][[:word:]_]*)/u + @match_hashtag ~r/^(?\#[[:word:]_]*[[:alpha:]_·][[:word:]_·]*)/u @prefix_extra [ "magnet:?", diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs index 49a592b..fcbc47e 100644 --- a/test/auto_linker_test.exs +++ b/test/auto_linker_test.exs @@ -164,6 +164,21 @@ defmodule AutoLinkerTest do ) == expected end + test "support French" do + text = "#administrateur·rice·s #ingénieur·e·s" + + expected = + "#administrateur·rice·s #ingénieur·e·s" + + 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" From d6a0318275b9afa641a7a31d912a12312db3058f Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 22 Feb 2019 17:41:23 +0700 Subject: [PATCH 12/25] add optional rel attribute handler --- lib/auto_linker/builder.ex | 41 +++++++++++++++++++++++++++----------- test/auto_linker_test.exs | 32 +++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/lib/auto_linker/builder.ex b/lib/auto_linker/builder.ex index a46ccf4..cb2d18e 100644 --- a/lib/auto_linker/builder.ex +++ b/lib/auto_linker/builder.ex @@ -7,11 +7,13 @@ defmodule AutoLinker.Builder do Create a link. """ def create_link(url, opts) do + url = add_scheme(url) + [] |> build_attrs(url, opts, :rel) |> build_attrs(url, opts, :target) |> build_attrs(url, opts, :class) - |> build_attrs(url, opts, :scheme) + |> build_attrs(url, opts, :href) |> format_url(url, opts) end @@ -23,6 +25,13 @@ defmodule AutoLinker.Builder do |> 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 + rel -> [{:rel, rel} | attrs] + end + end + defp build_attrs(attrs, _, opts, :rel) do if rel = Map.get(opts, :rel, "noopener noreferrer"), do: [{:rel, rel} | attrs], else: attrs end @@ -35,12 +44,14 @@ defmodule AutoLinker.Builder 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] + defp build_attrs(attrs, url, _opts, :href) do + [{:href, url} | attrs] end + defp add_scheme("http://" <> _ = url), do: url + defp add_scheme("https://" <> _ = url), do: url + defp add_scheme(url), do: "http://" <> url + defp format_url(attrs, url, opts) do url = url @@ -117,10 +128,11 @@ defmodule AutoLinker.Builder do url = mention_prefix <> name - [href: url] + [] |> build_attrs(url, opts, :rel) |> build_attrs(url, opts, :target) |> build_attrs(url, opts, :class) + |> build_attrs(url, opts, :href) |> format_mention(name, opts) end @@ -129,43 +141,48 @@ defmodule AutoLinker.Builder do url = hashtag_prefix <> tag - [href: url] + [] |> build_attrs(url, opts, :rel) |> build_attrs(url, opts, :target) |> build_attrs(url, opts, :class) + |> build_attrs(url, opts, :href) |> format_hashtag(tag, opts) end def create_email_link(email, opts) do [] |> build_attrs(email, opts, :class) + |> build_attrs("mailto:#{email}", opts, :href) |> format_email(email, opts) end def create_extra_link(uri, opts) do [] |> build_attrs(uri, opts, :class) + |> build_attrs(uri, opts, :rel) + |> build_attrs(uri, opts, :target) + |> build_attrs(uri, opts, :href) |> format_extra(uri, opts) end def format_mention(attrs, name, _opts) do attrs = format_attrs(attrs) - "@" <> name <> "" + "@#{name}" end def format_hashtag(attrs, tag, _opts) do attrs = format_attrs(attrs) - "#" <> tag <> "" + "##{tag}" end def format_email(attrs, email, _opts) do attrs = format_attrs(attrs) - ~s(#{email}) + ~s(#{email}) end def format_extra(attrs, uri, _opts) do - attrs = format_attributes(attrs) - ~s(#{uri}) + attrs = format_attrs(attrs) + ~s(#{uri}) end defp format_attributes(attrs) do diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs index fcbc47e..9bd0719 100644 --- a/test/auto_linker_test.exs +++ b/test/auto_linker_test.exs @@ -33,7 +33,7 @@ defmodule AutoLinkerTest do "hello google.com https://ddg.com 888 888-8888 user@email.com [google.com](http://google.com) irc:///mIRC" expected = - "hello google.com ddg.com 888 888-8888 user@email.com google.com irc:///mIRC" + "hello google.com ddg.com 888 888-8888 user@email.com google.com irc:///mIRC" assert AutoLinker.link(text, phone: true, @@ -47,6 +47,22 @@ defmodule AutoLinkerTest do ) == expected end + test "rel as function" do + text = "google.com" + + expected = "google.com" + + custom_rel = fn url -> + url |> String.split(".") |> List.last() + end + + assert AutoLinker.link(text, + class: false, + new_window: false, + rel: custom_rel + ) == expected + end + describe "custom handlers" do test "mentions handler" do text = "hello @user, @valid_user and @invalid_user" @@ -106,7 +122,7 @@ defmodule AutoLinkerTest do end expected = - "Hello again, @@user.<script></script>\nThis is on another :moominmamma: line. #2hu #epic #phantasmagoric" + "Hello again, @@user.<script></script>\nThis is on another :moominmamma: line. #2hu #epic #phantasmagoric" assert AutoLinker.link(text, mention: true, @@ -120,7 +136,7 @@ defmodule AutoLinkerTest do describe "mentions" do test "simple mentions" do expected = - ~s{hello @user and @anotherUser.} + ~s{hello @user and @anotherUser.} assert AutoLinker.link("hello @user and @anotherUser.", mention: true, @@ -132,7 +148,7 @@ defmodule AutoLinkerTest do text = "hey @user@example.com" expected = - "hey @user@example.com" + "hey @user@example.com" assert AutoLinker.link(text, mention: true, @@ -144,7 +160,7 @@ defmodule AutoLinkerTest do describe "hashtag links" do test "hashtag" do expected = - " one #2two three #four." + " one #2two three #four." assert AutoLinker.link(" one #2two three #four.", hashtag: true, @@ -289,7 +305,7 @@ defmodule AutoLinkerTest do expected = "xmpp:user@example.com" - assert AutoLinker.link(text, extra: true) == expected + assert AutoLinker.link(text, extra: true, new_window: false, rel: false) == expected end test "email" do @@ -305,7 +321,7 @@ defmodule AutoLinkerTest do expected = "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" - assert AutoLinker.link(text, extra: true) == expected + assert AutoLinker.link(text, extra: true, new_window: false, rel: false) == expected end test "dweb" do @@ -315,7 +331,7 @@ defmodule AutoLinkerTest do expected = "dweb://584faa05d394190ab1a3f0240607f9bf2b7e2bd9968830a11cf77db0cea36a21+v1.0.0/path/to/file.txt" - assert AutoLinker.link(text, extra: true) == expected + assert AutoLinker.link(text, extra: true, new_window: false, rel: false) == expected end end From cfc6355bbe85874ddee91a05557f4f0a4a106fe4 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Sat, 23 Feb 2019 20:09:43 +0700 Subject: [PATCH 13/25] fix link text --- lib/auto_linker/builder.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/auto_linker/builder.ex b/lib/auto_linker/builder.ex index cb2d18e..e2c0ec0 100644 --- a/lib/auto_linker/builder.ex +++ b/lib/auto_linker/builder.ex @@ -6,15 +6,15 @@ defmodule AutoLinker.Builder do @doc """ Create a link. """ - def create_link(url, opts) do - url = add_scheme(url) + def create_link(text, opts) do + url = add_scheme(text) [] |> build_attrs(url, opts, :rel) |> build_attrs(url, opts, :target) |> build_attrs(url, opts, :class) |> build_attrs(url, opts, :href) - |> format_url(url, opts) + |> format_url(text, opts) end def create_markdown_links(text, opts) do From 82ba30c6582ea6f5bfb7f484b8cb2934ab59559e Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Sat, 23 Feb 2019 20:19:46 +0700 Subject: [PATCH 14/25] cleanup --- lib/auto_linker/builder.ex | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/auto_linker/builder.ex b/lib/auto_linker/builder.ex index e2c0ec0..c2d3799 100644 --- a/lib/auto_linker/builder.ex +++ b/lib/auto_linker/builder.ex @@ -59,7 +59,7 @@ defmodule AutoLinker.Builder do |> truncate(Map.get(opts, :truncate, false)) attrs = format_attrs(attrs) - "" <> url <> "" + "#{url}" end defp format_attrs(attrs) do @@ -93,9 +93,7 @@ defmodule AutoLinker.Builder do defp strip_prefix(url, _), do: url - def create_phone_link([], buffer, _) do - buffer - end + 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) From ca4afd14ff3e490826506a39797e094e980b20f5 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 3 Apr 2019 14:55:30 +0700 Subject: [PATCH 15/25] add test coverage to CI --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index afce633..531c0e9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,5 +22,8 @@ lint: unit-testing: stage: test + coverage: '/(\d+\.\d+\%) \| Total/' + script: + - mix test --trace --cover script: - mix test --trace From df6f5dcab61dc74b87e535234a9470bbbb95873d Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 3 Apr 2019 14:57:06 +0700 Subject: [PATCH 16/25] fix credo warnings --- lib/auto_linker/parser.ex | 2 ++ test/auto_linker_test.exs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex index c37f56c..3395b9c 100644 --- a/lib/auto_linker/parser.ex +++ b/lib/auto_linker/parser.ex @@ -39,9 +39,11 @@ defmodule AutoLinker.Parser do # @user # @user@example.com + # credo:disable-for-next-line @match_mention ~r/^@[a-zA-Z\d_-]+@[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])?)*|@[a-zA-Z\d_-]+/u # https://www.w3.org/TR/html5/forms.html#valid-e-mail-address + # credo:disable-for-next-line @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/^(?\#[[:word:]_]*[[:alpha:]_·][[:word:]_·]*)/u diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs index 9bd0719..6fce60e 100644 --- a/test/auto_linker_test.exs +++ b/test/auto_linker_test.exs @@ -24,8 +24,8 @@ defmodule AutoLinkerTest do test "phone number and markdown link" do assert AutoLinker.link("888 888-8888 [ab](a.com)", phone: true, markdown: true) == - "888 888-8888" <> - " ab" + ~s(888 888-8888) <> + ~s( ab) end test "all kinds of links" do From 6dd627bfdf2fb62352c7da2dac7a4a84be65304c Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 3 Apr 2019 14:57:36 +0700 Subject: [PATCH 17/25] add credo to CI --- .gitlab-ci.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 531c0e9..91c100b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,11 +3,13 @@ image: elixir:1.7.2 cache: key: ${CI_COMMIT_REF_SLUG} paths: - - deps - - _build + - deps + - _build + stages: - lint - test + - analysis before_script: - mix local.hex --force @@ -25,5 +27,8 @@ unit-testing: coverage: '/(\d+\.\d+\%) \| Total/' script: - mix test --trace --cover + +analysis: + stage: analysis script: - - mix test --trace + - mix credo --strict From e862ee9d217f05cbce7603c6d49f78f212372630 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 3 Apr 2019 18:05:11 +0700 Subject: [PATCH 18/25] improve parsing (#2) --- lib/auto_linker/parser.ex | 21 +++++++++++++++------ test/auto_linker_test.exs | 8 ++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex index 3395b9c..5480386 100644 --- a/lib/auto_linker/parser.ex +++ b/lib/auto_linker/parser.ex @@ -29,22 +29,20 @@ defmodule AutoLinker.Parser do @match_url ~r{^[\w\.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+$} - @match_scheme ~r{^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+$} + @match_scheme ~r{^(?:\W*)?(?(?:\W*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{^(?:https?:\/\/)?(?:[^@\n]+\\w@)?(?[^:#~\/\n?]+)} + @match_hostname ~r{^(?:\W*https?:\/\/)?(?:[^@\n]+\\w@)?(?[^:#~\/\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])$" # @user # @user@example.com - # credo:disable-for-next-line - @match_mention ~r/^@[a-zA-Z\d_-]+@[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])?)*|@[a-zA-Z\d_-]+/u + @match_mention ~r"^@[a-zA-Z\d_-]+@[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])?)*|@[a-zA-Z\d_-]+"u # https://www.w3.org/TR/html5/forms.html#valid-e-mail-address - # credo:disable-for-next-line - @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_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/^(?\#[[:word:]_]*[[:alpha:]_·][[:word:]_·]*)/u @@ -261,6 +259,17 @@ defmodule AutoLinker.Parser do defp do_parse({<> <> text, user_acc}, opts, {buffer, acc, state}, handler), do: do_parse({text, user_acc}, opts, {buffer <> <>, acc, state}, handler) + def check_and_link(buffer, %{scheme: true} = opts, _user_acc) do + if is_url?(buffer, opts[:scheme]) do + case Regex.run(@match_scheme, buffer, capture: [:url]) do + [^buffer] -> link_url(true, buffer, opts) + [url] -> String.replace(buffer, url, link_url(true, url, opts)) + end + else + buffer + end + end + def check_and_link(buffer, opts, _user_acc) do buffer |> is_url?(opts[:scheme]) diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs index 6fce60e..a8ca800 100644 --- a/test/auto_linker_test.exs +++ b/test/auto_linker_test.exs @@ -242,6 +242,14 @@ defmodule AutoLinkerTest do assert AutoLinker.link(text, scheme: true) == expected end + test "turn urls with schema into urls" do + text = "📌https://google.com" + expected = "📌google.com" + + assert AutoLinker.link(text, scheme: true, class: false, new_window: false, rel: false) == + expected + end + test "hostname/@user" do text = "https://example.com/@user" From 50882b427a641e82957e17f206b41130628a5477 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 3 Apr 2019 18:53:35 +0700 Subject: [PATCH 19/25] support Telugu and probably other Asian scripts (#3) --- lib/auto_linker/parser.ex | 2 +- test/auto_linker_test.exs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex index 5480386..149f00d 100644 --- a/lib/auto_linker/parser.ex +++ b/lib/auto_linker/parser.ex @@ -44,7 +44,7 @@ defmodule AutoLinker.Parser do # 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/^(?\#[[:word:]_]*[[:alpha:]_·][[:word:]_·]*)/u + @match_hashtag ~r/^(?\#[[:word:]_]*[[:alpha:]_·][[:word:]_·\p{M}]*)/u @prefix_extra [ "magnet:?", diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs index a8ca800..e6706fa 100644 --- a/test/auto_linker_test.exs +++ b/test/auto_linker_test.exs @@ -195,6 +195,21 @@ defmodule AutoLinkerTest do ) == expected end + test "support Telugu" do + text = "#చక్రం #కకకకక్ #కకకకాక #కకకక్రకకకక" + + expected = + "#చక్రం #కకకకక్ #కకకకాక #కకకక్రకకకక" + + 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" From 4949c02cb62d36d25893fe80a00efe7eebd15f60 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 9 Apr 2019 13:08:13 +0700 Subject: [PATCH 20/25] fix parsing inside HTML tags --- lib/auto_linker/parser.ex | 15 ++------------- test/auto_linker_test.exs | 16 ++++++++++++++++ test/parser_test.exs | 13 +++++++++++++ 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex index 149f00d..9e4941f 100644 --- a/lib/auto_linker/parser.ex +++ b/lib/auto_linker/parser.ex @@ -204,19 +204,8 @@ defmodule AutoLinker.Parser do handler ) - defp do_parse( - {<>, user_acc}, - opts, - {buffer, acc, {:open, level}}, - handler - ) - when char in [" ", "\r", "\n"] do - do_parse( - {text, user_acc}, - opts, - {"", acc <> buffer <> char, {:attrs, level}}, - 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) end # default cases where state is not important diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs index e6706fa..46be2fd 100644 --- a/test/auto_linker_test.exs +++ b/test/auto_linker_test.exs @@ -144,6 +144,22 @@ defmodule AutoLinkerTest do ) == expected end + test "mentions inside html tags" do + text = + "

hello world

\n

<`em>another @user__test and @user__test google.com paragraph

\n" + + expected = + "

hello world

\n

<`em>another @user__test and @user__test google.com paragraph

\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" diff --git a/test/parser_test.exs b/test/parser_test.exs index 66bfb97..b977169 100644 --- a/test/parser_test.exs +++ b/test/parser_test.exs @@ -70,6 +70,19 @@ defmodule AutoLinker.ParserTest do end test "links url inside html" do + text = "
google.com
" + + expected = "" + + assert parse(text, class: false, rel: false, new_window: false) == expected + + text = "Check out
google.com
" + + expected = + "Check out " + + assert parse(text, class: false, rel: false, new_window: false) == expected + end text = "Check out
google.com
" expected = From 02d7cb1eba656627a183345dc1b77d78e7dce103 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 9 Apr 2019 13:20:48 +0700 Subject: [PATCH 21/25] fix test --- test/parser_test.exs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/parser_test.exs b/test/parser_test.exs index b977169..4e23ff0 100644 --- a/test/parser_test.exs +++ b/test/parser_test.exs @@ -83,13 +83,6 @@ defmodule AutoLinker.ParserTest do assert parse(text, class: false, rel: false, new_window: false) == expected end - text = "Check out
google.com
" - - expected = - "Check out " - - assert parse(text, class: false, rel: false, new_window: false) == expected - end test "excludes html with specified class" do text = "```Check out
google.com
```" From a6e87558631ffdc6955cf7dbc47b319fdfd8c976 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 9 Apr 2019 13:45:55 +0700 Subject: [PATCH 22/25] do not link inside `
` and ``

---
 lib/auto_linker/parser.ex | 12 ++++++++++++
 test/parser_test.exs      | 11 +++++++++++
 2 files changed, 23 insertions(+)

diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex
index 9e4941f..d16ea9f 100644
--- a/lib/auto_linker/parser.ex
+++ b/lib/auto_linker/parser.ex
@@ -162,9 +162,21 @@ defmodule AutoLinker.Parser do
   defp do_parse({" text, user_acc}, opts, {buffer, acc, :parsing}, handler),
     do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> " text, user_acc}, opts, {buffer, acc, :parsing}, handler),
+    do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> " text, user_acc}, opts, {buffer, acc, :parsing}, handler),
+    do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "" <> text, user_acc}, opts, {buffer, acc, :skip}, handler),
     do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "", :parsing}, handler)
 
+  defp do_parse({"
" <> text, user_acc}, opts, {buffer, acc, :skip}, handler), + do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "", :parsing}, handler) + + defp do_parse({"" <> text, user_acc}, opts, {buffer, acc, :skip}, handler), + do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> "", :parsing}, handler) + defp do_parse({"<" <> text, user_acc}, opts, {"", acc, :parsing}, handler), do: do_parse({text, user_acc}, opts, {"<", acc, {:open, 1}}, handler) diff --git a/test/parser_test.exs b/test/parser_test.exs index 4e23ff0..2bcf788 100644 --- a/test/parser_test.exs +++ b/test/parser_test.exs @@ -69,6 +69,17 @@ defmodule AutoLinker.ParserTest do assert parse(text) == text end + test "does not link inside `
` and ``" do
+      text = "
google.com
" + assert parse(text) == text + + text = "google.com" + assert parse(text) == text + + text = "
google.com
" + assert parse(text) == text + end + test "links url inside html" do text = "
google.com
" From 55636e859391bceec705b7152e25b65125d1879e Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 9 Apr 2019 14:32:30 +0700 Subject: [PATCH 23/25] fix links inside nested html --- lib/auto_linker/parser.ex | 4 ++++ test/parser_test.exs | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex index d16ea9f..89c1c62 100644 --- a/lib/auto_linker/parser.ex +++ b/lib/auto_linker/parser.ex @@ -180,6 +180,10 @@ defmodule AutoLinker.Parser do 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, {:html, level}}, handler) do + do_parse({text, user_acc}, opts, {"<", acc, {:open, level + 1}}, handler) + end + defp do_parse({">" <> text, user_acc}, opts, {buffer, acc, {:attrs, level}}, handler), do: do_parse( diff --git a/test/parser_test.exs b/test/parser_test.exs index 2bcf788..a5f0c29 100644 --- a/test/parser_test.exs +++ b/test/parser_test.exs @@ -95,6 +95,12 @@ defmodule AutoLinker.ParserTest do assert parse(text, class: false, rel: false, new_window: false) == expected end + test "links url inside nested html" do + text = "

google.com

" + expected = "

google.com

" + assert parse(text, class: false, rel: false, new_window: false) == expected + end + test "excludes html with specified class" do text = "```Check out
google.com
```" assert parse(text, exclude_patterns: ["```"]) == text From 32a6e41cd06bb5d9cc3f0bc962a3b70e88420696 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 9 Apr 2019 15:00:39 +0700 Subject: [PATCH 24/25] cleanup --- lib/auto_linker/builder.ex | 2 +- lib/auto_linker/parser.ex | 47 +++++++++++++++++--------------------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/lib/auto_linker/builder.ex b/lib/auto_linker/builder.ex index c2d3799..888fd82 100644 --- a/lib/auto_linker/builder.ex +++ b/lib/auto_linker/builder.ex @@ -82,7 +82,7 @@ defmodule AutoLinker.Builder do 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 + if String.length(url) > len, do: String.slice(url, 0, len - 2) <> "...", else: url end defp strip_prefix(url, true) do diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex index 89c1c62..f0dbe42 100644 --- a/lib/auto_linker/parser.ex +++ b/lib/auto_linker/parser.ex @@ -5,31 +5,11 @@ defmodule AutoLinker.Parser do 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") - ~s{Check out google.com} - - iex> AutoLinker.Parser.parse("call me at x9999", phone: true) - ~s{call me at x9999} - - iex> AutoLinker.Parser.parse("or at home on 555.555.5555", phone: true) - ~s{or at home on 555.555.5555} - - iex> AutoLinker.Parser.parse(", work (555) 555-5555", phone: true) - ~s{, work (555) 555-5555} - """ - @invalid_url ~r/(\.\.+)|(^(\d+\.){1,2}\d+$)/ @match_url ~r{^[\w\.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+$} - @match_scheme ~r{^(?:\W*)?(?(?:\W*https?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:\/?#[\]@!\$&'\(\)\*\+,;=.]+$)}u + @match_scheme ~r{^(?:\W*)?(?(?: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}))" @@ -64,6 +44,26 @@ defmodule AutoLinker.Parser do @default_opts ~w(url)a + @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") + ~s{Check out google.com} + + iex> AutoLinker.Parser.parse("call me at x9999", phone: true) + ~s{call me at x9999} + + iex> AutoLinker.Parser.parse("or at home on 555.555.5555", phone: true) + ~s{or at home on 555.555.5555} + + iex> AutoLinker.Parser.parse(", work (555) 555-5555", phone: true) + ~s{, work (555) 555-5555} + """ + 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, %{})) @@ -154,11 +154,6 @@ defmodule AutoLinker.Parser do 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({" text, user_acc}, opts, {buffer, acc, :parsing}, handler), do: do_parse({text, user_acc}, opts, {"", acc <> buffer <> " Date: Tue, 9 Apr 2019 15:03:39 +0700 Subject: [PATCH 25/25] =?UTF-8?q?test=20coverage=20100%!=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/auto_linker_test.exs | 18 ++++++++++++++++++ test/builder_test.exs | 14 ++++++++++++-- test/parser_test.exs | 7 ++++++- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs index 46be2fd..0478c57 100644 --- a/test/auto_linker_test.exs +++ b/test/auto_linker_test.exs @@ -61,6 +61,24 @@ defmodule AutoLinkerTest do new_window: false, rel: custom_rel ) == expected + + text = "google.com" + + expected = "google.com" + + 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", []) == + {"google.com", + []} end describe "custom handlers" do diff --git a/test/builder_test.exs b/test/builder_test.exs index 0742bd6..e20f6ea 100644 --- a/test/builder_test.exs +++ b/test/builder_test.exs @@ -17,6 +17,16 @@ defmodule AutoLinker.BuilderTest do "text" assert create_link("text", %{rel: "me"}) == expected + + expected = "t..." + + assert create_link("text", %{truncate: 3, rel: false}) == expected + + expected = "text" + assert create_link("text", %{truncate: 2, rel: false}) == expected + + expected = "http://text" + assert create_link("http://text", %{rel: false, strip_prefix: false}) == expected end test "create_markdown_links/2" do @@ -52,9 +62,9 @@ defmodule AutoLinker.BuilderTest do phrase = "my exten is x888. Call me." expected = - ~s'my exten is x888. Call me.' + ~s'my exten is x888. Call me.' - assert create_phone_link([["x888", ""]], phrase, []) == expected + assert create_phone_link([["x888", ""]], phrase, attributes: [test: "test"]) == expected end test "handles multiple links" do diff --git a/test/parser_test.exs b/test/parser_test.exs index a5f0c29..da68edc 100644 --- a/test/parser_test.exs +++ b/test/parser_test.exs @@ -85,7 +85,7 @@ defmodule AutoLinker.ParserTest do expected = "" - assert parse(text, class: false, rel: false, new_window: false) == expected + assert parse(text, class: false, rel: false, new_window: false, phone: false) == expected text = "Check out
google.com
" @@ -105,6 +105,11 @@ defmodule AutoLinker.ParserTest do text = "```Check out
google.com
```" assert parse(text, exclude_patterns: ["```"]) == text end + + test "do not link urls" do + text = "google.com" + assert parse(text, url: false, phone: true) == text + end end def valid_number?([list], number) do