Skip to content

Commit f9fe717

Browse files
committed
Chapter 7 - Environment
1 parent a08d148 commit f9fe717

File tree

9 files changed

+120
-30
lines changed

9 files changed

+120
-30
lines changed

app/Main.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ main :: IO ()
1515
main = do
1616
options <- parse
1717
case options of
18-
ConvertDir input output ->
19-
HsBlog.convertDirectory input output
18+
ConvertDir input output env ->
19+
HsBlog.convertDirectory env input output
2020

2121
ConvertSingle input output -> do
2222
(title, inputHandle) <-

app/OptParse.hs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module OptParse
1111
where
1212

1313
import Data.Maybe (fromMaybe)
14+
import HsBlog.Env
1415
import Options.Applicative
1516

1617
------------------------------------------------
@@ -19,7 +20,7 @@ import Options.Applicative
1920
-- | Model
2021
data Options
2122
= ConvertSingle SingleInput SingleOutput
22-
| ConvertDir FilePath FilePath
23+
| ConvertDir FilePath FilePath Env
2324
deriving Show
2425

2526
-- | A single input source
@@ -114,7 +115,7 @@ pOutputFile = OutputFile <$> parser
114115

115116
pConvertDir :: Parser Options
116117
pConvertDir =
117-
ConvertDir <$> pInputDir <*> pOutputDir
118+
ConvertDir <$> pInputDir <*> pOutputDir <*> pEnv
118119

119120
-- | Parser for input directory
120121
pInputDir :: Parser FilePath
@@ -135,3 +136,32 @@ pOutputDir =
135136
<> metavar "DIRECTORY"
136137
<> help "Output directory"
137138
)
139+
140+
-- | Parser for blog environment
141+
pEnv :: Parser Env
142+
pEnv =
143+
Env <$> pBlogName <*> pStylesheet
144+
145+
-- | Blog name parser
146+
pBlogName :: Parser String
147+
pBlogName =
148+
strOption
149+
( long "name"
150+
<> short 'N'
151+
<> metavar "STRING"
152+
<> help "Blog name"
153+
<> value (eBlogName defaultEnv)
154+
<> showDefault
155+
)
156+
157+
-- | Stylesheet parser
158+
pStylesheet :: Parser String
159+
pStylesheet =
160+
strOption
161+
( long "style"
162+
<> short 'S'
163+
<> metavar "FILE"
164+
<> help "Stylesheet filename"
165+
<> value (eStylesheetPath defaultEnv)
166+
<> showDefault
167+
)

hs-blog.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ library
3333
base
3434
, directory
3535
, filepath
36+
, mtl
3637
exposed-modules:
3738
HsBlog
3839
HsBlog.Convert
3940
HsBlog.Directory
41+
HsBlog.Env
4042
HsBlog.Html
4143
HsBlog.Html.Internal
4244
HsBlog.Markup

src/HsBlog.hs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import qualified HsBlog.Markup as Markup
1212
import qualified HsBlog.Html as Html
1313
import HsBlog.Convert (convert)
1414
import HsBlog.Directory (convertDirectory, buildIndex)
15+
import HsBlog.Env (defaultEnv)
1516

1617
import System.IO
1718

18-
convertSingle :: Html.Title -> Handle -> Handle -> IO ()
19+
convertSingle :: String -> Handle -> Handle -> IO ()
1920
convertSingle title input output = do
2021
content <- hGetContents input
2122
hPutStrLn output (process title content)
2223

23-
process :: Html.Title -> String -> String
24-
process title = Html.render . convert title . Markup.parse
24+
process :: String -> String -> String
25+
process title = Html.render . convert defaultEnv title . Markup.parse

src/HsBlog/Convert.hs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,25 @@
22

33
module HsBlog.Convert where
44

5+
import Prelude hiding (head)
6+
import HsBlog.Env (Env(..))
57
import qualified HsBlog.Markup as Markup
68
import qualified HsBlog.Html as Html
79

8-
convert :: Html.Title -> Markup.Document -> Html.Html
9-
convert title = Html.html_ title . foldMap convertStructure
10+
convert :: Env -> String -> Markup.Document -> Html.Html
11+
convert env title doc =
12+
let
13+
head =
14+
Html.title_ (eBlogName env <> " - " <> title)
15+
<> Html.stylesheet_ (eStylesheetPath env)
16+
article =
17+
foldMap convertStructure doc
18+
websiteTitle =
19+
Html.h_ 1 (Html.link_ "index.html" $ Html.txt_ $ eBlogName env)
20+
body =
21+
websiteTitle <> article
22+
in
23+
Html.html_ head body
1024

1125
convertStructure :: Markup.Structure -> Html.Structure
1226
convertStructure structure =

src/HsBlog/Directory.hs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ module HsBlog.Directory
1111
import qualified HsBlog.Markup as Markup
1212
import qualified HsBlog.Html as Html
1313
import HsBlog.Convert (convert, convertStructure)
14+
import HsBlog.Env (Env(..))
1415

1516
import Data.List (partition)
1617
import Data.Traversable (for)
1718
import Control.Monad (void, when)
19+
import Control.Monad.Reader (Reader, runReader, ask)
1820

1921
import System.IO (hPutStrLn, stderr)
2022
import Control.Exception (catch, displayException, SomeException(..))
@@ -38,12 +40,12 @@ import System.Directory
3840
-- '.html' files in the process. Recording unsuccessful reads and writes to stderr.
3941
--
4042
-- May throw an exception on output directory creation.
41-
convertDirectory :: FilePath -> FilePath -> IO ()
42-
convertDirectory inputDir outputDir = do
43+
convertDirectory :: Env -> FilePath -> FilePath -> IO ()
44+
convertDirectory env inputDir outputDir = do
4345
DirContents filesToProcess filesToCopy <- getDirFilesAndContent inputDir
4446
createOutputDirectoryOrExit outputDir
4547
let
46-
outputHtmls = txtsToRenderedHtml filesToProcess
48+
outputHtmls = runReader (txtsToRenderedHtml filesToProcess) env
4749
copyFiles outputDir filesToCopy
4850
writeFiles outputDir outputHtmls
4951
putStrLn "Done."
@@ -77,8 +79,9 @@ data DirContents
7779
------------------------------------
7880
-- * Build index page
7981

80-
buildIndex :: [(FilePath, Markup.Document)] -> Html.Html
81-
buildIndex files =
82+
buildIndex :: [(FilePath, Markup.Document)] -> Reader Env Html.Html
83+
buildIndex files = do
84+
env <- ask
8285
let
8386
previews =
8487
map
@@ -92,9 +95,10 @@ buildIndex files =
9295
Html.h_ 3 (Html.link_ file (Html.txt_ file))
9396
)
9497
files
95-
in
96-
Html.html_
97-
"Blog"
98+
pure $ Html.html_
99+
( Html.title_ (eBlogName env)
100+
<> Html.stylesheet_ (eStylesheetPath env)
101+
)
98102
( Html.h_ 1 (Html.link_ "index.html" (Html.txt_ "Blog"))
99103
<> Html.h_ 2 (Html.txt_ "Posts")
100104
<> mconcat previews
@@ -104,20 +108,22 @@ buildIndex files =
104108
-- * Conversion
105109

106110
-- | Convert text files to Markup, build an index, and render as html.
107-
txtsToRenderedHtml :: [(FilePath, String)] -> [(FilePath, String)]
108-
txtsToRenderedHtml txtFiles =
111+
txtsToRenderedHtml :: [(FilePath, String)] -> Reader Env [(FilePath, String)]
112+
txtsToRenderedHtml txtFiles = do
109113
let
110114
txtOutputFiles = map toOutputMarkupFile txtFiles
111-
index = ("index.html", buildIndex txtOutputFiles)
112-
in
113-
map (fmap Html.render) (index : map convertFile txtOutputFiles)
115+
index <- (,) "index.html" <$> buildIndex txtOutputFiles
116+
htmlPages <- traverse convertFile txtOutputFiles
117+
pure $ map (fmap Html.render) (index : htmlPages)
114118

115119
toOutputMarkupFile :: (FilePath, String) -> (FilePath, Markup.Document)
116120
toOutputMarkupFile (file, content) =
117121
(takeBaseName file <.> "html", Markup.parse content)
118122

119-
convertFile :: (FilePath, Markup.Document) -> (FilePath, Html.Html)
120-
convertFile (file, doc) = (file, convert file doc)
123+
convertFile :: (FilePath, Markup.Document) -> Reader Env (FilePath, Html.Html)
124+
convertFile (file, doc) = do
125+
env <- ask
126+
pure (file, convert env (takeBaseName file) doc)
121127

122128
------------------------------------
123129
-- * Output to directory

src/HsBlog/Env.hs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-- | src/HsBlog/Env.hs
2+
3+
module HsBlog.Env where
4+
5+
data Env
6+
= Env
7+
{ eBlogName :: String
8+
, eStylesheetPath :: FilePath
9+
}
10+
deriving Show
11+
12+
defaultEnv :: Env
13+
defaultEnv = Env "My Blog" "style.css"

src/HsBlog/Html.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
module HsBlog.Html
44
( Html
5-
, Title
5+
, Head
6+
, title_
7+
, stylesheet_
8+
, meta_
69
, Structure
710
, html_
811
, h_

src/HsBlog/Html/Internal.hs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
module HsBlog.Html.Internal where
44

5+
import Prelude hiding (head)
56
import Numeric.Natural
67

78
-- * Types
@@ -15,20 +16,40 @@ newtype Structure
1516
newtype Content
1617
= Content String
1718

18-
type Title
19-
= String
19+
newtype Head
20+
= Head String
2021

2122
-- * EDSL
2223

23-
html_ :: Title -> Structure -> Html
24-
html_ title content =
24+
html_ :: Head -> Structure -> Html
25+
html_ (Head head) content =
2526
Html
2627
( el "html"
27-
( el "head" (el "title" (escape title))
28+
( el "head" head
2829
<> el "body" (getStructureString content)
2930
)
3031
)
3132

33+
-- * Head
34+
35+
title_ :: String -> Head
36+
title_ = Head . el "title" . escape
37+
38+
stylesheet_ :: FilePath -> Head
39+
stylesheet_ path =
40+
Head $ "<link rel=\"stylesheet\" type=\"text/css\" href=\"" <> escape path <> "\">"
41+
42+
meta_ :: String -> String -> Head
43+
meta_ name content =
44+
Head $ "<meta name=\"" <> escape name <> "\" content=\"" <> escape content <> "\">"
45+
46+
instance Semigroup Head where
47+
(<>) (Head h1) (Head h2) =
48+
Head (h1 <> h2)
49+
50+
instance Monoid Head where
51+
mempty = Head ""
52+
3253
-- * Structure
3354

3455
p_ :: Content -> Structure

0 commit comments

Comments
 (0)