summaryrefslogtreecommitdiff
path: root/080_blog/100_Static-Page-Maker-in-Haskell/index.md
blob: 061907894c01be7283c60f898896d6d35e701031 (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
# Static Haskell Website Creator - Miguel's Lair

    April 12, 2018

About two weeks ago, for personal reasons, I decided to switch my homepage
from a well known PHP driven CMS solution, to a light and static set
of html pages. 

I used this fact as a pretence to write my own simple static website 
generator. And, Yes.. I know there are already hundereds of such genertors
out there, but I wanted to practice haskell and my masturbatory solution 
fits in under 100 lines of code. (Including comments and type signatures)

I use pandoc for all the heavy work, as syntax highlighting and conversion of 
markdown to html, anyway.

## Features

* No Documentation
* No Database
* Sitemap derived from Directory Tree
* Simple Markdown Files for Content

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.haskell .numberLines}
--
-- Staticus WWW - A minimalistic yet undocumented static website generator
--                coded in April 2018 by Michal Idziorek <m.i@gmx.at> 
--

module StaticusWWW where

import Control.Exception
import Control.Monad.Reader 
import Data.List
import Data.List.Split
import Data.List.Utils 
import Data.Maybe
import Data.Text(pack,unpack)
import Data.Tree            
import System.Directory     
import Text.Pandoc

-- generate a HTML link from a href & title 
htmlLink :: String -> String -> String
htmlLink href title="<a href='"++href++"'>"++replace "_" " " title++"</a>"

-- generate HTML breadcrumbs from a list of page titles
genBrc :: [String] -> String
genBrc b | length b < 2 = ""
         | otherwise = fst $ foldl lnk ("",0) b
         where lnk (a,c) v = (a++" / "++html c v,c+1)
               html c      = htmlLink (concat (replicate (length b-c-1) "../"))

-- pure function that generates a single HTML page from:
-- a html template, description, keywords, breadcrumbs, topMenue & submenue
genPage :: String->String->String->[String]->[String]->[String]->String->String
genPage tmpl dsc kw brc top chld md = 
        foldr rplc tmpl (replacers content (menu "/" top) (menu "./" chld) (genBrc brc) title dsc kw)
        where content = either (const "error") unpack res
                        where res = runPure $ do doc <- readMarkdown def{readerExtensions=(enableExtension Ext_raw_html pandocExtensions )} $ pack md
                                                 writeHtml5String def doc
              rplc v = replace ("##" ++ fst v ++ "##") (snd v)
              title  = if null brc then "Home" else replace "_" " " $ last brc
              menu p = foldr (fm p) "" 
              fm p s a = "<li>"++htmlLink (p++s) s ++"</li>"++a
              replacers c m m2 b t d k= 
                          [("CONTENT",        c)
                          ,("LOGO",           "/DATA/logo.png")
                          ,("STYLESHEET",     "/DATA/style.css")
                          ,("SITE_TITLE",     "Miguel's Lair")
                          ,("SITE_SUBTITLE",  "<br />where information sleepzzZZZzz ...")
                          ,("TITLE",          t)
                          ,("DESCRIPTION",    d)
                          ,("KEYWORDS",       k)
                          ,("AUTHOR",         "Michal Idziorek")
                          ,("MENU",           m)
                          ,("SUB_MENU",       m2)
                          ,("BREADCRUMBS",    b)
                          ,("FOOTER",         "(c) by Miguel 2018")]

-- get list of subdirectories in given directory, with full relative path
getDirList ::  FilePath -> IO [FilePath]
getDirList d = map ((d++"/")++) <$> listDirectory d
               >>= filterM doesDirectoryExist

-- sequence IO Action 'f' for each subdirectory of 'fp' recursively
trvDirTree :: FilePath -> (FilePath -> [FilePath] -> IO()) -> IO ()
trvDirTree fp f = unfoldTreeM unf fp >>= sequence_
                  where unf p = getDirList p >>= \s -> f p s >>= \l -> 
                                return (return l, s)

-- write the HTML page generated from current diectory by 'genPage'
wrtPage :: FilePath -> FilePath -> String -> [FilePath] -> FilePath -> [FilePath] -> IO () 
wrtPage idx out tmpl top p chld = do

            when (not home) (createDirectory outdir)

            md        <- readFile $ p ++ "/index.md"
            cfg       <- (map ((\(x:xs) -> (x,unwords xs)).words).lines) 
                              <$> catch (readFile $ p++"/config") 
                                  ((\_ -> return "") :: IOException -> IO String) 

            writeFile (outdir++"/index.html") 
                      (genPage tmpl (lkp cfg "dsc") (lkp cfg "kwd") 
                               brc (menu top) (if home then [] else menu chld) md)

            where home       = outdir==out++"/"
                  remUnder   = filter (not.null) . map (dropWhile(=='_').dropWhile(/='_'))
                  lkp cfg k  = fromMaybe "" (lookup k cfg)
                  menu       = remUnder . filter (not . isPrefixOf "00_") . map (last.splitOn "/") . sort  
                  outdir     = out ++ "/" ++  intercalate "/" brc
                  brc        = remUnder $ splitOn "/" (drop (length idx) p)

-- Main IO action: traverses index recursively and calls wrtPage in each 
-- subdir, which in turn generates the output directories and html pages
main :: IO ()
main = do tmpl <- readFile "./DATA/template.html"
          top  <- getDirList "./INDEX"
          trvDirTree "./INDEX" (wrtPage "./INDEX" "./OUT" tmpl top)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~