Initial commit

release
Chris Josten 2 years ago
commit 4a9cfda0bd
  1. 15
      .gitignore
  2. 2
      default.nix
  3. 13
      dub.json
  4. 21
      dub.selections.json
  5. 20
      dub.selections.json.bak
  6. 194
      dub.selections.nix
  7. 12
      mijnblog.nix
  8. 121
      mkDub.nix
  9. BIN
      public/static/img/256x256
  10. BIN
      public/static/img/chris-icoon.png
  11. BIN
      public/static/img/logo.gif
  12. BIN
      public/static/img/logo.png
  13. BIN
      public/static/img/logo.png.bak
  14. 1238
      public/static/script/prism.js
  15. 139
      public/static/style/base.css
  16. 62
      public/static/style/highlight.css
  17. 3
      public/static/style/mobile.css
  18. 45
      public/static/style/old.css
  19. BIN
      public/static/style/old/bullet.gif
  20. BIN
      public/static/style/old/chip.jpg
  21. BIN
      public/static/style/old/cursor-over.gif
  22. BIN
      public/static/style/old/cursor.gif
  23. BIN
      public/static/style/old/skull-border.gif
  24. BIN
      public/static/style/old/skull.gif
  25. 195
      public/static/style/prism.css
  26. 16
      shell.nix
  27. 144
      source/app.d
  28. 85
      source/article.d
  29. 119
      source/page.d
  30. 46
      source/project.d
  31. 16
      source/utils.d
  32. 115
      source/watcher.d
  33. 26
      views/pages/article-list.dt
  34. 17
      views/pages/article.dt
  35. 12
      views/pages/error.dt
  36. 12
      views/pages/page.dt
  37. 25
      views/pages/project-list.dt
  38. 0
      views/parts/article-fragment.dt
  39. 14
      views/parts/menu.dt
  40. 16
      views/parts/page.dt

15
.gitignore vendored

@ -0,0 +1,15 @@
.dub
docs.json
__dummy.html
docs/
/mijnblog
mijnblog.so
mijnblog.dylib
mijnblog.dll
mijnblog.a
mijnblog.lib
mijnblog-test-*
*.exe
*.o
*.obj
*.lst

@ -0,0 +1,2 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.callPackage ./mijnblog.nix {}

@ -0,0 +1,13 @@
{
"authors": [
"Chris Josten"
],
"copyright": "Copyright © 2019, Chris Josten",
"dependencies": {
"dyaml": "~>0.8.0",
"vibe-d": "~>0.8.6"
},
"description": "A blog based on Markdown and JSON",
"license": "AGPLv3",
"name": "mijnblog"
}

@ -0,0 +1,21 @@
{
"fileVersion": 1,
"versions": {
"botan": "1.12.10",
"botan-math": "1.0.3",
"diet-ng": "1.6.0",
"dyaml": "0.8.0",
"eventcore": "0.8.48",
"fswatch": "0.5.0",
"libasync": "0.8.4",
"libevent": "2.0.2+2.0.16",
"memutils": "0.4.13",
"mir-linux-kernel": "1.0.1",
"openssl": "1.1.6+1.0.1g",
"stdx-allocator": "2.77.5",
"taggedalgebraic": "0.11.7",
"tinyendian": "0.2.0",
"vibe-core": "1.7.0",
"vibe-d": "0.8.6"
}
}

@ -0,0 +1,20 @@
{
"fileVersion": 1,
"versions": {
"botan": "1.12.10",
"botan-math": "1.0.3",
"diet-ng": "1.5.0",
"dyaml": "0.8.0",
"eventcore": "0.8.43",
"libasync": "0.8.4",
"libevent": "2.0.2+2.0.16",
"memutils": "0.4.13",
"mir-linux-kernel": "1.0.1",
"openssl": "1.1.6+1.0.1g",
"stdx-allocator": "2.77.5",
"taggedalgebraic": "0.11.4",
"tinyendian": "0.2.0",
"vibe-core": "1.6.2",
"vibe-d": "0.8.6"
}
}

@ -0,0 +1,194 @@
# This file was generated by https://github.com/lionello/dub2nix v0.2.1
[ {
fetch = {
type = "git";
url = "https://github.com/D-Programming-Deimos/libevent.git";
rev = "v2.0.2+2.0.16";
sha256 = "1axv0pv5w26i61m191570ydfxb3gghgn8yh654qjyxi0cxvn92bf";
fetchSubmodules = false;
date = "2016-11-29T18:50:57-08:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/hyv33akqdvhk978p1inmy0r64vimhrdl-libevent";
};
} {
fetch = {
type = "git";
url = "https://github.com/kiith-sa/tinyendian.git";
rev = "v0.2.0";
sha256 = "086gf5aga52wr5rj2paq54daj8lafn980x77b706vvvqaz2mlis8";
fetchSubmodules = false;
date = "2018-06-10T11:04:28+02:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/9c7fsmi5am84j6dq2mp3va306x3ay291-tinyendian";
};
} {
fetch = {
type = "git";
url = "https://github.com/libmir/mir-linux-kernel.git";
rev = "v1.0.1";
sha256 = "1gcaavsni47352nvw9s41zkaswd582cafq3931i0zinhd9s3clag";
fetchSubmodules = false;
date = "2018-08-04T16:03:31+07:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/qz6axwci8kb1477j9dzgq85nscjfyvj1-mir-linux-kernel";
};
} {
fetch = {
type = "git";
url = "https://github.com/etcimon/botan-math.git";
rev = "v1.0.3";
sha256 = "0jbpgpd1sjkp5075xh0k7m4m2d3yahi5scx3ql35fj4yns2mn2fq";
fetchSubmodules = false;
date = "2016-08-23T10:45:25-04:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/dic7r8jab936cgqc9fck032r49nvgv07-botan-math";
};
} {
fetch = {
type = "git";
url = "https://github.com/etcimon/memutils.git";
rev = "v0.4.13";
sha256 = "12mssymhimfsw6flkchqg0ql3qrs91dlh88xjdw5il8rb4h4ddfz";
fetchSubmodules = false;
date = "2018-10-09T11:51:45-04:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/hszwaxwm26bqp3h761j3kq1hsbs3hpra-memutils";
};
} {
fetch = {
type = "git";
url = "https://github.com/s-ludwig/taggedalgebraic.git";
rev = "v0.11.7";
sha256 = "1q8pg3r1zw72114y76kgr73iylp77m4wrl180r6d632vc0x8r58f";
fetchSubmodules = false;
date = "2019-11-01T09:00:47+01:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/x1qwbzxp7nx0fpwvr4v30j0nnwf6pjcx-taggedalgebraic";
};
} {
fetch = {
type = "git";
url = "https://github.com/vibe-d/vibe-core.git";
rev = "v1.7.0";
sha256 = "0mp8daspzn0ww9jr6nf0zwdj6ff6j7l4j5h6h450z18zn7kjkd8x";
fetchSubmodules = false;
date = "2019-09-17T23:40:53+02:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/g46p3lfzkcsrdcvxv25a2g2vhcfghdgr-vibe-core";
};
} {
fetch = {
type = "git";
url = "https://github.com/vibe-d/vibe.d.git";
rev = "v0.8.6";
sha256 = "1zqan3rsjmkxnq8wxnzrj4vw912lnnp2fphzfz1sriyysj59b28f";
fetchSubmodules = false;
date = "2019-10-03T17:14:48+02:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/wsskmj5cs3p6nlkfc80z8bm90nxxvy0n-vibe.d";
};
} {
fetch = {
type = "git";
url = "https://github.com/wilzbach/stdx-allocator.git";
rev = "v2.77.5";
sha256 = "03av8zp5p6vf6fg005xbmwnjfw96jyrr4dcj4m56c4a3vx7v72pk";
fetchSubmodules = false;
date = "2018-12-23T13:54:22+01:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/b3h25asfh205wzwjfzjf2k2kkccpp96k-stdx-allocator";
};
} {
fetch = {
type = "git";
url = "https://github.com/etcimon/botan.git";
rev = "v1.12.10";
sha256 = "02zrq9wcb1hhsl9psgkvi65g6c7g990yy9d75gjgbz4b1wb61nwf";
fetchSubmodules = false;
date = "2018-06-22T15:33:04-04:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/a1431sfkpz8dk0jlix5zn90g19av1lnc-botan";
};
} {
fetch = {
type = "git";
url = "https://github.com/rejectedsoftware/diet-ng.git";
rev = "v1.6.0";
sha256 = "02pj2rf2qfi0acnw496nr480hj47ggy4isfhsvdajd6al4h03a6x";
fetchSubmodules = false;
date = "2019-08-16T19:55:02+02:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/jld9bk9vr9k2pfb3li1zcvhilka8gdzl-diet-ng";
};
} {
fetch = {
type = "git";
url = "https://github.com/WebFreak001/FSWatch.git";
rev = "v0.5.0";
sha256 = "1msv98hvxg1nr44yp8pm1as3byx0ibxfpr662564zdq5afci49fc";
fetchSubmodules = false;
date = "2019-06-13T21:48:00+02:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/88dmp1a4cygp3phm1xb7m663jpqhih42-FSWatch";
};
} {
fetch = {
type = "git";
url = "https://github.com/D-Programming-Deimos/openssl.git";
rev = "v1.1.6+1.0.1g";
sha256 = "0ramqjyq4v7xpqwf4nf4ddmsg6yk2fbzn2d1yj4b6dla3q5lv9i3";
fetchSubmodules = false;
date = "2017-11-05T20:15:26+01:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/jbfb5in6gzqs2byn1hdc1625yh8sx6j2-openssl";
};
} {
fetch = {
type = "git";
url = "https://github.com/vibe-d/eventcore.git";
rev = "v0.8.48";
sha256 = "1ifx4sr04x7mvw1w9g3n72szy83q487ya0yi212czys4bgwdvkiz";
fetchSubmodules = false;
date = "2019-10-25T22:46:08+02:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/65g2z4y29z49cfjvan2dz01nyd4x5mdp-eventcore";
};
} {
fetch = {
type = "git";
url = "https://github.com/kiith-sa/D-YAML.git";
rev = "v0.8.0";
sha256 = "1my9yck316q0kqalxkhfpffgvrfskin4qw3171md4iz3qbhwb799";
fetchSubmodules = false;
date = "2019-05-26T20:26:26+02:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/v30av8a9fqrvhqz09qqiljbcr88dbx0z-D-YAML";
};
} {
fetch = {
type = "git";
url = "https://github.com/etcimon/libasync.git";
rev = "v0.8.4";
sha256 = "13v3dg0838j9h377cs9bq2nk71nrchx6kix9mwiy0fhw7sf7nxb4";
fetchSubmodules = false;
date = "2019-02-28T10:02:56-05:00";
deepClone = false;
leaveDotGit = false;
path = "/nix/store/6b552a8zdl8z99gc11xgj95k52vwvlcw-libasync";
};
} ]

@ -0,0 +1,12 @@
{pkgs}:
with (import ./mkDub.nix {
inherit pkgs;
});
mkDubDerivation {
src = ./.;
dubJSON = ./dub.json;
selections = ./dub.selections.nix;
version = "0.0.1";
buildInputs = [ pkgs.openssl ];
propagatedBuildInputs = [ pkgs.nix-prefetch-git ];
}

@ -0,0 +1,121 @@
{ pkgs ? import <nixpkgs> {},
stdenv ? pkgs.stdenv,
rdmd ? pkgs.rdmd,
dmd ? pkgs.dmd,
dub ? pkgs.dub }:
with stdenv;
let
# Filter function to remove the .dub package folder from src
filterDub = name: type: let baseName = baseNameOf (toString name); in ! (
type == "directory" && baseName == ".dub"
);
# Convert a GIT rev string (tag) to a simple semver version
rev-to-version = builtins.replaceStrings ["v" "refs/tags/v"] ["" ""];
dep2src = dubDep: pkgs.fetchgit { inherit (dubDep.fetch) url rev sha256 fetchSubmodules; };
# Fetch a dependency (source only for now)
fromDub = dubDep: mkDerivation rec {
name = "${src.name}-${version}";
version = rev-to-version dubDep.fetch.rev;
nativeBuildInputs = [ rdmd dmd dub ];
src = dep2src dubDep;
buildPhase = ''
runHook preBuild
export HOME=$PWD
dub build -b=release
runHook postBuild
'';
# outputs = [ "lib" ];
# installPhase = ''
# runHook preInstall
# mkdir -p $out/bin
# runHook postInstall
# '';
};
# Adds a local package directory (e.g. a git repository) to Dub
dub-add-local = dubDep: "dub add-local ${(fromDub dubDep).src.outPath} ${rev-to-version dubDep.fetch.rev}";
# The target output of the Dub package
targetOf = package: "${package.targetPath or "."}/${package.targetName or package.name}";
# Remove reference to build tools and library sources
disallowedReferences = deps: [ dmd rdmd dub ] ++ builtins.map dep2src deps;
removeExpr = refs: ''remove-references-to ${lib.concatMapStrings (ref: " -t ${ref}") refs}'';
in {
inherit fromDub;
mkDubDerivation = lib.makeOverridable ({
src,
nativeBuildInputs ? [],
dubJSON ? src + "/dub.json",
selections ? src + "/dub.selections.nix",
deps ? import selections,
passthru ? {},
package ? lib.importJSON dubJSON,
...
} @ attrs: stdenv.mkDerivation (attrs // {
pname = package.name;
nativeBuildInputs = [ rdmd dmd dub pkgs.removeReferencesTo ] ++ nativeBuildInputs;
disallowedReferences = disallowedReferences deps;
passthru = passthru // {
inherit dub dmd rdmd pkgs;
};
src = lib.cleanSourceWith {
filter = filterDub;
src = lib.cleanSource src;
};
preFixup = ''
find $out/bin -type f -exec ${removeExpr (disallowedReferences deps)} '{}' + || true
'';
buildPhase = ''
runHook preBuild
export HOME=$PWD
${lib.concatMapStringsSep "\n" dub-add-local deps}
dub build -b release --combined --skip-registry=all
runHook postBuild
'';
checkPhase = ''
runHook preCheck
export HOME=$PWD
${lib.concatMapStringsSep "\n" dub-add-local deps}
dub test --combined --skip-registry=all
runHook postCheck
'';
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp -r "${targetOf package}" $out/bin
runHook postInstall
'';
meta = lib.optionalAttrs (package ? description) {
description = package.description;
} // attrs.meta or {};
} // lib.optionalAttrs (!(attrs ? version)) {
# Use name from dub.json, unless pname and version are specified
name = package.name;
}));
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because it is too large Load Diff

@ -0,0 +1,139 @@
@media (max-width: 850px) {
body {
flex-wrap: wrap;
}
body > main {
min-width: calc(100% - 2px - 4em);
}
body > nav {
width: 100%;
flex-grow: 1 !important;
}
}
:root {
font-family: "sans-serif";
--colour-bg: #f0f0f0;
--colour-bg-main: #ddd;
--colour-bg-code: #ccc;
--colour-fg: #000000;
--colour-fg-highlight: #7f0602;
}
body {
background-color: var(--colour-bg);
margin: 0;
display: flex;
justify-content: center;
min-height: 100vh;
}
body > nav {
flex: 0 0 160px;
padding: 2em;
}
body > nav > header {
text-align: center;
}
body > nav > header > img {
width: 100%;
max-width: 160px;
}
body > main {
flex: 1 1;
padding: 2em;
/* width: 600px; */
border-left: var(--colour-fg-highlight) dotted 1px;
border-right: var(--colour-fg-highlight) dotted 1px;
max-width: 600px;
background-color: var(--colour-bg-main);
}
h1, .title {
margin: 0;
/* font-size: 2em; */
}
.subtitle {
margin: 0;
font-size: 1rem;
font-weight: normal;
font-style: italic;
}
header {
border-bottom: 1px dotted black;
padding-top: 0.5em;
padding-bottom: 0.5em;
margin-bottom: 0.5em;
}
footer {
border-top: 1px dotted black;
padding-top: 0.5em;
padding-bottom: 0.5em;
margin-top: 0.5em;
}
nav ul {
padding: 0em;
list-style: none;
}
/* MARKDOWN */
:not(pre) > code {
background-color: var(--colour-bg-code);
padding: 0.2em 0.4em;
}
div.sourceCode, blockquote {
scrollbar-color: var(--colour-fg) var(--colour-bg-code);
scrollbar-width: thin;
background-color: var(--colour-bg-code);
}
pre.sourceCode {
}
pre, blockquote {
border-left: 1px solid var(--colour-fg-highlight);
padding-left: 1em;
padding-right: 1em;
margin-left: 1em;
margin-right: 1em;
}
blockquote footer {
text-align: right;
font-style: italic;
}
figure {
background-color: var(--colour-bg-code);
margin: 0;
text-align: center;
}
figure > img {
max-width: 100%;
}
figure > figcaption {
padding: 0.5em;
}
a {
color: var(--colour-fg-highlight);
text-decoration: underline dotted;
}
a:hover {
color: var(--colour-fg);
text-decoration: underline solid;
}

@ -0,0 +1,62 @@
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #000000;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */

@ -0,0 +1,3 @@
body {
background: red;
}

@ -0,0 +1,45 @@
body {
background-image: url(old/chip.jpg);
cursor: url(old/cursor.gif), auto;
justify-content: flex-start;
}
a:hover {
cursor: url(old/cursor-over.gif), auto;
}
body > nav {
background-color: yellow;
}
body > nav ul {
padding-left: 2em;
list-style-image: url(old/bullet.gif);
}
body > main {
background-color: black;
color: white;
border-image: url(old/skull-border.gif) 33% / 2em round;
/*border-left-width: 10px !important;
border-right-width: 10px !important;*/
}
.title {
width: 100%;
background-color: green;
}
pre {
background-color: blue;
}
blockquote {
background: #0000ff;
border-left: #0000cc 6px solid;
}
a {
color: blue;
text-decoration: underline;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

@ -0,0 +1,195 @@
/* PrismJS 1.17.1
https://prismjs.com/download.html#themes=prism-solarizedlight&languages=markup+css+clike+javascript+c+cpp+d+java+json&plugins=line-numbers */
/*
Solarized Color Schemes originally by Ethan Schoonover
http://ethanschoonover.com/solarized
Ported for PrismJS by Hector Matos
Website: https://krakendev.io
Twitter Handle: https://twitter.com/allonsykraken)
*/
/*
SOLARIZED HEX
--------- -------
base03 #002b36
base02 #073642
base01 #586e75
base00 #657b83
base0 #839496
base1 #93a1a1
base2 #eee8d5
base3 #fdf6e3
yellow #b58900
orange #cb4b16
red #dc322f
magenta #d33682
violet #6c71c4
blue #268bd2
cyan #2aa198
green #859900
*/
code[class*="language-"],
pre[class*="language-"] {
color: #657b83; /* base00 */
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
background: #073642; /* base02 */
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
background: #073642; /* base02 */
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background-color: #fdf6e3; /* base3 */
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #93a1a1; /* base1 */
}
.token.punctuation {
color: #586e75; /* base01 */
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #268bd2; /* blue */
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.url,
.token.inserted {
color: #2aa198; /* cyan */
}
.token.entity {
color: #657b83; /* base00 */
background: #eee8d5; /* base2 */
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #859900; /* green */
}
.token.function,
.token.class-name {
color: #b58900; /* yellow */
}
.token.regex,
.token.important,
.token.variable {
color: #cb4b16; /* orange */
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
pre[class*="language-"].line-numbers {
position: relative;
padding-left: 3.8em;
counter-reset: linenumber;
}
pre[class*="language-"].line-numbers > code {
position: relative;
white-space: inherit;
}
.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.8em;
width: 3em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
border-right: 1px solid #999;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.line-numbers-rows > span {
pointer-events: none;
display: block;
counter-increment: linenumber;
}
.line-numbers-rows > span:before {
content: counter(linenumber);
color: #999;
display: block;
padding-right: 0.8em;
text-align: right;
}

@ -0,0 +1,16 @@
with import <nixpkgs> {};
let
pkg = import ./default.nix { inherit pkgs; };
in mkShell {
buildInputs = [
# additional runtime dependencies go here
] ++ pkg.buildInputs ++ pkg.propagatedBuildInputs;
nativeBuildInputs = [
# additional dev dependencies go here
] ++ pkg.nativeBuildInputs;
}

@ -0,0 +1,144 @@
import std.experimental.logger;
import std.range;
import std.string;
import std.stdio;
import std.typecons;
import vibe.d;
import article;
import page;
import project;
import watcher;
/**
* Internal list of articles by slug.
*/
Article[string] articles;
Page[string] pages;
Project[string] projects;
immutable string articleSortPred = "a.firstPublished > b.firstPublished";
Article*[] articleList;
immutable string pageSortedPred = "a.title < b.title";
Page*[] pageList;
Project[] projectList;
enum OutputType {
HTML,
MARKDOWN
}
const string MIME_MARKDOWN = "text/markdown";
/**
* Get's the document type for the given slug based on extension
* and returns the slug without extension and the document type.
*/
private OutputType getOutputType(ref string slug) {
if (slug.endsWith(".md")) {
slug = chomp(slug, ".md");
return OutputType.MARKDOWN;
} else if (slug.endsWith(".html")){
// If explicitly asking for HTML, we'll return HTML
slug = chomp(slug, ".html");
return OutputType.HTML;
} else {
// If in the future, for any reason, we no longer use HTML
// this allows to us to keep the current urls with an option
// to change the output in the future.
return OutputType.HTML;
}
}
void getSingle(T, string templ)(ref T[string] array, HTTPServerRequest req, HTTPServerResponse res) {
string slug = req.params["slug"];
OutputType outputType = getOutputType(slug);
enforceHTTP(slug in array, HTTPStatus.notFound, "Page not found");
T content = array[slug];
res.headers["Cache-Control"] = "public";
switch(outputType) with (OutputType) {
case MARKDOWN:
res.writeBody(content.contentSource, MIME_MARKDOWN);
break;
default:
case HTML:
res.render!(templ, content);
break;
}
}
/**
* Generates response for /posts/:slug and /palen/:slug.
*/
void articleGetSingle(HTTPServerRequest req, HTTPServerResponse res) {
getSingle!(Article, "pages/article.dt")(articles, req, res);
}
/**
* Generates response for /posts/ and /palen/
*/
void articleGetOverview(HTTPServerRequest req, HTTPServerResponse res) {
res.headers["Cache-Control"] = "public";
render!("pages/article-list.dt", articleList)(res);
}
void projectGetOverview(HTTPServerRequest req, HTTPServerResponse res) {
res.headers["Cache-Control"] = "public";
render!("pages/project-list.dt", projectList)(res);
}
/**
* Generate response for a page
*/
void pageGet(HTTPServerRequest req, HTTPServerResponse res) {
if (("slug" in req.params) is null) {
req.params.addField("slug", "index");
}
getSingle!(Page, "pages/page.dt")(pages, req, res);
}
/**
* Generates response whenever an error occurs.
*/
@safe
void errorPage(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) {
render!("pages/error.dt", error)(res);
}
void main() {
//articles["hello-world"] = new Article("hello-world.yamd");
HTTPServerSettings settings = new HTTPServerSettings;
settings.bindAddresses = ["0.0.0.0"];
settings.port = 3465;
settings.serverString = "zeg ik lekker niet";
settings.errorPageHandler = toDelegate(&errorPage);
settings.keepAliveTimeout = dur!"seconds"(60);
debug {
settings.accessLogToConsole = true;
}
URLRouter router = new URLRouter;
router.get("/posts/:slug", &articleGetSingle);
router.get("/palen/:slug", &articleGetSingle);
router.get("/posts/", &articleGetOverview);
router.get("/palen/", &articleGetOverview);
router.get("/projects/", &projectGetOverview);
router.get("/projecten/", &projectGetOverview);
router.get("/static/*", serveStaticFiles("./public/"));
router.get("/:slug", &pageGet);
router.get("/", &pageGet);
listenHTTP(settings, router);
runTask({
initPages!(Page, pageSortedPred)(pages, pageList, "pages");
});
runTask({
initPages!(Article, articleSortPred)(articles, articleList, "articles");
});
runApplication();
}

@ -0,0 +1,85 @@
import std.file;
import std.stdio;
import std.string;
import std.datetime.date;
import std.experimental.logger;
import dyaml;
import page;
import vibe.d;
/**
* Represents an article on the blog
*/
class Article : Page {
private string m_author;
private string m_title;
private string m_slug;
private DateTime m_firstPublished;
private string m_excerpt;
/**
* Time that the file was last updated
*/
private DateTime m_updated;
/**
* Loads an article from a file.
*/
this(string file) {
this.m_headerShift = 1;
super(file);
// Find the first header and mark everything up to that as
if (m_excerpt is null) {
// an excerpt, used in search results.
const long seperatorIndex = indexOf(m_contentSource, "---\n");
this.m_excerpt = this.m_contentSource[seperatorIndex + 4..$];
long firstHeaderIndex = indexOf(this.m_excerpt, '#');
if (firstHeaderIndex >= 0) {
this.m_excerpt = this.m_excerpt[0..firstHeaderIndex];
}
}
}
/**
* Loads the metadata specific to Articles.
*/
@safe
override protected void loadHeader(Node headerNode) {
super.loadHeader(headerNode);
if (headerNode.containsKey("author")) {
this.m_author = headerNode["author"].as!string;
} else {
this.m_author = "<unknown author>";
}
if ("excerpt" in headerNode) {
this.m_excerpt = headerNode["excerpt"].as!string;
}
if ("firstPublished" in headerNode) {
try {
this.m_firstPublished = cast(DateTime) headerNode["firstPublished"].as!SysTime;
} catch(DateTimeException e) {
warningf("%s: invalid date format", this.m_slug);
}
} else {
this.m_firstPublished= DateTime.fromSimpleString("1970-Jan-01 00:00:00");
}
if ("updated" in headerNode) {
try {
this.m_updated = cast(DateTime) headerNode["updated"].as!SysTime();
} catch(DateTimeException e) {
warningf("%s: invalid date format", this.m_slug);
}
} else {
this.m_updated = this.m_firstPublished;
}
}
@property string excerpt() { return m_excerpt; }
@property string author() { return m_author; }
@property DateTime firstPublished() { return m_firstPublished; }
@property DateTime updated() { return m_updated; }
}

@ -0,0 +1,119 @@
import std.exception;
import std.experimental.logger;
import std.file;
import std.process;
import std.stdio;
import dyaml;
import vibe.vibe;
/**
* Exception thrown when a page has syntax errors e.g.
*/
class ArticleParseException : Exception {
mixin basicExceptionCtors;
}
/**
* Represents a page on the blog
*/
class Page {
/**
* Internal name of the article. Usually the file name.
*/
protected string m_name;
/**
* Slug either manually assigned or generated based on file name.
* Only used in the url.
*/
protected string m_slug;
protected string m_title;
protected string m_content;
protected string m_contentSource;
/**
* Option for the markdown parser: the amount of levels the header found in the markdown should
* be shifted. For example, 0 means H1 -> H1, 1 means H1 -> H2, 2 means H1 -> H3 and so on.
*/
protected int m_headerShift = 1;
private bool hasCalledSuper = false;
/**
* Creates a page from a file
*/
this(string file) {
this.m_name = file;
this.m_contentSource = readText(file);
// Find the seperator and split the string in two
const long seperatorIndex = indexOf(m_contentSource, "---\n");
enforce!ArticleParseException(seperatorIndex >= 0);
string header = m_contentSource[0..seperatorIndex];
Node node = Loader.fromString(header).load();
loadHeader(node);
assert(hasCalledSuper);
this.m_content = Page.parseMarkdown(m_contentSource[seperatorIndex + 4..$],
this.m_headerShift);
}
/**
* Parse metadata from the header.
* Params:
* headerNode = the YAML node to parse the header metadata from.
*/
@safe
protected void loadHeader(Node headerNode){
if (headerNode.containsKey("title")) {
this.m_title = headerNode["title"].as!string;
} else {
warningf("%s does not contain a title", this.m_name);
}
if (headerNode.containsKey("slug")) {
this.m_slug = headerNode["slug"].as!string;
} else {
this.m_slug = this.m_title;
infof("%s does not have a slug. Using %s", this.m_name, this.m_slug);
}
hasCalledSuper = true;
}
/**
* Starts pandoc to convert MarkDown to HTML
* Params:
* source = The MarkDown source as a string (not a path!)
* shiftHeader = (Optional) The amount a header needs to be shifted. If for example, it
* is set to 1, first level headings within MarkDown become second level
* headers within HTML.
*/
public static string parseMarkdown(string source, int shiftHeader = 0) {
string[] args = ["pandoc",
"-f", "markdown",
"-t", "html"];
if (shiftHeader != 0) args ~= "--shift-heading-level-by=" ~ to!string(shiftHeader);
ProcessPipes pandoc = pipeProcess(args);
pandoc.stdin.write(source);
pandoc.stdin.writeln();
pandoc.stdin.flush();
pandoc.stdin.close();
string result;
string line;
while ((line = pandoc.stdout.readln()) !is null) {
result ~= line;
}
return result;
}
@property string name() { return m_name; }
@property string title() { return m_title; }
@property string slug() { return m_slug; }
@property string content() { return m_content; }
@property string contentSource() { return m_contentSource; }
}

@ -0,0 +1,46 @@
import std.array;
import std.algorithm;
import dyaml;
import vibe.vibe;
import page;
import utils;