Compare commits

...

No commits in common. 'main' and '_site' have entirely different histories.
main ... _site

  1. 36
      .gitignore
  2. 1
      .ruby-version
  3. 83
      404/index.html
  4. 82
      500/index.html
  5. 5
      Brewfile
  6. 109
      Brewfile.lock.json
  7. 22
      README.md
  8. 1
      about-me/index.html
  9. 4
      app/author/Main.hs
  10. 4
      app/site/Main.hs
  11. 222
      archives/index.html
  12. 1954
      atom.xml
  13. 4
      bin/author
  14. 4
      bin/site
  15. 562
      blog/2012/11/07/using-perforce-chronicle-for-application-configuration/index.html
  16. 277
      blog/2012/11/16/scm-backed-application-configuration-with-perforce/index.html
  17. 146
      blog/2012/11/20/app-config-app-in-action/index.html
  18. 249
      blog/2012/11/28/promoting-changes-with-app-config-app/index.html
  19. 256
      blog/2021/12/05/redoing-my-website/index.html
  20. 244
      blog/2021/12/11/reasons-why-my-website-is-offline/index.html
  21. 170
      blog/2022/02/09/persistent-ssh-tunnels/index.html
  22. 886
      blog/2022/03/15/embracing-nondeterminism-part-1/index.html
  23. 1
      blog/archives/index.html
  24. 1
      blog/drafts/index.html
  25. 1
      blog/drafts/reasons-why-my-website-is-offline/index.html
  26. 1
      blog/drafts/redoing-my-website/index.html
  27. 1
      blog/functional-programming/2022/01/24/embracing-nondeterminism-part-1/index.html
  28. 209
      blog/index.html
  29. 10
      broken-links.cfg
  30. 14
      ci/base-site/Dockerfile
  31. 180
      ci/pipeline.yml
  32. 12
      ci/prebaked-site/Dockerfile
  33. 0
      code/LICENSE.md
  34. 0
      code/app-config/index.html
  35. 0
      code/app-config/module/controllers/IndexController.php
  36. 0
      code/app-config/module/module.ini
  37. 0
      code/app-config/module/views/scripts/index/diffs.phtml
  38. 0
      code/app-config/module/views/scripts/index/index.phtml
  39. 0
      code/app-config/stack_configuration.json
  40. 60
      config.yaml
  41. 105
      contact/index.html
  42. 1
      css/main.css
  43. 1
      css/pages/homepage.css
  44. 1
      css/posts/embracing-nondeterminism-part-1.css
  45. 1
      css/posts/reasons-why-my-website-is-offline.css
  46. 1
      css/posts/redoing-my-website.css
  47. 1
      drafts/embracing-nondeterminism-part-1/index.html
  48. 766
      drafts/embracing-nondeterminism-part-2/index.html
  49. 1
      drafts/functional-programming/embracing-nondeterminism-part-1/index.html
  50. 1
      drafts/functional-programming/embracing-nondeterminism-with-functors/index.html
  51. 102
      drafts/index.html
  52. 1
      drafts/reasons-why-my-website-is-offline/index.html
  53. BIN
      favicon.ico
  54. 0
      fonts/NotoSans/NotoSans-Bold.ttf
  55. 0
      fonts/NotoSans/NotoSans-BoldItalic.ttf
  56. 0
      fonts/NotoSans/NotoSans-Italic.ttf
  57. 0
      fonts/NotoSans/NotoSans-Regular.ttf
  58. 0
      fonts/NotoSansMono/NotoSansMono-Black.ttf
  59. 0
      fonts/NotoSansMono/NotoSansMono-Bold.ttf
  60. 0
      fonts/NotoSansMono/NotoSansMono-ExtraBold.ttf
  61. 0
      fonts/NotoSansMono/NotoSansMono-ExtraLight.ttf
  62. 0
      fonts/NotoSansMono/NotoSansMono-Light.ttf
  63. 0
      fonts/NotoSansMono/NotoSansMono-Medium.ttf
  64. 0
      fonts/NotoSansMono/NotoSansMono-Regular.ttf
  65. 0
      fonts/NotoSansMono/NotoSansMono-SemiBold.ttf
  66. 0
      fonts/NotoSansMono/NotoSansMono-Thin.ttf
  67. 0
      fonts/NotoSansMono/NotoSansMono-VariableFont_wdth,wght.ttf
  68. 0
      fonts/NotoSerif/NotoSerif-Bold.ttf
  69. 0
      fonts/NotoSerif/NotoSerif-BoldItalic.ttf
  70. 0
      fonts/NotoSerif/NotoSerif-Italic.ttf
  71. 0
      fonts/NotoSerif/NotoSerif-Regular.ttf
  72. 175
      go
  73. 313
      green.cabal
  74. 13
      hie.yaml
  75. 0
      images/about-me/headshot-w-meatball-doge_3024w.png
  76. 0
      images/about-me/headshot-w-meatball-doge_400w.png
  77. 0
      images/about-me/headshot-w-meatball-doge_768w.png
  78. 0
      images/about-me/meatball-n-jelly-doges_3024w.png
  79. 0
      images/about-me/meatball-n-jelly-doges_400w.png
  80. 0
      images/app-config/commit.png
  81. 0
      images/app-config/diffs-after-commit.png
  82. 0
      images/app-config/diffs.png
  83. 0
      images/app-config/history-diffs.png
  84. 0
      images/app-config/history.png
  85. 0
      images/app-config/start.png
  86. 0
      images/app-config2/aardvark_configuration-changed.png
  87. 0
      images/app-config2/aardvark_configuration.png
  88. 0
      images/app-config2/changes.png
  89. 0
      images/app-config2/start.png
  90. 0
      images/app-config3/p4v.png
  91. 0
      images/app-config3/p4v_integrate.png
  92. 0
      images/app-config3/p4v_integrate_result.png
  93. 0
      images/app-config3/pending_changes.png
  94. 0
      images/app-config3/promote_changes.png
  95. 0
      images/app-config3/promote_result.png
  96. 0
      images/embracing-nondeterminism/binary-tree-functor-1024x340.png
  97. 0
      images/embracing-nondeterminism/binary-tree-functor.png
  98. 0
      images/embracing-nondeterminism/list-functor-512x512.png
  99. 0
      images/embracing-nondeterminism/list-functor.png
  100. BIN
      images/grass-1024x1024.png

36
.gitignore

@ -1,36 +0,0 @@
.idea/
*.iml
*.ipr
.stack-work/
_cache/
_site/
*~
dist
dist-*
cabal-dev
*.o
*.hi
*.hie
*.chi
*.chs.h
*.dyn_o
*.dyn_hi
.hpc
.hsenv
.cabal-sandbox/
cabal.sandbox.config
*.prof
*.aux
*.hp
*.eventlog
cabal.project.local
cabal.project.local~
.HTF/
.ghc.environment.*
.sass-cache/
project/
.bsp/
target/
_site/
_test/
gh-pages/

1
.ruby-version

@ -1 +0,0 @@
system

83
404/index.html

@ -0,0 +1,83 @@
<!doctype html>
<html lang="en" prefix="og: https://ogp.me/ns#">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>This Field Was Green - 404 Not Found</title>
<link rel="canonical" href="https://thisfieldwas.green/404/">
<link rel="shortcut icon" sizes="16x16 32x32 48x48 64x64 96x96 128x128 256x256" href="https://thisfieldwas.green/favicon.ico">
<link rel="stylesheet" href="/css/main.css">
<script src="/js/main.js"></script>
<script src="/js/scroll-shadows.js"></script>
<script>
let scrollShadowsXSelectors = ["pre.sourceCode",]
let scrollShadowsYSelectors = []
window.addEventListener("load", () => {
scrollShadowsXSelectors.forEach(updateScrollShadowsXSelector)
scrollShadowsYSelectors.forEach(updateScrollShadowsYSelector)
})
window.addEventListener("resize", () => {
scrollShadowsXSelectors.forEach(updateScrollShadowsXSelector)
})
</script>
</head>
<body>
<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>
<nav class="main-nav">
<a href="/">Home</a>
<a href="/blog/">Blog</a>
<a href="/resume/">Resume</a>
<a href="/contact/">Contact</a>
</nav>
</div>
</div>
</header>
<main class="page-content">
<div class="content-bound">
<div class="">
<h1 class="page-title">404 Not Found</h1>
<div class="estimated-reading-time">
<p>Estimated reading time: <span class="length">3s</span></p>
</div>
<p>False
The field you were looking for has browned and vanished.</p>
</div>
</div>
</main>
<footer class="page-footer">
<div class="content-bound">
<nav class="main-nav">
<a href="/">Home</a>
<a href="/blog/">Blog</a>
<a href="/resume/">Resume</a>
<a href="/contact/">Contact</a>
</nav>
<p class="copyright">Copyright &copy; <span class="copyright-date">2012</span> Logan McGrath. All rights reserved.</p>
<ul class="acks">
<li>Site proudly generated by <a href="http://jaspervdj.be/hakyll">Hakyll</a>.</li>
<li><a href="https://www.flaticon.com/free-icon/grass_2790190">Icon</a> made by <a href="https://www.flaticon.com/authors/good-ware">Good Ware</a> from <a href="https://www.flaticon.com/">Flaticon</a>.</li>
</ul>
<p class="generated">This page was rendered from <a href="https://bitsof.thisfieldwas.green/keywordsalad/thisfieldwas.green/src/commit/a96d7a9/site/_errors/404.md">404.md</a> at commit <a class="commit-link" href="https://bitsof.thisfieldwas.green/keywordsalad/thisfieldwas.green/src/commit/a96d7a9">a96d7a9</a>.</p>
</div>
</footer>
</body>
</html>

82
500/index.html

@ -0,0 +1,82 @@
<!doctype html>
<html lang="en" prefix="og: https://ogp.me/ns#">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>This Field Was Green - 500 Server Error</title>
<link rel="canonical" href="https://thisfieldwas.green/500/">
<link rel="shortcut icon" sizes="16x16 32x32 48x48 64x64 96x96 128x128 256x256" href="https://thisfieldwas.green/favicon.ico">
<link rel="stylesheet" href="/css/main.css">
<script src="/js/main.js"></script>
<script src="/js/scroll-shadows.js"></script>
<script>
let scrollShadowsXSelectors = ["pre.sourceCode",]
let scrollShadowsYSelectors = []
window.addEventListener("load", () => {
scrollShadowsXSelectors.forEach(updateScrollShadowsXSelector)
scrollShadowsYSelectors.forEach(updateScrollShadowsYSelector)
})
window.addEventListener("resize", () => {
scrollShadowsXSelectors.forEach(updateScrollShadowsXSelector)
})
</script>
</head>
<body>
<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>
<nav class="main-nav">
<a href="/">Home</a>
<a href="/blog/">Blog</a>
<a href="/resume/">Resume</a>
<a href="/contact/">Contact</a>
</nav>
</div>
</div>
</header>
<main class="page-content">
<div class="content-bound">
<div class="">
<h1 class="page-title">500 Server Error</h1>
<div class="estimated-reading-time">
<p>Estimated reading time: <span class="length">3s</span></p>
</div>
<p>The field has been caught with its pants brown. It will be green again soon.</p>
</div>
</div>
</main>
<footer class="page-footer">
<div class="content-bound">
<nav class="main-nav">
<a href="/">Home</a>
<a href="/blog/">Blog</a>
<a href="/resume/">Resume</a>
<a href="/contact/">Contact</a>
</nav>
<p class="copyright">Copyright &copy; <span class="copyright-date">2012</span> Logan McGrath. All rights reserved.</p>
<ul class="acks">
<li>Site proudly generated by <a href="http://jaspervdj.be/hakyll">Hakyll</a>.</li>
<li><a href="https://www.flaticon.com/free-icon/grass_2790190">Icon</a> made by <a href="https://www.flaticon.com/authors/good-ware">Good Ware</a> from <a href="https://www.flaticon.com/">Flaticon</a>.</li>
</ul>
<p class="generated">This page was rendered from <a href="https://bitsof.thisfieldwas.green/keywordsalad/thisfieldwas.green/src/commit/a96d7a9/site/_errors/500.md">500.md</a> at commit <a class="commit-link" href="https://bitsof.thisfieldwas.green/keywordsalad/thisfieldwas.green/src/commit/a96d7a9">a96d7a9</a>.</p>
</div>
</footer>
</body>
</html>

5
Brewfile

@ -1,5 +0,0 @@
tap "homebrew/bundle"
tap "homebrew/core"
tap "sass/sass"
brew "haskell-stack"
brew "sass"

109
Brewfile.lock.json

@ -1,109 +0,0 @@
{
"entries": {
"tap": {
"homebrew/bundle": {
"revision": "3b280b116f481e3d596789b7ee93cb4cf5ad23b4"
},
"homebrew/core": {
"revision": "af049caf0ac40679b8cd25b78995f65a883f0a22"
},
"sass/sass": {
"revision": "9aeb8c728d9966e2f2b9c6c7bc591d08d2bd610e"
}
},
"brew": {
"haskell-stack": {
"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:5e9185c5fb43ee4aa892bd5e9460fba19874c741df8cb0791af25ec7dab40575",
"sha256": "5e9185c5fb43ee4aa892bd5e9460fba19874c741df8cb0791af25ec7dab40575"
},
"catalina": {
"cellar": ":any_skip_relocation",
"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: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.43.1",
"bottle": false
},
"make": {
"version": "4.3",
"bottle": {
"rebuild": 1,
"root_url": "https://ghcr.io/v2/homebrew/core",
"files": {
"arm64_big_sur": {
"cellar": "/opt/homebrew/Cellar",
"url": "https://ghcr.io/v2/homebrew/core/make/blobs/sha256:eab3fbc3688aecec0fe90b8d0fe3cb7beb84ed773ba0411fc2f855c66deaf882",
"sha256": "eab3fbc3688aecec0fe90b8d0fe3cb7beb84ed773ba0411fc2f855c66deaf882"
},
"big_sur": {
"cellar": "/usr/local/Cellar",
"url": "https://ghcr.io/v2/homebrew/core/make/blobs/sha256:2019ba646e4471d42e09c28a0992c59dd82e292bf8275b0b3bfcce3220ef9c1b",
"sha256": "2019ba646e4471d42e09c28a0992c59dd82e292bf8275b0b3bfcce3220ef9c1b"
},
"catalina": {
"cellar": "/usr/local/Cellar",
"url": "https://ghcr.io/v2/homebrew/core/make/blobs/sha256:39fc5ebff5ff708c2e3eea597b9f2eb79b910a122d30c3ac9bb93ebe313f030c",
"sha256": "39fc5ebff5ff708c2e3eea597b9f2eb79b910a122d30c3ac9bb93ebe313f030c"
},
"mojave": {
"cellar": "/usr/local/Cellar",
"url": "https://ghcr.io/v2/homebrew/core/make/blobs/sha256:0c0a08eef68bcd78b0345f5f57a6efffcc7be877bcb3b803f39ac8916b882477",
"sha256": "0c0a08eef68bcd78b0345f5f57a6efffcc7be877bcb3b803f39ac8916b882477"
},
"high_sierra": {
"cellar": "/usr/local/Cellar",
"url": "https://ghcr.io/v2/homebrew/core/make/blobs/sha256:429177235322c3209e1657bea36364cd84222075b636939f6ed93a1cd04aeb21",
"sha256": "429177235322c3209e1657bea36364cd84222075b636939f6ed93a1cd04aeb21"
}
}
}
}
}
},
"system": {
"macos": {
"catalina": {
"HOMEBREW_VERSION": "3.1.2",
"HOMEBREW_PREFIX": "/usr/local",
"Homebrew/homebrew-core": "c64afbfd40223456aedcc37e60873ac5d5f1a409",
"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"
}
}
}
}

22
README.md

@ -1,22 +0,0 @@
# Readme
## Setup
Run the following command to set up the necessary dependencies:
```shell
make init
```
### Troubleshooting:
**`stack install hakyll` fails on macOS:**
If this error occurs:
`hakyll> <command line>: can't load framework: Cocoa (not found)`
Follow [this workaround](https://github.com/yairchu/macos11-haskell-workaround/) to build `macos11ghcwa.dylib` and then re-run the
command with the path to the library:
```
DYLD_INSERT_LIBRARIES="<PATH_TO>/macos11ghcwa.dylib" stack install hakyll
```

1
about-me/index.html

@ -0,0 +1 @@
<!DOCTYPE html><html><head><meta charset="utf-8"/><meta name="generator" content="hakyll"/><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="refresh" content="0; url=/"><link rel="canonical" href="/"><title>Permanent Redirect</title></head><body><p>The page has moved to: <a href="/">this page</a></p></body></html>

4
app/author/Main.hs

@ -1,4 +0,0 @@
import Green
main :: IO ()
main = authorMain

4
app/site/Main.hs

@ -1,4 +0,0 @@
import Green
main :: IO ()
main = siteMain

222
archives/index.html

@ -0,0 +1,222 @@
<!doctype html>
<html lang="en" prefix="og: https://ogp.me/ns#">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>This Field Was Green - These Posts Are Still Green</title>
<link rel="canonical" href="https://thisfieldwas.green/archives/">
<link rel="shortcut icon" sizes="16x16 32x32 48x48 64x64 96x96 128x128 256x256" href="https://thisfieldwas.green/favicon.ico">
<meta property="og:site_name" content="">
<meta property="og:title" content="These Posts Are Still Green">
<meta property="og:url" content="https://thisfieldwas.green/archives/">
<meta property="og:description" content="">
<meta property="og:image:url" content="https://thisfieldwas.green/images/grass-256x256.png">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@thisgreenfield">
<meta name="twitter:title" content="These Posts Are Still Green">
<link rel="stylesheet" href="/css/main.css">
<script src="/js/main.js"></script>
<script src="/js/scroll-shadows.js"></script>
<script>
let scrollShadowsXSelectors = ["pre.sourceCode",]
let scrollShadowsYSelectors = []
window.addEventListener("load", () => {
scrollShadowsXSelectors.forEach(updateScrollShadowsXSelector)
scrollShadowsYSelectors.forEach(updateScrollShadowsYSelector)
})
window.addEventListener("resize", () => {
scrollShadowsXSelectors.forEach(updateScrollShadowsXSelector)
})
</script>
</head>
<body>
<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>
<nav class="main-nav">
<a href="/">Home</a>
<a href="/blog/">Blog</a>
<a href="/resume/">Resume</a>
<a href="/contact/">Contact</a>
</nav>
</div>
</div>
</header>
<main class="page-content">
<div class="content-bound">
<div class="">
<h1 class="page-title">These Posts Are Still Green</h1>
Here you can find all my previous posts:
<ul class="post-list">
<li>
<p class="post-title">
<strong><a href="/blog/2022/03/15/embracing-nondeterminism-part-1/">Embracing Nondeterminism Part I: Contexts and Effects</a></strong>
</p>
<div class="post-meta">
<p class="post-published">
Posted on March 15, 2022
by Logan McGrath
</p>
<p class="post-updated">
Last updated on May 15, 2022
</p>
<p class="post-tags">
Tags: <a class="tag" href="/tags/functional-programming">functional programming</a>, <a class="tag" href="/tags/programming">programming</a>, <a class="tag" href="/tags/scala">scala</a>, <a class="tag" href="/tags/design-patterns">design patterns</a>
</p>
</div>
<li>
<p class="post-title">
<strong><a href="/blog/2022/02/09/persistent-ssh-tunnels/">Highly available ssh tunnels</a></strong>
</p>
<div class="post-meta">
<p class="post-published">
Posted on February 9, 2022
by Logan McGrath
</p>
<p class="post-tags">
Tags: <a class="tag" href="/tags/self-host">self host</a>, <a class="tag" href="/tags/duct-tape">duct tape</a>, <a class="tag" href="/tags/yak-shave">yak shave</a>
</p>
</div>
<li>
<p class="post-title">
<strong><a href="/blog/2021/12/11/reasons-why-my-website-is-offline/">Reasons why my website is offline</a></strong>
</p>
<div class="post-meta">
<p class="post-published">
Posted on December 11, 2021
by Logan McGrath
</p>
<p class="post-tags">
Tags: <a class="tag" href="/tags/self-host">self host</a>, <a class="tag" href="/tags/configuration-management">configuration management</a>, <a class="tag" href="/tags/tinfoil-hat">tinfoil hat</a>, <a class="tag" href="/tags/duct-tape">duct tape</a>, <a class="tag" href="/tags/yak-shave">yak shave</a>
</p>
</div>
<li>
<p class="post-title">
<strong><a href="/blog/2021/12/05/redoing-my-website/">Redoing my website with Haskell and Hakyll</a></strong>
</p>
<div class="post-meta">
<p class="post-published">
Posted on December 5, 2021
by Logan McGrath
</p>
<p class="post-tags">
Tags: <a class="tag" href="/tags/hakyll">hakyll</a>, <a class="tag" href="/tags/haskell">haskell</a>, <a class="tag" href="/tags/yak-shave">yak shave</a>, <a class="tag" href="/tags/programming">programming</a>
</p>
</div>
<li>
<p class="post-title">
<strong><a href="/blog/2012/11/28/promoting-changes-with-app-config-app/">Promoting changes with App-Config-App</a></strong>
</p>
<div class="post-meta">
<p class="post-published">
Posted on November 28, 2012
by Logan McGrath
</p>
<p class="post-tags">
Tags: <a class="tag" href="/tags/angularjs">angularjs</a>, <a class="tag" href="/tags/perforce">perforce</a>, <a class="tag" href="/tags/sinatra">sinatra</a>, <a class="tag" href="/tags/configuration-management">configuration management</a>
</p>
</div>
<li>
<p class="post-title">
<strong><a href="/blog/2012/11/20/app-config-app-in-action/">App-Config-App in Action</a></strong>
</p>
<div class="post-meta">
<p class="post-published">
Posted on November 20, 2012
by Logan McGrath
</p>
<p class="post-tags">
Tags: <a class="tag" href="/tags/angularjs">angularjs</a>, <a class="tag" href="/tags/perforce">perforce</a>, <a class="tag" href="/tags/sinatra">sinatra</a>, <a class="tag" href="/tags/configuration-management">configuration management</a>
</p>
</div>
<li>
<p class="post-title">
<strong><a href="/blog/2012/11/16/scm-backed-application-configuration-with-perforce/">SCM-Backed Application Configuration with Perforce</a></strong>
</p>
<div class="post-meta">
<p class="post-published">
Posted on November 16, 2012
by Logan McGrath
</p>
<p class="post-tags">
Tags: <a class="tag" href="/tags/configuration-management">configuration management</a>, <a class="tag" href="/tags/perforce">perforce</a>, <a class="tag" href="/tags/sinatra">sinatra</a>, <a class="tag" href="/tags/angularjs">angularjs</a>
</p>
</div>
<li>
<p class="post-title">
<strong><a href="/blog/2012/11/07/using-perforce-chronicle-for-application-configuration/">Using Perforce Chronicle for application configuration</a></strong>
</p>
<div class="post-meta">
<p class="post-published">
Posted on November 7, 2012
by Logan McGrath
</p>
<p class="post-tags">
Tags: <a class="tag" href="/tags/perforce">perforce</a>, <a class="tag" href="/tags/configuration-management">configuration management</a>
</p>
</div>
</li>
</ul>
</div>
</div>
</main>
<footer class="page-footer">
<div class="content-bound">
<nav class="main-nav">
<a href="/">Home</a>
<a href="/blog/">Blog</a>
<a href="/resume/">Resume</a>
<a href="/contact/">Contact</a>
</nav>
<p class="copyright">Copyright &copy; <span class="copyright-date">2012</span> Logan McGrath. All rights reserved.</p>
<ul class="acks">
<li>Site proudly generated by <a href="http://jaspervdj.be/hakyll">Hakyll</a>.</li>
<li><a href="https://www.flaticon.com/free-icon/grass_2790190">Icon</a> made by <a href="https://www.flaticon.com/authors/good-ware">Good Ware</a> from <a href="https://www.flaticon.com/">Flaticon</a>.</li>
</ul>
<p class="generated">This page was rendered from <a href="https://bitsof.thisfieldwas.green/keywordsalad/thisfieldwas.green/src/commit/9bc36dd/site/archives.html">archives.html</a> at commit <a class="commit-link" href="https://bitsof.thisfieldwas.green/keywordsalad/thisfieldwas.green/src/commit/9bc36dd">9bc36dd</a>.</p>
</div>
</footer>
</body>
</html>

1954
atom.xml

File diff suppressed because it is too large

4
bin/author

@ -1,4 +0,0 @@
#!/usr/bin/env bash
make build
stack exec author -- "$@"

4
bin/site

@ -1,4 +0,0 @@
#!/usr/bin/env bash
make build
stack exec site "$@"

562
blog/2012/11/07/using-perforce-chronicle-for-application-configuration/index.html

@ -0,0 +1,562 @@
<!doctype html>
<html lang="en" prefix="og: https://ogp.me/ns#">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>This Field Was Green - Using Perforce Chronicle for application configuration</title>
<link rel="canonical" href="https://thisfieldwas.green/blog/2012/11/07/using-perforce-chronicle-for-application-configuration/">
<link rel="shortcut icon" sizes="16x16 32x32 48x48 64x64 96x96 128x128 256x256" href="https://thisfieldwas.green/favicon.ico">
<meta property="og:site_name" content="">
<meta property="og:title" content="Using Perforce Chronicle for application configuration">
<meta property="og:url" content="https://thisfieldwas.green/blog/2012/11/07/using-perforce-chronicle-for-application-configuration/">
<meta property="og:description" content="">
<meta property="og:image:url" content="https://thisfieldwas.green/images/grass-256x256.png">
<meta property="article:published_time" content="2012-11-07T13:54:00-06:00">
<meta property="article:modified_time" content="2012-11-07T13:54:00-06:00">
<meta property="article:author" content="Logan McGrath">
<meta property="article:tag" content="perforce">
<meta property="article:tag" content="configuration management">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@thisgreenfield">
<meta name="twitter:title" content="Using Perforce Chronicle for application configuration">
<link rel="stylesheet" href="/css/main.css">
<script src="/js/main.js"></script>
<script src="/js/scroll-shadows.js"></script>
<script>
let scrollShadowsXSelectors = ["pre.sourceCode",]
let scrollShadowsYSelectors = []
window.addEventListener("load", () => {
scrollShadowsXSelectors.forEach(updateScrollShadowsXSelector)
scrollShadowsYSelectors.forEach(updateScrollShadowsYSelector)
})
window.addEventListener("resize", () => {
scrollShadowsXSelectors.forEach(updateScrollShadowsXSelector)
})
</script>
</head>
<body>
<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>
<nav class="main-nav">
<a href="/">Home</a>
<a href="/blog/">Blog</a>
<a href="/resume/">Resume</a>
<a href="/contact/">Contact</a>
</nav>
</div>
</div>
</header>
<main class="page-content">
<div class="content-bound">
<div class="post post-full">
<h1 class="post-title">Using Perforce Chronicle for application configuration</h1>
<div class="post-meta">
<p class="post-published">
Posted on November 7, 2012
by Logan McGrath
</p>
<p class="post-tags">
Tags: <a class="tag" href="/tags/perforce">perforce</a>, <a class="tag" href="/tags/configuration-management">configuration management</a>
</p>
</div>
<div class="estimated-reading-time">
<p>Estimated reading time: <span class="length">15m 56s</span></p>
</div>
<p>Following Paul Hammant’s post <a href="http://paulhammant.com/2012/07/10/app-config-workflow-using-scm/">App-config workflow using SCM</a> and subsequent
<a href="http://paulhammant.com/2012/08/14/app-config-using-git-and-angular/">proof of concept</a> backed by Git, I will show that an app-config application
backed by Perforce is possible using <a href="http://www.perforce.com/products/chronicle">Perforce Chronicle</a>.</p>
<!--more-->
<h2 id="perforce-and-permissions-for-branches">Perforce and permissions for branches</h2>
<p><a href="http://en.wikipedia.org/wiki/Perforce">Perforce</a> is an enterprise-class source control management (SCM) system,
remarkably similar to Subversion (Subversion was inspired by Perforce :)
Perforce is more bulletproof than Subversion in many ways and it’s generally
faster. Git does not impose any security constraints or permissions on branches,
Perforce gives comprehensive security options allowing you to control access to
different branches: for example, development, staging, and production.
Subversion, however, can support permissions on branches with some extra
configuration (Apache plus mod_dav_svn/mod_dav_authz). For these reasons,
Perforce is a better option for storing configuration data than either Git or
Subversion.</p>
<h2 id="perforce-cms-as-an-application-server">Perforce CMS as an application server</h2>
<p><a href="http://www.perforce.com/products/chronicle">Perforce Chronicle</a> is a content management system (CMS) using Perforce as
the back-end store for configuration and content. The app-config application is
built on top of Chronicle because Perforce does not offer a web view into the
depot the way Subversion can through Apache. Branching and maintaining
divergence between environments can be managed through the user interface, and
Chronicle provides user authentication and management, so access between
different configuration files can be restricted appropriately. The INSTALL.txt
file that is distributed with Chronicle helps with an easy install, mine being
set up to run locally from <code>http://localhost</code>.</p>
<p>There is a key issue in using Chronicle, however. The system is designed for the
management of <em>content</em> and not necessarily arbitrary <em>files</em>. In order to make
the app-config application work, I had to add a custom content type and write a
module. Configuration and HTML are both plain-text content, so I created a ”
Plain Text” content type with the fields <em>title</em> and <em>content</em>:</p>
<ol type="1">
<li>Go to “Manage” &gt; “Content Types”</li>
<li>Click “Add Content Type”</li>
<li>Enter the following information:</li>
</ol>
<div class="sourceCode" id="cb1"><pre class="sourceCode numberSource ini numberLines"><code class="sourceCode ini"><span id="cb1-1"><a href="#cb1-1"></a><span class="dt">Id: plaintext</span></span>
<span id="cb1-2"><a href="#cb1-2"></a><span class="dt">Label: Plain Text</span></span>
<span id="cb1-3"><a href="#cb1-3"></a><span class="dt">Group: Assets</span></span>
<span id="cb1-4"><a href="#cb1-4"></a><span class="dt">Elements:</span></span>
<span id="cb1-5"><a href="#cb1-5"></a></span>
<span id="cb1-6"><a href="#cb1-6"></a><span class="kw">[title]</span></span>
<span id="cb1-7"><a href="#cb1-7"></a><span class="dt">type </span><span class="ot">=</span><span class="st"> text</span></span>
<span id="cb1-8"><a href="#cb1-8"></a><span class="dt">options.label </span><span class="ot">=</span><span class="st"> Title</span></span>
<span id="cb1-9"><a href="#cb1-9"></a><span class="dt">options.required </span><span class="ot">=</span><span class="st"> </span><span class="kw">true</span></span>
<span id="cb1-10"><a href="#cb1-10"></a><span class="dt">display.tagName </span><span class="ot">=</span><span class="st"> h1</span></span>
<span id="cb1-11"><a href="#cb1-11"></a><span class="dt">display.filters.0 </span><span class="ot">=</span><span class="st"> HtmlSpecialChars</span></span>
<span id="cb1-12"><a href="#cb1-12"></a></span>
<span id="cb1-13"><a href="#cb1-13"></a><span class="kw">[content]</span></span>
<span id="cb1-14"><a href="#cb1-14"></a><span class="dt">type </span><span class="ot">=</span><span class="st"> textarea</span></span>
<span id="cb1-15"><a href="#cb1-15"></a><span class="dt">options.label </span><span class="ot">=</span><span class="st"> &quot;Content&quot;</span></span>
<span id="cb1-16"><a href="#cb1-16"></a><span class="dt">options.required </span><span class="ot">=</span><span class="st"> </span><span class="kw">true</span></span>
<span id="cb1-17"><a href="#cb1-17"></a><span class="dt">display.tagName </span><span class="ot">=</span><span class="st"> pre</span></span>
<span id="cb1-18"><a href="#cb1-18"></a><span class="dt">display.filters.0 </span><span class="ot">=</span><span class="st"> HtmlSpecialChars</span></span></code></pre></div>
<p>Click “Save”.</p>
<h2 id="the-config-app">The Config App</h2>
<p>I’ve borrowed heavily from Paul’s <a href="https://github.com/paul-hammant/app-config-app/blob/master/index.html">app-config HTML page</a>, which uses
<a href="http://angularjs.org/">AngularJS</a> to manage the UI and interaction with the server. Where Paul’s
app-config app used the <a href="http://kmkeen.com/jshon/">jshon</a> command to encode and decode JSON, Zend
Framework has a utility class for encoding, decoding, and pretty-printing JSON,
and Chronicle also ships with the <a href="https://github.com/paulgb/simplediff/">simplediff</a> utility for performing diffs
with PHP.</p>
<p>The source JSON configuration is the same, albeit sorted:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode numberSource json numberLines"><code class="sourceCode json"><span id="cb2-1"><a href="#cb2-1"></a><span class="fu">{</span></span>
<span id="cb2-2"><a href="#cb2-2"></a> <span class="dt">&quot;bannedNicks&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb2-3"><a href="#cb2-3"></a> <span class="st">&quot;derek&quot;</span><span class="ot">,</span></span>
<span id="cb2-4"><a href="#cb2-4"></a> <span class="st">&quot;dino&quot;</span><span class="ot">,</span></span>
<span id="cb2-5"><a href="#cb2-5"></a> <span class="st">&quot;ffff&quot;</span><span class="ot">,</span></span>
<span id="cb2-6"><a href="#cb2-6"></a> <span class="st">&quot;jjjj&quot;</span><span class="ot">,</span></span>
<span id="cb2-7"><a href="#cb2-7"></a> <span class="st">&quot;werwer&quot;</span></span>
<span id="cb2-8"><a href="#cb2-8"></a> <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb2-9"><a href="#cb2-9"></a> <span class="dt">&quot;defaultErrorReciever&quot;</span><span class="fu">:</span> <span class="st">&quot;piglet@thoughtworks.com&quot;</span><span class="fu">,</span></span>
<span id="cb2-10"><a href="#cb2-10"></a> <span class="dt">&quot;lighton&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb2-11"><a href="#cb2-11"></a> <span class="dt">&quot;loadMaxPercent&quot;</span><span class="fu">:</span> <span class="st">&quot;88&quot;</span><span class="fu">,</span></span>
<span id="cb2-12"><a href="#cb2-12"></a> <span class="dt">&quot;nextShutdownDate&quot;</span><span class="fu">:</span> <span class="st">&quot;8</span><span class="ch">\/</span><span class="st">9</span><span class="ch">\/</span><span class="st">2012&quot;</span></span>
<span id="cb2-13"><a href="#cb2-13"></a><span class="fu">}</span></span></code></pre></div>
<p>The <code>index.html</code> page has been modified from the original to support only the
basic <em>commit</em> and <em>diffs</em> functionality:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode numberSource html numberLines"><code class="sourceCode html"><span id="cb3-1"><a href="#cb3-1"></a><span class="dt">&lt;!DOCTYPE </span>html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot;</span>
<span id="cb3-2"><a href="#cb3-2"></a> &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;<span class="dt">&gt;</span></span>
<span id="cb3-3"><a href="#cb3-3"></a><span class="kw">&lt;html</span> <span class="er">lang</span><span class="ot">=</span><span class="st">&quot;en&quot;</span> <span class="er">xmlns:ng</span><span class="ot">=</span><span class="st">&quot;http://angularjs.org&quot;</span><span class="kw">&gt;</span></span>
<span id="cb3-4"><a href="#cb3-4"></a><span class="kw">&lt;head&gt;</span></span>
<span id="cb3-5"><a href="#cb3-5"></a><span class="kw">&lt;meta</span> <span class="er">http-equiv</span><span class="ot">=</span><span class="st">&quot;content-type&quot;</span> <span class="er">content</span><span class="ot">=</span><span class="st">&quot;text/html; charset=UTF-8&quot;</span><span class="kw">&gt;</span></span>
<span id="cb3-6"><a href="#cb3-6"></a> <span class="kw">&lt;title&gt;</span>Configuration application (alpha)<span class="kw">&lt;/title&gt;</span></span>
<span id="cb3-7"><a href="#cb3-7"></a> <span class="kw">&lt;script</span><span class="ot"> type=</span><span class="st">&quot;text/javascript&quot;</span> <span class="er">ng:autobind</span> <span class="er">src</span><span class="ot">=</span><span class="st">&quot;http://code.angularjs.org/0.9.19/angular-0.9.19.min.js&quot;</span><span class="kw">&gt;&lt;/script&gt;</span></span>
<span id="cb3-8"><a href="#cb3-8"></a> <span class="kw">&lt;style</span> <span class="er">type</span><span class="ot">=</span><span class="st">&quot;text/css&quot;</span><span class="kw">&gt;</span></span>
<span id="cb3-9"><a href="#cb3-9"></a> ins { <span class="kw">color</span>: <span class="cn">#00CC00</span><span class="op">;</span> <span class="kw">text-decoration</span>: <span class="dv">none</span><span class="op">;</span> }</span>
<span id="cb3-10"><a href="#cb3-10"></a> del { <span class="kw">color</span>: <span class="cn">#CC0000</span><span class="op">;</span> <span class="kw">text-decoration</span>: <span class="dv">none</span><span class="op">;</span> }</span>
<span id="cb3-11"><a href="#cb3-11"></a> <span class="kw">&lt;/style&gt;</span></span>
<span id="cb3-12"><a href="#cb3-12"></a><span class="kw">&lt;/head&gt;</span></span>
<span id="cb3-13"><a href="#cb3-13"></a><span class="kw">&lt;body</span> <span class="er">ng:controller</span><span class="ot">=</span><span class="st">&quot;AppCfg&quot;</span><span class="kw">&gt;</span></span>
<span id="cb3-14"><a href="#cb3-14"></a><span class="kw">&lt;script</span><span class="ot"> type=</span><span class="st">&quot;text/javascript&quot;</span><span class="kw">&gt;</span></span>
<span id="cb3-15"><a href="#cb3-15"></a> <span class="kw">function</span> <span class="fu">AppCfg</span>($resource<span class="op">,</span> $xhr) {</span>
<span id="cb3-16"><a href="#cb3-16"></a> <span class="kw">var</span> self <span class="op">=</span> <span class="kw">this</span><span class="op">;</span></span>
<span id="cb3-17"><a href="#cb3-17"></a> <span class="kw">this</span><span class="op">.</span><span class="at">newNickname</span> <span class="op">=</span> <span class="st">&quot;&quot;</span><span class="op">;</span></span>
<span id="cb3-18"><a href="#cb3-18"></a> <span class="kw">this</span><span class="op">.</span><span class="at">svrMessage</span><span class="op">;</span></span>
<span id="cb3-19"><a href="#cb3-19"></a> <span class="kw">this</span><span class="op">.</span><span class="at">message</span><span class="op">;</span></span>
<span id="cb3-20"><a href="#cb3-20"></a> <span class="kw">this</span><span class="op">.</span><span class="at">cfg</span> <span class="op">=</span> <span class="fu">$resource</span>(<span class="st">&quot;/appconfig/stack_configuration.json&quot;</span>)<span class="op">.</span><span class="fu">get</span>({})<span class="op">;</span></span>
<span id="cb3-21"><a href="#cb3-21"></a></span>
<span id="cb3-22"><a href="#cb3-22"></a> <span class="kw">this</span><span class="op">.</span><span class="at">save</span> <span class="op">=</span> <span class="kw">function</span>() {</span>
<span id="cb3-23"><a href="#cb3-23"></a> self<span class="op">.</span><span class="at">cfg</span><span class="op">.</span><span class="fu">$save</span>({<span class="dt">message</span><span class="op">:</span> self<span class="op">.</span><span class="at">message</span>}<span class="op">,</span> <span class="kw">function</span>() {</span>
<span id="cb3-24"><a href="#cb3-24"></a> <span class="fu">alert</span>(<span class="st">&quot;Config saved to server&quot;</span>)<span class="op">;</span></span>
<span id="cb3-25"><a href="#cb3-25"></a> }<span class="op">,</span> <span class="kw">function</span>() {</span>
<span id="cb3-26"><a href="#cb3-26"></a> <span class="fu">alert</span>(<span class="st">&quot;ERROR on save&quot;</span>)<span class="op">;</span></span>
<span id="cb3-27"><a href="#cb3-27"></a> })<span class="op">;</span></span>
<span id="cb3-28"><a href="#cb3-28"></a> self<span class="op">.</span><span class="at">message</span> <span class="op">=</span> <span class="st">&quot;&quot;</span><span class="op">;</span></span>
<span id="cb3-29"><a href="#cb3-29"></a> }<span class="op">;</span></span>
<span id="cb3-30"><a href="#cb3-30"></a></span>
<span id="cb3-31"><a href="#cb3-31"></a> <span class="kw">this</span><span class="op">.</span><span class="at">newNick</span> <span class="op">=</span> <span class="kw">function</span>() {</span>
<span id="cb3-32"><a href="#cb3-32"></a> self<span class="op">.</span><span class="at">cfg</span><span class="op">.</span><span class="at">bannedNicks</span><span class="op">.</span><span class="fu">push</span>(self<span class="op">.</span><span class="at">newNickname</span>)<span class="op">;</span></span>
<span id="cb3-33"><a href="#cb3-33"></a> self<span class="op">.</span><span class="at">newNickname</span> <span class="op">=</span> <span class="st">&quot;&quot;</span><span class="op">;</span></span>
<span id="cb3-34"><a href="#cb3-34"></a> }<span class="op">;</span></span>
<span id="cb3-35"><a href="#cb3-35"></a></span>
<span id="cb3-36"><a href="#cb3-36"></a> <span class="kw">this</span><span class="op">.</span><span class="at">diffs</span> <span class="op">=</span> <span class="kw">function</span>() {</span>
<span id="cb3-37"><a href="#cb3-37"></a> <span class="fu">$xhr</span>(<span class="st">&quot;post&quot;</span><span class="op">,</span> <span class="st">&quot;/appconfig/diffs/stack_configuration.json&quot;</span><span class="op">,</span> angular<span class="op">.</span><span class="fu">toJson</span>(self<span class="op">.</span><span class="at">cfg</span>)<span class="op">,</span> <span class="kw">function</span>(code<span class="op">,</span> svrMessage) {</span>
<span id="cb3-38"><a href="#cb3-38"></a> self<span class="op">.</span><span class="at">svrMessage</span> <span class="op">=</span> svrMessage<span class="op">;</span></span>
<span id="cb3-39"><a href="#cb3-39"></a> })<span class="op">;</span></span>
<span id="cb3-40"><a href="#cb3-40"></a> }<span class="op">;</span></span>
<span id="cb3-41"><a href="#cb3-41"></a></span>
<span id="cb3-42"><a href="#cb3-42"></a> <span class="kw">this</span><span class="op">.</span><span class="at">deleteNick</span> <span class="op">=</span> <span class="kw">function</span>(nick) {</span>
<span id="cb3-43"><a href="#cb3-43"></a> <span class="kw">var</span> oldBannedNicks <span class="op">=</span> self<span class="op">.</span><span class="at">cfg</span><span class="op">.</span><span class="at">bannedNicks</span><span class="op">;</span></span>
<span id="cb3-44"><a href="#cb3-44"></a> self<span class="op">.</span><span class="at">cfg</span><span class="op">.</span><span class="at">bannedNicks</span> <span class="op">=</span> []<span class="op">;</span></span>
<span id="cb3-45"><a href="#cb3-45"></a> angular<span class="op">.</span><span class="fu">forEach</span>(oldBannedNicks<span class="op">,</span> <span class="kw">function</span>(n) {</span>
<span id="cb3-46"><a href="#cb3-46"></a> <span class="cf">if</span> (nick <span class="op">!=</span> n) {</span>
<span id="cb3-47"><a href="#cb3-47"></a> self<span class="op">.</span><span class="at">cfg</span><span class="op">.</span><span class="at">bannedNicks</span><span class="op">.</span><span class="fu">push</span>(n)<span class="op">;</span></span>
<span id="cb3-48"><a href="#cb3-48"></a> }</span>
<span id="cb3-49"><a href="#cb3-49"></a> })<span class="op">;</span></span>
<span id="cb3-50"><a href="#cb3-50"></a> }<span class="op">;</span></span>
<span id="cb3-51"><a href="#cb3-51"></a> }</span>
<span id="cb3-52"><a href="#cb3-52"></a></span>
<span id="cb3-53"><a href="#cb3-53"></a> AppCfg<span class="op">.</span><span class="at">$inject</span> <span class="op">=</span> [<span class="st">&quot;$resource&quot;</span><span class="op">,</span> <span class="st">&quot;$xhr&quot;</span>]<span class="op">;</span></span>
<span id="cb3-54"><a href="#cb3-54"></a><span class="kw">&lt;/script&gt;</span></span>
<span id="cb3-55"><a href="#cb3-55"></a> Light is on: <span class="kw">&lt;input</span> <span class="er">type</span><span class="ot">=</span><span class="st">&quot;checkbox&quot;</span> <span class="er">name</span><span class="ot">=</span><span class="st">&quot;cfg.lighton&quot;</span><span class="kw">/&gt;</span> <span class="kw">&lt;br/&gt;</span></span>
<span id="cb3-56"><a href="#cb3-56"></a> Default Error Reciever (email): <span class="kw">&lt;input</span> <span class="er">name</span><span class="ot">=</span><span class="st">&quot;cfg.defaultErrorReciever&quot;</span> <span class="er">ng:validate</span><span class="ot">=</span><span class="st">&quot;email&quot;</span><span class="kw">/&gt;</span> <span class="kw">&lt;br/&gt;</span></span>
<span id="cb3-57"><a href="#cb3-57"></a> Max Load Percentage: <span class="kw">&lt;input</span> <span class="er">name</span><span class="ot">=</span><span class="st">&quot;cfg.loadMaxPercent&quot;</span> <span class="er">ng:validate</span><span class="ot">=</span><span class="st">&quot;number:0:100&quot;</span><span class="kw">/&gt;</span> <span class="kw">&lt;br/&gt;</span></span>
<span id="cb3-58"><a href="#cb3-58"></a> Next Shutdown Date: <span class="kw">&lt;input</span> <span class="er">name</span><span class="ot">=</span><span class="st">&quot;cfg.nextShutdownDate&quot;</span> <span class="er">ng:validate</span><span class="ot">=</span><span class="st">&quot;date&quot;</span><span class="kw">/&gt;</span> <span class="kw">&lt;br/&gt;</span></span>
<span id="cb3-59"><a href="#cb3-59"></a> Banned nicks:</span>
<span id="cb3-60"><a href="#cb3-60"></a> <span class="kw">&lt;ol&gt;</span></span>
<span id="cb3-61"><a href="#cb3-61"></a> <span class="kw">&lt;li</span> <span class="er">ng:repeat</span><span class="ot">=</span><span class="st">&quot;nick in cfg.bannedNicks&quot;</span><span class="kw">&gt;&lt;span&gt;</span>{{nick}} <span class="dv">&amp;nbsp;&amp;nbsp;</span><span class="kw">&lt;a</span> <span class="er">ng:click</span><span class="ot">=</span><span class="st">&quot;deleteNick(nick)&quot;</span><span class="kw">&gt;</span>[X]<span class="kw">&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;</span></span>
<span id="cb3-62"><a href="#cb3-62"></a> <span class="kw">&lt;/ol&gt;</span></span>
<span id="cb3-63"><a href="#cb3-63"></a> <span class="kw">&lt;form</span> <span class="er">ng:submit</span><span class="ot">=</span><span class="st">&quot;newNick()&quot;</span><span class="kw">&gt;</span></span>
<span id="cb3-64"><a href="#cb3-64"></a> <span class="kw">&lt;input</span> <span class="er">type</span><span class="ot">=</span><span class="st">&quot;text&quot;</span> <span class="er">name</span><span class="ot">=</span><span class="st">&quot;newNickname&quot;</span> <span class="er">size</span><span class="ot">=</span><span class="st">&quot;20&quot;</span><span class="kw">/&gt;</span></span>
<span id="cb3-65"><a href="#cb3-65"></a> <span class="kw">&lt;input</span> <span class="er">type</span><span class="ot">=</span><span class="st">&quot;submit&quot;</span> <span class="er">value</span><span class="ot">=</span><span class="st">&quot;</span><span class="dv">&amp;lt;</span><span class="st">-- Add Nick&quot;</span><span class="kw">/&gt;&lt;br/&gt;</span></span>
<span id="cb3-66"><a href="#cb3-66"></a> <span class="kw">&lt;/form&gt;</span></span>
<span id="cb3-67"><a href="#cb3-67"></a> <span class="kw">&lt;hr/&gt;</span></span>
<span id="cb3-68"><a href="#cb3-68"></a> <span class="kw">&lt;button</span> <span class="er">ng:click</span><span class="ot">=</span><span class="st">&quot;diffs()&quot;</span><span class="kw">&gt;</span>View Diffs<span class="kw">&lt;/button&gt;&lt;br/&gt;</span></span>
<span id="cb3-69"><a href="#cb3-69"></a> <span class="kw">&lt;button</span> <span class="er">ng:disabled</span><span class="ot">=</span><span class="st">&quot;{{!message}}&quot;</span> <span class="er">ng:click</span><span class="ot">=</span><span class="st">&quot;save()&quot;</span><span class="kw">&gt;</span>Commit Changes<span class="kw">&lt;/button&gt;</span> Commit Message: <span class="kw">&lt;input</span> <span class="er">name</span><span class="ot">=</span><span class="st">&quot;message&quot;</span><span class="kw">&gt;&lt;/button&gt;&lt;br/&gt;</span></span>
<span id="cb3-70"><a href="#cb3-70"></a> Last Server operation: <span class="kw">&lt;br/&gt;</span></span>
<span id="cb3-71"><a href="#cb3-71"></a> <span class="kw">&lt;div</span> <span class="er">ng:bind</span><span class="ot">=</span><span class="st">&quot;svrMessage | html:&#39;unsafe&#39;&quot;</span><span class="kw">&gt;</span></span>
<span id="cb3-72"><a href="#cb3-72"></a> <span class="kw">&lt;/div&gt;</span></span>
<span id="cb3-73"><a href="#cb3-73"></a><span class="kw">&lt;/body&gt;</span></span>
<span id="cb3-74"><a href="#cb3-74"></a><span class="kw">&lt;/html&gt;</span></span></code></pre></div>
<p>Both of these assets were added by performing:</p>
<ol type="1">
<li>Click “Add” from the top navbar</li>
<li>Click “Add Content”</li>
<li>Select “Assets” &gt; “Plain Text”</li>
<li>For “Title”, enter “<code>index.html</code>” or “<code>stack_configuration.json</code></li>
<li>Paste in the appropriate “Content”</li>
<li>Click “URL”, select “Custom”, and enter the same value as “Title” (otherwise,
Chronicle will convert underscores to dashes, so be careful!)</li>
<li>Click “Save”, enter a commit message, then click the next “Save”</li>
<li>Both assets should be viewable as mangled Chronicle content entries
from <code>http://localhost/index.html</code>
and <code>http://localhost/stack_configuration.json</code>. <em>You normally will not use
these URLs</em>.</li>
</ol>
<p>At this point, neither asset is actually usable. Most content is heavily
decorated with additional HTML and then displayed within a layout template, but
I want both the <code>index.html</code> and <code>stack_configuration.json</code> assets to be
viewable as standalone files and provide a REST interface for AngularJS to work
against.</p>
<h2 id="come-back-php-all-is-forgiven">Come back PHP! All is forgiven</h2>
<p>Chronicle is largely built using <a href="http://framework.zend.com/">Zend Framework</a> and makes adding extra
modules to the system pretty easy. My module needs to be able to display
plaintext assets, update their content using an <code>HTTP POST</code>, and provide diffs
between the last commit and the current content.</p>
<p>To create the module, the following paths need to be added:</p>
<ul>
<li><code>INSTALL/application/appconfig</code></li>
<li><code>INSTALL/application/appconfig/controllers</code></li>
<li><code>INSTALL/application/appconfig/views/scripts/index</code></li>
</ul>
<p>Declare the module with <code>INSTALL/application/appconfig/module.ini</code>:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode numberSource ini numberLines"><code class="sourceCode ini"><span id="cb4-1"><a href="#cb4-1"></a><span class="dt">version </span><span class="ot">=</span><span class="st"> </span><span class="fl">1.0</span></span>
<span id="cb4-2"><a href="#cb4-2"></a><span class="dt">description </span><span class="ot">=</span><span class="st"> Application config proof of concept</span></span>
<span id="cb4-3"><a href="#cb4-3"></a><span class="dt">icon </span><span class="ot">=</span><span class="st"> images/icon.png</span></span>
<span id="cb4-4"><a href="#cb4-4"></a><span class="dt">tags </span><span class="ot">=</span><span class="st"> config</span></span>
<span id="cb4-5"><a href="#cb4-5"></a></span>
<span id="cb4-6"><a href="#cb4-6"></a><span class="kw">[maintainer]</span></span>
<span id="cb4-7"><a href="#cb4-7"></a><span class="dt">name </span><span class="ot">=</span><span class="st"> Perforce Software</span></span>
<span id="cb4-8"><a href="#cb4-8"></a><span class="dt">email </span><span class="ot">=</span><span class="st"> support@perforce.com</span></span>
<span id="cb4-9"><a href="#cb4-9"></a><span class="dt">url </span><span class="ot">=</span><span class="st"> http://www.perforce.com</span></span>
<span id="cb4-10"><a href="#cb4-10"></a></span>
<span id="cb4-11"><a href="#cb4-11"></a><span class="kw">[routes]</span></span>
<span id="cb4-12"><a href="#cb4-12"></a><span class="dt">appconfig.type </span><span class="ot">=</span><span class="st"> Zend_Controller_Router_Route_Regex</span></span>
<span id="cb4-13"><a href="#cb4-13"></a><span class="dt">appconfig.route </span><span class="ot">=</span><span class="st"> &#39;appconfig/(.+)&#39;</span></span>
<span id="cb4-14"><a href="#cb4-14"></a><span class="dt">appconfig.reverse </span><span class="ot">=</span><span class="st"> appconfig/%s</span></span>
<span id="cb4-15"><a href="#cb4-15"></a><span class="dt">appconfig.defaults.module </span><span class="ot">=</span><span class="st"> appconfig</span></span>
<span id="cb4-16"><a href="#cb4-16"></a><span class="dt">appconfig.defaults.controller </span><span class="ot">=</span><span class="st"> index</span></span>
<span id="cb4-17"><a href="#cb4-17"></a><span class="dt">appconfig.defaults.action </span><span class="ot">=</span><span class="st"> index</span></span>
<span id="cb4-18"><a href="#cb4-18"></a><span class="dt">appconfig.map.resource </span><span class="ot">=</span><span class="st"> </span><span class="dv">1</span></span>
<span id="cb4-19"><a href="#cb4-19"></a></span>
<span id="cb4-20"><a href="#cb4-20"></a><span class="dt">appconfig-operation.type </span><span class="ot">=</span><span class="st"> Zend_Controller_Router_Route_Regex</span></span>
<span id="cb4-21"><a href="#cb4-21"></a><span class="dt">appconfig-operation.route </span><span class="ot">=</span><span class="st"> &#39;appconfig/([^/]+)/(.+)&#39;</span></span>
<span id="cb4-22"><a href="#cb4-22"></a><span class="dt">appconfig-operation.reverse </span><span class="ot">=</span><span class="st"> appconfig/%s/%s</span></span>
<span id="cb4-23"><a href="#cb4-23"></a><span class="dt">appconfig-operation.defaults.module </span><span class="ot">=</span><span class="st"> appconfig</span></span>
<span id="cb4-24"><a href="#cb4-24"></a><span class="dt">appconfig-operation.defaults.controller </span><span class="ot">=</span><span class="st"> index</span></span>
<span id="cb4-25"><a href="#cb4-25"></a><span class="dt">appconfig-operation.defaults.action </span><span class="ot">=</span><span class="st"> index</span></span>
<span id="cb4-26"><a href="#cb4-26"></a><span class="dt">appconfig-operation.map.action </span><span class="ot">=</span><span class="st"> </span><span class="dv">1</span></span>
<span id="cb4-27"><a href="#cb4-27"></a><span class="dt">appconfig-operation.map.resource </span><span class="ot">=</span><span class="st"> </span><span class="dv">2</span></span></code></pre></div>
<p>Add a view script for displaying plaintext
assets, <code>INSTALL/application/appconfig/views/scripts/index/index.phtml</code>:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode numberSource php numberLines"><code class="sourceCode php"><span id="cb5-1"><a href="#cb5-1"></a><span class="kw">&lt;?</span><span class="op">=</span><span class="va">$this</span>-&gt;entry-&gt;getValue(<span class="st">&#39;content&#39;</span>) <span class="kw">?&gt;</span></span></code></pre></div>
<p>Add a view script for displaying
diffs, <code>INSTALL/application/appconfig/views/scripts/index/diffs.phtml</code>:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode numberSource php numberLines"><code class="sourceCode php"><span id="cb6-1"><a href="#cb6-1"></a><span class="op">&lt;</span>pre<span class="op">&gt;&lt;</span><span class="ot">?</span><span class="op">=</span><span class="va">$this</span>-&gt;diffs <span class="kw">?&gt;</span><span class="op">&lt;/</span>pre<span class="op">&gt;</span></span></code></pre></div>
<p>And a controller
at <code>INSTALL/application/appconfig/controllers/IndexController.phtml</code>:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode numberSource php numberLines"><code class="sourceCode php"><span id="cb7-1"><a href="#cb7-1"></a><span class="kw">&lt;?php</span></span>
<span id="cb7-2"><a href="#cb7-2"></a></span>
<span id="cb7-3"><a href="#cb7-3"></a><span class="fu">defined</span>(<span class="st">&#39;LIBRARY_PATH&#39;</span>) <span class="op">or</span> <span class="fu">define</span>(<span class="st">&#39;LIBRARY_PATH&#39;</span><span class="ot">,</span> <span class="fu">dirname</span>(<span class="cn">__DIR__</span>))<span class="ot">;</span></span>
<span id="cb7-4"><a href="#cb7-4"></a><span class="kw">require_once</span> <span class="cn">LIBRARY_PATH</span> <span class="op">.</span> <span class="st">&#39;/simplediff/simplediff.php&#39;</span><span class="ot">;</span></span>
<span id="cb7-5"><a href="#cb7-5"></a></span>
<span id="cb7-6"><a href="#cb7-6"></a><span class="kw">class</span> <span class="cn">A</span>ppconfig_IndexController <span class="kw">extends</span> <span class="cn">Z</span>end_Controller_Action</span>
<span id="cb7-7"><a href="#cb7-7"></a>{</span>
<span id="cb7-8"><a href="#cb7-8"></a> <span class="kw">private</span> <span class="va">$entry</span><span class="ot">;</span></span>
<span id="cb7-9"><a href="#cb7-9"></a> </span>
<span id="cb7-10"><a href="#cb7-10"></a> <span class="kw">private</span> <span class="va">$mimeTypes</span> <span class="op">=</span> <span class="dt">array</span>(</span>
<span id="cb7-11"><a href="#cb7-11"></a> <span class="st">&#39;.html&#39;</span> =&gt; <span class="st">&#39;text/html&#39;</span><span class="ot">,</span></span>
<span id="cb7-12"><a href="#cb7-12"></a> <span class="st">&#39;.json&#39;</span> =&gt; <span class="st">&#39;application/json&#39;</span><span class="ot">,</span></span>
<span id="cb7-13"><a href="#cb7-13"></a> )<span class="ot">;</span></span>
<span id="cb7-14"><a href="#cb7-14"></a> </span>
<span id="cb7-15"><a href="#cb7-15"></a> <span class="kw">public</span> <span class="kw">function</span> preDispatch()</span>
<span id="cb7-16"><a href="#cb7-16"></a> {</span>
<span id="cb7-17"><a href="#cb7-17"></a> <span class="va">$request</span> <span class="op">=</span> <span class="va">$this</span>-&gt;getRequest()<span class="ot">;</span></span>
<span id="cb7-18"><a href="#cb7-18"></a> <span class="va">$request</span>-&gt;setParams(<span class="cn">U</span>rl_Model_Url::fetch(<span class="va">$request</span>-&gt;getParam(<span class="st">&#39;resource&#39;</span>))-&gt;getParams())<span class="ot">;</span></span>
<span id="cb7-19"><a href="#cb7-19"></a> <span class="va">$this</span>-&gt;entry <span class="op">=</span> <span class="cn">P</span><span class="er">4</span>Cms_Content::fetch(<span class="va">$request</span>-&gt;getParam(<span class="st">&#39;id&#39;</span>)<span class="ot">,</span> <span class="dt">array</span>(<span class="st">&#39;includeDeleted&#39;</span> =&gt; <span class="kw">true</span>))<span class="ot">;</span></span>
<span id="cb7-20"><a href="#cb7-20"></a> }</span>
<span id="cb7-21"><a href="#cb7-21"></a> </span>
<span id="cb7-22"><a href="#cb7-22"></a> <span class="kw">public</span> <span class="kw">function</span> indexAction()</span>
<span id="cb7-23"><a href="#cb7-23"></a> {</span>
<span id="cb7-24"><a href="#cb7-24"></a> <span class="va">$this</span>-&gt;getResponse()-&gt;setHeader(<span class="st">&#39;Content-Type&#39;</span><span class="ot">,</span> <span class="va">$this</span>-&gt;getMimeType()<span class="ot">,</span> <span class="kw">true</span>)<span class="ot">;</span></span>
<span id="cb7-25"><a href="#cb7-25"></a> <span class="va">$this</span>-&gt;view-&gt;entry <span class="op">=</span> <span class="va">$this</span>-&gt;entry<span class="ot">;</span></span>
<span id="cb7-26"><a href="#cb7-26"></a> </span>
<span id="cb7-27"><a href="#cb7-27"></a> <span class="cf">if</span> (<span class="va">$this</span>-&gt;getRequest()-&gt;isPost()) {</span>
<span id="cb7-28"><a href="#cb7-28"></a> <span class="va">$this</span>-&gt;entry-&gt;setValue(<span class="st">&#39;content&#39;</span><span class="ot">,</span> <span class="va">$this</span>-&gt;getJsonPost())<span class="ot">;</span></span>
<span id="cb7-29"><a href="#cb7-29"></a> <span class="va">$this</span>-&gt;entry-&gt;save(<span class="va">$this</span>-&gt;getRequest()-&gt;getParam(<span class="st">&#39;message&#39;</span>))<span class="ot">;</span></span>
<span id="cb7-30"><a href="#cb7-30"></a> }</span>
<span id="cb7-31"><a href="#cb7-31"></a> }</span>
<span id="cb7-32"><a href="#cb7-32"></a> </span>
<span id="cb7-33"><a href="#cb7-33"></a> <span class="kw">private</span> <span class="kw">function</span> getMimeType()</span>
<span id="cb7-34"><a href="#cb7-34"></a> {</span>
<span id="cb7-35"><a href="#cb7-35"></a> <span class="va">$url</span> <span class="op">=</span> <span class="va">$this</span>-&gt;entry-&gt;getValue(<span class="st">&#39;url&#39;</span>)<span class="ot">;</span></span>
<span id="cb7-36"><a href="#cb7-36"></a> <span class="va">$suffix</span> <span class="op">=</span> <span class="fu">substr</span>(<span class="va">$url</span>[<span class="st">&#39;path&#39;</span>]<span class="ot">,</span> <span class="fu">strrpos</span>(<span class="va">$url</span>[<span class="st">&#39;path&#39;</span>]<span class="ot">,</span> <span class="st">&#39;.&#39;</span>))<span class="ot">;</span></span>
<span id="cb7-37"><a href="#cb7-37"></a> </span>
<span id="cb7-38"><a href="#cb7-38"></a> <span class="cf">if</span> (<span class="fu">array_key_exists</span>(<span class="va">$suffix</span><span class="ot">,</span> <span class="va">$this</span>-&gt;mimeTypes)) {</span>
<span id="cb7-39"><a href="#cb7-39"></a> <span class="cf">return</span> <span class="va">$this</span>-&gt;mimeTypes[<span class="va">$suffix</span>]<span class="ot">;</span></span>
<span id="cb7-40"><a href="#cb7-40"></a> } <span class="cf">else</span> {</span>
<span id="cb7-41"><a href="#cb7-41"></a> <span class="cf">return</span> <span class="st">&#39;text/plain&#39;</span><span class="ot">;</span></span>
<span id="cb7-42"><a href="#cb7-42"></a> }</span>
<span id="cb7-43"><a href="#cb7-43"></a> }</span>
<span id="cb7-44"><a href="#cb7-44"></a> </span>
<span id="cb7-45"><a href="#cb7-45"></a> <span class="kw">public</span> <span class="kw">function</span> diffsAction()</span>
<span id="cb7-46"><a href="#cb7-46"></a> {</span>
<span id="cb7-47"><a href="#cb7-47"></a> <span class="va">$this</span>-&gt;getResponse()-&gt;setHeader(<span class="st">&#39;Content-Type&#39;</span><span class="ot">,</span> <span class="st">&#39;text/html&#39;</span><span class="ot">,</span> <span class="kw">true</span>)<span class="ot">;</span></span>
<span id="cb7-48"><a href="#cb7-48"></a> <span class="va">$this</span>-&gt;view-&gt;diffs <span class="op">=</span> htmlDiff(<span class="va">$this</span>-&gt;entry-&gt;getValue(<span class="st">&#39;content&#39;</span>)<span class="ot">,</span> <span class="va">$this</span>-&gt;getJsonPost())<span class="ot">;</span></span>
<span id="cb7-49"><a href="#cb7-49"></a> }</span>
<span id="cb7-50"><a href="#cb7-50"></a> </span>
<span id="cb7-51"><a href="#cb7-51"></a> <span class="kw">public</span> <span class="kw">function</span> postDispatch()</span>
<span id="cb7-52"><a href="#cb7-52"></a> {</span>
<span id="cb7-53"><a href="#cb7-53"></a> <span class="va">$this</span>-&gt;getHelper(<span class="st">&#39;layout&#39;</span>)-&gt;disableLayout()<span class="ot">;</span></span>
<span id="cb7-54"><a href="#cb7-54"></a> }</span>
<span id="cb7-55"><a href="#cb7-55"></a> </span>
<span id="cb7-56"><a href="#cb7-56"></a> <span class="kw">private</span> <span class="kw">function</span> getJsonPost()</span>
<span id="cb7-57"><a href="#cb7-57"></a> {</span>
<span id="cb7-58"><a href="#cb7-58"></a> <span class="cf">if</span> (<span class="va">$this</span>-&gt;getRequest()-&gt;isPost()) {</span>
<span id="cb7-59"><a href="#cb7-59"></a> <span class="cf">return</span> <span class="va"></