Studio
Studio converts Markdown files into organized HTML pages while preserving the flexibility to change templates and add custom CSS and JavaScript. Static pages make the site fast, secure, and easy to deploy.
I built this program during the summer of 2014 to improve my Haskell knowledge and start posting online. I tried other static-site generators but found that they did not meet my needs.
At the time, I was having fun with code golf, so I set a goal of creating a static-site generator in 100 lines of Haskell. I reached that goal at the expense of readability. I later corrected this by adding spaces and expanding the expressions.
Markdown (.md) files are Studio's inputs. Markdown provides a standard way to add titles, links, and images. Studio extracts the titles and creates a table of contents on the main page, with a link to each article.
Haskell is a functional language, so I had to separate pure functions from the parts that interact with the outside world. It is also concise: compared with many other languages, it can express many transformations in a few lines of code. The program has three main steps.
I created a data structure called Page that stores a file path, a list of static assets, and a date.
data Page = Page { title :: FilePath -- Path to words
, static :: [FilePath] -- List of static files
, date :: [String] -- Month,day,year
} deriving (Show)
Create and Order Pages
Each folder in a directory is added to a list and sent to the getPages function, which returns the pages it finds inside an IO action. The getStatic function creates a list of extra JavaScript or CSS files linked to an article, allowing each article to have custom assets.
getPages :: [FilePath] -> IO [Page]
getPages = mapM (\x -> do static <- getStatic ("Articles/" ++ x)
date <- getPageDate ("Articles/" ++ x ++ "/words.md")
return (Page x static date))
Create HTML from the Template and Pages
Writing a page required an HTML template and the Page value defined above. The Pandoc library converted the Markdown text to HTML.
writePage :: String -> Page -> IO ()
writePage template page = do
let year = last $ date page
let urlTitle = urlConvert (title page)
mdArt <- readFile ("Articles/" ++ urlTitle ++ "/words.md")
let pandocArt = readMarkdown def mdArt
let html = writeHtmlString (siteOptions template) pandocArt
createDirectoryIfMissing True $ "Output/" ++ year ++ "/" ++ urlTitle
writeFile ("Output/" ++ year ++ "/" ++ urlTitle ++ "/index.html") html
Write the Table of Contents
The table of contents also uses the template, along with some additional HTML for presentation.
writeTOC :: String -> [Page] -> IO ()
writeTOC template pages = do
list <- mapM getItem pages
let html = writeHtmlString (siteOptions template) (tocWrap list)
createDirectoryIfMissing True $ "Output/archives"
writeFile "Output/archives/index.html" html
Those functions account for roughly 80% of Studio's behaviour; most of the remaining work involves moving files. Thank you for reading. You can find the code on GitHub.