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