
38 changed files with 681 additions and 511 deletions
@ -1,11 +0,0 @@ |
|||
kind: pipeline |
|||
type: docker |
|||
name: build-test-deploy |
|||
|
|||
steps: |
|||
- name: build and test |
|||
image: haskell:8.10.7 |
|||
commands: |
|||
- stack build |
|||
- stack exec test |
|||
- stack exec site build |
@ -1,55 +0,0 @@ |
|||
build: |
|||
set -e; source ./commands.sh; build |
|||
.PHONY: build |
|||
|
|||
clean: |
|||
set -e; source ./commands.sh; clean |
|||
.PHONY: clean |
|||
|
|||
clean-all: |
|||
set -e; source ./commands.sh; clean_all |
|||
.PHONY: clean-all |
|||
|
|||
rebuild: |
|||
set -e; source ./commands.sh; rebuild |
|||
.PHONY: rebuild |
|||
|
|||
rebuild-all: |
|||
set -e; source ./commands.sh; rebuild_all |
|||
.PHONY: rebuild-all |
|||
|
|||
watch: |
|||
set -e; source ./commands.sh; watch |
|||
.PHONY: watch |
|||
|
|||
publish: |
|||
set -e; source ./commands.sh; publish |
|||
.PHONY: publish |
|||
|
|||
init: |
|||
set -e; source ./commands.sh; init |
|||
.PHONY: init |
|||
|
|||
test: |
|||
set -e; source ./commands.sh; test |
|||
.PHONY: test |
|||
|
|||
hpack: |
|||
stack exec hpack |
|||
.PHONY: hpack |
|||
|
|||
upload: |
|||
set -e; source ./commands.sh; upload |
|||
.PHONY: upload |
|||
|
|||
datestamp: |
|||
set -e; source ./commands.sh; datestamp |
|||
.PHONY: datestamp |
|||
|
|||
favicon: |
|||
set -e; source ./commands.sh; favicon |
|||
.PHONY: favicon |
|||
|
|||
default_og_image: |
|||
set -e; source ./commands.sh; default_og_image |
|||
.PHONY: default_og_image |
@ -1,146 +0,0 @@ |
|||
#!/usr/bin/env bash |
|||
set -e |
|||
|
|||
ARGS=() |
|||
|
|||
if [ -n "$VERBOSE" ]; then |
|||
ARGS+=("--verbose") |
|||
fi |
|||
|
|||
init () { |
|||
git config core.hooksPath .githooks |
|||
brew bundle |
|||
|
|||
stack install hakyll |
|||
if [ $? -ne 0 ]; then |
|||
echo "Failed to install Hakyll, check README.md for troubleshooting" |
|||
exit 1 |
|||
fi |
|||
|
|||
echo |
|||
echo "Setup completed successfully" |
|||
echo |
|||
} |
|||
|
|||
build () { |
|||
if ! command -v stack &> /dev/null; then |
|||
init |
|||
fi |
|||
|
|||
stack build |
|||
stack exec site build -- ${ARGS[@]} |
|||
favicon |
|||
default_og_image |
|||
} |
|||
|
|||
clean () { |
|||
rm -rf _cache/* |
|||
} |
|||
|
|||
clean_all () { |
|||
clean |
|||
stack clean |
|||
rm -rf _cache/* _site/* |
|||
} |
|||
|
|||
rebuild () { |
|||
clean |
|||
build |
|||
} |
|||
|
|||
touch_all () { |
|||
# touch all files so they're built again |
|||
touch site |
|||
} |
|||
|
|||
rebuild_all () { |
|||
clean_all |
|||
touch_all |
|||
build |
|||
} |
|||
|
|||
watch () { |
|||
build |
|||
stack exec site watch -- ${ARGS[@]} |
|||
} |
|||
|
|||
publish () { |
|||
init |
|||
|
|||
current_branch="$(git branch --show-current)" |
|||
if [[ "$current_branch" -ne "main" ]]; then |
|||
echo "Can't publish from current branch: $current_branch" |
|||
exit 1 |
|||
fi |
|||
test_sync "main" |
|||
build |
|||
|
|||
if [ ! -d _site ] || [ -z "$(ls -A _site)" ]; then |
|||
git clone --branch _site "$(git config --get remote.origin.url)" _site |
|||
fi |
|||
|
|||
sha="$(git log -1 HEAD --pretty=format:%h)" |
|||
pushd ./_site |
|||
test_sync "_site" |
|||
git add . |
|||
git commit -m "Build on $(date) generated from $sha" |
|||
git push origin "_site" |
|||
scp -r * thisfieldwas.green:/var/www/thisfieldwas.green/ |
|||
popd |
|||
} |
|||
|
|||
test_sync () { |
|||
branch=$1 |
|||
git switch $branch |
|||
git fetch origin $branch |
|||
|
|||
rev_parse_remote="$(git rev-parse origin/$branch)" |
|||
rev_parse_local="$(git rev-parse $branch)" |
|||
|
|||
if [ "$rev_parse_local" != "$rev_parse_remote" ]; then |
|||
echo "ERROR: Branch $branch not in sync with remote!" |
|||
exit 1 |
|||
fi |
|||
|
|||
echo "INFO: Local branch $branch is up to date with remote" |
|||
} |
|||
|
|||
test () { |
|||
stack test |
|||
} |
|||
|
|||
upload () { |
|||
rsync -ahp _site/* closet.oflogan.xyz:/usr/share/nginx/thisfieldwas.green/ |
|||
} |
|||
|
|||
datestamp () { |
|||
DATE=$(date +"%Y-%m-%dT%H:%M:%S%z") |
|||
echo "$DATE" | pbcopy |
|||
echo "Copied to clipboard: $DATE" |
|||
} |
|||
|
|||
favicon () { |
|||
src_file="$(pwd)/site/images/grass.svg" |
|||
out_file="$(pwd)/_site/favicon.ico" |
|||
|
|||
mkdir -p _cache/favicon_tmp |
|||
pushd _cache/favicon_tmp |
|||
|
|||
sizes=(16 32 48 64 96 128 256) |
|||
for x in ${sizes[@]}; do |
|||
inkscape -w $x -h $x -y 0 -o $x.png "$src_file" |
|||
done |
|||
|
|||
files=("${sizes[@]/%/.png}") |
|||
convert "${files[@]}" "$out_file" |
|||
identify "$out_file" |
|||
popd |
|||
} |
|||
|
|||
default_og_image () { |
|||
src_file="$(pwd)/site/images/grass.svg" |
|||
out_file="$(pwd)/_site/grass_og-image.png" |
|||
inkscape -w 1024 -h 1024 -b "aliceblue" -o "$out_file" "$src_file" |
|||
identify "$out_file" |
|||
} |
|||
|
@ -1,22 +0,0 @@ |
|||
[Site] |
|||
title = This Field Was Green |
|||
description = "" |
|||
root = https://thisfieldwas.green |
|||
authorName = Logan McGrath |
|||
authorEmail = logan.mcgrath@thisfieldwas.green |
|||
linkedInProfile = https://www.linkedin.com/in/loganmcgrath |
|||
gitWebUrl = https://bitsof.thisfieldwas.green/keywordsalad/thisfieldwas.green/src/commit |
|||
|
|||
[DisplayFormats] |
|||
dateShortFormat = %B %e, %Y |
|||
dateLongFormat = %B %e, %Y %l:%M %P %EZ |
|||
timeFormat = %l:%M %p %EZ |
|||
imageWidths = 320, 768, 1024, 1920, 3840 |
|||
|
|||
[Hakyll] |
|||
providerDirectory = site |
|||
destinationDirectory = _site |
|||
|
|||
[Debug] |
|||
preview = false |
|||
rawCss = false |
@ -0,0 +1,35 @@ |
|||
default: &default |
|||
site-info: |
|||
title: This Field Was Green |
|||
root: http://localhost:8000 |
|||
author-name: Logan McGrath |
|||
author-email: logan.mcgrath@thisfieldwas.green |
|||
linkedin-profile: https://www.linkedin.com/in/loganmcgrath |
|||
twitter-profile: https://twitter.com/thisgreenfield |
|||
git-web-url: https://bitsof.thisfieldwas.green/keywordsalad/thisfieldwas.green/src/commit |
|||
|
|||
display-formats: |
|||
date-short-format: '%B %e, %Y' |
|||
date-long-format: '%B %e, %Y %l:%M %P %EZ' |
|||
time-format: '%l:%M %p %EZ' |
|||
robot-date: '%Y-%m-%d' |
|||
robot-time: '%Y-%m-%dT%H:%M:%S%Ez' |
|||
image-widths: [320, 768, 1024, 1920, 3840] |
|||
|
|||
hakyll-config: |
|||
provider-directory: site |
|||
destination-directory: _site |
|||
|
|||
debug-settings: |
|||
preview: true |
|||
inflate-css: true |
|||
|
|||
prod: |
|||
<<: *default |
|||
|
|||
site-info: |
|||
root: https://thisfieldwas.green |
|||
|
|||
debug-settings: |
|||
preview: false |
|||
inflate-css: false |
@ -0,0 +1,150 @@ |
|||
#!/usr/bin/env bash |
|||
# This "./go" script is the build script. |
|||
# For context behind the "./go" script, please read these: |
|||
# https://blog.thepete.net/blog/2014/03/28/_-attributes-of-an-amazing-dev-toolchain/ |
|||
# https://code.ofvlad.xyz/vlad/lightning-runner |
|||
set -e |
|||
|
|||
_verify-prerequisites () { |
|||
git config core.hooksPath .githooks |
|||
|
|||
if ! command -v stack &> /dev/null |
|||
then |
|||
_bad-message "Install haskell-stack to continue" |
|||
exit 1 |
|||
fi |
|||
|
|||
if ! command -v hakyll-init &> /dev/null |
|||
then |
|||
stack install hakyll |
|||
if [ $? -ne 0 ]; then |
|||
_bad-message "Failed to install Hakyll, check README.md for troubleshooting" |
|||
exit 1 |
|||
fi |
|||
fi |
|||
} |
|||
|
|||
⚡build () { |
|||
_help-line "Compile the site generator and generate the site" |
|||
stack build |
|||
stack exec site build |
|||
⚡favicon |
|||
⚡default_og_image |
|||
} |
|||
|
|||
⚡clean () { |
|||
_help-line "Clean generated site files" |
|||
rm -rf _cache/* _site/* |
|||
} |
|||
|
|||
⚡clean_all () { |
|||
_help-line "Clean generated site files and site generator binaries" |
|||
⚡clean |
|||
stack clean |
|||
} |
|||
|
|||
⚡rebuild () { |
|||
_help-line "Clean and then rebuild the generated site" |
|||
⚡clean |
|||
⚡build |
|||
} |
|||
|
|||
⚡rebuild_all () { |
|||
_help-line "Clean and then rebuild both the generated site and the site generator binary" |
|||
⚡clean_all |
|||
⚡build |
|||
} |
|||
|
|||
⚡watch () { |
|||
_help-line "Build the site generator, generate the site, and then serve it so that it may be viewed in a browser" |
|||
⚡build |
|||
stack exec site watch |
|||
} |
|||
|
|||
⚡publish () { |
|||
_help-line "Build the site and then publish it live" |
|||
current_branch="$(git branch --show-current)" |
|||
if [[ "$current_branch" -ne "main" ]]; then |
|||
_bad-message "Can only publish from main branch; tried to publish from $current_branch" |
|||
exit 1 |
|||
fi |
|||
⚡test_sync "main" |
|||
⚡build |
|||
|
|||
if [ ! -d _site ] || [ -z "$(ls -A _site)" ]; then |
|||
git clone --branch _site "$(git config --get remote.origin.url)" _site |
|||
fi |
|||
|
|||
sha="$(git log -1 HEAD --pretty=format:%h)" |
|||
pushd ./_site |
|||
test_sync "_site" |
|||
git add . |
|||
git commit -m "Build on $(date) generated from $sha" |
|||
git push origin "_site" |
|||
rsync -ahp * closet.oflogan.xyz:/usr/share/nginx/thisfieldwas.green/ |
|||
popd |
|||
} |
|||
|
|||
⚡test_sync () { |
|||
_help-line "Verify that the current or specified local branch is up to date with the remote branch" |
|||
|
|||
branch=${1:-$(git branch --show-current)} |
|||
git switch $branch |
|||
git fetch origin $branch |
|||
|
|||
rev_parse_remote="$(git rev-parse origin/$branch)" |
|||
rev_parse_local="$(git rev-parse $branch)" |
|||
|
|||
if [ "$rev_parse_local" != "$rev_parse_remote" ]; then |
|||
_bad-message "Branch $branch not in sync with remote!" |
|||
exit 1 |
|||
fi |
|||
|
|||
_good-message "Local branch $branch is up to date with remote" |
|||
} |
|||
|
|||
⚡test () { |
|||
_help-line "Run hspec tests" |
|||
stack test |
|||
} |
|||
|
|||
⚡force-publish () { |
|||
_help-line "Publish generated site as-is. Only use this for emergencies!" |
|||
rsync -ahp _site/* closet.oflogan.xyz:/usr/share/nginx/thisfieldwas.green/ |
|||
} |
|||
|
|||
⚡datestamp () { |
|||
_help-line "Generate ISO-8601 datestamp with time and offset" |
|||
DATE=$(date +"%Y-%m-%dT%H:%M:%S%z") |
|||
echo "$DATE" | pbcopy |
|||
echo "Copied to clipboard: $DATE" |
|||
} |
|||
|
|||
⚡favicon () { |
|||
_help-line "Generate favicon from grass.svg" |
|||
src_file="$(pwd)/site/images/grass.svg" |
|||
out_file="$(pwd)/_site/favicon.ico" |
|||
|
|||
mkdir -p _cache/favicon_tmp |
|||
pushd _cache/favicon_tmp |
|||
|
|||
sizes=(16 32 48 64 96 128 256) |
|||
for x in ${sizes[@]}; do |
|||
inkscape -w $x -h $x -b "aliceblue" -o $x.png "$src_file" |
|||
done |
|||
|
|||
files=("${sizes[@]/%/.png}") |
|||
convert "${files[@]}" "$out_file" |
|||
identify "$out_file" |
|||
popd |
|||
} |
|||
|
|||
⚡default_og_image () { |
|||
_help-line "Generate og:image for open graph" |
|||
src_file="$(pwd)/site/images/grass.svg" |
|||
out_file="$(pwd)/_site/grass_og-image.png" |
|||
inkscape -w 1024 -h 1024 -b "aliceblue" -o "$out_file" "$src_file" |
|||
identify "$out_file" |
|||
} |
|||
|
|||
source ⚡ |
@ -0,0 +1,5 @@ |
|||
--- |
|||
title: 500 Server Error |
|||
layout: page |
|||
--- |
|||
The field has been caught with its pants brown. It will be green again soon. |
After Width: | Height: | Size: 5.1 MiB |
@ -1,9 +0,0 @@ |
|||
module Green.Site.Download where |
|||
|
|||
import Green.Common |
|||
|
|||
downloads :: Rules () |
|||
downloads = do |
|||
match "downloads/**" do |
|||
route $ setExtension ".txt" |
|||
compile copyFileCompiler |
@ -1,18 +1,19 @@ |
|||
module Green.Site.Pages where |
|||
|
|||
import Control.Monad (forM_) |
|||
import Green.Common |
|||
import Green.Route |
|||
import Green.Template.Custom |
|||
import Hakyll (fromGlob) |
|||
|
|||
pages :: Context String -> Rules () |
|||
pages context = |
|||
match "_pages/**" do |
|||
pages context = forM_ ["_pages/", "_errors/"] \dir -> do |
|||
match (fromGlob $ dir ++ "**") do |
|||
route $ |
|||
gsubRoute "_pages/" (const "") |
|||
gsubRoute dir (const "") |
|||
`composeRoutes` setExtension "html" |
|||
`composeRoutes` indexRoute |
|||
compile $ |
|||
getResourceBody |
|||
>>= contentCompiler context |
|||
>>= layoutCompiler context |
|||
>>= relativizeUrls |
|||
(getResourceBody, context) `applyTemplates` do |
|||
contentTemplate |
|||
layoutTemplate |
|||
|
@ -1,14 +1,47 @@ |
|||
module Green.Template.Custom |
|||
( module Green.Template, |
|||
module Green.Template.Custom.Compiler, |
|||
module Green.Template.Custom, |
|||
module Green.Template.Custom.DateField, |
|||
module Green.Template.Custom.GitField, |
|||
module Green.Template.Custom.HtmlField, |
|||
) |
|||
where |
|||
|
|||
import Control.Monad.State.Strict (evalStateT, forM_) |
|||
import Data.List (nub) |
|||
import Green.Common |
|||
import Green.Template |
|||
import Green.Template.Custom.Compiler |
|||
import Green.Template.Custom.DateField |
|||
import Green.Template.Custom.GitField |
|||
import Green.Template.Custom.HtmlField |
|||
|
|||
applyTemplates :: (Compiler (Item a), Context a) -> TemplateRunner a () -> Compiler (Item a) |
|||
applyTemplates (itemM, context) = |
|||
applyTemplatesWith itemM context |
|||
|
|||
applyTemplatesWith :: Compiler (Item a) -> Context a -> TemplateRunner a () -> Compiler (Item a) |
|||
applyTemplatesWith itemM context templates = |
|||
itemM >>= evalStateT (templates >> tplItem) . templateRunner context |
|||
|
|||
contentTemplate :: TemplateRunner String () |
|||
contentTemplate = do |
|||
applyAsTemplate |
|||
tplModifyItem $ lift . pandocCompiler |
|||
|
|||
layoutTemplate :: TemplateRunner String () |
|||
layoutTemplate = loadAndApplyTemplate "_layouts/from-context.html" |
|||
|
|||
fileTemplate :: FilePath -> TemplateRunner String () |
|||
fileTemplate filePath = |
|||
loadAndApplyTemplate (fromFilePath filePath) |
|||
|
|||
withCompiler :: (Item a -> Compiler (Item a)) -> TemplateRunner a () |
|||
withCompiler compiler = |
|||
tplModifyItem $ lift . compiler |
|||
|
|||
saveSnapshots :: [String] -> TemplateRunner String () |
|||
saveSnapshots snapshots = do |
|||
item <- tplItem |
|||
lift $ forM_ snapshots' (`saveSnapshot` item) |
|||
where |
|||
snapshots' = nub ("_content" : snapshots) |
|||
|
@ -1,17 +0,0 @@ |
|||
module Green.Template.Custom.Compiler where |
|||
|
|||
import Control.Monad.State.Strict |
|||
import Data.List (nub) |
|||
import Green.Common |
|||
import Green.Template |
|||
|
|||
contentCompiler :: Context String -> Item String -> Compiler (Item String) |
|||
contentCompiler context = pandocCompiler <=< applyAsTemplate' context |
|||
|
|||
layoutCompiler :: Context String -> Item String -> Compiler (Item String) |
|||
layoutCompiler = loadAndApplyTemplate' $ fromFilePath "_layouts/from-context.html" |
|||
|
|||
snapshotCompiler :: [String] -> Item String -> Compiler (Item String) |
|||
snapshotCompiler snapshots item = foldM (flip saveSnapshot) item snapshots' |
|||
where |
|||
snapshots' = nub ("_content" : snapshots) |