prep for first release
This commit is contained in:
parent
12f80c9edb
commit
59d41265fc
8 changed files with 355 additions and 26 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 E-MetroTel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
42
README.md
42
README.md
|
@ -1,19 +1,47 @@
|
|||
# AutoLinker
|
||||
|
||||
**TODO: Add description**
|
||||
[![Build Status](https://travis-ci.org/smpallen99/coherence.png?branch=master)](https://travis-ci.org/smpallen99/coherence) [![Hex Version][hex-img]][hex] [![License][license-img]][license]
|
||||
|
||||
[hex-img]: https://img.shields.io/hexpm/v/coherence.svg
|
||||
[hex]: https://hex.pm/packages/coherence
|
||||
[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 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
|
||||
|
||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||
by adding `auto_linker` to your list of dependencies in `mix.exs`:
|
||||
The package can be installed by adding `auto_linker` to your list of dependencies in `mix.exs`:
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
[{:auto_linker, "~> 0.1.0"}]
|
||||
[{:auto_linker, "~> 0.1"}]
|
||||
end
|
||||
```
|
||||
|
||||
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||
be found at [https://hexdocs.pm/auto_linker](https://hexdocs.pm/auto_linker).
|
||||
## Usage
|
||||
|
||||
```
|
||||
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>"
|
||||
```
|
||||
|
||||
See the docs for more examples
|
||||
|
||||
## License
|
||||
|
||||
`auto_linker` is Copyright (c) 2017 E-MetroTel
|
||||
|
||||
The source is released under the MIT License.
|
||||
|
||||
Check [LICENSE](LICENSE) for more information.
|
||||
|
|
|
@ -3,7 +3,7 @@ defmodule AutoLinker do
|
|||
Create url links from text containing urls.
|
||||
|
||||
Turns an input string like `"Check out google.com"` into
|
||||
`Check out `"<a href='http://google.com' target='_blank' rel='noopener noreferrer'>google.com</a>"`
|
||||
`Check out "<a href='http://google.com' target='_blank' rel='noopener noreferrer'>google.com</a>"`
|
||||
|
||||
## Examples
|
||||
|
||||
|
@ -21,6 +21,27 @@ defmodule AutoLinker do
|
|||
|
||||
@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
|
||||
|
||||
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
|
||||
opts =
|
||||
|
@ -28,7 +49,7 @@ defmodule AutoLinker do
|
|||
|> Application.get_all_env()
|
||||
|> Keyword.merge(opts)
|
||||
|
||||
parse text, opts
|
||||
parse text, Enum.into(opts, %{})
|
||||
end
|
||||
|
||||
|
||||
|
|
59
lib/auto_linker/builder.ex
Normal file
59
lib/auto_linker/builder.ex
Normal file
|
@ -0,0 +1,59 @@
|
|||
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
|
||||
|
||||
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(" ")
|
||||
"<a #{attrs}>" <> url <> "</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
|
||||
end
|
96
lib/auto_linker/parser.ex
Normal file
96
lib/auto_linker/parser.ex
Normal file
|
@ -0,0 +1,96 @@
|
|||
defmodule AutoLinker.Parser do
|
||||
@moduledoc """
|
||||
Module to handle parsing the the input string.
|
||||
"""
|
||||
|
||||
alias AutoLinker.Builder
|
||||
|
||||
@doc """
|
||||
Parse the given string.
|
||||
|
||||
Parses the string, replacing the matching urls 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>"
|
||||
"""
|
||||
|
||||
def parse(text, opts \\ %{})
|
||||
def parse(text, list) when is_list(list), do: parse(text, Enum.into(list, %{}))
|
||||
|
||||
def parse(text, opts) do
|
||||
if (exclude = Map.get(opts, :exclude_pattern, false)) && String.starts_with?(text, exclude) do
|
||||
text
|
||||
else
|
||||
parse(text, Map.get(opts, :scheme, false), opts, {"", "", :parsing})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# state = {buffer, acc, state}
|
||||
|
||||
defp parse("", _scheme, _opts ,{"", acc, _}),
|
||||
do: acc
|
||||
|
||||
defp parse("", scheme, opts ,{buffer, acc, _}),
|
||||
do: acc <> check_and_link(buffer, scheme, opts)
|
||||
defp parse("<" <> text, scheme, opts, {"", acc, :parsing}),
|
||||
do: parse(text, scheme, opts, {"<", acc, {:open, 1}})
|
||||
|
||||
defp parse(">" <> text, scheme, opts, {buffer, acc, {:attrs, level}}),
|
||||
do: parse(text, scheme, opts, {"", acc <> buffer <> ">", {:html, level}})
|
||||
|
||||
defp parse(<<ch::8>> <> text, scheme, opts, {"", acc, {:attrs, level}}),
|
||||
do: parse(text, scheme, opts, {"", acc <> <<ch::8>>, {:attrs, level}})
|
||||
|
||||
defp parse("</" <> text, scheme, opts, {buffer, acc, {:html, level}}),
|
||||
do: parse(text, scheme, opts,
|
||||
{"", acc <> check_and_link(buffer, scheme, opts) <> "</", {:close, level}})
|
||||
|
||||
defp parse(">" <> text, scheme, opts, {buffer, acc, {:close, 1}}),
|
||||
do: parse(text, scheme, opts, {"", acc <> buffer <> ">", :parsing})
|
||||
|
||||
defp parse(">" <> text, scheme, opts, {buffer, acc, {:close, level}}),
|
||||
do: parse(text, scheme, opts, {"", acc <> buffer <> ">", {:html, level - 1}})
|
||||
|
||||
defp parse(" " <> text, scheme, opts, {buffer, acc, {:open, level}}),
|
||||
do: parse(text, scheme, opts, {"", acc <> buffer <> " ", {:attrs, level}})
|
||||
defp parse("\n" <> text, scheme, opts, {buffer, acc, {:open, level}}),
|
||||
do: parse(text, scheme, opts, {"", acc <> buffer <> "\n", {:attrs, level}})
|
||||
|
||||
# default cases where state is not important
|
||||
defp parse(" " <> text, scheme, opts, {buffer, acc, state}),
|
||||
do: parse(text, scheme, opts,
|
||||
{"", acc <> check_and_link(buffer, scheme, opts) <> " ", state})
|
||||
defp parse("\n" <> text, scheme, opts, {buffer, acc, state}),
|
||||
do: parse(text, scheme, opts,
|
||||
{"", acc <> check_and_link(buffer, scheme, opts) <> "\n", state})
|
||||
|
||||
defp parse(<<ch::8>> <> text, scheme, opts, {buffer, acc, state}),
|
||||
do: parse(text, scheme, opts, {buffer <> <<ch::8>>, acc, state})
|
||||
|
||||
|
||||
defp check_and_link(buffer, scheme, opts) do
|
||||
buffer
|
||||
|> is_url?(scheme)
|
||||
|> link_url(buffer, opts)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def is_url?(buffer, true) do
|
||||
re = ~r{^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$}
|
||||
Regex.match? re, buffer
|
||||
end
|
||||
def is_url?(buffer, _) do
|
||||
re = ~r{^[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$}
|
||||
Regex.match? re, buffer
|
||||
end
|
||||
|
||||
@doc false
|
||||
def link_url(true, buffer, opts) do
|
||||
Builder.create_link(buffer, opts)
|
||||
end
|
||||
def link_url(_, buffer, _opts), do: buffer
|
||||
|
||||
end
|
44
mix.exs
44
mix.exs
|
@ -1,33 +1,43 @@
|
|||
defmodule AutoLinker.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
@version "0.1.0"
|
||||
|
||||
def project do
|
||||
[app: :auto_linker,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.4",
|
||||
build_embedded: Mix.env == :prod,
|
||||
start_permanent: Mix.env == :prod,
|
||||
deps: deps()]
|
||||
[
|
||||
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
|
||||
#
|
||||
# Type "mix help compile.app" for more information
|
||||
def application do
|
||||
# Specify extra applications you'll use from Erlang/Elixir
|
||||
[extra_applications: [:logger]]
|
||||
end
|
||||
|
||||
# Dependencies can be Hex packages:
|
||||
#
|
||||
# {:my_dep, "~> 0.3.0"}
|
||||
#
|
||||
# Or git/path repositories:
|
||||
#
|
||||
# {:my_dep, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||
#
|
||||
# Type "mix help deps" for more examples and options
|
||||
defp deps do
|
||||
[]
|
||||
[
|
||||
{:ex_doc, "~> 0.15", 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 priv web README.md mix.exs LICENSE)]
|
||||
end
|
||||
end
|
||||
|
|
2
mix.lock
Normal file
2
mix.lock
Normal file
|
@ -0,0 +1,2 @@
|
|||
%{"earmark": {:hex, :earmark, "1.2.0", "bf1ce17aea43ab62f6943b97bd6e3dc032ce45d4f787504e3adf738e54b42f3a", [:mix], []},
|
||||
"ex_doc": {:hex, :ex_doc, "0.15.0", "e73333785eef3488cf9144a6e847d3d647e67d02bd6fdac500687854dd5c599f", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]}}
|
92
test/parser_test.exs
Normal file
92
test/parser_test.exs
Normal file
|
@ -0,0 +1,92 @@
|
|||
defmodule AutoLinker.ParserTest do
|
||||
use ExUnit.Case
|
||||
doctest AutoLinker.Parser
|
||||
|
||||
import AutoLinker.Parser
|
||||
|
||||
|
||||
describe "is_url" do
|
||||
test "valid scheme true" do
|
||||
valid_scheme_urls()
|
||||
|> Enum.each(fn url ->
|
||||
assert is_url?(url, true)
|
||||
end)
|
||||
end
|
||||
test "invalid scheme true" do
|
||||
invalid_scheme_urls()
|
||||
|> Enum.each(fn url ->
|
||||
refute is_url?(url, true)
|
||||
end)
|
||||
end
|
||||
test "valid scheme false" do
|
||||
valid_non_scheme_urls()
|
||||
|> Enum.each(fn url ->
|
||||
assert is_url?(url, false)
|
||||
end)
|
||||
end
|
||||
test "invalid scheme false" do
|
||||
invalid_non_scheme_urls()
|
||||
|> Enum.each(fn url ->
|
||||
refute is_url?(url, false)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "parse" do
|
||||
test "does not link attributes" do
|
||||
text = "Check out <a href='google.com'>google</a>"
|
||||
assert parse(text) == text
|
||||
text = "Check out <img src='google.com' alt='google.com'/>"
|
||||
assert parse(text) == text
|
||||
text = "Check out <span><img src='google.com' alt='google.com'/></span>"
|
||||
assert parse(text) == text
|
||||
end
|
||||
|
||||
test "links url inside html" do
|
||||
text = "Check out <div class='section'>google.com</div>"
|
||||
expected = "Check out <div class='section'><a href='http://google.com'>google.com</a></div>"
|
||||
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_pattern: "```") == text
|
||||
end
|
||||
end
|
||||
|
||||
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."
|
||||
]
|
||||
|
||||
end
|
Reference in a new issue