aboutsummaryrefslogtreecommitdiff
path: root/site.hs
blob: bc4421c6998d565803c858ac3f9e0a55fc802954 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}

import Control.Monad (unless, when)
import Control.Monad.IO.Class
import Control.Monad.Trans.State.Lazy
import Data.Default (def)
import Data.Foldable (traverse_)
import Data.List (nub)
import Data.List.Extra (stripSuffix)
import qualified Data.Map as M
import Data.Maybe (fromMaybe, isJust)
import Data.String (fromString)
import qualified Data.Text as T
import qualified Data.Text.Encoding
import qualified Data.Text.IO as TIO
import qualified Data.Yaml as Y
import Hakyll.Core.Util.File (getRecursiveContents, makeDirectories)
import Lens.Micro
import Lens.Micro.Aeson
import Lens.Micro.Mtl
import Lens.Micro.TH
import System.Environment (getArgs)
import System.FilePath ((</>), splitPath)
import qualified Text.Mustache as Mu
import Text.Pandoc.Class (runIOorExplode)
import qualified Text.Pandoc.Definition
import qualified Text.Pandoc.Extensions
import Text.Pandoc.Options (ReaderOptions(..))
import Text.Pandoc.Readers.Markdown (readMarkdown)

import Debug.Trace

io :: MonadIO m => IO a -> m a
io = liftIO

just _ (Just val) = val
just err Nothing = error ("Missing: " ++ err)

data PageInfo =
  PageInfo
    { _pagePath :: FilePath
    , _pageMeta :: Y.Value
    , _pagePandoc :: Text.Pandoc.Definition.Pandoc
    }
  deriving (Show)

makeLenses ''PageInfo

data SiteState =
  SiteState
    { _pages :: M.Map FilePath PageInfo
    , _redirects :: M.Map FilePath FilePath -- from -> to
    , _htags :: M.Map [String] [FilePath] -- tag sequence -> tagged locs
    , _installs :: M.Map FilePath FilePath -- file hash -> install location
    , _templates :: M.Map FilePath Mu.Template -- TODO mustache templates
    , _outputDir :: FilePath
    , _defaultTemplate :: FilePath
    }
  deriving (Show)

makeLenses ''SiteState

emptySiteState out =
  SiteState
    { _pages = M.empty
    , _redirects = M.empty
    , _htags = M.empty
    , _installs = M.empty
    , _templates = M.empty
    , _outputDir = out
    , _defaultTemplate = "default.html"
    }

type Site a = StateT SiteState IO a

hasSuffix s = isJust . stripSuffix s

sourcePages :: FilePath -> Site ()
sourcePages fp = do
  links <-
    io $ filter (hasSuffix ".md" . last . splitPath) <$>
    getRecursiveContents (pure . const False) fp
  traverse_ loadPage (map (fp </>) links)

markdownReadOpts =
  def
    { readerExtensions =
        Text.Pandoc.Extensions.enableExtension
          Text.Pandoc.Extensions.Ext_smart
          Text.Pandoc.Extensions.pandocExtensions
    }

loadPage :: FilePath -> Site ()
loadPage fp = do
  txt <- io $ TIO.readFile fp
  (T.take 4 txt == "---\n") `unless`
    error ("metadata block start missing in " ++ fp)
  let (meta, markdown) = T.breakOn "\n---\n" (T.drop 4 txt)
  T.null meta `when` error ("metadata block bad in " ++ fp)
  yml <- Y.decodeThrow $ Data.Text.Encoding.encodeUtf8 meta
  md <- io $ runIOorExplode $ readMarkdown markdownReadOpts (T.drop 5 markdown)
  let mount =
        T.unpack . just ("mount point of " ++ fp) $ yml ^? key "title" . _String
  pages %=
    M.insert mount PageInfo {_pagePath = fp, _pageMeta = yml, _pagePandoc = md}

pageTemplate :: PageInfo -> Site FilePath
pageTemplate pi = do
  dt <- use defaultTemplate
  pure . maybe dt T.unpack $ pi ^? pageMeta . key "template" . _String

pageTemplates :: Site [FilePath]
pageTemplates = nub <$> (gets (^.. pages . traverse) >>= traverse pageTemplate)

sourceTemplates :: FilePath -> Site ()
sourceTemplates templdir = do
  ts <- pageTemplates
  templs' <- fmap sequence . traverse (io . Mu.automaticCompile [templdir]) $ ts
  case templs' of
    Left err -> error $ "template compilation: " ++ show err
    Right templs -> templates .= M.fromList (zip ts templs)

indexFilename :: FilePath -> Site FilePath
indexFilename mount = do
  od <- use outputDir
  pure (od </> mount </> "index.html")

installPage :: FilePath -> PageInfo -> Site ()
installPage mount pi = do
  tname <- fromString <$> pageTemplate pi
  templ <- use $ templates . to (M.! tname)
  file <- indexFilename mount
  io $ do
    putStrLn $ ">>> " ++ file
    makeDirectories file
    TIO.writeFile file . Mu.substitute templ $ pi ^. pageMeta

installFile :: FilePath -> Site FilePath
installFile = undefined

makeRedirect :: FilePath -> FilePath -> Site ()
makeRedirect = undefined

makeRedirects :: Site ()
makeRedirects = undefined

renderTag :: [String] -> Site ()
renderTag = undefined

renderTags :: Site ()
renderTags = undefined

main = do
  [targetDir] <- getArgs
  flip runStateT (emptySiteState targetDir) $ do
    traverse sourcePages ["external"]
    sourceTemplates "templates"
    use pages >>= traverse (uncurry installPage) . M.assocs
    get >>= io . print