troyfigiel.com

This repository contains the source code of https://troyfigiel.com. It is generated from Scheme code using Haunt.

Building this website locally

To build this website locally, ensure you have Git and Guix installed, and run the following commands:

git clone https://git.sr.ht/~troyfigiel/troyfigiel.com
cd troyfigiel.com
git checkout 1f79657
guix time-machine -C channels.scm -- shell -m manifest.scm --container -- make

The generated website can be found in the site directory.

Dependencies

The Guix repository is our only dependency and its commit is pinned in channels.scm. This ensures the required older versions of the packages specified in the manifest.scm are used automatically.

Domain

This website is hosted on SourceHut pages.

Licensing

Unless otherwise specified, the code in this repository is licensed under the GNU GPLv3. See COPYING for more details.

manifest.scm

(specifications->manifest
 '("emacs"
   "emacs-htmlize"
   "emacs-ox-haunt"
   "git"
   "guile"
   "haunt"
   "make"))

channels.scm

(list
 (channel
  (name 'guix)
  (url "https://git.savannah.gnu.org/git/guix.git")
  (branch "master")
  (commit
   "966b6b0157c3a66238c63d100f50a8a1e2de0bb5")
  (introduction
   (make-channel-introduction
    "9edb3f66fd807b096b48283debdcddccfea34bad"
    (openpgp-fingerprint
     "BBB0 2DDF 2CEA F6A8 0D1D  E643 A2A0 6DF2 A33A 54FA")))))

Makefile

Tangling

emacs --batch --eval "(progn (require 'org) (org-babel-tangle-file \"README.org\"))"
git diff --exit-code

Building the site

Tangling README.org.

Exporting Org files to HTML.

emacs --batch --load org/export.el --eval "(export-all)"

Building website using Haunt.

haunt build

Final file

site: tangle channels.scm manifest.scm
        <<site-build-steps>>

.PHONY: tangle
tangle: README.org channels.scm manifest.scm
        <<tangle-readme>>

.PHONY: clean
clean:
        rm -rf pages/programs/website posts site

.build.yml

image: guix
oauth: pages.sr.ht/PAGES:RW
packages:
  - gzip
  - hut
  - tar
environment:
  shell: guix time-machine -C channels.scm -- shell -m manifest.scm --container
tasks:
- install: |
    cd troyfigiel.com
    $shell -- git --no-pager log --oneline -1
- build: |
    cd troyfigiel.com
    $shell -- make site
- upload: |
    cd troyfigiel.com
    tar -cvzC site . -f site.tar.gz
    hut pages publish -d troyfigiel.com site.tar.gz

export.el

(require 'org)
(require 'ox-haunt)

(defun export--single-org-post (org-post-path)
  "Export a single Org post specified by its ORG-POST-PATH."
  (with-temp-buffer
    (insert-file-contents org-post-path)
    (org-mode)
    (let ((buffer-file-name org-post-path))
      (ox-haunt-export-to-html))))


(defun export--all-org-posts ()
  "Export all org posts in the `org/' directory."
  (let ((ox-haunt-base-dir ".")
        (all-org-posts (directory-files (file-name-concat "org" "posts") t "\\.org")))
    (make-directory (file-name-concat ox-haunt-base-dir "posts") t)
    (mapc #'export--single-org-post all-org-posts)))


(defun export--literate-website ()
  "Export all org posts in the `org/' directory."
  (with-temp-buffer
    (insert-file-contents "README.org")
    (org-mode)
    (let* ((backup-inhibited t)
           (html-buffer (ox-haunt-export-as-html))
           (website-dir (file-name-concat "pages" "programs" "website")))
      (make-directory website-dir t)
      (with-current-buffer html-buffer
        (write-file (file-name-concat website-dir "index.html"))
        (kill-buffer (current-buffer))))))

(defun export-all ()
  (let ((org-confirm-babel-evaluate nil)
        (org-export-with-section-numbers nil)
        (org-export-with-toc nil)
        (org-html-head-include-default-style nil)
        (org-html-htmlize-output-type 'css))
    (org-babel-do-load-languages 'org-babel-load-languages
                                 '((shell . t)))
    (export--all-org-posts)
    (export--literate-website)))

haunt.scm

(use-modules (haunt builder assets)
             (haunt builder atom)
             (haunt builder blog)
             (haunt builder flat-pages)
             (haunt post)
             (haunt reader)
             (haunt site)
             (src theme))

(define blog-collections
  `(("Recent Posts" "blog/index.html" ,posts/reverse-chronological)))

(define (dirname-slug post)
  (string-append (post-slug-v2 post) "/index"))

(site #:title "troyfigiel"
      #:domain "troyfigiel.com"
      #:posts-directory "posts"
      #:build-directory "site"
      #:default-metadata '((author . "Troy Figiel"))
      #:readers (list html-reader sxml-reader)
      #:builders (list (blog #:theme troyfigiel-theme
                             #:post-prefix "blog"
                             #:collections blog-collections)
                       (atom-feed)
                       (flat-pages "pages"
                                   #:template (theme-layout troyfigiel-theme))
                       (static-directory "assets/openpgpkey" ".well-known/openpgpkey")
                       (static-directory "assets/fonts" "fonts")
                       (static-directory "assets/css" "css"))
      #:make-slug dirname-slug)

theme.scm

(define-module (src theme)
  #:use-module (haunt builder blog)
  #:use-module (haunt post)
  #:use-module (haunt site)
  #:use-module (ice-9 popen)
  #:use-module (ice-9 rdelim)
  #:use-module (srfi srfi-19)
  #:export (troyfigiel-theme))

(define (stylesheet name)
  `(link (@ (rel "stylesheet")
            (href ,(string-append "/css/" name ".css")))))

(define (link name uri)
  `(a (@ (href ,uri)) ,name))

(define troyfigiel-theme
  (theme #:name "troyfigiel"
         #:layout
         (lambda (site title body)
           `((doctype "html")
             (head
              (meta (@ (charset "utf-8")))
              (title ,(string-append title " - " (site-title site)))
              (link (@ (rel "alternate")
                       (type "application/atom+xml")
                       (title "Atom feed")
                       (href "/feed.xml")))
              ,(stylesheet "fonts")
              ,(stylesheet "site")
              ,(stylesheet "code"))
             (body
              (div (@ (class "container"))
                   (nav
                    (ul (li ,(link "Troy Figiel" "/"))
                        (li ,(link "Blog" "/blog"))
                        (li ,(link "Literate programs" "/programs"))
                        (li ,(link "Contact" "/contact"))))
                   ,body))))
         #:post-template
         (lambda (post)
           `((h1 (@ (class "title"))
                 ,(post-ref post 'title))
             (div (@ (class "post"))
                  ,(post-sxml post))))
         #:collection-template
         (lambda (site title posts prefix)
           `((h1 ,title)
             ,(map (lambda (post)
                     (let ((url (string-append prefix
                                               "/"
                                               (post-slug-v2 post))))
                       `(div (@ (class "summary"))
                             (h2 (a (@ (href ,url))
                                    ,(post-ref post 'title)))
                             (div (@ (class "date"))
                                  ,(date->string (post-date post) "~d.~m.~Y"))
                             (div (@ (class "post"))
                                  ,(car (post-sxml post))))))
                   posts)))))

fonts.css

@font-face {
  font-family: "Open Sans";
  src: url("fonts/OpenSans-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
}

@font-face {
  font-family: "Open Sans";
  src: url("fonts/OpenSans-Italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
}

@font-face {
  font-family: "Open Sans";
  src: url("fonts/OpenSans-SemiBold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
}

@font-face {
  font-family: "Open Sans";
  src: url("fonts/OpenSans-SemiBoldItalic.woff2") format("woff2");
  font-weight: 600;
  font-style: italic;
}

@font-face {
  font-family: "Open Sans";
  src: url("fonts/OpenSans-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
}

@font-face {
  font-family: "Open Sans";
  src: url("fonts/OpenSans-BoldItalic.woff2") format("woff2");
  font-weight: 700;
  font-style: italic;
}

@font-face {
  font-family: "Droid Sans Mono";
  src: url("fonts/DroidSansMono.woff2") format("woff2");
}

code.css

code,
pre {
    font-family: var(--mono-font);
    color: var(--text);
}

.src {
    background-color: #dfdfdf;
    border-radius: 0.3rem;
}

code {
    padding-inline: 0.3rem;
}

pre {
    padding: 1rem 1.4rem;
    max-width: 100%;
    overflow: auto;
    color: var(--text);
}

.org-builtin {
    color: #775228;
    font-weight: bold;
}

.org-comment {
    color: #6a5937;
    font-style: italic;
}

.org-comment-delimiter {
    color: #6a5937;
    font-style: italic;
}

.org-constant {
    color: #006e50;
}

.org-doc {
    color: #42573f;
    font-style: italic;
}

.org-function-name {
    color: #882000;
}

.org-keyword {
    color: #702f1f;
    font-weight: bold;
}

.org-makefile-targets {
    color: #882000;
}

.org-string {
    color: #3a7800;
}

.org-type {
    color: #226022;
}

.org-variable-name {
    color: #125a7f;
}

site.css

:root,
::backdrop {
    --sans-font: "Open Sans", sans-serif;
    --mono-font: "Droid Sans Mono", monospace;
    --standard-border-radius: 5px;

    --bg: #f0efea;
    --text: #23211c;
}

/* Reset box-sizing */
*, *::before, *::after {
    box-sizing: border-box;
}

html {
    font-family: var(--sans-font);
    scroll-behavior: smooth;
    background-color: var(--bg);
    height: 100%;
}

/* Make the body a nice central block */
body {
    color: var(--text);
    font-size: 1.15rem;
    line-height: 1.5;
    display: grid;
    grid-template-columns: 1fr min(55rem, 90%) 1fr;
    margin: 0;
}
body > * {
    grid-column: 2;
}

/* Make the header bg full width, but the content inline with body */
/* TODO: I will need to find a way to get the header out of the nested divs. */
body > div > div > header {
    grid-column: 1 / -1;
}

body > div > div > header > *:only-child {
    margin-block-start: 2rem;
}

body > div > div > header h1 {
    max-width: 1200px;
    margin: 1rem auto;
}

body > div > div > header p {
    font-style: italic;
}

/* Add a little padding to ensure spacing is correct between content and header > nav */
main {
    padding-top: 1.5rem;
}

/* TODO: Why is the footer in a div? */
body > div > footer {
    margin-top: 4rem;
    padding: 2rem 1rem 1.5rem 1rem;
    font-size: 0.9rem;
    border-top: 1px solid var(--text);
}

/* Format headers */
h1 {
    font-size: 3rem;
}

h2 {
    font-size: 1.8rem;
    margin-top: 3rem;
}

p {
    margin: 1.5rem 0;
}

/* Prevent long strings from overflowing container */
p, h1, h2, h3, h4, h5, h6 {
    overflow-wrap: break-word;
}

/* Fix line height when title wraps */
h1,
h2,
h3 {
    line-height: 1.1;
}

/* Reduce header size on mobile */
@media only screen and (max-width: 720px) {
    h1 {
        font-size: 2.5rem;
    }

    h2 {
        font-size: 2.1rem;
    }

    h3 {
        font-size: 1.75rem;
    }

    h4 {
        font-size: 1.25rem;
    }
}

/* Format links & buttons */
a,
a:visited {
    /* Make links semibold */
    font-weight: 600;
    color: var(--text);
}

a:hover {
    text-decoration: none;
}

/* Format navigation */
nav {
    line-height: 2;
    padding: 1rem 0 0 0;
    border-bottom: 1px solid var(--text);
    font-weight: 600;
}

/* Use flexbox to allow items to wrap, as needed */
nav ul {
    align-content: space-around;
    align-items: center;
    justify-content: left;
    list-style-type: none;
    padding: 0;
}

/* List items are inline elements, make them behave more like blocks */
nav ul li {
    margin: 0 4rem 0 0;
    display: inline-block;
    font-weight: 600;
}

nav a,
nav a:visited {
    margin: 0 2rem 2rem 0.5rem;
    color: var(--text);
    display: inline-block;
    text-decoration: underline;
}

nav a:hover,
nav a.current,
nav a[aria-current="page"] {
    border-color: var(--text);
    color: var(--text);
    cursor: pointer;
    text-decoration: none;
}

section {
    border-top: 1px solid var(--text);
    border-bottom: 1px solid var(--text);
    padding: 2rem 1rem;
    margin: 3rem 0;
}

/* Don't double separators when chaining sections */
section + section,
section:first-child {
    border-top: 0;
    padding-top: 0;
}

section:last-child {
    border-bottom: 0;
    padding-bottom: 0;
}

table {
    border-collapse: collapse;
    margin: 1.5rem 0;
}

figure > table {
    width: max-content;
    margin: 0;
}

td,
th {
    border: 1px solid var(--text);
    text-align: start;
    padding: 0.5rem;
}

th {
    font-weight: 600;
}

table caption {
    font-weight: 600;
    margin-bottom: 0.5rem;
}

/* Misc body elements */
hr {
    border: none;
    height: 1px;
    margin: 1rem auto;
}

figure {
    margin: 0;
    display: block;
    overflow-x: auto;
}

sup, sub {
    vertical-align: baseline;
    position: relative;
}

sup {
    top: -0.4em;
}

sub {
    top: 0.3em;
}

.gitignore

/pages/programs/website/
/posts/
/site/