first commit
This commit is contained in:
commit
88e3735a22
32
exe/Config.hs
Normal file
32
exe/Config.hs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}
|
||||||
|
|
||||||
|
module Config where
|
||||||
|
|
||||||
|
import Data.Aeson
|
||||||
|
import Data.Text
|
||||||
|
import GHC.Generics
|
||||||
|
import System.FilePath
|
||||||
|
import qualified Data.ByteString.Lazy as B
|
||||||
|
|
||||||
|
data Config = Config
|
||||||
|
{ confNumFront :: Int
|
||||||
|
, confNumRSS :: Int
|
||||||
|
, confTitle :: String
|
||||||
|
, confDesc :: String
|
||||||
|
, confAuthor :: String
|
||||||
|
, confEmail :: String
|
||||||
|
, confRoot :: String
|
||||||
|
} deriving (Show, Generic)
|
||||||
|
|
||||||
|
instance FromJSON Config
|
||||||
|
instance ToJSON Config
|
||||||
|
|
||||||
|
configName :: FilePath
|
||||||
|
configName = "config.json"
|
||||||
|
|
||||||
|
getConfig :: FilePath -> IO Config
|
||||||
|
getConfig dir = do
|
||||||
|
c <- (eitherDecode <$> (B.readFile (dir </> configName)))
|
||||||
|
case c of
|
||||||
|
Left e -> error e
|
||||||
|
Right conf -> return conf
|
9
exe/Main.hs
Normal file
9
exe/Main.hs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import Options
|
||||||
|
import Site
|
||||||
|
|
||||||
|
import Options.Applicative
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = generateSite =<< execParser getOptions
|
82
exe/Options.hs
Normal file
82
exe/Options.hs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
module Options where
|
||||||
|
|
||||||
|
import System.FilePath.Posix
|
||||||
|
import Data.Maybe
|
||||||
|
import Data.Semigroup ((<>))
|
||||||
|
import Data.List.Predicate
|
||||||
|
import Options.Applicative
|
||||||
|
|
||||||
|
data Options = Options
|
||||||
|
{ optIn :: String
|
||||||
|
, optOut :: String
|
||||||
|
, optTmp :: String
|
||||||
|
, optStore :: String
|
||||||
|
}
|
||||||
|
|
||||||
|
validateOptions :: Options -> Maybe [String]
|
||||||
|
validateOptions opts =
|
||||||
|
case res of
|
||||||
|
[] -> Nothing
|
||||||
|
es -> Just es
|
||||||
|
where
|
||||||
|
res = catMaybes $ map (\f -> f opts) optionTests
|
||||||
|
|
||||||
|
optionTests :: [(Options -> Maybe String)]
|
||||||
|
optionTests = reverse
|
||||||
|
[ (validPath optIn "input")
|
||||||
|
, (validPath optOut "output")
|
||||||
|
, (validPath optTmp "temp")
|
||||||
|
, (validPath optStore "store")
|
||||||
|
, uniqPaths
|
||||||
|
]
|
||||||
|
|
||||||
|
validPath :: (Options -> String) -> String -> Options -> Maybe String
|
||||||
|
validPath f n o =
|
||||||
|
if isValid (f o)
|
||||||
|
then Nothing
|
||||||
|
else Just $ "invalid " ++ n ++ " path: " ++ (f o)
|
||||||
|
|
||||||
|
uniqPaths :: Options -> Maybe String
|
||||||
|
uniqPaths o =
|
||||||
|
if allUnique $ pathList o
|
||||||
|
then Nothing
|
||||||
|
else Just $ "directories must be unique (i think)"
|
||||||
|
where
|
||||||
|
pathList o=
|
||||||
|
[ (optIn o)
|
||||||
|
, (optOut o)
|
||||||
|
, (optTmp o)
|
||||||
|
, (optStore o)
|
||||||
|
]
|
||||||
|
|
||||||
|
options :: Parser Options
|
||||||
|
options = Options
|
||||||
|
<$> strOption
|
||||||
|
( long "input"
|
||||||
|
<> short 'i'
|
||||||
|
<> metavar "DIR"
|
||||||
|
<> help "source input directory" )
|
||||||
|
<*> strOption
|
||||||
|
( long "output"
|
||||||
|
<> short 'o'
|
||||||
|
<> metavar "DIR"
|
||||||
|
<> help "build output directory" )
|
||||||
|
<*> strOption
|
||||||
|
( long "temp"
|
||||||
|
<> short 't'
|
||||||
|
<> metavar "DIR"
|
||||||
|
<> showDefault
|
||||||
|
<> value "./tmp"
|
||||||
|
<> help "temp directory (possibly unused)" )
|
||||||
|
<*> strOption
|
||||||
|
( long "store"
|
||||||
|
<> short 's'
|
||||||
|
<> metavar "DIR"
|
||||||
|
<> showDefault
|
||||||
|
<> value "./store"
|
||||||
|
<> help "store directory (possibly unused)" )
|
||||||
|
|
||||||
|
getOptions = info (options <**> helper)
|
||||||
|
( fullDesc
|
||||||
|
<> progDesc "build a static site from a set of input files"
|
||||||
|
<> header "rf-hakyll - a static site generator" )
|
172
exe/Site.hs
Normal file
172
exe/Site.hs
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module Site where
|
||||||
|
|
||||||
|
import Config
|
||||||
|
import Options
|
||||||
|
|
||||||
|
import Data.Maybe
|
||||||
|
import Data.Monoid
|
||||||
|
import Data.List
|
||||||
|
import Data.Typeable
|
||||||
|
import GHC.IO.Encoding
|
||||||
|
import Hakyll
|
||||||
|
import Hakyll.Favicon
|
||||||
|
import System.FilePath.Posix
|
||||||
|
import System.Exit
|
||||||
|
import System.IO
|
||||||
|
|
||||||
|
report :: [String] -> IO ()
|
||||||
|
report es = do
|
||||||
|
hPutStrLn stderr "@ ERRORs @"
|
||||||
|
hPutStrLn stderr "----------"
|
||||||
|
mapM_ (hPutStrLn stderr) es
|
||||||
|
exitWith $ ExitFailure 1
|
||||||
|
|
||||||
|
optionsToConfig :: Options.Options -> Configuration
|
||||||
|
optionsToConfig c = defaultConfiguration
|
||||||
|
{ destinationDirectory = (optOut c)
|
||||||
|
, storeDirectory = (optStore c)
|
||||||
|
, tmpDirectory = (optTmp c)
|
||||||
|
, providerDirectory = (optIn c)
|
||||||
|
}
|
||||||
|
|
||||||
|
generateSite :: Options.Options -> IO ()
|
||||||
|
generateSite opts = do
|
||||||
|
case validateOptions opts of
|
||||||
|
Just errs -> report errs
|
||||||
|
Nothing -> do
|
||||||
|
setLocaleEncoding utf8
|
||||||
|
feedConf <- getConfig (optIn opts)
|
||||||
|
let rfConf = optionsToConfig opts
|
||||||
|
hakyllWithArgs rfConf (Hakyll.Options True Rebuild) $ do
|
||||||
|
|
||||||
|
-- favicon generation
|
||||||
|
faviconsRules "icons/favicon.svg"
|
||||||
|
|
||||||
|
-- straight copy
|
||||||
|
match (fromList ["humans.txt", "robots.txt", "fonts/*"]) $ do
|
||||||
|
route idRoute
|
||||||
|
compile copyFileCompiler
|
||||||
|
|
||||||
|
-- css compilation + minification
|
||||||
|
match "css/*" $ do
|
||||||
|
route idRoute
|
||||||
|
compile compressCssCompiler
|
||||||
|
|
||||||
|
-- formatted pages
|
||||||
|
match (fromList ["about.md", "contact.md"]) $ do
|
||||||
|
route $ cleanRoute
|
||||||
|
compile $ pandocCompiler
|
||||||
|
>>= loadAndApplyTemplate "templates/default.html" ctx
|
||||||
|
>>= relativizeUrls
|
||||||
|
>>= cleanIndexUrls
|
||||||
|
|
||||||
|
-- render atom + rss feeds
|
||||||
|
create ["atom.xml"] $ do
|
||||||
|
route idRoute
|
||||||
|
compileFeed feedConf renderAtom
|
||||||
|
|
||||||
|
create ["rss.xml"] $ do
|
||||||
|
route idRoute
|
||||||
|
compileFeed feedConf renderRss
|
||||||
|
|
||||||
|
-- template compilation
|
||||||
|
match "templates/*" $ compile templateBodyCompiler
|
||||||
|
|
||||||
|
-- archive page + post list compilation
|
||||||
|
match "archive.md" $ do
|
||||||
|
route $ cleanRoute
|
||||||
|
compile $ do
|
||||||
|
posts <- recentFirst =<< loadAll "posts/*"
|
||||||
|
let archiveCtx = listField "posts" postCtx (return posts) <> ctx
|
||||||
|
pandocCompiler
|
||||||
|
>>= loadAndApplyTemplate "templates/archive.html" archiveCtx
|
||||||
|
>>= loadAndApplyTemplate "templates/default.html" archiveCtx
|
||||||
|
>>= relativizeUrls
|
||||||
|
>>= cleanIndexUrls
|
||||||
|
|
||||||
|
-- post compilation
|
||||||
|
match "index.md" $ do
|
||||||
|
route $ setExtension "html"
|
||||||
|
compile $ pandocCompiler
|
||||||
|
>>= loadAndApplyTemplate "templates/index.html" ctx
|
||||||
|
>>= relativizeUrls
|
||||||
|
>>= cleanIndexUrls
|
||||||
|
|
||||||
|
compile $ do
|
||||||
|
posts <- (return . (take (confNumFront feedConf)))
|
||||||
|
=<< recentFirst
|
||||||
|
=<< loadAll "posts/*"
|
||||||
|
let indexCtx = listField "posts" postCtx (return posts) <> ctx
|
||||||
|
pandocCompiler
|
||||||
|
>>= applyAsTemplate indexCtx
|
||||||
|
>>= loadAndApplyTemplate "templates/index.html" indexCtx
|
||||||
|
>>= loadAndApplyTemplate "templates/default.html" indexCtx
|
||||||
|
>>= relativizeUrls
|
||||||
|
>>= cleanIndexUrls
|
||||||
|
|
||||||
|
-- post compilation + snapshots for feeds
|
||||||
|
match "posts/*" $ do
|
||||||
|
route $ cleanRoute
|
||||||
|
compile $ pandocCompiler
|
||||||
|
>>= loadAndApplyTemplate "templates/post.html" postCtx
|
||||||
|
>>= saveSnapshot "content"
|
||||||
|
>>= loadAndApplyTemplate "templates/default.html" postCtx
|
||||||
|
>>= relativizeUrls
|
||||||
|
>>= cleanIndexUrls
|
||||||
|
>>= cleanIndexHtmls
|
||||||
|
|
||||||
|
-- abstract feed creation
|
||||||
|
compileFeed :: Config ->
|
||||||
|
(FeedConfiguration
|
||||||
|
-> Context String
|
||||||
|
-> [Item String]
|
||||||
|
-> Compiler (Item String))
|
||||||
|
-> Rules ()
|
||||||
|
compileFeed c f = compile $ do
|
||||||
|
let feedCtx = postCtx <> bodyField "description"
|
||||||
|
posts <- fmap (take (confNumRSS c)) . recentFirst
|
||||||
|
=<< loadAllSnapshots "posts/*" "content"
|
||||||
|
f (feedConfig c) feedCtx posts
|
||||||
|
|
||||||
|
feedConfig :: Config -> FeedConfiguration
|
||||||
|
feedConfig c = FeedConfiguration
|
||||||
|
{ feedTitle = (confTitle c)
|
||||||
|
, feedDescription = (confDesc c)
|
||||||
|
, feedAuthorName = (confAuthor c)
|
||||||
|
, feedAuthorEmail = (confEmail c)
|
||||||
|
, feedRoot = (confRoot c)
|
||||||
|
}
|
||||||
|
|
||||||
|
-- default context
|
||||||
|
ctx :: Context String
|
||||||
|
ctx = defaultContext <> faviconsField
|
||||||
|
|
||||||
|
-- default post context
|
||||||
|
postCtx :: Context String
|
||||||
|
postCtx = (dateField "date" "%B %e, %Y") <> ctx
|
||||||
|
|
||||||
|
-- cleaner urls
|
||||||
|
cleanRoute :: Routes
|
||||||
|
cleanRoute = customRoute createIndexRoute
|
||||||
|
where
|
||||||
|
createIndexRoute ident =
|
||||||
|
takeDirectory p </> takeBaseName p </> "index.html"
|
||||||
|
where p = toFilePath ident
|
||||||
|
|
||||||
|
cleanIndexUrls :: Item String -> Compiler (Item String)
|
||||||
|
cleanIndexUrls = return . fmap (withUrls cleanIndex)
|
||||||
|
|
||||||
|
cleanIndexHtmls :: Item String -> Compiler (Item String)
|
||||||
|
cleanIndexHtmls = return . fmap (replaceAll pattern replacement)
|
||||||
|
where
|
||||||
|
pattern = "/index.html"
|
||||||
|
replacement = const "/"
|
||||||
|
|
||||||
|
cleanIndex :: String -> String
|
||||||
|
cleanIndex url
|
||||||
|
| idx `isSuffixOf` url = take (length url - length idx) url
|
||||||
|
| otherwise = url
|
||||||
|
where
|
||||||
|
idx = "index.html"
|
35
rf-hakyll.cabal
Normal file
35
rf-hakyll.cabal
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
cabal-version: 1.12
|
||||||
|
|
||||||
|
name: rf-hakyll
|
||||||
|
version: 0.0.0.1
|
||||||
|
license: MIT
|
||||||
|
build-type: Simple
|
||||||
|
|
||||||
|
synopsis: a static site generator
|
||||||
|
description: the static site generator used to build "regular flolloping"
|
||||||
|
homepage: <NA>
|
||||||
|
|
||||||
|
author: Thorn Avery
|
||||||
|
maintainer: ta@p7.co.nz
|
||||||
|
|
||||||
|
executable rf-hakyll
|
||||||
|
main-is: Main.hs
|
||||||
|
ghc-options: -O2
|
||||||
|
hs-source-dirs:
|
||||||
|
exe
|
||||||
|
build-depends:
|
||||||
|
aeson
|
||||||
|
, base
|
||||||
|
, bytestring
|
||||||
|
, filepath
|
||||||
|
, hakyll
|
||||||
|
, hakyll-favicon
|
||||||
|
, list-predicate
|
||||||
|
, optparse-applicative
|
||||||
|
, process
|
||||||
|
, text
|
||||||
|
other-modules:
|
||||||
|
Options
|
||||||
|
Site
|
||||||
|
Config
|
||||||
|
default-language: Haskell2010
|
Loading…
Reference in a new issue