Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F116062
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
17 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/README.md b/README.md
index f5feb02..253229e 100644
--- a/README.md
+++ b/README.md
@@ -1,66 +1,69 @@
# AutoLinker
[![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.
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`:
```elixir
def deps do
[{:auto_linker, "~> 0.2"}]
end
```
## Usage
The following examples illustrate some examples on how to use the auto linker.
iex> AutoLinker.link("google.com")
"<a href='http://google.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>google.com</a>"
iex> AutoLinker.link("google.com", new_window: false, rel: false)
"<a href='http://google.com' class='auto-linker'>google.com</a>"
iex> AutoLinker.link("google.com", new_window: false, rel: false, class: false)
"<a href='http://google.com'>google.com</a>"
iex> AutoLinker.link("call me at x9999")
~s{call me at <a href="" class="phone-number" data-number="9999">x9999</a>}
iex> AutoLinker.link("or at home on 555.555.5555")
~s{or at home on <a href="" class="phone-number" data-number="55555555555">555.555.5555</a>}
iex> AutoLinker.link(", work (555) 555-5555")
~s{, work <a href="" class="phone-number" data-number="5555555555">(555) 555-5555</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>"
+
See the [Docs](https://hexdocs.pm/auto_linker/) for more examples
## Configuration
By default, link parsing is enabled and phone parsing is disabled.
```elixir
# enable phone parsing, and disable link parsing
config :auto_linker, opts: [phone: true, url: false]
```
## License
`auto_linker` is Copyright (c) 2017 E-MetroTel
The source is released under the MIT License.
Check [LICENSE](LICENSE) for more information.
diff --git a/lib/auto_linker.ex b/lib/auto_linker.ex
index 721878e..c1eadaa 100644
--- a/lib/auto_linker.ex
+++ b/lib/auto_linker.ex
@@ -1,50 +1,57 @@
defmodule AutoLinker do
@moduledoc """
Create url links from text containing urls.
Turns an input string like `"Check out google.com"` into
`Check out "<a href='http://google.com' target='_blank' rel='noopener noreferrer'>google.com</a>"`
## Examples
iex> AutoLinker.link("google.com")
"<a href='http://google.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>google.com</a>"
iex> AutoLinker.link("google.com", new_window: false, rel: false)
"<a href='http://google.com' class='auto-linker'>google.com</a>"
iex> AutoLinker.link("google.com", new_window: false, rel: false, class: false)
"<a href='http://google.com'>google.com</a>"
+
+ iex> AutoLinker.link("[Google](http://google.com)", markdown: true, new_window: false, rel: false, class: false)
+ "<a href='http://google.com'>Google</a>"
+
+ iex> AutoLinker.link("[Google Search](http://google.com)", markdown: true)
+ "<a href='http://google.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>Google Search</a>"
"""
import AutoLinker.Parser
@doc """
Auto link a string.
Options:
* `class: "auto-linker"` - specify the class to be added to the generated link. false to clear
* `rel: "noopener noreferrer"` - override the rel attribute. false to clear
* `new_window: true` - set to false to remove `target='_blank'` attribute
* `scheme: false` - Set to true to link urls with schema `http://google`
* `truncate: false` - Set to a number to truncate urls longer then the number. Truncated urls will end in `..`
* `strip_prefix: true` - Strip the scheme prefix
* `exclude_class: false` - Set to a class name when you don't want urls auto linked in the html of the give class
* `exclude_id: false` - Set to an element id when you don't want urls auto linked in the html of the give element
* `exclude_patterns: ["```"] - Don't link anything between the the pattern
+ * `markdown: false` - link markdown style links
Each of the above options can be specified when calling `link(text, opts)`
or can be set in the `:auto_linker's configuration. For example:
config :auto_linker,
class: false,
new_window: false
Note that passing opts to `link/2` will override the configuration settings.
"""
def link(text, opts \\ []) do
parse text, opts
end
end
diff --git a/lib/auto_linker/builder.ex b/lib/auto_linker/builder.ex
index 00c42be..3489d00 100644
--- a/lib/auto_linker/builder.ex
+++ b/lib/auto_linker/builder.ex
@@ -1,91 +1,111 @@
defmodule AutoLinker.Builder do
@moduledoc """
Module for building the auto generated link.
"""
@doc """
Create a link.
"""
def create_link(url, opts) do
[]
|> build_attrs(url, opts, :rel)
|> build_attrs(url, opts, :target)
|> build_attrs(url, opts, :class)
|> build_attrs(url, opts, :scheme)
|> format_url(url, opts)
end
+ def create_markdown_links(text, opts) do
+ []
+ |> build_attrs(text, opts, :rel)
+ |> build_attrs(text, opts, :target)
+ |> build_attrs(text, opts, :class)
+ |> format_markdown(text, opts)
+ end
+
defp build_attrs(attrs, _, opts, :rel) do
if rel = Map.get(opts, :rel, "noopener noreferrer"),
do: [{:rel, rel} | attrs], else: attrs
end
defp build_attrs(attrs, _, opts, :target) do
if Map.get(opts, :new_window, true),
do: [{:target, :_blank} | attrs], else: attrs
end
defp build_attrs(attrs, _, opts, :class) do
if cls = Map.get(opts, :class, "auto-linker"),
do: [{:class, cls} | attrs], else: attrs
end
defp build_attrs(attrs, url, _opts, :scheme) do
if String.starts_with?(url, ["http://", "https://"]),
do: [{:href, url} | attrs], else: [{:href, "http://" <> url} | attrs]
end
defp format_url(attrs, url, opts) do
url =
url
|> strip_prefix(Map.get(opts, :strip_prefix, true))
|> truncate(Map.get(opts, :truncate, false))
- attrs =
- attrs
- |> Enum.map(fn {key, value} -> ~s(#{key}='#{value}') end)
- |> Enum.join(" ")
+ attrs = format_attrs(attrs)
"<a #{attrs}>" <> url <> "</a>"
end
+ defp format_attrs(attrs) do
+ attrs
+ |> Enum.map(fn {key, value} -> ~s(#{key}='#{value}') end)
+ |> Enum.join(" ")
+ end
+
+ defp format_markdown(attrs, text, _opts) do
+ attrs =
+ case format_attrs(attrs) do
+ "" -> ""
+ attrs -> " " <> attrs
+ end
+ Regex.replace(~r/\[(.+?)\]\((.+?)\)/, text, "<a href='\\2'#{attrs}>\\1</a>")
+ end
+
defp truncate(url, false), do: url
defp truncate(url, len) when len < 3, do: url
defp truncate(url, len) do
if String.length(url) > len, do: String.slice(url, 0, len - 2) <> "..", else: url
end
defp strip_prefix(url, true) do
url
|> String.replace(~r/^https?:\/\//, "")
|> String.replace(~r/^www\./, "")
end
defp strip_prefix(url, _), do: url
def create_phone_link([], buffer, _) do
buffer
end
def create_phone_link([h | t], buffer, opts) do
create_phone_link t, format_phone_link(h, buffer, opts), opts
end
def format_phone_link([h | _], buffer, opts) do
val =
h
|> String.replace(~r/[\.\+\- x\(\)]+/, "")
|> format_phone_link(h, opts)
# val = ~s'<a href="#" class="phone-number" data-phone="#{number}">#{h}</a>'
String.replace(buffer, h, val)
end
def format_phone_link(number, original, opts) do
tag = opts[:tag] || "a"
class = opts[:class] || "phone-number"
data_phone = opts[:data_phone] || "data-phone"
attrs = format_attributes(opts[:attributes] || [])
href = opts[:href] || "#"
~s'<#{tag} href="#{href}" class="#{class}" #{data_phone}="#{number}"#{attrs}>#{original}</#{tag}>'
end
defp format_attributes(attrs) do
Enum.reduce(attrs, "", fn {name, value}, acc ->
acc <> ~s' #{name}="#{value}"'
end)
end
end
diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex
index 23ec909..946ae1d 100644
--- a/lib/auto_linker/parser.ex
+++ b/lib/auto_linker/parser.ex
@@ -1,185 +1,191 @@
defmodule AutoLinker.Parser do
@moduledoc """
Module to handle parsing the the input string.
"""
alias AutoLinker.Builder
@doc """
Parse the given string, identifying items to link.
Parses the string, replacing the matching urls and phone numbers with an html link.
## Examples
iex> AutoLinker.Parser.parse("Check out google.com")
"Check out <a href='http://google.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>google.com</a>"
iex> AutoLinker.Parser.parse("call me at x9999", phone: true)
~s{call me at <a href="#" class="phone-number" data-phone="9999">x9999</a>}
iex> AutoLinker.Parser.parse("or at home on 555.555.5555", phone: true)
~s{or at home on <a href="#" class="phone-number" data-phone="5555555555">555.555.5555</a>}
iex> AutoLinker.Parser.parse(", work (555) 555-5555", phone: true)
~s{, work <a href="#" class="phone-number" data-phone="5555555555">(555) 555-5555</a>}
"""
# @invalid_url ~r/\.\.+/
@invalid_url ~r/(\.\.+)|(^(\d+\.){1,2}\d+$)/
@match_url ~r{^[\w\.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$}
@match_scheme ~r{^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$}
@match_phone ~r"((?:x\d{2,7})|(?:(?:\+?1\s?(?:[.-]\s?)?)?(?:\(\s?(?:[2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s?\)|(?:[2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s?(?:[.-]\s?)?)(?:[2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s?(?:[.-]\s?)?(?:[0-9]{4}))"
@default_opts ~w(url)a
def parse(text, opts \\ %{})
def parse(text, list) when is_list(list), do: parse(text, Enum.into(list, %{}))
def parse(text, opts) do
config =
:auto_linker
|> Application.get_env(:opts, [])
|> Enum.into(%{})
|> Map.put(:attributes,
Application.get_env(:auto_linker, :attributes, [])
)
opts =
Enum.reduce @default_opts, opts, fn opt, acc ->
if is_nil(opts[opt]) and is_nil(config[opt]) do
Map.put acc, opt, true
else
acc
end
end
do_parse text, Map.merge(config, opts)
end
defp do_parse(text, %{phone: false} = opts), do: do_parse(text, Map.delete(opts, :phone))
defp do_parse(text, %{url: false} = opts), do: do_parse(text, Map.delete(opts, :url))
+ defp do_parse(text, %{markdown: true} = opts) do
+ text
+ |> Builder.create_markdown_links(opts)
+ |> do_parse(Map.delete(opts, :markdown))
+ end
defp do_parse(text, %{phone: _} = opts) do
text
|> do_parse(false, opts, {"", "", :parsing}, &check_and_link_phone/3)
|> do_parse(Map.delete(opts, :phone))
end
defp do_parse(text, %{url: _} = opts) do
if (exclude = Map.get(opts, :exclude_pattern, false)) && String.starts_with?(text, exclude) do
text
else
do_parse(text, Map.get(opts, :scheme, false), opts, {"", "", :parsing}, &check_and_link/3)
end
|> do_parse(Map.delete(opts, :url))
end
defp do_parse(text, _), do: text
- # state = {buffer, acc, state}
-
defp do_parse("", _scheme, _opts ,{"", acc, _}, _handler),
do: acc
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)
+
+ defp do_parse("</a>" <> text, scheme, opts, {buffer, acc, :skip}, handler),
+ do: do_parse(text, scheme, opts, {"", acc <> buffer <> "</a>", :parsing}, handler)
defp do_parse("<" <> text, scheme, opts, {"", acc, :parsing}, handler),
do: do_parse(text, scheme, opts, {"<", acc, {:open, 1}}, handler)
defp do_parse(">" <> text, scheme, opts, {buffer, acc, {:attrs, level}}, handler),
do: do_parse(text, scheme, opts, {"", acc <> buffer <> ">", {:html, level}}, handler)
defp do_parse(<<ch::8>> <> text, scheme, opts, {"", acc, {:attrs, level}}, handler),
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)
defp do_parse(">" <> text, scheme, opts, {buffer, acc, {:close, 1}}, handler),
do: do_parse(text, scheme, opts, {"", acc <> buffer <> ">", :parsing}, handler)
defp do_parse(">" <> text, scheme, opts, {buffer, acc, {:close, level}}, handler),
do: do_parse(text, scheme, opts, {"", acc <> buffer <> ">", {:html, level - 1}}, handler)
defp do_parse(" " <> text, scheme, opts, {buffer, acc, {:open, level}}, handler),
do: do_parse(text, scheme, opts, {"", acc <> buffer <> " ", {:attrs, level}}, handler)
defp do_parse("\n" <> text, scheme, opts, {buffer, acc, {:open, level}}, handler),
do: do_parse(text, scheme, opts, {"", acc <> buffer <> "\n", {:attrs, level}}, handler)
# default cases where state is not important
defp do_parse(" " <> text, scheme, %{phone: _} = opts, {buffer, acc, state}, handler),
do: do_parse(text, scheme, opts, {buffer <> " ", acc, state}, handler)
defp do_parse(" " <> text, scheme, opts, {buffer, acc, 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)
defp do_parse(<<ch::8>>, scheme, opts, {buffer, acc, state}, handler),
do: do_parse("", scheme, opts,
{"", acc <> handler.(buffer <> <<ch::8>>, scheme, opts), state}, handler)
defp do_parse(<<ch::8>> <> text, scheme, opts, {buffer, acc, state}, handler),
do: do_parse(text, scheme, opts, {buffer <> <<ch::8>>, acc, state}, handler)
-
def check_and_link(buffer, scheme, opts) do
buffer
|> is_url?(scheme)
|> link_url(buffer, opts)
end
def check_and_link_phone(buffer, _, opts) do
buffer
|> match_phone
|> link_phone(buffer, opts)
end
@doc false
def is_url?(buffer, true) do
if Regex.match? @invalid_url, buffer do
false
else
Regex.match? @match_scheme, buffer
end
end
def is_url?(buffer, _) do
- # IO.puts "..... '#{buffer}'"
if Regex.match? @invalid_url, buffer do
false
else
Regex.match? @match_url, buffer
end
end
@doc false
def match_phone(buffer) do
case Regex.scan @match_phone, buffer do
[] -> nil
other -> other
end
end
def link_phone(nil, buffer, _), do: buffer
def link_phone(list, buffer, opts) do
Builder.create_phone_link list, buffer, opts
end
@doc false
def link_url(true, buffer, opts) do
Builder.create_link(buffer, opts)
end
def link_url(_, buffer, _opts), do: buffer
end
diff --git a/mix.exs b/mix.exs
index 0255b74..a25e7f6 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,43 +1,43 @@
defmodule AutoLinker.Mixfile do
use Mix.Project
- @version "0.2.1"
+ @version "0.2.2"
def project do
[
app: :auto_linker,
version: @version,
elixir: "~> 1.4",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps(),
docs: [extras: ["README.md"]],
package: package(),
name: "AutoLinker",
description: """
AutoLinker is a basic package for turning website names into links.
"""
]
end
# Configuration for the OTP application
def application do
# Specify extra applications you'll use from Erlang/Elixir
[extra_applications: [:logger]]
end
# Dependencies can be Hex packages:
defp deps do
[
{:ex_doc, "~> 0.18", only: :dev},
{:earmark, "~> 1.2", only: :dev, override: true},
]
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)]
end
end
diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs
index e00bf59..079ddb7 100644
--- a/test/auto_linker_test.exs
+++ b/test/auto_linker_test.exs
@@ -1,11 +1,26 @@
defmodule AutoLinkerTest do
use ExUnit.Case
doctest AutoLinker
test "phone number" do
assert AutoLinker.link(", work (555) 555-5555", phone: true) ==
~s{, work <a href="#" class="phone-number" data-phone="5555555555">(555) 555-5555</a>}
end
+ test "default link" do
+ assert AutoLinker.link("google.com") ==
+ "<a href='http://google.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>google.com</a>"
+ end
+
+ test "markdown" do
+ assert AutoLinker.link("[google.com](http://google.com)", markdown: true) ==
+ "<a href='http://google.com' class='auto-linker' target='_blank' rel='noopener noreferrer'>google.com</a>"
+ end
+
+ test "does on link existing links" do
+ assert AutoLinker.link("<a href='http://google.com'>google.com</a>") ==
+ "<a href='http://google.com'>google.com</a>"
+ end
+
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 30, 5:54 AM (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41377
Default Alt Text
(17 KB)
Attached To
Mode
R19 linkify
Attached
Detach File
Event Timeline
Log In to Comment