Skip to content

Commit 8d62bbe

Browse files
brandonchinn178ysangkok
authored andcommitted
Add email markup implementation
1 parent 5e48f72 commit 8d62bbe

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed

hackage-server.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ library lib-server
265265
Distribution.Server.Util.CountingMap
266266
Distribution.Server.Util.CabalRevisions
267267
Distribution.Server.Util.DocMeta
268+
Distribution.Server.Util.Email
268269
Distribution.Server.Util.Parse
269270
Distribution.Server.Util.ServeTarball
270271
Distribution.Server.Util.Validators

src/Distribution/Server/Util/Email.hs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
{-# LANGUAGE LambdaCase #-}
2+
{-# LANGUAGE OverloadedStrings #-}
3+
4+
module Distribution.Server.Util.Email
5+
( EmailContent(..)
6+
, emailContentStr
7+
, emailContentLBS
8+
, emailContentDisplay
9+
, emailContentIntercalate
10+
, emailContentUrl
11+
12+
-- * Rendering email content
13+
, fromEmailContent
14+
, toPlainContent
15+
, toHtmlContent
16+
) where
17+
18+
import qualified Data.ByteString.Lazy as Lazy (ByteString)
19+
import Data.List (intersperse)
20+
import Data.String (IsString(..))
21+
import Data.Text (Text)
22+
import qualified Data.Text as Text
23+
import qualified Data.Text.Lazy as TextL
24+
import qualified Data.Text.Lazy.Encoding as TextL
25+
import Distribution.Pretty (Pretty)
26+
import Distribution.Text (display)
27+
import Network.Mail.Mime
28+
import Network.URI (URI, uriToString)
29+
30+
{- $setup
31+
>>> :set -XOverloadedStrings
32+
>>> import qualified Data.Text.IO as Text
33+
>>> import Network.URI (parseURI)
34+
-}
35+
36+
data EmailContent
37+
= EmailContentText Text
38+
| EmailContentLink Text URI
39+
| EmailContentSoftBreak
40+
| EmailContentParagraph EmailContent
41+
| EmailContentList [EmailContent]
42+
| EmailContentConcat EmailContent EmailContent
43+
deriving (Show)
44+
45+
instance IsString EmailContent where
46+
fromString = EmailContentText . Text.pack
47+
48+
instance Semigroup EmailContent where
49+
(<>) = EmailContentConcat
50+
51+
instance Monoid EmailContent where
52+
mempty = EmailContentText ""
53+
54+
emailContentStr :: String -> EmailContent
55+
emailContentStr = EmailContentText . Text.pack
56+
57+
emailContentLBS :: Lazy.ByteString -> EmailContent
58+
emailContentLBS = EmailContentText . TextL.toStrict . TextL.decodeUtf8
59+
60+
emailContentDisplay :: Pretty a => a -> EmailContent
61+
emailContentDisplay = EmailContentText . Text.pack . display
62+
63+
emailContentIntercalate :: EmailContent -> [EmailContent] -> EmailContent
64+
emailContentIntercalate x = mconcat . intersperse x
65+
66+
emailContentUrl :: URI -> EmailContent
67+
emailContentUrl uri = EmailContentLink (uriToText uri) uri
68+
69+
fromEmailContent :: EmailContent -> Alternatives
70+
fromEmailContent emailContent =
71+
[ Part
72+
{ partType = contentType <> "; charset=utf-8"
73+
, partEncoding = None
74+
, partDisposition = DefaultDisposition
75+
, partHeaders = []
76+
, partContent = PartContent $ TextL.encodeUtf8 $ TextL.fromStrict content
77+
}
78+
| (contentType, content) <- contents
79+
]
80+
where
81+
contents =
82+
[ ("text/plain", toPlainContent emailContent)
83+
, ("text/html", toHtmlContent emailContent)
84+
]
85+
86+
-- | Convert an 'EmailContent' to plain text.
87+
--
88+
-- >>> let Just haskellURI = parseURI "https://haskell.org"
89+
-- >>> let Just hackageURI = parseURI "https://hackage.haskell.org"
90+
-- >>> :{
91+
-- Text.putStr . toPlainContent . mconcat $
92+
-- [ EmailContentParagraph "Haskell is fun!"
93+
-- , EmailContentList
94+
-- [ "Website: " <> EmailContentLink "haskell.org" haskellURI
95+
-- , EmailContentLink "Hackage" hackageURI
96+
-- ]
97+
-- ]
98+
-- :}
99+
-- Haskell is fun!
100+
-- <BLANKLINE>
101+
-- * Website: haskell.org (https://haskell.org)
102+
-- * Hackage (https://hackage.haskell.org)
103+
-- <BLANKLINE>
104+
toPlainContent :: EmailContent -> Text
105+
toPlainContent = \case
106+
EmailContentText s -> s
107+
EmailContentLink s uri -> s <> " (" <> uriToText uri <> ")"
108+
EmailContentSoftBreak -> "\n"
109+
EmailContentParagraph content -> toPlainContent content <> "\n\n"
110+
EmailContentList items ->
111+
let renderListItem item = "* " <> toPlainContent item
112+
in Text.intercalate "\n" (map renderListItem items) <> "\n\n"
113+
EmailContentConcat a b -> toPlainContent a <> toPlainContent b
114+
115+
-- | Convert an 'EmailContent' to HTML.
116+
--
117+
-- >>> let Just haskellURI = parseURI "https://haskell.org"
118+
-- >>> let Just hackageURI = parseURI "https://hackage.haskell.org"
119+
-- >>> :{
120+
-- Text.putStr . toHtmlContent . mconcat $
121+
-- [ EmailContentParagraph "Haskell is fun!"
122+
-- , EmailContentList
123+
-- [ "Website: " <> EmailContentLink "haskell.org" haskellURI
124+
-- , EmailContentLink "Hackage" hackageURI
125+
-- ]
126+
-- ]
127+
-- :}
128+
-- <BLANKLINE>
129+
-- <p>
130+
-- Haskell is fun!
131+
-- </p>
132+
-- <ul>
133+
-- <li>Website: <a href="https://haskell.org">haskell.org</a></li>
134+
-- <li><a href="https://hackage.haskell.org">Hackage</a></li>
135+
-- </ul>
136+
toHtmlContent :: EmailContent -> Text
137+
toHtmlContent = \case
138+
EmailContentText s -> s
139+
EmailContentLink s uri -> "<a href=\"" <> uriToText uri <> "\">" <> s <> "</a>"
140+
EmailContentSoftBreak -> "\n<br />"
141+
EmailContentParagraph content -> "\n<p>\n" <> toHtmlContent content <> "\n</p>"
142+
EmailContentList items ->
143+
let renderListItem item = " <li>" <> toHtmlContent item <> "</li>"
144+
in "\n<ul>\n" <> Text.unlines (map renderListItem items) <> "</ul>"
145+
EmailContentConcat a b -> toHtmlContent a <> toHtmlContent b
146+
147+
uriToText :: URI -> Text
148+
uriToText uri = Text.pack $ uriToString id uri ""

0 commit comments

Comments
 (0)