Skip to content

Commit

Permalink
Merge pull request #86 from danschultzer/formatting
Browse files Browse the repository at this point in the history
Enforce formatting
  • Loading branch information
danschultzer authored Nov 20, 2023
2 parents ad0b8cc + 1ff6b38 commit d37523f
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 50 deletions.
3 changes: 3 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
inputs: ["{mix,.formatter}.exs", "{lib,test}/**/*.{ex,exs}"]
]
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
- run: mix deps.get
- run: mix compile --warnings-as-errors
- run: mix credo --strict
- run: mix format --check-formatted

test:
strategy:
Expand Down
33 changes: 23 additions & 10 deletions lib/premailex/html_inline_styles.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,34 @@ defmodule Premailex.HTMLInlineStyles do
* `:all` - apply all optimization steps
* `:remove_style_tags` - Remove style tags (can be combined in a list)
"""
@spec process(String.t() | HTMLParser.html_tree(), [CSSParser.rule_set()] | nil, Keyword.t() | nil) :: String.t()
@spec process(
String.t() | HTMLParser.html_tree(),
[CSSParser.rule_set()] | nil,
Keyword.t() | nil
) :: String.t()
def process(html_or_html_tree, css_rule_sets_or_options \\ nil, options \\ nil)

def process(html, css_rule_sets_or_options, options) when is_binary(html) do
html
|> HTMLParser.parse()
|> process(css_rule_sets_or_options, options)
end

def process(html_tree, css_rule_sets_or_options, nil) do
case Keyword.keyword?(css_rule_sets_or_options) do
true -> process(html_tree, nil, css_rule_sets_or_options)
true -> process(html_tree, nil, css_rule_sets_or_options)
false -> process(html_tree, css_rule_sets_or_options, [])
end
end

def process(html_tree, nil, options) do
css_selector = Keyword.get(options, :css_selector, "style,link[rel=\"stylesheet\"][href]")
css_selector = Keyword.get(options, :css_selector, "style,link[rel=\"stylesheet\"][href]")
css_rule_sets = load_styles(html_tree, css_selector)
options = Keyword.put_new(options, :css_selector, css_selector)
options = Keyword.put_new(options, :css_selector, css_selector)

process(html_tree, css_rule_sets, options)
end

def process(html_tree, css_rules_sets, options) do
optimize_steps = Keyword.get(options, :optimize, :none)
optimize_options = Keyword.take(options, [:css_selector])
Expand Down Expand Up @@ -67,16 +75,17 @@ defmodule Premailex.HTMLInlineStyles do
end)

visible_html_tree =
Enum.reduce(hidden_elements, html_tree, fn {_index, hidden_element, placeholder}, html_tree ->
Enum.reduce(hidden_elements, html_tree, fn {_index, hidden_element, placeholder},
html_tree ->
Util.traverse_until_first(html_tree, hidden_element, fn _element -> placeholder end)
end)

styles
|> Enum.reduce(visible_html_tree, &add_rule_set_to_html(&1, &2))
|> Util.traverse("premailex", fn {"premailex", attrs, _children} ->
{"data-index", index} = Enum.find(attrs, & elem(&1, 0) == "data-index")
{"data-index", index} = Enum.find(attrs, &(elem(&1, 0) == "data-index"))

{_index, hidden_element, _replacement} = Enum.find(hidden_elements, & elem(&1, 0) == index)
{_index, hidden_element, _replacement} = Enum.find(hidden_elements, &(elem(&1, 0) == index))

hidden_element
end)
Expand All @@ -98,14 +107,17 @@ defmodule Premailex.HTMLInlineStyles do
|> parse_body(http_adapter, url)
end

defp parse_body({:ok, %{status: status, body: body}}, _http_adapter, _url) when status in 200..399 do
defp parse_body({:ok, %{status: status, body: body}}, _http_adapter, _url)
when status in 200..399 do
CSSParser.parse(body)
end

defp parse_body({:ok, %{status: status}}, _http_adapter, url) do
Logger.warning("Ignoring #{url} styles because received unexpected HTTP status: #{status}")

nil
end

defp parse_body({:error, error}, http_adapter, url) do
Logger.warning(
"Ignoring #{url} styles because of unexpected error from #{inspect(http_adapter)}:\n\n#{inspect(error)}"
Expand Down Expand Up @@ -173,7 +185,7 @@ defmodule Premailex.HTMLInlineStyles do
|> CSSParser.merge()
|> CSSParser.to_string()
|> case do
"" -> current_style
"" -> current_style
style -> style
end

Expand All @@ -194,6 +206,7 @@ defmodule Premailex.HTMLInlineStyles do
end

defp maybe_remove_style_tags(tree, _steps, nil), do: tree

defp maybe_remove_style_tags(tree, steps, css_selector) do
case Enum.member?(steps, :remove_style_tags) do
true -> HTMLParser.filter(tree, css_selector)
Expand All @@ -212,7 +225,7 @@ defmodule Premailex.HTMLInlineStyles do
defp http_adapter do
case Application.get_env(:premailex, :http_adapter, Premailex.HTTPAdapter.Httpc) do
{adapter, opts} -> {adapter, opts}
adapter -> {adapter, nil}
adapter -> {adapter, nil}
end
end
end
6 changes: 3 additions & 3 deletions lib/premailex/html_parser/floki.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ defmodule Premailex.HTMLParser.Floki do
"< 0.24.0"
|> floki_version_match?()
|> case do
true -> apply(Floki, :parse, args)
true -> apply(Floki, :parse, args)
false -> apply(Floki, :parse_document, args)
end
|> case do
{:ok, [html]} -> html
{:ok, [html]} -> html
{:ok, document} -> document
any -> any
any -> any
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/premailex/html_parser/meeseeks.ex
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ defmodule Premailex.HTMLParser.Meeseeks do
@doc false
def filter(tree, selector) do
selector = CSS.compile_selectors(selector)
tree = Meeseeks.parse(tree, :tuple_tree)
tree = Meeseeks.parse(tree, :tuple_tree)

tree
|> Meeseeks.all(selector)
Expand Down
13 changes: 7 additions & 6 deletions lib/premailex/http_adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ defmodule Premailex.HTTPAdapter do
@moduledoc false

@type header :: {binary(), binary()}
@type t :: %__MODULE__{
status: integer(),
headers: [header()],
body: binary()
}
@type t :: %__MODULE__{
status: integer(),
headers: [header()],
body: binary()
}

defstruct status: 200, headers: [], body: ""
end
Expand All @@ -18,7 +18,8 @@ defmodule Premailex.HTTPAdapter do
@type body :: binary() | nil
@type headers :: [{binary(), binary()}]

@callback request(method(), binary(), body(), headers(), Keyword.t()) :: {:ok, map()} | {:error, any()}
@callback request(method(), binary(), body(), headers(), Keyword.t()) ::
{:ok, map()} | {:error, any()}

@spec user_agent_header() :: {binary(), binary()}
def user_agent_header do
Expand Down
24 changes: 15 additions & 9 deletions lib/premailex/http_adapter/httpc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,43 +20,49 @@ defmodule Premailex.HTTPAdapter.Httpc do
end

defp httpc_request(url, body, headers) do
url = to_charlist(url)
headers = Enum.map(headers, fn {k, v} -> {to_charlist(k), to_charlist(v)} end)
url = to_charlist(url)
headers = Enum.map(headers, fn {k, v} -> {to_charlist(k), to_charlist(v)} end)

do_httpc_request(url, body, headers)
end

defp do_httpc_request(url, nil, headers) do
{url, headers}
end

defp do_httpc_request(url, body, headers) do
{content_type, headers} = split_content_type_headers(headers)
body = to_charlist(body)
body = to_charlist(body)

{url, headers, content_type, body}
end

defp split_content_type_headers(headers) do
case List.keytake(headers, 'content-type', 0) do
nil -> {'text/plain', headers}
case List.keytake(headers, ~c"content-type", 0) do
nil -> {~c"text/plain", headers}
{{_, ct}, headers} -> {ct, headers}
end
end

defp format_response({:ok, {{_, status, _}, headers, body}}) do
headers = Enum.map(headers, fn {key, value} -> {String.downcase(to_string(key)), to_string(value)} end)
body = IO.iodata_to_binary(body)
headers =
Enum.map(headers, fn {key, value} ->
{String.downcase(to_string(key)), to_string(value)}
end)

body = IO.iodata_to_binary(body)

{:ok, %HTTPResponse{status: status, headers: headers, body: body}}
end

defp format_response({:error, error}), do: {:error, error}

defp parse_httpc_opts(nil, url), do: default_httpc_opts(url)
defp parse_httpc_opts(opts, _url), do: opts

defp default_httpc_opts(url) do
case certifi_and_ssl_verify_fun_available?() do
true -> [ssl: ssl_opts(url)]
true -> [ssl: ssl_opts(url)]
false -> []
end
end
Expand All @@ -71,7 +77,7 @@ defmodule Premailex.HTTPAdapter.Httpc do
defp app_available?(app) do
case :application.get_key(app, :vsn) do
{:ok, _vsn} -> true
_ -> false
_ -> false
end
end

Expand Down
16 changes: 12 additions & 4 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@ defmodule Premailex.Mixfile do
elixir: "~> 1.11",
start_permanent: Mix.env() == :prod,
deps: deps(),
xref: [exclude: [:certifi, :httpc, Meeseeks, Meeseeks.Document, Meeseeks.Selector.CSS, :ssl_verify_hostname]],
xref: [
exclude: [
:certifi,
:httpc,
Meeseeks,
Meeseeks.Document,
Meeseeks.Selector.CSS,
:ssl_verify_hostname
]
],

# Hex
description: "Add inline styling to your HTML emails, and transform them to text",
Expand All @@ -31,13 +40,12 @@ defmodule Premailex.Mixfile do
defp deps do
[
{:floki, "~> 0.19"},

{:meeseeks, "~> 0.11", optional: true},
{:certifi, ">= 0.0.0", optional: true},
{:ssl_verify_fun, ">= 0.0.0", optional: true},

{:credo, "~> 1.4", only: [:dev, :test], runtime: false},
# Development and test
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
{:credo, "~> 1.4", only: [:dev, :test], runtime: false},
{:test_server, "~> 0.1.5", only: [:test]}
]
end
Expand Down
55 changes: 38 additions & 17 deletions test/premailex/html_inline_styles_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ defmodule Premailex.HTMLInlineStylesTest do
@input

_ ->
TestServer.add("/styles.css", to: fn conn -> Plug.Conn.send_resp(conn, 200, @css_link_content) end)
TestServer.add("/styles.css",
to: fn conn -> Plug.Conn.send_resp(conn, 200, @css_link_content) end
)

String.replace(@input, "http://localhost", TestServer.url())
end

Expand Down Expand Up @@ -134,14 +137,20 @@ defmodule Premailex.HTMLInlineStylesTest do

refute parsed =~ "This is a comment"

assert parsed =~ ~r/(#{Regex.escape("<!--[if (gte mso 9)|(IE)]>")})|(#{Regex.escape("<!-- [if (gte mso 9)|(IE)]>")})/
assert parsed =~
~r/(#{Regex.escape("<!--[if (gte mso 9)|(IE)]>")})|(#{Regex.escape("<!-- [if (gte mso 9)|(IE)]>")})/

assert parsed =~ "<p>Downlevel-hidden comment</p>"
assert parsed =~ ~r/(#{Regex.escape("<![endif]-->")})|(#{Regex.escape("<![endif] -->")})/

assert parsed =~ ~r/(#{Regex.escape("<!--[if !mso]><!-- -->")})|(#{Regex.escape("<!-- [if !mso]><!-- -->")})/
assert parsed =~
"<p style=\"background-color: #000; color: #000 !important; font-family: Arial, sans-serif; font-size: 16px; font-weight: bold; line-height: 22px; margin: 0; padding: 0;\">Downlevel-revealed comment</p>"
assert parsed =~ ~r/(#{Regex.escape("<!--<![endif]-->")})|(#{Regex.escape("<!-- <![endif] -->")})/
~r/(#{Regex.escape("<!--[if !mso]><!-- -->")})|(#{Regex.escape("<!-- [if !mso]><!-- -->")})/

assert parsed =~
"<p style=\"background-color: #000; color: #000 !important; font-family: Arial, sans-serif; font-size: 16px; font-weight: bold; line-height: 22px; margin: 0; padding: 0;\">Downlevel-revealed comment</p>"

assert parsed =~
~r/(#{Regex.escape("<!--<![endif]-->")})|(#{Regex.escape("<!-- <![endif] -->")})/

assert parsed =~ "<div class=\"match-order-test-1 same-match\" style=\"color: yellow;\">"
assert parsed =~ "<div class=\"match-order-test-2 same-match\" style=\"color: yellow;\">"
Expand All @@ -152,8 +161,10 @@ defmodule Premailex.HTMLInlineStylesTest do
@tag test_server: false
test "process/3 when styles can't be loaded due to no network", %{input: input} do
assert CaptureLog.capture_log(fn ->
refute Premailex.HTMLInlineStyles.process(input) =~ "<html xmlns=\"http://www.w3.org/1999/xhtml\" style=\"color: black;\">"
end) =~ "Ignoring http://localhost/styles.css styles because of unexpected error from Premailex.HTTPAdapter.Httpc:"
refute Premailex.HTMLInlineStyles.process(input) =~
"<html xmlns=\"http://www.w3.org/1999/xhtml\" style=\"color: black;\">"
end) =~
"Ignoring http://localhost/styles.css styles because of unexpected error from Premailex.HTTPAdapter.Httpc:"
end

@tag test_server: false
Expand All @@ -162,8 +173,10 @@ defmodule Premailex.HTMLInlineStylesTest do
input = String.replace(input, "http://localhost", TestServer.url())

assert CaptureLog.capture_log(fn ->
refute Premailex.HTMLInlineStyles.process(input) =~ "<html xmlns=\"http://www.w3.org/1999/xhtml\" style=\"color: black;\">"
end) =~ "Ignoring #{TestServer.url()}/styles.css styles because received unexpected HTTP status: 404"
refute Premailex.HTMLInlineStyles.process(input) =~
"<html xmlns=\"http://www.w3.org/1999/xhtml\" style=\"color: black;\">"
end) =~
"Ignoring #{TestServer.url()}/styles.css styles because received unexpected HTTP status: 404"
end

@tag test_server: false
Expand All @@ -172,14 +185,18 @@ defmodule Premailex.HTMLInlineStylesTest do
input = String.replace(input, "http://localhost", TestServer.url())

assert CaptureLog.capture_log(fn ->
refute Premailex.HTMLInlineStyles.process(input) =~ "<html xmlns=\"http://www.w3.org/1999/xhtml\" style=\"color: black;\">"
end) =~ ":unknown_ca"
refute Premailex.HTMLInlineStyles.process(input) =~
"<html xmlns=\"http://www.w3.org/1999/xhtml\" style=\"color: black;\">"
end) =~ ":unknown_ca"
end

@tag test_server: :false
@tag test_server: false
test "process/3 when styles loads on SSL", %{input: input} do
TestServer.start(scheme: :https)
TestServer.add("/styles.css", to: fn conn -> Plug.Conn.send_resp(conn, 200, @css_link_content) end)

TestServer.add("/styles.css",
to: fn conn -> Plug.Conn.send_resp(conn, 200, @css_link_content) end
)

on_exit(fn ->
Application.delete_env(:premailex, :http_adapter)
Expand All @@ -190,7 +207,7 @@ defmodule Premailex.HTMLInlineStylesTest do
verify: :verify_peer,
depth: 99,
cacerts: TestServer.x509_suite().cacerts,
verify_fun: {&:ssl_verify_hostname.verify_fun/3, check_hostname: 'localhost'}
verify_fun: {&:ssl_verify_hostname.verify_fun/3, check_hostname: ~c"localhost"}
]

Application.put_env(:premailex, :http_adapter, {Premailex.HTTPAdapter.Httpc, [ssl: ssl_opts]})
Expand Down Expand Up @@ -251,17 +268,21 @@ defmodule Premailex.HTMLInlineStylesTest do
assert parsed =~ "<html xmlns=\"http://www.w3.org/1999/xhtml\" style=\"color: black;\">"
assert parsed =~ "<style>"
assert parsed =~ "<link href"
assert parsed =~ "<body style=\"color: #333333; font-family: Arial, sans-serif; font-size: 14px; line-height: 22px;\">"

assert parsed =~
"<body style=\"color: #333333; font-family: Arial, sans-serif; font-size: 14px; line-height: 22px;\">"
end

test "process/3 accepts html tree as first argument and options as second", %{input: input} do
html_tree = Premailex.HTMLParser.parse(input)
parsed = Premailex.HTMLInlineStyles.process(html_tree, [optimize: :all])
parsed = Premailex.HTMLInlineStyles.process(html_tree, optimize: :all)

assert parsed =~ "<html xmlns=\"http://www.w3.org/1999/xhtml\" style=\"color: black;\">"
refute parsed =~ "<style>"
refute parsed =~ "<link href"
assert parsed =~ "<body style=\"color: #333333; font-family: Arial, sans-serif; font-size: 14px; line-height: 22px;\">"

assert parsed =~
"<body style=\"color: #333333; font-family: Arial, sans-serif; font-size: 14px; line-height: 22px;\">"
end

@tag test_server: false
Expand Down
Loading

0 comments on commit d37523f

Please sign in to comment.