-
Notifications
You must be signed in to change notification settings - Fork 5
/
Heckle.hs
144 lines (117 loc) · 4.44 KB
/
Heckle.hs
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
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Heckle where
import Control.Applicative
import Control.Monad
import Data.Either
import Data.Function (on)
import Data.List.Split (splitOn)
import Data.String (IsString)
import Data.Monoid
--import System.FilePath
import Text.Blaze.Html5 as H hiding (main, map)
import Text.Blaze.Html5.Attributes as A
import Text.Blaze.Html.Renderer.Pretty
import qualified Text.HTML.TagSoup as TagSoup
import Data.Time
import Text.Pandoc.Definition hiding (Format)
import Text.Pandoc.Options (def)
import Text.Pandoc.Readers.LaTeX (readLaTeX)
import Text.Pandoc.Readers.Markdown (readMarkdown)
import Text.Pandoc.Shared (stringify)
import Text.Pandoc.Writers.HTML (writeHtmlString)
instance Show Html where
show = renderHtml
displayDate :: UTCTime -> String
displayDate = formatTime defaultTimeLocale "%-d %B %Y"
postsToHtml :: [Post] -> Html
postsToHtml xs = do
ul ! A.id "blog-posts" $
forM_ xs postToHtml
postToHtml :: Post -> Html
postToHtml Post{..} = li ! class_ "blog-post" $ do
a ! class_ "post-link" ! href (stringValue ("posts/" ++ fileName ++ ext)) $ toHtml postTitle
H.div ! class_ "post-date" $ toHtml (displayDate postDate)
where
ext = getOutputExtension format
data Format = LaTeX | Markdown
deriving (Show, Eq)
getOutputExtension :: Format -> String
getOutputExtension LaTeX = ".pdf"
getOutputExtension Markdown = ".html"
newtype Title = Title { getTitle :: String }
deriving (Show, Eq, IsString, ToMarkup)
data Post = Post
{ fileName :: String -- TODO make this more typed
, postTitle :: Title
, postDate :: UTCTime
, format :: Format
, pd :: Pandoc
}
deriving (Show, Eq)
instance Ord Post where
compare = compare `on` postDate
parseAbsoluteDate :: String -> Either String UTCTime
parseAbsoluteDate s = case parseAbsoluteDate' s of
Just a -> Right a
Nothing -> Left "Date does not match valid formats"
-- | Valid formats:
-- | 6 January 2012
-- | January 6, 2012
-- | 9:47AM 6 January 2012
-- | 9:47AM January 6, 2012
parseAbsoluteDate' :: String -> Maybe UTCTime
parseAbsoluteDate' s = foldr (<|>) Nothing results
where
results = map ($ s) options
options = map (parseTimeM True defaultTimeLocale) formats
formats = ["%-d %B %Y", "%B %-d, %Y", "%-l:%M%p %-d %B %Y", "%-l:%M%p %B %-d, %Y"]
getMeta :: (Meta -> [Inline]) -> Pandoc -> Either String String
getMeta f (Pandoc m _) = case f m of
[] -> Left "Couldn't find it"
xs -> Right (stringify xs)
-- | Creates a post given a constructor for a post
createPost
:: Show a
=> Format
-> String
-> Either a Pandoc
-> Either String Post
createPost _ _ (Left e) = Left (show e)
createPost format fileName (Right pd) = do
postTitle <- Title <$> getMeta docTitle pd
postDate <- getMeta docDate pd >>= parseAbsoluteDate
return Post{..}
splitExtension :: String -> Maybe (String, String)
splitExtension s = case splitOn "." s of
[a,b] -> Just (a, b)
_ -> Nothing
fileToPost :: String -> IO (Either String Post)
fileToPost fileName =
case splitExtension fileName of
Just (name, "pdf") ->
return . createPost LaTeX name . readLaTeX def =<< readFile ("posts/" <> name <> ".tex")
Just (name, "md") ->
return . createPost Markdown name . readMarkdown def =<< readFile ("posts/" <> fileName)
_ -> pure (Left "Not a LaTeX or MD file")
injectIndex :: String -> Html -> Maybe String
injectIndex layout ul = injectAt [ TagSoup.TagOpen "ul" [("id","blog-posts")]
, TagSoup.TagClose "ul"]
layout (show ul)
injectTemplate :: String -> Post -> Maybe String
injectTemplate layout post
| format post == Markdown = injectAt tags layout inp
| otherwise = Nothing
where
tags = [TagSoup.TagOpen "div" [("id","blog-post")], TagSoup.TagClose "div"]
inp = "<div id='blog-post'>" <> writeHtmlString def (pd post) <> "</div>"
injectAt :: [TagSoup.Tag String] -> String -> String -> Maybe String
injectAt p layout insert = case splitOn p (TagSoup.parseTags layout) of
[beg, end] -> Just $ TagSoup.renderTags (beg <> TagSoup.parseTags insert <> end)
_ -> Nothing
writeHTML :: String -> Post -> Maybe (IO ())
writeHTML template p@Post{..} =
writeFile ("posts/" <> fileName <> ".html") <$> injectTemplate template p