diff --git a/lib/mail.ex b/lib/mail.ex index 728029a..8533922 100644 --- a/lib/mail.ex +++ b/lib/mail.ex @@ -95,9 +95,6 @@ defmodule Mail do end) end - def get_text(%Mail.Message{headers: %{"content-type" => "text/plain" <> _}} = message), - do: message - def get_text(%Mail.Message{headers: %{"content-type" => ["text/plain" | _]}} = message), do: message @@ -261,8 +258,10 @@ defmodule Mail do @doc """ Add a new `subject` header - Mail.put_subject(%Mail.Message{}, "Welcome to DockYard!") - %Mail.Message{headers: %{subject: "Welcome to DockYard!"}} + ## Examples + + iex> Mail.put_subject(%Mail.Message{}, "Welcome to DockYard!") + %Mail.Message{headers: %{"subject" => "Welcome to DockYard!"}} """ def put_subject(message, subject), do: Mail.Message.put_header(message, "subject", subject) @@ -279,15 +278,17 @@ defmodule Mail do Recipients can be added as a single string or a list of strings. The list of recipients will be concated to the previous value. - Mail.put_to(%Mail.Message{}, "one@example.com") - %Mail.Message{headers: %{to: ["one@example.com"]}} + ## Examples + + iex> Mail.put_to(%Mail.Message{}, "one@example.com") + %Mail.Message{headers: %{"to" => ["one@example.com"]}} - Mail.put_to(%Mail.Message{}, ["one@example.com", "two@example.com"]) - %Mail.Message{headers: %{to: ["one@example.com", "two@example.com"]}} + iex> Mail.put_to(%Mail.Message{}, ["one@example.com", "two@example.com"]) + %Mail.Message{headers: %{"to" => ["one@example.com", "two@example.com"]}} - Mail.put_to(%Mail.Message{}, "one@example.com") - |> Mail.put_to(["two@example.com", "three@example.com"]) - %Mail.Message{headers: %{to: ["one@example.com", "two@example.com", "three@example.com"]}} + iex> Mail.put_to(%Mail.Message{}, "one@example.com") + iex> |> Mail.put_to(["two@example.com", "three@example.com"]) + %Mail.Message{headers: %{"to" => ["one@example.com", "two@example.com", "three@example.com"]}} The value of a recipient must conform to either a string value or a tuple with two elements, otherwise an `ArgumentError` is raised. @@ -319,15 +320,17 @@ defmodule Mail do Recipients can be added as a single string or a list of strings. The list of recipients will be concated to the previous value. - Mail.put_cc(%Mail.Message{}, "one@example.com") - %Mail.Message{headers: %{cc: ["one@example.com"]}} + ## Examples - Mail.put_cc(%Mail.Message{}, ["one@example.com", "two@example.com"]) - %Mail.Message{headers: %{cc: ["one@example.com", "two@example.com"]}} + iex> Mail.put_cc(%Mail.Message{}, "one@example.com") + %Mail.Message{headers: %{"cc" => ["one@example.com"]}} - Mail.put_cc(%Mail.Message{}, "one@example.com") - |> Mail.put_cc(["two@example.com", "three@example.com"]) - %Mail.Message{headers: %{cc: ["one@example.com", "two@example.com", "three@example.com"]}} + iex> Mail.put_cc(%Mail.Message{}, ["one@example.com", "two@example.com"]) + %Mail.Message{headers: %{"cc" => ["one@example.com", "two@example.com"]}} + + iex> Mail.put_cc(%Mail.Message{}, "one@example.com") + iex> |> Mail.put_cc(["two@example.com", "three@example.com"]) + %Mail.Message{headers: %{"cc" => ["one@example.com", "two@example.com", "three@example.com"]}} The value of a recipient must conform to either a string value or a tuple with two elements, otherwise an `ArgumentError` is raised. @@ -359,15 +362,17 @@ defmodule Mail do Recipients can be added as a single string or a list of strings. The list of recipients will be concated to the previous value. - Mail.put_bcc(%Mail.Message{}, "one@example.com") - %Mail.Message{headers: %{bcc: ["one@example.com"]}} + ## Examples + + iex> Mail.put_bcc(%Mail.Message{}, "one@example.com") + %Mail.Message{headers: %{"bcc" => ["one@example.com"]}} - Mail.put_bcc(%Mail.Message{}, ["one@example.com", "two@example.com"]) - %Mail.Message{headers: %{bcc: ["one@example.com", "two@example.com"]}} + iex> Mail.put_bcc(%Mail.Message{}, ["one@example.com", "two@example.com"]) + %Mail.Message{headers: %{"bcc" => ["one@example.com", "two@example.com"]}} - Mail.put_bcc(%Mail.Message{}, "one@example.com") - |> Mail.put_bcc(["two@example.com", "three@example.com"]) - %Mail.Message{headers: %{bcc: ["one@example.com", "two@example.com", "three@example.com"]}} + iex> Mail.put_bcc(%Mail.Message{}, "one@example.com") + iex> |> Mail.put_bcc(["two@example.com", "three@example.com"]) + %Mail.Message{headers: %{"bcc" => ["one@example.com", "two@example.com", "three@example.com"]}} The value of a recipient must conform to either a string value or a tuple with two elements, otherwise an `ArgumentError` is raised. @@ -396,8 +401,10 @@ defmodule Mail do @doc """ Add a new `from` header - Mail.put_from(%Mail.Message{}, "user@example.com") - %Mail.Message{headers: %{from: "user@example.com"}} + ## Examples + + iex> Mail.put_from(%Mail.Message{}, "user@example.com") + %Mail.Message{headers: %{"from" => "user@example.com"}} """ def put_from(message, sender), do: Mail.Message.put_header(message, "from", sender) @@ -411,8 +418,10 @@ defmodule Mail do @doc """ Add a new `reply-to` header - Mail.put_reply_to(%Mail.Message{}, "user@example.com") - %Mail.Message{headers: %{reply_to: "user@example.com"}} + ## Examples + + iex> Mail.put_reply_to(%Mail.Message{}, "user@example.com") + %Mail.Message{headers: %{"reply-to" => "user@example.com"}} """ def put_reply_to(message, reply_address), do: Mail.Message.put_header(message, "reply-to", reply_address) diff --git a/lib/mail/message.ex b/lib/mail/message.ex index a6d707b..59ec8a8 100644 --- a/lib/mail/message.ex +++ b/lib/mail/message.ex @@ -9,7 +9,10 @@ defmodule Mail.Message do @doc """ Add new part - Mail.Message.put_part(%Mail.Message{}, %Mail.Message{}) + ## Examples + + iex> Mail.Message.put_part(%Mail.Message{}, %Mail.Message{}) + %Mail.Message{parts: [%Mail.Message{}]} """ def put_part(message, %Mail.Message{} = part) do put_in(message.parts, message.parts ++ [part]) @@ -27,10 +30,14 @@ defmodule Mail.Message do @doc """ Will match on a full or partial content type - Mail.Message.match_content_type?(message, ~r/text/) + ## Examples + + iex> message = %Mail.Message{headers: %{"content-type" => ["text/plain", {"charset", "UTF-8"}]}} + iex> Mail.Message.match_content_type?(message, ~r/text/) true - Mail.Message.match_content_type?(message, "text/html") + iex> message = %Mail.Message{headers: %{"content-type" => ["text/plain", {"charset", "UTF-8"}]}} + iex> Mail.Message.match_content_type?(message, "text/html") false """ def match_content_type?(message, string_or_regex) @@ -52,7 +59,10 @@ defmodule Mail.Message do @doc """ Add a new header key/value pair - Mail.Message.put_header(%Mail.Message{}, :content_type, "text/plain") + ## Examples + + iex> Mail.Message.put_header(%Mail.Message{}, :content_type, "text/plain") + %Mail.Message{headers: %{"content-type" => "text/plain"}} The individual headers will be in the `headers` field on the `%Mail.Message{}` struct @@ -72,7 +82,9 @@ defmodule Mail.Message do @doc """ Deletes a specific header key - Mail.Message.delete_header(%Mail.Message{headers: %{foo: "bar"}}, :foo) + ## Examples + + iex> Mail.Message.delete_header(%Mail.Message{headers: %{"foo" => "bar"}}, :foo) %Mail.Message{headers: %{}} """ def delete_header(message, header), @@ -81,7 +93,9 @@ defmodule Mail.Message do @doc """ Deletes a list of headers - Mail.Message.delete_headers(%Mail.Message{headers: %{foo: "bar", baz: "qux"}}, [:foo, :baz]) + ## Examples + + iex> Mail.Message.delete_headers(%Mail.Message{headers: %{"foo" => "bar", "baz" => "qux"}}, [:foo, :baz]) %Mail.Message{headers: %{}} """ def delete_headers(message, headers) @@ -104,24 +118,35 @@ defmodule Mail.Message do The value will always be wrapped in a `List` - Mail.Message.put_content_type(%Mail.Message{}, "text/plain") - %Mail.Message{headers: %{content_type: ["text/plain"]}} + ## Examples + + iex> Mail.Message.put_content_type(%Mail.Message{}, "text/plain") + %Mail.Message{headers: %{"content-type" => ["text/plain"]}} + + iex> Mail.Message.put_content_type(%Mail.Message{}, ["text/plain", {"charset", "UTF-8"}]) + %Mail.Message{headers: %{"content-type" => ["text/plain", {"charset", "UTF-8"}]}} """ - def put_content_type(message, content_type), - do: put_header(message, :content_type, content_type) + def put_content_type(message, content_type) when is_binary(content_type), + do: put_content_type(message, [content_type]) + + def put_content_type(message, content_type) do + put_header(message, :content_type, content_type) + end @doc """ Gets the `content_type` from the header Will ensure the `content_type` is always wrapped in a `List` - Mail.Message.get_content_type(%Mail.Message{}) + ## Examples + + iex> Mail.Message.get_content_type(%Mail.Message{}) [""] - Mail.Message.get_content_type(%Mail.Message{content_type: "text/plain"}) + iex> Mail.Message.get_content_type(%Mail.Message{headers: %{"content-type" => "text/plain"}}) ["text/plain"] - Mail.Message.get_content_type(%Mail.Message{headers: %{content_type: ["multipart/mixed", {"boundary", "foobar"}]}}) + iex> Mail.Message.get_content_type(%Mail.Message{headers: %{"content-type" => ["multipart/mixed", {"boundary", "foobar"}]}}) ["multipart/mixed", {"boundary", "foobar"}] """ def get_content_type(message), @@ -135,11 +160,13 @@ defmodule Mail.Message do Will overwrite existing `boundary` key in the list. Will preserve other values in the list - Mail.Message.put_boundary(%Mail.Message{}, "foobar") - %Mail.Message{headers: %{content_type: ["", {"boundary", "foobar"}]}} + ## Examples + + iex> Mail.Message.put_boundary(%Mail.Message{}, "foobar") + %Mail.Message{headers: %{"content-type" => ["", {"boundary", "foobar"}]}} - Mail.Message.put_boundary(%Mail.Message{headers: %{content_type: ["multipart/mixed", {"boundary", "bazqux"}]}}) - %Mail.Message{headers: %{content_type: ["multipart/mixed", {"boundary", "foobar"}]}} + iex> Mail.Message.put_boundary(%Mail.Message{headers: %{"content-type" => ["multipart/mixed", {"boundary", "bazqux"}]}}, "foobar") + %Mail.Message{headers: %{"content-type" => ["multipart/mixed", {"boundary", "foobar"}]}} """ def put_boundary(message, boundary) do content_type = @@ -154,10 +181,12 @@ defmodule Mail.Message do Will retrieve the boundary value. If one is not set a random one is generated. - Mail.Message.get_boundary(%Mail.Message{headers: %{content_type: ["multipart/mixed", {"boundary", "foobar"}]}}) + ## Examples + + iex> Mail.Message.get_boundary(%Mail.Message{headers: %{"content-type" => ["multipart/mixed", {"boundary", "foobar"}]}}) "foobar" - Mail.Message.get_boundary(%Mail.Message{headers: %{content_type: ["multipart/mixed"]}}) + iex> Mail.Message.get_boundary(%Mail.Message{headers: %{"content-type" => ["multipart/mixed", {"boundary", "ASDFSHNEW3473423"}]}}) "ASDFSHNEW3473423" """ def get_boundary(message) do @@ -177,7 +206,9 @@ defmodule Mail.Message do @doc """ Sets the `body` field on the part - Mail.Message.put_body(%Mail.Message{}, "Some data") + ## Examples + + iex> Mail.Message.put_body(%Mail.Message{}, "Some Data") %Mail.Message{body: "Some Data", headers: %{}} """ def put_body(part, body), @@ -186,11 +217,13 @@ defmodule Mail.Message do @doc """ Build a new text message - Mail.Message.build_text("Some text") - %Mail.Message{body: "Some text", headers: %{content_type: "text/plain"}} + ## Examples - Mail.Message.build_text("Some text", charset: "UTF-8") - %Mail.Message{body: "Some text", headers: %{content_type: ["text/plain", {"charset", "UTF-8"}]}} + iex> Mail.Message.build_text("Some text") + %Mail.Message{body: "Some text", headers: %{"content-type" => ["text/plain", {"charset", "UTF-8"}], "content-transfer-encoding" => :quoted_printable}} + + iex> Mail.Message.build_text("Some text", charset: "us-ascii") + %Mail.Message{body: "Some text", headers: %{"content-type" => ["text/plain", {"charset", "us-ascii"}], "content-transfer-encoding" => :quoted_printable}} ## Options @@ -214,11 +247,13 @@ defmodule Mail.Message do @doc """ Build a new HTML message - Mail.Message.build_html("

Some HTML

") - %Mail.Message{body: "

Some HTML

", headers: %{content_type: "text/html"}} + ## Examples + + iex> Mail.Message.build_html("

Some HTML

") + %Mail.Message{body: "

Some HTML

", headers: %{"content-type" => ["text/html", {"charset", "UTF-8"}], "content-transfer-encoding" => :quoted_printable}} - Mail.Message.build_html("

Some HTML

", charset: "UTF-8") - %Mail.Message{body: "

Some HTML

", headers: %{content_type: ["text/html", {"charset", "UTF-8"}]}} + iex> Mail.Message.build_html("

Some HTML

", charset: "UTF-8") + %Mail.Message{body: "

Some HTML

", headers: %{"content-type" => ["text/html", {"charset", "UTF-8"}], "content-transfer-encoding" => :quoted_printable}} ## Options @@ -255,11 +290,13 @@ defmodule Mail.Message do The mimetype of the file is determined by the file extension. - Mail.Message.build_attachment("README.md") - %Mail.Message{data: "base64 encoded", headers: %{content_type: ["text/x-markdown"], content_disposition: ["attachment", filename: "README.md"], content_transfer_encoding: :base64}} + ## Examples - Mail.Message.build_attachment({"README.md", "file contents"}) - %Mail.Message{data: "base64 encoded", headers: %{content_type: ["text/x-markdown"], content_disposition: ["attachment", filename: "README.md"], content_transfer_encoding: :base64}} + iex> message = Mail.Message.build_attachment("README.md") + %Mail.Message{body: <<"# Mail\\n", _::binary>>, headers: %{"content-type" => ["text/markdown"], "content-disposition" => ["attachment", {"filename", "README.md"}], "content-transfer-encoding" => :base64}} = message + + iex> message = Mail.Message.build_attachment({"README.md", "file contents"}) + %Mail.Message{body: "file contents", headers: %{"content-type" => ["text/markdown"], "content-disposition" => ["attachment", {"filename", "README.md"}], "content-transfer-encoding" => :base64}} = message ## Options @@ -297,18 +334,20 @@ defmodule Mail.Message do * `:headers` - Headers to be merged ## Examples - Mail.Message.put_attachment(%Mail.Message{}, "README.md") - %Mail.Message{data: "base64 encoded", headers: %{content_type: ["text/x-markdown"], content_disposition: ["attachment", filename: "README.md"], content_transfer_encoding: :base64}} - Mail.Message.put_attachment(%Mail.Message{}, {"README.md", "file contents"}) - %Mail.Message{data: "base64 encoded", headers: %{content_type: ["text/x-markdown"], content_disposition: ["attachment", filename: "README.md"], content_transfer_encoding: :base64}} + iex> message = Mail.Message.put_attachment(%Mail.Message{}, "README.md") + %Mail.Message{body: <<"# Mail\\n", _::binary>>, headers: %{"content-type" => ["text/markdown"], "content-disposition" => ["attachment", {"filename", "README.md"}], "content-transfer-encoding" => :base64}} = message + + iex> Mail.Message.put_attachment(%Mail.Message{}, {"README.md", "file contents"}) + %Mail.Message{body: "file contents", headers: %{"content-type" => ["text/markdown"], "content-disposition" => ["attachment", {"filename", "README.md"}], "content-transfer-encoding" => :base64}} ### Adding custom headers - Mail.Message.put_attachment(%Mail.Message{}, "README.md", headers: [content_id: "attachment-id"]) - %Mail.Message{data: "base64 encoded", headers: %{content_type: ["text/x-markdown"], content_disposition: ["attachment", filename: "README.md"], content_transfer_encoding: :base64, content_id: "attachment-id"}} - Mail.Message.put_attachment(%Mail.Message{}, {"README.md", data}, headers: [content_id: "attachment-id"]) - %Mail.Message{data: "base64 encoded", headers: %{content_type: ["text/x-markdown"], content_disposition: ["attachment", filename: "README.md"], content_transfer_encoding: :base64, content_id: "attachment-id"}} + iex> message =Mail.Message.put_attachment(%Mail.Message{}, "README.md", headers: [content_id: "attachment-id"]) + %Mail.Message{body: <<"# Mail\\n", _::binary>>, headers: %{"content-type" => ["text/markdown"], "content-disposition" => ["attachment", {"filename", "README.md"}], "content-transfer-encoding" => :base64, "content-id" => "attachment-id"}} = message + + iex> message = Mail.Message.put_attachment(%Mail.Message{}, {"README.md", "file contents"}, headers: [content_id: "attachment-id"]) + %Mail.Message{body: "file contents", headers: %{"content-type" => ["text/markdown"], "content-disposition" => ["attachment", {"filename", "README.md"}], "content-transfer-encoding" => :base64, "content-id" => "attachment-id"}} = message """ def put_attachment(message, path_or_file_tuple, opts \\ []) diff --git a/lib/mail/parsers/rfc_2822.ex b/lib/mail/parsers/rfc_2822.ex index e3c1c80..92566f6 100644 --- a/lib/mail/parsers/rfc_2822.ex +++ b/lib/mail/parsers/rfc_2822.ex @@ -1,12 +1,24 @@ defmodule Mail.Parsers.RFC2822 do - @moduledoc """ + @moduledoc ~S""" RFC2822 Parser Will attempt to parse a valid RFC2822 message back into a `%Mail.Message{}` data model. - Mail.Parsers.RFC2822.parse(message) - %Mail.Message{body: "Some message", headers: %{to: ["user@example.com"], from: "other@example.com", subject: "Read this!"}} + ## Examples + + iex> message = \""" + ...> To: user@example.com\r + ...> From: me@example.com\r + ...> Subject: Test Email\r + ...> Content-Type: text/plain; foo=bar;\r + ...> baz=qux;\r + ...> \r + ...> This is the body!\r + ...> It has more than one line\r + ...> \""" + iex> Mail.Parsers.RFC2822.parse(message) + %Mail.Message{body: "This is the body!\r\nIt has more than one line", headers: %{"to" => ["user@example.com"], "from" => "me@example.com", "subject" => "Test Email", "content-type" => ["text/plain", {"foo", "bar"}, {"baz", "qux"}]}} """ @months ~w(jan feb mar apr may jun jul aug sep oct nov dec) @@ -204,16 +216,6 @@ defmodule Mail.Parsers.RFC2822 do def to_datetime(invalid_datetime), do: {:error, invalid_datetime} - # # Fixes invalid value: Wed, 14 10 2015 12:34:17 - # def to_datetime( - # <> - # ) do - # month_name = get_month_name(month_digits) - # to_datetime("#{date} #{month_name} #{year} #{hour}:#{minute}:#{second}#{rest}") - # end - defp to_four_digit_year(year) when year >= 0 and year < 50, do: 2000 + year defp to_four_digit_year(year) when year < 100 and year >= 50, do: 1900 + year diff --git a/test/mail/message_test.exs b/test/mail/message_test.exs index 9b33da8..5b8dfe0 100644 --- a/test/mail/message_test.exs +++ b/test/mail/message_test.exs @@ -1,5 +1,6 @@ defmodule Mail.MessageTest do use ExUnit.Case, async: true + doctest Mail.Message test "put_part" do part = %Mail.Message{body: "new part"} @@ -51,7 +52,7 @@ defmodule Mail.MessageTest do test "put_content_type" do message = Mail.Message.put_content_type(%Mail.Message{}, "multipart/mixed") - assert Mail.Message.get_header(message, :content_type) == "multipart/mixed" + assert Mail.Message.get_header(message, :content_type) == ["multipart/mixed"] end test "get_content_type" do diff --git a/test/mail/parsers/rfc_2822_test.exs b/test/mail/parsers/rfc_2822_test.exs index 05a806b..9670d8b 100644 --- a/test/mail/parsers/rfc_2822_test.exs +++ b/test/mail/parsers/rfc_2822_test.exs @@ -1,5 +1,6 @@ defmodule Mail.Parsers.RFC2822Test do use ExUnit.Case, async: true + doctest Mail.Parsers.RFC2822 test "parses a singlepart message" do message = diff --git a/test/mail_test.exs b/test/mail_test.exs index 046541b..53db91b 100644 --- a/test/mail_test.exs +++ b/test/mail_test.exs @@ -1,5 +1,6 @@ defmodule MailTest do use ExUnit.Case, async: true + doctest Mail defmodule TestRenderer do def render(message) do @@ -191,7 +192,11 @@ defmodule MailTest do mail = Mail.Message.put_part( Mail.build_multipart(), - Mail.Message.put_content_type(%Mail.Message{}, "text/plain; charset=UTF-8; format=flowed") + Mail.Message.put_content_type(%Mail.Message{}, [ + "text/plain", + {"charset", "UTF-8"}, + {"format", "flowed"} + ]) |> Mail.Message.put_header(:content_transfer_encoding, :"8bit") |> Mail.Message.put_body(text) )