
65 changed files with 1075 additions and 680 deletions
@ -1,3 +0,0 @@ |
|||
[submodule "gh-pages"] |
|||
path = gh-pages |
|||
url = ssh://git@bitsof.thisfieldwas.green:2222/ThisFieldWasGreen/thisfieldwas.green.git |
@ -0,0 +1,13 @@ |
|||
[site] |
|||
title = This Field Was Green |
|||
root = https://thisfieldwas.green |
|||
authorName = Logan McGrath |
|||
authorEmail = blog@thisfieldwas.green |
|||
gitWebUrl = https://bitsof.thisfieldwas.green/ThisFieldWasGreen/thisfieldwas.green |
|||
|
|||
[hakyll] |
|||
destinationDirectory = _site |
|||
allowedFiles = .nojekyll |
|||
|
|||
[feed] |
|||
description = "" |
@ -1,15 +0,0 @@ |
|||
--- |
|||
templates: default, skeleton |
|||
--- |
|||
<header class="banner"> |
|||
<h1>This Field Was Green</h1> |
|||
<div class="logo"></div> |
|||
<h2>Logan McGrath's Online CV</h2> |
|||
</header> |
|||
<nav> |
|||
<a href="$route-to("index.html")$">Home</a> |
|||
<a href="$route-to("archives.html")$">Blog</a> |
|||
<a href="$route-to("about-me.md")$">About</a> |
|||
<a href="$route-to("resume.md")$">Resume</a> |
|||
<a href="$route-to("contact.md")$">Contact</a> |
|||
</nav> |
@ -0,0 +1,25 @@ |
|||
--- |
|||
layout: skeleton |
|||
--- |
|||
<header> |
|||
<div class="logo"> |
|||
<a href='$route-to("pages/index.md")$'><span class="logo-icon"></span></a> |
|||
<a href='$route-to("pages/index.md")$'>This Field Was Green</a> |
|||
</div> |
|||
<nav> |
|||
<a href='$route-to("pages/index.md")$'>Home</a> |
|||
<a href='$route-to("pages/blog.md")$'>Posts</a> |
|||
<a href='$route-to("pages/about-me.md")$'>About</a> |
|||
<a href='$route-to("pages/resume.md")$'>Resume</a> |
|||
<a href='$route-to("pages/contact.md")$'>Contact</a> |
|||
</nav> |
|||
</header> |
|||
|
|||
<main> |
|||
<article> |
|||
<h1>$title$</h1> |
|||
$body$ |
|||
</article> |
|||
</main> |
|||
|
|||
$partial("partials/footer.html")$ |
@ -0,0 +1,5 @@ |
|||
--- |
|||
layout: default |
|||
--- |
|||
|
|||
$partial("partials/post.html")$ |
@ -1,4 +1,4 @@ |
|||
# Generated from commit $gitSha1$ |
|||
# Generated from commit $git-sha1$ |
|||
User-agent: * |
|||
Disallow: $site-root$/drafts/* |
|||
Sitemap: $site-root$/sitemap.xml |
@ -1,5 +1,7 @@ |
|||
--- |
|||
layout: default |
|||
title: "404 Not Found" |
|||
page-type: static |
|||
--- |
|||
|
|||
The resource you were looking for does not exist. |
@ -1,10 +1,10 @@ |
|||
--- |
|||
layout: default |
|||
title: "About" |
|||
date: 2015-01-23T11:42:00 PST |
|||
comments: false |
|||
sharing: true |
|||
footer: true |
|||
page-type: static |
|||
--- |
|||
|
|||
## About |
@ -1,2 +1,8 @@ |
|||
--- |
|||
layout: default |
|||
title: "These Posts Were Green" |
|||
--- |
|||
|
|||
Here you can find all my previous posts: |
|||
|
|||
$partial("partials/post-list.html")$ |
@ -0,0 +1,7 @@ |
|||
--- |
|||
layout: default |
|||
title: "This Blog Was Green" |
|||
--- |
|||
|
|||
$latest-post$ |
|||
$partial("partials/previous-posts.html")$ |
@ -1,5 +1,7 @@ |
|||
--- |
|||
title: Contact |
|||
layout: default |
|||
title: "Contact" |
|||
page-type: static |
|||
--- |
|||
|
|||
I live in a small hut in the mountains of Kumano Kodล on Kii Hantล and would not |
@ -0,0 +1,7 @@ |
|||
--- |
|||
layout: default |
|||
title: "These Drafts Are Still Green" |
|||
--- |
|||
|
|||
Here you can find all my drafts: |
|||
$partial("partials/post-list.html")$ |
@ -0,0 +1,7 @@ |
|||
--- |
|||
layout: default |
|||
title: "Logan McGrath's Online CV" |
|||
page-type: static |
|||
--- |
|||
|
|||
TODO finish this |
@ -0,0 +1,7 @@ |
|||
--- |
|||
layout: default |
|||
title: "Logan McGrath's Online CV" |
|||
page-type: static |
|||
--- |
|||
|
|||
TODO finish this |
@ -1,12 +1,12 @@ |
|||
<article class="post"> |
|||
<header> |
|||
<h1>$title$</h1> |
|||
<em class="published"> |
|||
<p class="published"> |
|||
Posted on $date$ |
|||
$if(author)$ |
|||
by $author$ |
|||
$endif$ |
|||
</em> |
|||
</p> |
|||
</header> |
|||
$body$ |
|||
</article> |
@ -0,0 +1,19 @@ |
|||
<article class="previous-posts"> |
|||
<header> |
|||
<h1>Previous Posts</h1> |
|||
</header> |
|||
$for(previous-posts)$ |
|||
<section> |
|||
<header> |
|||
<h1>$title$</h1> |
|||
<p class="published"> |
|||
Posted on $date$ |
|||
$if(author)$ |
|||
by $author$ |
|||
$endif$ |
|||
</p> |
|||
</header> |
|||
$demote-headers-by("1", teaser)$ |
|||
</section> |
|||
$endfor$ |
|||
</article> |
@ -1,13 +1,13 @@ |
|||
<p class="generated-from"> |
|||
This page was generated |
|||
$if(isGenerated)$ |
|||
$if(is-generated)$ |
|||
from commit |
|||
$else$ |
|||
from file <code><a href="$githubUrl$/blob/$gitSha1$/$path$">$path$</a></code> |
|||
at commit |
|||
from file <code><a href="$git-web-url$/blob/$git-sha1$/$path$">$path$</a></code> |
|||
and commit |
|||
$endif$ |
|||
<a class="commit-link" href="$githubUrl$/commit/$gitSha1$">[$gitSha1$] $gitMessage$</a> |
|||
$if(isChanged)$ |
|||
with local modifications |
|||
<a class="commit-link" href="$git-web-url$/commit/$git-sha1$">[$git-sha1$] $git-message$</a> |
|||
$if(is-changed)$ |
|||
including local modifications |
|||
$endif$ |
|||
</p> |
|||
|
@ -1,29 +1,30 @@ |
|||
module Site where |
|||
module Site (site) where |
|||
|
|||
import Hakyll |
|||
import Site.Configuration |
|||
import Site.Context.Field |
|||
import Site.Context.Git |
|||
import qualified Data.Text as T |
|||
import Site.Common |
|||
import Site.Rule |
|||
import Site.Util |
|||
import System.Environment (getEnvironment) |
|||
|
|||
site :: IO () |
|||
site = do |
|||
env <- getEnvironment |
|||
hakyllWith hakyllConfiguration do |
|||
tags <- buildTags "blog/*" $ fromCapture "tags/*.html" |
|||
let baseCtx = |
|||
constField "bodyClass" "default" |
|||
<> tagsField "tags" tags |
|||
<> cleanIndexPaths "url" |
|||
<> mconcat gitCommitFields |
|||
<> imgField |
|||
<> includeCodeField |
|||
<> youtubeField |
|||
<> routeToField |
|||
<> commentField |
|||
<> siteRootField |
|||
<> defaultContext |
|||
configIniText <- T.pack <$> readFile "config.ini" |
|||
siteConfig <- case parseConfigIni env configIniText of |
|||
Left e -> fail e |
|||
Right config -> return $ config & siteContext %~ initContext config |
|||
hakyllWith (siteConfig ^. siteHakyllConfiguration) (rules siteConfig) |
|||
|
|||
rules env baseCtx |
|||
initContext :: SiteConfig -> Context String -> Context String |
|||
initContext config context = |
|||
constField "body-class" "default" |
|||
<> constField "site-root" (config ^. siteRoot) |
|||
<> cleanIndexPaths "url" |
|||
<> gitCommits (config ^. siteGitWebUrl) |
|||
<> imgField |
|||
<> includeCodeField |
|||
<> youtubeField |
|||
<> routeToField |
|||
<> commentField |
|||
<> siteRootField config |
|||
<> demoteHeadersByField |
|||
<> context |
|||
|
@ -1,24 +1,44 @@ |
|||
module Site.Common |
|||
( module Hakyll, |
|||
( module Control.Applicative, |
|||
module Control.Monad, |
|||
module Data.Bool, |
|||
module Data.Foldable, |
|||
module Data.Functor, |
|||
module Data.Maybe, |
|||
module Hakyll, |
|||
module Lens.Micro, |
|||
module Lens.Micro.TH, |
|||
module Site.Config, |
|||
module Site.Compiler, |
|||
-- Control.Monad |
|||
join, |
|||
(>=>), |
|||
(<=<), |
|||
-- Data.Bool |
|||
bool, |
|||
-- Data.Functor |
|||
(<&>), |
|||
-- Data.Maybe |
|||
fromJust, |
|||
fromMaybe, |
|||
isJust, |
|||
module Site.Compiler.Layout, |
|||
module Site.Compiler.Pandoc, |
|||
module Site.Context.Field, |
|||
module Site.Context.GitCommits, |
|||
module Site.Context.Post, |
|||
module Site.Context.Tag, |
|||
module Site.Metadata, |
|||
module Site.Route, |
|||
module Site.Util, |
|||
) |
|||
where |
|||
|
|||
import Control.Applicative ((<|>)) |
|||
import Control.Monad (join, (<=<), (>=>)) |
|||
import Data.Bool (bool) |
|||
import Data.Foldable (sequenceA_) |
|||
import Data.Functor ((<&>)) |
|||
import Data.Maybe (fromJust, fromMaybe, isJust) |
|||
import Hakyll |
|||
import Lens.Micro hiding ((<&>)) |
|||
import Lens.Micro.TH |
|||
import Site.Compiler |
|||
import Site.Compiler.Layout |
|||
import Site.Compiler.Pandoc |
|||
import Site.Config |
|||
import Site.Context.Field |
|||
import Site.Context.GitCommits |
|||
import Site.Context.Post |
|||
import Site.Context.Tag |
|||
import Site.Metadata |
|||
import Site.Route |
|||
import Site.Util |
|||
|
@ -0,0 +1,84 @@ |
|||
module Site.Compiler.Layout where |
|||
|
|||
import Control.Monad ((<=<)) |
|||
import Data.Binary as B |
|||
import Data.ByteString.Lazy as LBS |
|||
import GHC.Generics hiding (to) |
|||
import Hakyll |
|||
import Lens.Micro |
|||
import Lens.Micro.TH |
|||
import Site.Config |
|||
|
|||
data Layout = Layout |
|||
{ _layoutStack :: [Item Template], |
|||
_layoutScripts :: [Item String], |
|||
_layoutStylesheets :: [Item String] |
|||
} |
|||
deriving stock (Generic) |
|||
|
|||
makeLenses ''Layout |
|||
|
|||
instance Binary Layout where |
|||
get = Layout <$> get <*> get <*> get |
|||
put layout = |
|||
put (layout ^. layoutStack) |
|||
>> put (layout ^. layoutScripts) |
|||
>> put (layout ^. layoutStylesheets) |
|||
|
|||
instance Writable Layout where |
|||
write p = LBS.writeFile p . B.encode . itemBody |
|||
|
|||
layoutContext :: SimpleGetter Layout (Context String) |
|||
layoutContext = to \layout -> |
|||
listField "scripts" context (return $ layout ^. layoutScripts) |
|||
<> listField "stylesheets" context (return $ layout ^. layoutStylesheets) |
|||
where |
|||
context = bodyField "src" |
|||
|
|||
applyLayoutFromMetadata :: SiteConfig -> Item String -> Compiler (Item String) |
|||
applyLayoutFromMetadata config item = do |
|||
metadata <- getMetadata $ itemIdentifier item |
|||
maybeLayout <- sequence (loadLayout . fromLayoutName <$> lookupString "layout" metadata) |
|||
body <- getResourceBody |
|||
let f layout = applyLayout config layout body |
|||
maybe (return body) f maybeLayout |
|||
|
|||
applyLayout :: SiteConfig -> Item Layout -> Item String -> Compiler (Item String) |
|||
applyLayout config layout = go templates |
|||
where |
|||
templates = itemBody <$> layout ^. to itemBody . layoutStack |
|||
context = bodyField "body" <> config ^. siteContext |
|||
go (t : ts) = go ts <=< applyTemplate t context |
|||
go [] = return |
|||
|
|||
loadLayout :: Identifier -> Compiler (Item Layout) |
|||
loadLayout = load |
|||
|
|||
layoutCompiler :: Compiler (Item Layout) |
|||
layoutCompiler = do |
|||
metadata <- getMetadata =<< getUnderlying |
|||
template <- makeItem =<< compileTemplateItem =<< getResourceBody |
|||
parent <- sequence (loadLayout . fromLayoutName <$> lookupString "layout" metadata) |
|||
|
|||
let parentScripts = parentItems layoutScripts parent |
|||
scripts = parentScripts ++ toUrlItems (lookupStringList "scripts" metadata) |
|||
|
|||
parentStylesheets = parentItems layoutStylesheets parent |
|||
stylesheets = parentStylesheets ++ toUrlItems (lookupStringList "stylesheets" metadata) |
|||
|
|||
parentStack = parentItems layoutStack parent |
|||
stack = template : parentStack |
|||
|
|||
makeItem |
|||
Layout |
|||
{ _layoutStack = stack, |
|||
_layoutScripts = scripts, |
|||
_layoutStylesheets = stylesheets |
|||
} |
|||
where |
|||
toUrlItems = maybe [] (fmap toUrlItem) |
|||
toUrlItem filePath = Item (fromFilePath filePath) (toUrl filePath) |
|||
parentItems lens' = maybe [] (^. to itemBody . lens') |
|||
|
|||
fromLayoutName :: String -> Identifier |
|||
fromLayoutName name = fromFilePath ("layouts/" ++ name ++ ".html") |
@ -0,0 +1,59 @@ |
|||
module Site.Compiler.Pandoc |
|||
( interpolateResourceBody, |
|||
compilePandocWith, |
|||
interpolateItem, |
|||
) |
|||
where |
|||
|
|||
import Control.Monad ((>=>)) |
|||
import Debug.Trace |
|||
import Hakyll |
|||
import Lens.Micro |
|||
import Site.Config |
|||
import Text.Pandoc.Definition |
|||
import Text.Pandoc.Highlighting (pygments) |
|||
import qualified Text.Pandoc.Options as Opt |
|||
|
|||
pandocCompilerForCodeInsertion :: Item String -> Compiler (Item String) |
|||
pandocCompilerForCodeInsertion = compilePandocWith return |
|||
|
|||
compilePandocWith :: (Item Pandoc -> Compiler (Item Pandoc)) -> Item String -> Compiler (Item String) |
|||
compilePandocWith f = |
|||
readPandocWith readerOpts |
|||
>=> f |
|||
>=> traverse return |
|||
>=> return . writePandocWith writerOpts |
|||
|
|||
interpolateResourceBody :: SiteConfig -> Compiler (Item String) |
|||
interpolateResourceBody config = |
|||
interpolateItem config =<< getResourceBody |
|||
|
|||
interpolateItem :: SiteConfig -> Item String -> Compiler (Item String) |
|||
interpolateItem config = |
|||
applyAsTemplate (config ^. siteContext) . printDebug config |
|||
>=> pandocCompilerForCodeInsertion |
|||
|
|||
printDebug :: SiteConfig -> Item String -> Item String |
|||
printDebug config item = |
|||
let sep = "=================================================\n" |
|||
y = toFilePath (itemIdentifier item) ++ sep |
|||
z = itemBody item ++ sep |
|||
in if config ^. siteDebug |
|||
then trace (sep ++ y ++ z) item |
|||
else item |
|||
|
|||
readerOpts :: Opt.ReaderOptions |
|||
readerOpts = |
|||
defaultHakyllReaderOptions |
|||
{ Opt.readerExtensions = defaultExtensions <> customExtensions |
|||
} |
|||
where |
|||
defaultExtensions = Opt.readerExtensions defaultHakyllReaderOptions |
|||
customExtensions = Opt.extensionsFromList [] |
|||
|
|||
writerOpts :: Opt.WriterOptions |
|||
writerOpts = |
|||
defaultHakyllWriterOptions |
|||
{ Opt.writerHTMLMathMethod = Opt.MathJax "", |
|||
Opt.writerHighlightStyle = Just pygments |
|||
} |
@ -0,0 +1,81 @@ |
|||
module Site.Config where |
|||
|
|||
import Control.Applicative ((<|>)) |
|||
import Data.Ini.Config |
|||
import Data.Maybe (isJust) |
|||
import Data.String (IsString) |
|||
import Data.Text (Text) |
|||
import Hakyll |
|||
import Hakyll.Core.Configuration as HC |
|||
import Lens.Micro.TH |
|||
import System.FilePath |
|||
|
|||
data SiteConfig = SiteConfig |
|||
{ _siteEnv :: [(String, String)], |
|||
_siteRoot :: String, |
|||
_siteTitle :: String, |
|||
_siteAuthorName :: String, |
|||
_siteAuthorEmail :: String, |
|||
_siteGitWebUrl :: String, |
|||
_sitePreview :: Bool, |
|||
_siteDebug :: Bool, |
|||
_siteHakyllConfiguration :: Configuration, |
|||
_siteFeedConfiguration :: FeedConfiguration, |
|||
_siteContext :: Context String |
|||
} |
|||
|
|||
makeLenses ''SiteConfig |
|||
|
|||
hasEnvFlag :: String -> [(String, String)] -> Bool |
|||
hasEnvFlag f e = isJust (lookup f e) |
|||
|
|||
parseConfigIni :: [(String, String)] -> Text -> Either String SiteConfig |
|||
parseConfigIni env iniText = parseIniFile iniText do |
|||
feedDescription <- section "feed" $ fieldOf "description" string |
|||
|
|||
hakyllConfiguration <- section "hakyll" do |
|||
destinationDir <- fieldOf "destinationDirectory" string |
|||
allowedFiles <- fieldOfStrings "allowedFiles" |
|||
return |
|||
HC.defaultConfiguration |
|||
{ destinationDirectory = destinationDir, |
|||
ignoreFile = customIgnoreFile allowedFiles |
|||
} |
|||
|
|||
section "site" do |
|||
root <- fieldOf "root" string |
|||
title <- fieldOf "title" string |
|||
authorName <- fieldOf "authorName" string |
|||
authorEmail <- fieldOf "authorEmail" string |
|||
preview <- fieldDefOf "preview" flag False <|> pure (hasEnvFlag "SITE_PREVIEW" env) |
|||
debug <- fieldDefOf "debug" flag False <|> pure (hasEnvFlag "SITE_DEBUG" env) |
|||
gitWebUrl <- fieldOf "gitWebUrl" string |
|||
return |
|||
SiteConfig |
|||
{ _siteEnv = env, |
|||
_siteRoot = root, |
|||
_siteTitle = title, |
|||
_siteAuthorName = authorName, |
|||
_siteAuthorEmail = authorEmail, |
|||
_siteGitWebUrl = gitWebUrl, |
|||
_sitePreview = preview, |
|||
_siteDebug = debug, |
|||
_siteHakyllConfiguration = hakyllConfiguration, |
|||
_siteFeedConfiguration = |
|||
FeedConfiguration |
|||
{ feedTitle = title, |
|||
feedDescription = feedDescription, |
|||
feedAuthorName = authorName, |
|||
feedAuthorEmail = authorEmail, |
|||
feedRoot = root |
|||
}, |
|||
_siteContext = defaultContext |
|||
} |
|||
where |
|||
customIgnoreFile allowedFiles path = |
|||
ignoreFile defaultConfiguration path && fileName `notElem` allowedFiles |
|||
where |
|||
fileName = takeFileName path |
|||
|
|||
fieldOfStrings :: IsString a => Text -> SectionParser [a] |
|||
fieldOfStrings k = fieldDefOf k (listWithSeparator "," string) [] |
@ -1,28 +0,0 @@ |
|||
module Site.Configuration where |
|||
|
|||
import Hakyll |
|||
import System.FilePath (takeFileName) |
|||
|
|||
hakyllConfiguration :: Configuration |
|||
hakyllConfiguration = |
|||
defaultConfiguration |
|||
{ destinationDirectory = "gh-pages", |
|||
ignoreFile = customIgnoreFile |
|||
} |
|||
|
|||
customIgnoreFile :: FilePath -> Bool |
|||
customIgnoreFile path = |
|||
ignoreFile defaultConfiguration path && fileName `notElem` allowedFiles |
|||
where |
|||
fileName = takeFileName path |
|||
allowedFiles = [".nojekyll"] |
|||
|
|||
feedConfig :: String -> FeedConfiguration |
|||
feedConfig siteRoot = |
|||
FeedConfiguration |
|||
{ feedTitle = "This Field Was Green", |
|||
feedDescription = "", |
|||
feedAuthorName = "Logan McGrath", |
|||
feedAuthorEmail = "blog@thisfieldwas.green", |
|||
feedRoot = siteRoot |
|||
} |
@ -1,19 +1,20 @@ |
|||
module Site.Context.Git (gitCommitFields) where |
|||
module Site.Context.GitCommits (gitCommits) where |
|||
|
|||
import Site.Common |
|||
import Data.Bool (bool) |
|||
import Data.Maybe (fromJust, isJust) |
|||
import Hakyll |
|||
import System.Directory (doesFileExist) |
|||
import System.Exit |
|||
import System.Process |
|||
|
|||
gitCommitFields :: [Context String] |
|||
gitCommitFields = |
|||
[ constField "githubUrl" "https://github.com/ThisFieldWasGreen/thisfieldwasgreen.github.io", |
|||
field "gitSha1" gitSha1Compiler, |
|||
field "gitMessage" gitMessageCompiler, |
|||
field "isChanged" isChangedCompiler, |
|||
field "isGenerated" isGeneratedCompiler, |
|||
field "gitBranch" gitBranchCompiler |
|||
] |
|||
gitCommits :: String -> Context String |
|||
gitCommits gitWebUrl = |
|||
constField "git-web-url" gitWebUrl |
|||
<> field "git-sha1" gitSha1Compiler |
|||
<> field "git-message" gitMessageCompiler |
|||
<> field "is-changed" isChangedCompiler |
|||
<> field "is-generated" isGeneratedCompiler |
|||
<> field "git-branch" gitBranchCompiler |
|||
|
|||
itemSourcePath :: Item a -> FilePath |
|||
itemSourcePath item = toFilePath (itemIdentifier item) |
@ -1,76 +1,9 @@ |
|||
module Site.Metadata where |
|||
|
|||
import Control.Applicative ((<|>)) |
|||
import Data.Aeson |
|||
import Data.Aeson.Types |
|||
import Data.String.Utils as S |
|||
import qualified Data.Text as T |
|||
import Data.Time |
|||
( UTCTime, |
|||
defaultTimeLocale, |
|||
iso8601DateFormat, |
|||
parseTimeM, |
|||
) |
|||
import GHC.Generics |
|||
import Hakyll |
|||
|
|||
data PageMetadata = |