Browse Source

Templating working, most things rendering correctly

image-sizing
Logan McGrath 1 year ago
parent
commit
2dae329456
  1. 1
      .ruby-version
  2. 40
      Brewfile.lock.json
  3. 28
      green.cabal
  4. 1
      package.yaml
  5. 5
      site/404.md
  6. 10
      site/_drafts/lessons-from-sterling.md
  7. 2
      site/_drafts/morphisms-are-lossy.md
  8. 4
      site/_drafts/open-source-in-the-age-of-github.md
  9. 2
      site/_drafts/sterling-benchmarks.md
  10. 4
      site/_drafts/sterling-with-memoization.md
  11. 2
      site/_layouts/bare-content.html
  12. 20
      site/_layouts/default.html
  13. 8
      site/_layouts/page.html
  14. 25
      site/_layouts/post.html
  15. 18
      site/_layouts/skeleton.html
  16. 6
      site/_partials/about-me.html
  17. 8
      site/_partials/main-nav.html
  18. 6
      site/_partials/post-list.html
  19. 32
      site/_partials/teaser-list.html
  20. 30
      site/_posts/2012-11-07-using-perforce-chronicle-for-application-configuration.md
  21. 12
      site/_posts/2012-11-16-scm-backed-application-configuration-with-perforce.md
  22. 4
      site/_posts/2012-11-20-app-config-app-in-action.md
  23. 14
      site/_posts/2012-11-28-promoting-changes-with-app-config-app.md
  24. 2
      site/_templates/code.md
  25. 14
      site/_templates/image.html
  26. 6
      site/_templates/robots.txt
  27. 12
      site/_templates/sitemap.xml
  28. 12
      site/_templates/youtube.html
  29. 4
      site/blog/archives.html
  30. 4
      site/blog/drafts.html
  31. 30
      site/blog/index.html
  32. 18
      site/code/app-config/index.html
  33. 9
      site/contact.md
  34. 6
      site/index.html
  35. 7
      site/resume.md
  36. 8
      src/Green.hs
  37. 2
      src/Green/Common.hs
  38. 16
      src/Green/Config.hs
  39. 103
      src/Green/Context.hs
  40. 106
      src/Green/Context/DateFields.hs
  41. 6
      src/Green/Rule/Blog.hs
  42. 6
      src/Green/Rule/Index.hs
  43. 9
      src/Green/Rule/Page.hs
  44. 3
      src/Green/Rule/Robot.hs
  45. 4
      src/Green/Template.hs
  46. 280
      src/Green/Template/Ast.hs
  47. 247
      src/Green/Template/Compiler.hs
  48. 261
      src/Green/Template/Context.hs
  49. 14
      src/Green/Template/Custom.hs
  50. 26
      src/Green/Template/Custom/Compiler.hs
  51. 28
      src/Green/Template/Custom/Context.hs
  52. 114
      src/Green/Template/Custom/DateFields.hs
  53. 15
      src/Green/Template/Custom/GitFields.hs
  54. 47
      src/Green/Template/Custom/HtmlFields.hs
  55. 171
      src/Green/Template/Fields.hs
  56. 20
      src/Green/Template/Functions.hs
  57. 33
      src/Green/Template/Pandoc.hs
  58. 756
      src/Green/Template/Parser.hs
  59. 3
      src/Green/Template/Source.hs
  60. 297
      src/Green/Template/Source/Lexer.hs
  61. 279
      src/Green/Template/Source/Parser.hs
  62. 79
      src/Green/Template/Source/Util.hs
  63. 5
      src/Green/Util.hs
  64. 135
      test/Green/Template/AstStructure.hs
  65. 572
      test/Green/Template/ParserSpec.hs
  66. 129
      test/Green/Template/Source/LexerSpec.hs
  67. 27
      test/Green/Template/Source/TestSupport.hs
  68. 139
      test/Green/Template/Structural.hs
  69. 5
      test/Green/TestSupport/Config.hs
  70. 2
      test/Green/TestSupport/TestEnv.hs

1
.ruby-version

@ -0,0 +1 @@
system

40
Brewfile.lock.json

@ -2,42 +2,52 @@
"entries": {
"tap": {
"homebrew/bundle": {
"revision": "9ee0d9912401eca4f21310bd19876a8cbac63f6d"
"revision": "3b280b116f481e3d596789b7ee93cb4cf5ad23b4"
},
"homebrew/core": {
"revision": "c64afbfd40223456aedcc37e60873ac5d5f1a409"
"revision": "af049caf0ac40679b8cd25b78995f65a883f0a22"
},
"sass/sass": {
"revision": "42d9d93d3175c522388e5734b4e8d74d7220ec35"
"revision": "9aeb8c728d9966e2f2b9c6c7bc591d08d2bd610e"
}
},
"brew": {
"haskell-stack": {
"version": "2.5.1",
"version": "2.7.3",
"bottle": {
"rebuild": 0,
"root_url": "https://ghcr.io/v2/homebrew/core",
"files": {
"arm64_big_sur": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/haskell-stack/blobs/sha256:1febdf95d90161093914f0b130a2e560e3e536316b414ab4d894195f2ffbec61",
"sha256": "1febdf95d90161093914f0b130a2e560e3e536316b414ab4d894195f2ffbec61"
},
"big_sur": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/haskell-stack/blobs/sha256:2f6c0dc9279cc4dadc27305b448e1f27ac3f3f9189e667806b2f47ba323cc2e7",
"sha256": "2f6c0dc9279cc4dadc27305b448e1f27ac3f3f9189e667806b2f47ba323cc2e7"
"url": "https://ghcr.io/v2/homebrew/core/haskell-stack/blobs/sha256:5e9185c5fb43ee4aa892bd5e9460fba19874c741df8cb0791af25ec7dab40575",
"sha256": "5e9185c5fb43ee4aa892bd5e9460fba19874c741df8cb0791af25ec7dab40575"
},
"catalina": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/haskell-stack/blobs/sha256:a3e160e30048c2223853f8fd977797ed95e0fb198977c230fdc5397b610a1bb8",
"sha256": "a3e160e30048c2223853f8fd977797ed95e0fb198977c230fdc5397b610a1bb8"
"url": "https://ghcr.io/v2/homebrew/core/haskell-stack/blobs/sha256:eff4da14356490588c31bbdf4d327605c5209957956d2964eb42e65bb9f687ba",
"sha256": "eff4da14356490588c31bbdf4d327605c5209957956d2964eb42e65bb9f687ba"
},
"mojave": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/haskell-stack/blobs/sha256:1e73da7200f3de9ca57d571ae707815c94b1737840dd16e5c260c15e682f5cbe",
"sha256": "1e73da7200f3de9ca57d571ae707815c94b1737840dd16e5c260c15e682f5cbe"
"url": "https://ghcr.io/v2/homebrew/core/haskell-stack/blobs/sha256:f57fdcf4118acc46b507b6e091f8898f9f1200f5041d20460ac97cc57fe21364",
"sha256": "f57fdcf4118acc46b507b6e091f8898f9f1200f5041d20460ac97cc57fe21364"
},
"x86_64_linux": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/haskell-stack/blobs/sha256:1c0e6d39df1e8c28c0ed815df4a2f02a3e302a758fb9dade1aaf3d13212ce5ad",
"sha256": "1c0e6d39df1e8c28c0ed815df4a2f02a3e302a758fb9dade1aaf3d13212ce5ad"
}
}
}
},
"sass": {
"version": "1.32.10",
"version": "1.43.1",
"bottle": false
},
"make": {
@ -85,6 +95,14 @@
"CLT": "12.4.0.0.1.1610135815",
"Xcode": "12.4",
"macOS": "10.15.7"
},
"big_sur": {
"HOMEBREW_VERSION": "3.2.15",
"HOMEBREW_PREFIX": "/usr/local",
"Homebrew/homebrew-core": "af049caf0ac40679b8cd25b78995f65a883f0a22",
"CLT": "13.0.0.0.1.1630607135",
"Xcode": "13.0",
"macOS": "11.6"
}
}
}

28
green.cabal

@ -26,9 +26,6 @@ library
Green.Common
Green.Compiler
Green.Config
Green.Context
Green.Context.DateFields
Green.Context.GitCommits
Green.Lens
Green.Lens.Hakyll
Green.Lens.TH
@ -47,9 +44,18 @@ library
Green.Template.Ast
Green.Template.Compiler
Green.Template.Context
Green.Template.Functions
Green.Template.Custom
Green.Template.Custom.Compiler
Green.Template.Custom.Context
Green.Template.Custom.DateFields
Green.Template.Custom.GitFields
Green.Template.Custom.HtmlFields
Green.Template.Fields
Green.Template.Pandoc
Green.Template.Parser
Green.Template.Source
Green.Template.Source.Lexer
Green.Template.Source.Parser
Green.Template.Source.Util
Green.Util
other-modules:
Paths_green
@ -97,7 +103,7 @@ library
TypeSynonymInstances
UndecidableInstances
ViewPatterns
ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-patterns -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wmissing-home-modules -Wname-shadowing -Wpartial-fields -Wredundant-constraints -Wunused-packages -Wunused-type-patterns
ghc-options: -fprint-potential-instances -Wall -Werror -Wcompat -Widentities -Wincomplete-patterns -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wmissing-home-modules -Wname-shadowing -Wpartial-fields -Wredundant-constraints -Wunused-packages -Wunused-type-patterns
build-depends:
MissingH
, aeson
@ -174,7 +180,7 @@ executable author
TypeSynonymInstances
UndecidableInstances
ViewPatterns
ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-patterns -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wmissing-home-modules -Wname-shadowing -Wpartial-fields -Wredundant-constraints -Wunused-packages -Wunused-type-patterns -threaded -rtsopts -with-rtsopts=-N
ghc-options: -fprint-potential-instances -Wall -Werror -Wcompat -Widentities -Wincomplete-patterns -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wmissing-home-modules -Wname-shadowing -Wpartial-fields -Wredundant-constraints -Wunused-packages -Wunused-type-patterns -threaded -rtsopts -with-rtsopts=-N
build-depends:
base >=4.14 && <5
, green
@ -228,7 +234,7 @@ executable site
TypeSynonymInstances
UndecidableInstances
ViewPatterns
ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-patterns -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wmissing-home-modules -Wname-shadowing -Wpartial-fields -Wredundant-constraints -Wunused-packages -Wunused-type-patterns -threaded -rtsopts -with-rtsopts=-N
ghc-options: -fprint-potential-instances -Wall -Werror -Wcompat -Widentities -Wincomplete-patterns -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wmissing-home-modules -Wname-shadowing -Wpartial-fields -Wredundant-constraints -Wunused-packages -Wunused-type-patterns -threaded -rtsopts -with-rtsopts=-N
build-depends:
base >=4.14 && <5
, green
@ -240,8 +246,10 @@ test-suite test
other-modules:
Green.RouteSpec
Green.Rule.BlogSpec
Green.Template.AstStructure
Green.Template.ParserSpec
Green.Template.Structural
Green.Template.Source.LexerSpec
Green.Template.Source.TestSupport
Green.TestSupport
Green.TestSupport.Compiler
Green.TestSupport.Config
@ -293,7 +301,7 @@ test-suite test
TypeSynonymInstances
UndecidableInstances
ViewPatterns
ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-patterns -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wmissing-home-modules -Wname-shadowing -Wpartial-fields -Wredundant-constraints -Wunused-packages -Wunused-type-patterns -threaded -rtsopts -with-rtsopts=-N
ghc-options: -fprint-potential-instances -Wall -Werror -Wcompat -Widentities -Wincomplete-patterns -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wmissing-home-modules -Wname-shadowing -Wpartial-fields -Wredundant-constraints -Wunused-packages -Wunused-type-patterns -threaded -rtsopts -with-rtsopts=-N
build-depends:
base >=4.14 && <5
, containers

1
package.yaml

@ -123,6 +123,7 @@ default-extensions:
- ViewPatterns
ghc-options:
- -fprint-potential-instances
- -Wall
- -Werror
- -Wcompat

5
site/404.md

@ -1 +1,6 @@
---
title: 404 Not Found
layout: page
---
The field you were looking for has browned and vanished.

10
site/_drafts/lessons-from-sterling.md

@ -1,10 +1,10 @@
---
layout: post
title: "Lessons from Sterling"
author: "Logan McGrath"
date: 2013-08-05T09:37:00-07:00
comments: false
tags: Sterling, Functional Programming, Language Design
layout: post
---
I've spent the last seven months developing a language called [Sterling][].
@ -113,7 +113,7 @@ apply = (x y) -> x y
selectFirst = (x y) -> x
selectSecond = (x y) -> y
conditional = (condition) -> if condition.true? then selectFirst else selectSecond end
friday? = say $$ conditional (today.is :friday) 'Yay Friday!' 'Awww...'
friday? = say $ conditional (today.is :friday) 'Yay Friday!' 'Awww...'
```
Because Sterling was intended to be immutable, objects would be used to
@ -155,8 +155,8 @@ metaprogramming and eventually dependency injection.
@component { uses: [ :productionDb ] }
@useWhen (runtime -> runtime.env is :production)
Inventory = (db) -> object {
numberOfItems: db.asInt $$ db.scalarQuery "SELECT COUNT(*) FROM thingies",
priceCheck: (thingy) -> db.asMoney $$ db.scalarQuery "SELECT price FROM
numberOfItems: db.asInt $ db.scalarQuery "SELECT COUNT(*) FROM thingies",
priceCheck: (thingy) -> db.asMoney $ db.scalarQuery "SELECT price FROM
thingies WHERE id = :id" { id: thingy.id },
}
@ -405,6 +405,6 @@ memoization becomes a near requirement in order to make lazy evaluation useful.
[virtually anywhere for anything]: http://brandonbyars.com/2008/07/21/orthogonality/
[directly into a language]: http://paulhammant.com/blog/crazy-bob-and-type-safety-for-dependency-injection.html/
[open/closed principle]: http://en.wikipedia.org/wiki/Open/closed_principle
[memoization]: $getRoute("_drafts/sterling-with-memoization.md")$
[memoization]: {{route '_drafts/sterling-with-memoization.md'}}
[visitor pattern]: http://en.wikipedia.org/wiki/Visitor_pattern#Java_example
[single dispatch]: http://en.wikipedia.org/wiki/Multiple_dispatch#Java

2
site/_drafts/morphisms-are-lossy.md

@ -1,5 +1,4 @@
---
layout: post
title: "Morphisms are Lossy"
author: "Logan McGrath"
date: 2021-06-18T18:02:00-07:00
@ -9,6 +8,7 @@ tags:
- Category Theory
- Scala
- Cats
layout: post
---
Outline:

4
site/_drafts/open-source-in-the-age-of-github.md

@ -4,13 +4,13 @@ author: "Logan McGrath"
date: 2021-06-18T18:02:00-07:00
comments: false
published: false
layout: post
body-class: open-source-in-the-age-of-github
tags:
- Open Source
- Software Licenses
layout: post
---
```{.markdown}
$getCode("LICENSE.md")$
{{code "LICENSE.md"}}
```

2
site/_drafts/sterling-benchmarks.md

@ -1,9 +1,9 @@
---
layout: post
title: "Sterling Benchmarks"
date: 2013-06-16T21:12:00-07:00
comments: false
tags: Functional Programming, Sterling, Language Design
layout: post
---
Since [mid January][], I’ve been developing a functional scripting language I

4
site/_drafts/sterling-with-memoization.md

@ -1,10 +1,10 @@
---
layout: post
title: "Sterling With Memoization"
author: "Logan McGrath"
date: 2013-06-17T04:26:00-07:00
comments: false
tags: Sterling, Functional Programming, Language Design
layout: post
---
In my [last post][] I wrote about performance in the [Sterling][] programming
@ -133,7 +133,7 @@ function should not leverage memoization.
* [Commit containing memoization changes][]
* [Benchmark showing O(1) complexity][]
[last post]: $getRoute("_drafts/sterling-benchmarks.md")$
[last post]: {{route '_drafts/sterling-benchmarks.md'}}
[Sterling]: https://github.com/lmcgrath/sterling
[Memoization]: https://en.wikipedia.org/wiki/Memoization
[Commit containing memoization changes]: https://github.com/lmcgrath/sterling/commit/7d69d49a911d2d916701fa973e02ffabe82afe9d

2
site/_layouts/bare-content.html

@ -1 +1 @@
$body$
{{body}}

20
site/_layouts/default.html

@ -1,35 +1,33 @@
---
layout: skeleton
---
{{@layout "skeleton"}}
<header class="page-header">
<div class="content-bound">
<div class="logo-icon"><a href="/"></a></div>
<div class="logo-nav">
<h1 class="logo"><a href="/">This Field Was Green</a></h1>
$partial("_partials/main-nav.html")$
{{partial "main-nav"}}
</div>
</div>
</header>
<main class="page-content">
<div class="content-bound">
$body$
{{body}}
</div>
</main>
<footer class="page-footer">
<div class="content-bound">
$partial("_partials/main-nav.html")$
{{partial "main-nav"}}
<p class="copyright">Copyright &copy; <span class="copyright-date">2012</span> Logan McGrath. All rights reserved.</p>
<p class="generated">
Site proudly generated by <a href="http://jaspervdj.be/hakyll">Hakyll</a>.
This page was rendered from
$if(sourceFileName)$
<a href="$gitWebUrl$/blob/$gitSha1$/$sourceFilePath$">$sourceFileName$</a>
$else$
{{#if gitFileName}}
<a href="{{gitWebUrl}}/blob/{{gitSha1}}/{{gitFilePath}}">{{gitFileName}}</a>
{{#else}}
derived data
$endif$
at commit <a class="commit-link" href="$gitWebUrl$/commit/$gitSha1$">[$gitSha1$] $gitMessage$</a>$if(isChanged)$ with local changes$endif$.
{{#end}}
at commit <a class="commit-link" href="{{gitWebUrl}}/commit/{{gitSha1}}">[{{gitSha1}}] {{gitMessage}}</a>{{#if isChanged}} with local changes{{#end}}.
</p>
</div>
</footer>

8
site/_layouts/page.html

@ -1,5 +1,3 @@
---
layout: default
---
<h1 class="page-title">$title$</h1>
$body$
{{@layout "default"}}
<h1 class="page-title">{{title}}</h1>
{{body}}

25
site/_layouts/post.html

@ -1,20 +1,17 @@
---
layout: default
body-class: post-page
---
{{@layout "default"}}
<article class="post">
<header class="post-header">
<h1 class="post-title page-title"><a href="$url$">$title$</a></h1>
<h1 class="post-title page-title"><a href="{{url}}">{{title}}</a></h1>
<p class="post-published">
$if(published)$
Posted on $published(shortDate)$
$else$
Drafted on $date(shortDate)$
$endif$
$if(author)$
by $author$
$endif$
{{#if published}}
Posted on {{published | dateAs shortDate}}
{{#else}}
Drafted on {{date | dateAs shortDate}}
{{#end}}
{{#if author}}
by {{author}}
{{#end}}
</p>
</header>
$body$
{{body}}
</article>

18
site/_layouts/skeleton.html

@ -1,7 +1,3 @@
---
stylesheet: /css/main.css
script: /js/main.js
---
<!doctype html>
<html lang="en">
<head>
@ -10,18 +6,16 @@ script: /js/main.js
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>$siteTitle$$if(title)$ - $title$$endif$</title>
<title>{{siteTitle}}{{#if title}} - {{title}}{{#end}}</title>
$if(author)$
<link rel="author" href='/' content="$author$">
$endif$
{{#if author}}<link rel="author" href='/' content="{{author}}">{{#end}}
<link rel="icon" href="/images/grass.svg">
$for(stylesheets)$<link rel="stylesheet" href="$href$">$endfor$
$for(scripts)$<script src="$src$"></script>$endfor$
<link rel="stylesheet" href="/css/main.css">
<script src="/js/main.js"></script>
</head>
<body class="$bodyClass$">
$body$
<body class='{{bodyClass | default "default"}}'>
{{body}}
</body>
</html>

6
site/_partials/about-me.html

@ -2,7 +2,7 @@
<aside class="meatball-headshot">
<picture>
<img width="288" height="384"
src='$getRoute("images/about-me/headshot-w-meatball-doge_400w.png")$'
src="{{route 'images/about-me/headshot-w-meatball-doge_400w.png'}}"
title="Myself and Meatball. He's cute cuz he's little."
alt="Mysql and Meatball">
<figcaption>
@ -40,13 +40,13 @@
</p>
</section>
$partial("_partials/employment.html")$
{{partial "employment"}}
<section class="personal-stuff">
<div class="meatball-jellybean">
<figure>
<img width="200" height="266"
src='$getRoute("images/about-me/meatball-n-jelly-doges_400w.png")$'
src="{{route 'images/about-me/meatball-n-jelly-doges_400w.png'}}"
title="Meatball is the little one, Jellybean is the big one."
alt="Meatball and Jellybean">
<figcaption>

8
site/_partials/main-nav.html

@ -1,6 +1,6 @@
<nav class="main-nav">
<a href='$getRoute("index.html")$'>Home</a>
<a href='$getRoute("blog/index.html")$'>Blog</a>
<a href='$getRoute("resume.md")$'>Resume</a>
<a href='$getRoute("contact.md")$'>Contact</a>
<a href="{{route 'index.html'}}">Home</a>
<a href="{{route 'blog/index.html'}}">Blog</a>
<a href="{{route 'resume.md'}}">Resume</a>
<a href="{{route 'contact.md'}}">Contact</a>
</nav>

6
site/_partials/post-list.html

@ -1,7 +1,7 @@
<ul>
$for(posts)$
{{#for posts}}
<li>
<a href="$url$">$title$</a> - $date$
<a href="{{url}}">{{title}}</a> - {{date}}
</li>
$endfor$
{{#end}}
</ul>

32
site/_partials/teaser-list.html

@ -2,27 +2,25 @@
<header>
<h2>Latest Blog Posts</h2>
</header>
$for(recentPosts)$
{{#for recentPosts}}
<section class="teaser-item post">
<header class="post-header">
<hgroup>
<h3 class="post-title"><a href="$url$">$title$</a></h3>
<div class="post-published">
<p>
$if(published)$
Posted on $published(shortDate)$
$else$
Drafted on $date(shortDate)$
$endif$
$if(author)$
by $author$
$endif$
</p>
</div>
<h3 class="post-title"><a href="{{url}}">{{title}}</a></h3>
<p class="post-published">
{{#if published}}
Posted on {{published | with format: shortDate}}
{{#else}}
Drafted on {{date | with format: shortDate}}
{{#end}}
{{#if author}}
by {{author}}
{{#end}}
</p>
</hgroup>
</header>
$if(teaser)$$teaser$$endif$
<p class="read-more"><a href="$url$">Read more...</a></p>
{{#if teaser}}{{teaser}}{{#end}}
<p class="read-more"><a href="{{url}}">Read more...</a></p>
</section>
$endfor$
{{#end}}
</article>

30
site/_posts/2012-11-07-using-perforce-chronicle-for-application-configuration.md

@ -1,10 +1,10 @@
---
layout: post
title: "Using Perforce Chronicle for application configuration"
author: "Logan McGrath"
date: 2012-11-07T13:54:00-06:00
published: 2012-11-07T13:54:00-06:00
tags: Perforce, Configuration Management
layout: post
---
Following Paul Hammant's post [App-config workflow using SCM][] and subsequent
@ -83,14 +83,14 @@ with PHP.
The source JSON configuration is the same, albeit sorted:
```{.json .numberLines}
$getCode("app-config/stack_configuration.json")$
{{code "app-config/stack_configuration.json"}}
```
The `index.html` page has been modified from the original to support only the
basic _commit_ and _diffs_ functionality:
```{.html .numberLines}
$getCode("app-config/index.html")$
{{code "app-config/index.html"}}
```
Both of these assets were added by performing:
@ -130,28 +130,28 @@ To create the module, the following paths need to be added:
Declare the module with `INSTALL/application/appconfig/module.ini`:
```{.ini .numberLines}
$getCode("app-config/module/module.ini")$
{{code "app-config/module/module.ini"}}
```
Add a view script for displaying plaintext
assets, `INSTALL/application/appconfig/views/scripts/index/index.phtml`:
```{.php .numberLines}
$getCode("app-config/module/views/scripts/index/index.phtml")$
{{code "app-config/module/views/scripts/index/index.phtml"}}
```
Add a view script for displaying
diffs, `INSTALL/application/appconfig/views/scripts/index/diffs.phtml`:
```{.php .numberLines}
$getCode("app-config/module/views/scripts/index/diffs.phtml")$
{{code "app-config/module/views/scripts/index/diffs.phtml"}}
```
And a controller
at `INSTALL/application/appconfig/controllers/IndexController.phtml`:
```{.php .numberLines}
$getCode("app-config/module/controllers/IndexController.php")$
{{code "app-config/module/controllers/IndexController.php"}}
```
## AngularJS
@ -167,27 +167,27 @@ stack_configuration.json and post changes back.
From `http://localhost/appconfig/index.html`, the data from
stack_configuration.json is loaded into the form:
$img("img-config-form", "/images/app-config/start.png")$
{{img id: "img-config-form", src: "/images/app-config/start.png"}}
Edits to stack_configuration.json can be made using the form, and the diffs
viewed by clicking on "View Diffs":
$img("img-config-diffs", "/images/app-config/diffs.png")$
{{img id: "img-config-diffs", src: "/images/app-config/diffs.png"}}
The changes can be saved by entering a commit message and clicking "Commit
Changes". After which, clicking "View Diffs" will show no changes:
$img("img-config-commit", "/images/app-config/diffs-after-commit.png")$
{{img id: "img-config-commit", src: "/images/app-config/diffs-after-commit.png"}}
To show that edits have in fact been made to stack_configuration.json, go
to `http://localhost/stack_configuration.json`, select "History" and click on "
History List":
$img("img-config-history", "/images/app-config/history.png")$
{{img id: "img-config-history", src: "/images/app-config/history.png"}}
Chronicle also provides an interface for viewing diffs between revisions:
$img("img-config-revisions", "/images/app-config/history-diffs.png")$
{{img id: "img-config-revisions", src: "/images/app-config/history-diffs.png"}}
## Disk Usage
@ -277,9 +277,9 @@ I had never written professionally before or been aware of configuration managem
The other posts in this series were also written with guidance from Paul:
- $linkedTitle("_posts/2012-11-16-scm-backed-application-configuration-with-perforce.md")$
- $linkedTitle("_posts/2012-11-20-app-config-app-in-action.md")$
- $linkedTitle("_posts/2012-11-28-promoting-changes-with-app-config-app.md")$
- {{linkedTitle "_posts/2012-11-16-scm-backed-application-configuration-with-perforce.md"}}
- {{linkedTitle "_posts/2012-11-20-app-config-app-in-action.md"}}
- {{linkedTitle "_posts/2012-11-28-promoting-changes-with-app-config-app.md"}}
The subject of configuration as described in these posts is still fresh even after nearly ten years. Even now configuration as code still doesn't have a perfect solution, though products have become available that make managing configuration easier. Changing configuration in a running process as a general solution remains elusive, as supporting it imposes a lot of constraints on design.

12
site/_posts/2012-11-16-scm-backed-application-configuration-with-perforce.md

@ -1,11 +1,11 @@
---
layout: post
title: "SCM-Backed Application Configuration with Perforce"
author: "Logan McGrath"
date: 2012-11-16T17:00:00-06:00
published: 2012-11-16T17:00:00-06:00
comments: false
tags: SCM, Perforce, Sinatra, AngularJS
layout: post
---
Continuing from my [last post][], I've [forked][] Paul Hammant's original
@ -41,21 +41,21 @@ All installation and example setup details may be found in
When you login, you should see this screen:
$img("img-app-start", "/images/app-config2/start.png")$
{{img id: "img-app-start", src: "/images/app-config2/start.png"}}
You'll notice I made the extra effort to add colors and drop shadows :D The
application works from the project root in Perforce, so the files in each branch
are viewable here. Clicking on "Dev" > "aardvark_configuration.html" will bring
up a form for editing `aardvark_configuration.json` as in the previous version:
$img("img-app-config", "/images/app-config2/aardvark_configuration.png")$
{{img id: "img-app-config", src: "/images/app-config2/aardvark_configuration.png"}}
Changes to the form data are automatically saved. After making a view edits, you
can click "View Diff" to get the diffs or "Revert" your changes. Go ahead and
change the email address and fiddle around with the banned nicks, then go click
"Pending Changes":
$img("img-app-changes", "/images/app-config2/changes.png")$
{{img id: "img-app-changes", src: "/images/app-config2/changes.png"}}
This screen shows all files that were changed and their diffs as well. You can
"Revert" each file individually, and if you want to commit all changes, then
@ -63,7 +63,7 @@ enter a commit message and click "Commit Changes". If you commit the changes and
go back to "Dev" > "aardvark_configuration.html", you'll see the new values in
the form:
$img("image-app-commit", "/images/app-config2/aardvark_configuration-changed.png")$
{{img id: "image-app-commit", src: "/images/app-config2/aardvark_configuration-changed.png"}}
## Security and Permissions
@ -170,7 +170,7 @@ existing infrastructure.
The reason I point out Subversion and TFS is largely due to support of
per-branch permissions.
[last post]: $getRoute("_posts/2012-11-07-using-perforce-chronicle-for-application-configuration.md")$
[last post]: {{route '_posts/2012-11-07-using-perforce-chronicle-for-application-configuration.md'}}
[forked]: https://github.com/lmcgrath/App-Config-App/
[AngularJS]: http://angularjs.org/
[App-Config-App's README]: https://github.com/lmcgrath/app-config-app/blob/master/README.md

4
site/_posts/2012-11-20-app-config-app-in-action.md

@ -1,11 +1,11 @@
---
layout: post
title: "App-Config-App in Action"
author: "Logan McGrath"
date: 2012-11-20T17:00:00-06:00
published: 2012-11-20T17:00:00-06:00
comments: false
tags: AngularJS, Perforce, SCM, Sinatra, Configuration Management
layout: post
---
Paul Hammant found this cool [Server-Side Piano][] and I've modified it to be
@ -15,7 +15,7 @@ configuration without reloading the UI.
<!--more-->
$youtube("hZbQhF6fsEo", "app-config-demo")$
{{youtube video: "hZbQhF6fsEo", id: "app-config-demo"}}
## Making it work for yourself

14
site/_posts/2012-11-28-promoting-changes-with-app-config-app.md

@ -1,11 +1,11 @@
---
layout: post
title: "Promoting changes with App-Config-App"
author: "Logan McGrath"
date: 2012-11-28T13:04:00-06:00
published: 2012-11-28T13:04:00-06:00
comments: false
tags: AngularJS, Perforce, SCM, Sinatra, Configuration Management
layout: post
---
The App-Config-App now lets you promote changes between environments!
@ -40,7 +40,7 @@ staging-prod staging prod
If you login to App-Config-App and go to "Promote Changes," you get an interface
showing these relationships:
$img("img-promote-changes", "/images/app-config3/promote_changes.png")$
{{img id: "img-promote-changes", src: "/images/app-config3/promote_changes.png"}}
Changes between environments can be promoted in either direction along a mapping
configuration. The receiving environment accepts all changes (developers would
@ -49,11 +49,11 @@ the changes by clicking on the "Pending Changes" link.
For example, I've promoted changes from "qa" to "dev":
$img("img-promote-result", "/images/app-config3/promote_result.png")$
{{img id: "img-promote-result", src: "/images/app-config3/promote_result.png"}}
I can then review the changes by clicking on "Pending Changes":
$img("img-pending-changes", "/images/app-config3/pending_changes.png")$
{{img id: "img-pending-changes", src: "/images/app-config3/pending_changes.png"}}
Changes may be edited or reverted before committing them.
@ -66,7 +66,7 @@ changes get promoted, but it requires a little more work.
I've connected P4V to my App-Config-App user workspace to perform the same
promotion from "qa" to "dev":
$img("img-config-promotion", "/images/app-config3/p4v.png")$
{{img id: "img-config-promotion", src: "/images/app-config3/p4v.png"}}
Select the "qa" folder, then from the menu bar go to "Actions" >
"Merge/Integrate". This will bring up a wizard for performing the integration.
@ -82,12 +82,12 @@ Resolve option: "Accept source"
And ensure the direction of integration is "Target" < "Source":
$img("img-config-integrate", "/images/app-config3/p4v_integrate.png")$
{{img id: "img-config-integrate", src: "/images/app-config3/p4v_integrate.png"}}
Finally, click "Merge". If you expand the "dev" folder, you can see the where
the changes are:
$img("img-config-results", "/images/app-config3/p4v_integrate_result.png")$
{{img id: "img-config-results", src: "/images/app-config3/p4v_integrate_result.png"}}
You are now free to modify the files further before finally committing the
changes.

2
site/_templates/code.md

@ -1 +1 @@
$body$
{{body}}

14
site/_templates/image.html

@ -1,10 +1,12 @@
<aside id="$imgId$-aside" class="image">
<aside {{#if id}}id="{{id}}-aside" {{#end}}class="image">
<figure>
<a href="$imgSrc$" class="image-link">
<img id="$imgId$" src="$imgSrc$" title="$imgTitle$" alt="$imgAlt$">
<a href="{{src}}" class="image-link">
<img src="{{src}}" {{#if id}}id="{{id}}"{{#end}}
{{#if title}}title="{{title}}"{{#end}}
{{#if alt}}alt="{{alt}}"{{#end}}>
</a>
</figure>
$if(imgTitle)$
<figcaption>$imgTitle$</figcaption>
$endif$
{{#if title}}
<figcaption>{{title}}</figcaption>
{{#end}}
</aside>

6
site/_templates/robots.txt

@ -1,4 +1,4 @@
# Generated from commit $gitSha1$
# Generated from commit {{gitSha1}}
User-agent: *
Disallow: $siteRoot$/drafts/*
Sitemap: $siteRoot$/sitemap.xml
Disallow: {{siteRoot}}/drafts/*
Sitemap: {{siteRoot}}/sitemap.xml

12
site/_templates/sitemap.xml

@ -5,12 +5,12 @@
xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0"
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
$for(pages)$
{{#for pages}}
<url>
<loc>$siteRoot$$url$</loc>
<lastmod>$if(updated)$$updated$$else$$if(date)$$date$$endif$$endif$</lastmod>
<changefreq>$if(changefreq)$$changefreq$$else$weekly$endif$</changefreq>
<priority>$if(priority)$$priority$$else$0.8$endif$</priority>
<loc>{{siteRoot}}{{url}}</loc>
<lastmod>{{#if updated}}{{updated}}{{#else if date}}{{date}}{{#end}}</lastmod>
<changefreq>{{#if changefreq}}{{changefreq}}{{#else}}weekly{{#end}}</changefreq>
<priority>{{#if priority}}{{priority}}{{#else}}0.8{{#end}}</priority>
</url>
$endfor$
{{#end}}
</urlset>

12
site/_templates/youtube.html

@ -1,14 +1,14 @@
<aside $if(youtubeAsideId)$id="$youtubeAsideId$"$endif$ class="youtube">
<aside class="youtube"{{#if id}} id="{{id}}"{{#end}}>
<figure>
<iframe src="https://www.youtube.com/embed/$youtubeVideoId$"
$if(youtubeVideoTitle)$title="$youtubeVideoTitle$"$endif$
<iframe src="https://www.youtube.com/embed/{{video}}"
{{#if title}}title="{{video}}"{{#end}}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen>
</iframe>
</figure>
$if(youtubeVideoTitle)$
{{#if title}}
<figcaption>
$youtubeVideoTitle$
{{title}}
</figcaption>
$endif$
{{#end}}
</aside>

4
site/blog/archives.html

@ -1,9 +1,9 @@
---
layout: page
title: "These Posts Were Green"
updated: 2021-05-24T20:07:11-05:00
layout: page.html
---
Here you can find all my previous posts:
$partial("_partials/post-list.html")$
{{partial "post-list.html"}}

4
site/blog/drafts.html

@ -1,8 +1,8 @@
---
layout: page
title: "These Drafts Are Still Green"
updated: 2021-05-24T20:07:11-05:00
layout: page.html
---
Here you can find all my drafts:
$partial("_partials/post-list.html")$
{{partial "post-list.html"}}

30
site/blog/index.html

@ -1,32 +1,32 @@
---
layout: default
title: "Blogging About Life and Tech"
updated: $latestPostDate$
body-class: blog
updated: {{latestPostDate}}
bodyClass: blog
layout: page.html
---
<header>
<h1 class="page-title">$title$ -- Latest Posts</h1>
<h1 class="page-title">{{title}} -- Latest Posts</h1>
</header>
<article class="post latest-post">
<header class="post-header">
<h1 class="post-title"><a href="$latestPostUrl$">$latestPostTitle$</a></h1>
<h1 class="post-title"><a href="{{latestPostUrl}}">{{latestPostTitle}}</a></h1>
<div class="post-published">
<p>
This latest post
$if(latestPostPublished)$
published on $latestPostPublished$
$else$
drafted on $latestPostDate$
$endif$
$if(latestPostAuthor)$
by $latestPostAuthor$
$endif$
{{#if latestPostPublished}}
published on {{latestPostPublished}}
{{#else}}
drafted on {{latestPostDate}}
{{#end}}
{{#if latestPostAuthor}}
by {{latestPostAuthor}}
{{#end}}
</p>
</div>
</header>
$latestPostBody$
{{latestPostBody}}
</article>
$partial("_partials/teaser-list.html")$
{{partial "teaser-list.html"}}

18
site/code/app-config/index.html

@ -18,7 +18,7 @@
this.svrMessage;
this.message;
this.cfg = $resource("/appconfig/stack_configuration.json").get({});
this.save = function() {
self.cfg.$save({message: self.message}, function() {
alert("Config saved to server");
@ -27,18 +27,18 @@
});
self.message = "";
};
this.newNick = function() {
self.cfg.bannedNicks.push(self.newNickname);
self.newNickname = "";
};
this.diffs = function() {
$xhr("post", "/appconfig/diffs/stack_configuration.json", angular.toJson(self.cfg), function(code, svrMessage) {
self.svrMessage = svrMessage;
});
};
this.deleteNick = function(nick) {
var oldBannedNicks = self.cfg.bannedNicks;
self.cfg.bannedNicks = [];
@ -49,25 +49,25 @@
});
};
}
AppCfg.$inject = ["$resource", "$xhr"];
</script>
Light is on: <input type="checkbox" name="cfg.lighton"/> <br/>
Default Error Reciever (email): <input name="cfg.defaultErrorReciever" ng:validate="email"/> <br/>
Max Load Percentage: <input name="cfg.loadMaxPercent" ng:validate="number:0:100"/> <br/>
Next Shutdown Date: <input name="cfg.nextShutdownDate" ng:validate="date"/> <br/>
Banned nicks:
Banned nicks:
<ol>
<li ng:repeat="nick in cfg.bannedNicks"><span>{{nick}} &nbsp;&nbsp;<a ng:click="deleteNick(nick)">[X]</a></span></li>
</ol>
<form ng:submit="newNick()">
<input type="text" name="newNickname" size="20"/>
<input type="submit" value="&lt;-- Add Nick"/><br/>
</form>
</form>
<hr/>
<button ng:click="diffs()">View Diffs</button><br/>
<button ng:disabled="{{!message}}" ng:click="save()">Commit Changes</button> Commit Message: <input name="message"></input><br/>
Last Server operation: <br/>
<button ng:disabled="{{!message}}" ng:click="save()">Commit Changes</button> Commit Message: <input name="message"></button><br/>
Last Server operation: <br/>
<div ng:bind="svrMessage | html:'unsafe'">
</div>
</body>

9
site/contact.md

@ -1,5 +1,8 @@
{{@ layout "page.html" }}
---
title: Contact Me
layout: page
---
I live in a tiramisù and have pudding in my ears. Please contact me via {{ mailto authorEmail }} and I shall free my eyes to read and reply heartily; my fingers typing though they have been soaking like biscuits in espresso and cream. I welcome conversations and questions about what I do and I might even ask a few questions of my own, but I do not know how I came to be floating in mascarpone.
I live in a tiramisù and have pudding in my ears. Please contact me via <a href="mailto:{{authorEmail}}">{{authorEmail}}</a> and I shall free my eyes to read and reply heartily; my fingers typing though they have been soaking like biscuits in espresso and cream. I welcome conversations and questions about what I do and I might even ask a few questions of my own, but I do not know how I came to be floating in mascarpone.
{{ partial "employment.html" }}
{{partial "employment"}}

6
site/index.html

@ -1,7 +1,7 @@
---
title: "Mowing My Technical Lawn"
bodyClass: homepage
layout: page
body-class: homepage
---
$partial("_partials/about-me.html")$
$partial("_partials/teaser-list.html")$
{{partial "about-me"}}
{{partial "teaser-list"}}

7
site/resume.md

@ -1 +1,6 @@
Please see my [LinkedIn profile]({{ linkedInProfile }}) for my employment history.
---
title: My Resume
layout: page
---
Please see my [LinkedIn profile]({{linkedInProfile}}) for my employment history.

8
src/Green.hs

@ -5,7 +5,6 @@ import Data.Time
import Green.Command
import Green.Common
import Green.Config
import Green.Context
import Green.Rule
import qualified Hakyll as H
import Options.Applicative
@ -27,8 +26,7 @@ author = do
loadSiteConfig :: IO SiteConfig
loadSiteConfig = do
env <- getEnvironment
time <- utcToLocalTime <$> getCurrentTimeZone <*> getCurrentTime
time <- utcToZonedTime <$> getCurrentTimeZone <*> getCurrentTime
configIniText <- TIO.readFile "config.ini"
case parseConfigIni env defaultTimeLocale time configIniText of
Left e -> fail e
Right config -> return $ config & siteContext .~ baseContext config
let result = parseConfigIni env defaultTimeLocale time configIniText
either fail return result

2
src/Green/Common.hs

@ -41,7 +41,7 @@ import Data.Foldable (sequenceA_)
import Data.Functor ((<&>))
import Data.List (intercalate)
import Data.Maybe (fromJust, fromMaybe, isJust, isNothing, maybe, maybeToList)
import Data.Time (LocalTime)
import Data.Time (ZonedTime)
import Data.Time.Format
import Hakyll.Core.Compiler
import Hakyll.Core.Dependencies

16
src/Green/Config.hs

@ -1,12 +1,10 @@
module Green.Config where
import Data.Ini.Config
import Data.String (IsString)
import Data.Text (Text)
import qualified Data.Text as T
import Green.Common
import Green.Lens
import Green.Template
import Hakyll.Core.Configuration as HC
data SiteDebug = SiteDebug
@ -42,8 +40,7 @@ data SiteConfig = SiteConfig
_siteGitWebUrl :: String,
_siteDebug :: SiteDebug,
_siteHakyllConfiguration :: Configuration,
_siteTime :: LocalTime,
_siteContext :: Context String,
_siteTime :: ZonedTime,
_siteTimeLocale :: TimeLocale,
_siteDisplayFormat :: SiteDisplayFormat
}
@ -77,7 +74,7 @@ siteInMemoryCache = siteHakyllConfiguration . inMemoryCacheL
hasEnvFlag :: String -> [(String, String)] -> Bool
hasEnvFlag f e = isJust (lookup f e)
parseConfigIni :: [(String, String)] -> TimeLocale -> LocalTime -> Text -> Either String SiteConfig
parseConfigIni :: [(String, String)] -> TimeLocale -> ZonedTime -> Text -> Either String SiteConfig
parseConfigIni env timeLocale time iniText = parseIniFile iniText do
hakyllConfiguration <- section "Hakyll" do
providerDirectory' <- fieldOf "providerDirectory" string
@ -113,7 +110,6 @@ parseConfigIni env timeLocale time iniText = parseIniFile iniText do
<*> pure debugSettings
<*> pure hakyllConfiguration
<*> pure time
<*> pure mempty
<*> pure timeLocale
<*> pure displayFormat
where
@ -121,7 +117,7 @@ parseConfigIni env timeLocale time iniText = parseIniFile iniText do
ignoreFile defaultConfiguration path
&& takeFileName path `notElem` allowedFiles
fieldOfStrings :: IsString a => Text -> SectionParser [a]
fieldOfStrings :: Text -> SectionParser [String]
fieldOfStrings k = fieldDefOf k (listWithSeparator "," string) []
configEnvFlag :: String -> String -> Bool -> [(String, String)] -> SectionParser Bool
@ -131,9 +127,9 @@ configEnvFlag configKey envKey defaultValue env =
Nothing -> fieldFlagDef (T.pack configKey) defaultValue
configEnvMbOf :: String -> String -> (Text -> Either String a) -> [(String, String)] -> SectionParser (Maybe a)
configEnvMbOf configKey envKey parse env =
fieldFromEnv <|> fieldMbOf (T.pack configKey) parse
configEnvMbOf configKey envKey parseFn env =
fieldFromEnv <|> fieldMbOf (T.pack configKey) parseFn
where
fieldFromEnv = sequence $ getValue . parse . T.pack <$> lookup envKey env
fieldFromEnv = sequence $ getValue . parseFn . T.pack <$> lookup envKey env
getValue (Left e) = error e
getValue (Right v) = return v

103
src/Green/Context.hs

@ -1,103 +0,0 @@
module Green.Context
( module Green.Context,
module Green.Context.DateFields,
module Green.Context.GitCommits,
)
where
import Data.String.Utils (endswith)
import Green.Common
import Green.Config
import Green.Context.DateFields
import Green.Context.GitCommits
import Green.Template
import Green.Util (dropIndex, stripSuffix)
import Hakyll.Web.Html (escapeHtml)
baseContext :: SiteConfig -> Context String
baseContext config =
mconcat
[ constField "siteTitle" (config ^. siteTitle),
constField "siteRoot" (config ^. siteRoot),
constField "linkedInProfile" (config ^. siteLinkedInProfile),
constField "authorEmail" (config ^. siteAuthorEmail),
trimmedUrlField "url",
dateFields config,
gitCommits config,
imgField,
youtubeField,
getRouteField,
defaultContext,
linkedTitleField,
getCodeField
]
-- | Trims @index.html@ from @$url$@'s
trimmedUrlField :: String -> Context String
trimmedUrlField = mapField dropIndex . urlField
siteRootField :: String -> Context String
siteRootField = constField "siteRoot"
getCodeField :: Context String
getCodeField = constField "getCode" f
where
f :: FunctionValue String String String
f contentsPath _ _ = trimStartEndLines <$> (tryLoad codeId <|> tryLoad fileId)
where
codeId = fromFilePath $ "code/" ++ contentsPath
fileId = fromFilePath contentsPath
tryLoad = fmap itemBody . compilePandoc <=< load
trimStartEndLines =
unlines
. reverse
. dropWhile null
. reverse
. dropWhile null
. lines
imgField :: Context String
imgField = constField "img" f
where
templatePath = "_templates/image.html"
f :: FunctionValue String String String
f src context _ = do
let context' = constField "src" src <> context
template <- load templatePath
applied <- applyAsTemplate context' template
return $ itemBody applied
youtubeField :: Context String
youtubeField = constField "youtube" f
where
templatePath = "_templates/youtube.html"
f :: FunctionValue String String String
f videoId context _ = do
let context' = constField "videoId" videoId <> context
template <- load templatePath
rendered <- applyAsTemplate context' template
return $ itemBody rendered
getRouteField :: Context String
getRouteField = constField "route" f
where
f :: FunctionValue String String String
f filePath _ _ = do
let id' = fromFilePath filePath
getRoute id' >>= \case
Just r -> return $ "/" ++ stripSuffix "index.html" r
Nothing -> error $ "no route to " ++ show id'
linkedTitleField :: Context String
linkedTitleField = constField "linkedTitle" f
where
f :: FunctionValue String String String
f filePath context _ = do
linkedItem <- load (fromFilePath filePath)
makeLink <$> getField "title" linkedItem <*> getField "url" linkedItem
where
getField key = intoString <=< unContext context key
makeLink title url
| endswith ".html" filePath = "<a href=\"" ++ url ++ "\">" ++ escapeHtml title ++ "</a>"
| endswith ".md" filePath = "[" ++ escapeHtml title ++ "](" ++ url ++ ")"
| otherwise = title ++ " <" ++ url ++ ">"

106
src/Green/Context/DateFields.hs

@ -1,106 +0,0 @@
module Green.Context.DateFields (dateFields) where
import Data.List (tails)
import Data.String.Utils
import Green.Common
import Green.Config
import Green.Template.Context
import Green.Util
import qualified Hakyll as H
dateFields :: SiteConfig -> Context a
dateFields config =
mconcat fields
<> longDateFormatField
<> shortDateFormatField
<> timeFormatField
where
fields = uncurry mkFields <$> fieldKeys
mkFields f k = f timeLocale k
fieldKeys =
[ (dateField, "date"),
(publishedField, "published"),
(updatedField, "updated")
]
timeLocale = config ^. siteTimeLocale
longDateFormatField = constField "longDate" $ displayFormat ^. displayDateLongFormat
shortDateFormatField = constField "shortDate" $ displayFormat ^. displayDateShortFormat
timeFormatField = constField "timeOnly" $ displayFormat ^. displayTimeFormat
displayFormat = config ^. siteDisplayFormat
dateField :: TimeLocale -> String -> Context a
dateField = mconcat . (fns <*>) . pure
where
fns = [dateField' ["published", "date"], dateFromFilePathField]
publishedField :: TimeLocale -> String -> Context a
publishedField = dateField' ["published"]
updatedField :: TimeLocale -> String -> Context a
updatedField = dateField' ["updated"]
dateField' :: forall a. [String] -> TimeLocale -> String -> Context a
dateField' sourceKeys timeLocale targetKey = constField targetKey f
where
f :: FunctionValue String String a
f dateFormat _ item = do
maybeDate <- dateFromMetadata sourceKeys timeLocale item
let maybeFormatted = formatTime timeLocale dateFormat <$> maybeDate
maybe notFound return maybeFormatted
notFound = noResult $ "Could not find date field field " ++ show targetKey ++ " from metadata keys " ++ show sourceKeys
dateFromMetadata :: [String] -> TimeLocale -> Item a -> Compiler (Maybe LocalTime)
dateFromMetadata sourceKeys timeLocale item = do
maybeDates <- mapM findDate sourceKeys
return $ firstMaybe maybeDates
where
id' = itemIdentifier item
tryParseDate' = tryParseDate timeLocale metadataDateFormats
findDate sourceKey = do
maybeString <- H.lookupString sourceKey <$> H.getMetadata id'
return (tryParseDate' =<< maybeString)
dateFromFilePathField :: forall a. TimeLocale -> String -> Context a
dateFromFilePathField timeLocale targetKey = constField targetKey f
where
f :: FunctionValue String String a
f dateFormat _ item = maybe notFound return maybeFormatted
where
maybeDate = resolveDateFromFilePath timeLocale item
maybeFormatted = formatTime timeLocale dateFormat <$> maybeDate
notFound = noResult $ "Could not find " ++ show targetKey ++ " from file path " ++ show filePath
filePath = toFilePath (itemIdentifier item)
resolveDateFromFilePath :: TimeLocale -> Item a -> Maybe LocalTime
resolveDateFromFilePath timeLocale item =
let paths = splitDirectories $ dropExtension $ toFilePath $ itemIdentifier item
in firstMaybe $
dateFromPath
<$> [take 3 $ split "-" fnCand | fnCand <- reverse paths]
++ [fnCand | fnCand <- map (take 3) $ reverse $ tails paths]
where
dateFromPath = tryParseDate timeLocale ["%Y-%m-%d"] . intercalate "-"
tryParseDate :: (ParseTime a) => TimeLocale -> [String] -> String -> Maybe a
tryParseDate timeLocale dateFormats = firstMaybe . flip fmap dateFormats . parse
where
parse = flip $ parseTimeM True timeLocale
metadataDateFormats :: [String]
metadataDateFormats =
[ "%Y-%m-%d",
"%Y-%m-%dT%H:%M:%S%Z",
"%Y-%m-%dT%H:%M:%S",
"%Y-%m-%d %H:%M:%S%Z",
"%Y-%m-%d %H:%M:%S",
"%a, %d %b %Y %H:%M:%S %Z",
"%a, %d %b %Y %H:%M:%S",
"%B %e, %Y %l:%M %p %EZ",
"%B %e, %Y %l:%M %p",
"%b %e, %Y %l:%M %p %EZ",
"%b %e, %Y %l:%M %p",
"%B %e, %Y",
"%B %d, %Y",
"%b %e, %Y",
"%b %d, %Y"
]

6
src/Green/Rule/Blog.hs

@ -3,7 +3,7 @@ module Green.Rule.Blog where
import Green.Common
import Green.Config
import Green.Route
import Green.Template
import Green.Template.Custom
import qualified Hakyll as H
{-----------------------------------------------------------------------------}
@ -84,10 +84,10 @@ draftRoute =
{-----------------------------------------------------------------------------}
postCompiler :: SiteConfig -> Compiler (Item String)
postCompiler localConfig = applyAsTemplate (localConfig ^. siteContext) =<< getResourceBody
postCompiler = pageCompiler
draftCompiler :: SiteConfig -> Compiler (Item String)
draftCompiler localConfig = applyAsTemplate (localConfig ^. siteContext) =<< getResourceBody
draftCompiler = pageCompiler
blogCompiler :: SiteConfig -> Compiler (Item String)
blogCompiler _ = makeItem "blog"

6
src/Green/Rule/Index.hs

@ -2,12 +2,10 @@ module Green.Rule.Index where
import Green.Common
import Green.Config
import Green.Template.Custom
indexRules :: SiteConfig -> Rules ()
indexRules config =
match "index.html" do
route idRoute
compile $ indexCompiler config
indexCompiler :: SiteConfig -> Compiler (Item String)
indexCompiler _ = makeItem "index"
compile $ pageCompiler config

9
src/Green/Rule/Page.hs

@ -3,7 +3,7 @@ module Green.Rule.Page (pageRules) where
import Green.Common
import Green.Config
import Green.Route
import Green.Template
import Green.Template.Custom
pageRules :: SiteConfig -> Rules ()
pageRules config =
@ -16,10 +16,3 @@ pageRules config =
"resume.md",
"404.md"
]
pageCompiler :: SiteConfig -> Compiler (Item String)
pageCompiler _ = do
id' <- getUnderlying
debugCompiler $ "Compiling page " ++ show id'
getResourceBody
>>= applyAsTemplate defaultContext

3
src/Green/Rule/Robot.hs

@ -3,6 +3,7 @@ module Green.Rule.Robot where
import Green.Common
import Green.Config
import Green.Template
import Green.Template.Custom
robotsTxtRules :: SiteConfig -> Rules ()
robotsTxtRules config = do
@ -15,4 +16,4 @@ robotsTxtCompiler config = do
makeItem ""
>>= loadAndApplyTemplate
(fromFilePath "_templates/robots.txt")
(config ^. siteContext)
(customContext config)

4
src/Green/Template.hs

@ -1,7 +1,7 @@
module Green.Template
( module Green.Template.Ast,
module Green.Template.Compiler,
module Green.Template.Parser,
module Green.Template.Source.Parser,
module Green.Template.Context,
module Green.Template.Pandoc,
)
@ -11,4 +11,4 @@ import Green.Template.Ast
import Green.Template.Compiler
import Green.Template.Context
import Green.Template.Pandoc
import Green.Template.Parser hiding (applyTemplate)
import Green.Template.Source.Parser

280
src/Green/Template/Ast.hs

@ -1,69 +1,72 @@