grug

A static website generator written for Guile Scheme
Log | Files | Refs | README | LICENSE

commit 55d561f5c11fe91ca0aacc27755dcdabdf70f29e
parent 4fb352c507697ba4410211f4080952215fcabcaa
Author: Luke Willis <lukejw@loquat.dev>
Date:   Fri,  4 Jul 2025 23:05:12 -0400

Cleanup metadata parsing and usage

Diffstat:
Mexample/pages/about.md | 2+-
Mexample/posts/hello-world.md | 1-
Mexample/posts/update-situation.md | 1-
Mgrug/builders.scm | 75+++++++++++++++++++++++++--------------------------------------------------
Mgrug/utils.scm | 23++++++++++++++++++++++-
5 files changed, 48 insertions(+), 54 deletions(-)

diff --git a/example/pages/about.md b/example/pages/about.md @@ -1,2 +1,2 @@ -# About Me +`((title . "About Me")) This is some information about me. diff --git a/example/posts/hello-world.md b/example/posts/hello-world.md @@ -1,3 +1,2 @@ `((title . "Hello, world!") (description . "You'll never guess what this post says")) -# GRUG Is Finally Working!!! This is a test post. diff --git a/example/posts/update-situation.md b/example/posts/update-situation.md @@ -1,3 +1,2 @@ `((title . "Update Situation") (description . "In this post, I talk about another update.")) -# The Situation Boys, we did it. There's another situation. diff --git a/grug/builders.scm b/grug/builders.scm @@ -1,12 +1,9 @@ (define-module (grug builders) - #:use-module (ice-9 popen) - #:use-module (ice-9 textual-ports) #:use-module (ice-9 pretty-print) - #:use-module (ice-9 rdelim) - #:use-module (ice-9 eval-string) + #:use-module (ice-9 receive) #:use-module (htmlprag) - #:use-module (grug readers) #:use-module (grug utils) + #:use-module (grug readers) #:export (copy-directory simple-pages blog)) @@ -30,14 +27,15 @@ (copy-file path output-path))) (reverse (ls-recursive directory)))) -(define (basic-template body) +;; metadata should be an a-list +(define (basic-template body metadata) `(*TOP* (*DECL* DOCTYPE html) (html (head (meta (@ (charset "utf-8"))) - (title "Hello, world!")) + (title ,(assoc-ref metadata 'title))) (body - (h1 "Test site") + (h1 ,(assoc-ref metadata 'title)) ,@body)))) ;; Build the pages in the given directory and copy them to the site. @@ -62,19 +60,15 @@ ".html"))) (unless (file-exists? output-dir) (mkdir output-dir)) (format #t "\t~A -> ~A\n" path output-path) - ;; Build html (no metadata parsing) - (call-with-input-file path - (lambda (port) - (let* ((input (get-string-all port)) - (base-shtml (cmark input)) - (built-shtml (template base-shtml)) - (output (shtml->html built-shtml))) - (call-with-output-file - output-path - (lambda (port) - (display output port)))))))) + (receive (metadata contents) + (load-string-with-metadata path) + (let* ((base-shtml (cmark contents)) + (built-shtml (template base-shtml metadata)) + (output (shtml->html built-shtml))) + (write-string-to-path output output-path))))) (reverse (ls-recursive directory)))) +;; `posts` should be a list of a-lists containing post metadata (define (basic-collection-template posts) `((h1 "Posts") ,@(map (lambda (post) @@ -83,23 +77,17 @@ (p ,(assoc-ref post 'description)))) posts))) - ;; Build a blog using the posts in the given directory. ;; ;; Builds prefix/index.html and posts in prefix/post-prefix/. -;; Post files should define metadata on the first in the form of an a-list. -;; -;; Example: `((title . "Hello, world!") (description . "Foo bar baz...")) -;; -;; This will be parsed and a list of all the collected metadata will be passed to -;; collection-template, where you can process it any way you like. -;; A single entry titled uri will be added. -;; +;; Posts have 'uri added to their metadata. +;; ;; TODO: Don't assume a markdown reader (define* (blog directory #:key (prefix "site") (post-prefix "posts") + (metadata `((title . "Recent Posts"))) (template basic-template) (collection-template basic-collection-template)) (display "blog\n") @@ -123,29 +111,16 @@ output-name))) (unless (file-exists? output-dir) (mkdir output-dir)) (format #t "\t~A -> ~A\n" path output-path) - - (call-with-input-file path - (lambda (port) - ;; Strip and parse post metadata from the first line of the file - (define metadata (eval-string (read-line port))) - - ;; Build post html from the rest of the file - (let* ((input (get-string-all port)) - (base-shtml (cmark input)) - (built-shtml (template base-shtml)) - (output (shtml->html built-shtml))) - (call-with-output-file - output-path - (lambda (port) - (display output port)))) - - ;; Collect metadata with uri added - (acons 'uri relative-output-path metadata))))) + (receive (metadata contents) + (load-string-with-metadata path) + (let* ((base-shtml (cmark contents)) + (built-shtml (template base-shtml metadata)) + (output (shtml->html built-shtml))) + (write-string-to-path output output-path)) + (acons 'uri relative-output-path metadata)))) (reverse (ls-recursive directory))))) ;; Build index.html - (let ((output (shtml->html (template (collection-template posts)))) + (let ((output (shtml->html (template (collection-template posts) metadata))) (output-path (string-append prefix "/index.html"))) (format #t "\t~A\n" output-path) - (call-with-output-file output-path - (lambda (port) - (display output port)))))) + (write-string-to-path output output-path)))) diff --git a/grug/utils.scm b/grug/utils.scm @@ -1,8 +1,13 @@ ;;; Utility functions for Grug. (define-module (grug utils) + #:use-module (ice-9 eval-string) #:use-module (ice-9 ftw) - #:export (ls-recursive)) + #:use-module (ice-9 rdelim) + #:use-module (ice-9 textual-ports) + #:export (ls-recursive + load-string-with-metadata + write-string-to-path)) ;; Return a list of file paths by recursively searching a given directory. ;; This is basically like running `ls -aR`. @@ -12,3 +17,19 @@ (err (lambda (path stat errno result) (error "Failed to list file" errno path))) (noop (lambda (path stat result) result))) (file-system-fold enter? leaf noop noop noop err '() directory))) + +;; Load a file, parse metadata from the top of the file and return the rest as a string. +;; Using eval-string might be overkill. Perhaps a custom parser should be written? +;; TODO: Allow for multi-line metadata +(define (load-string-with-metadata path) + (call-with-input-file + path + (lambda (port) + (values (eval-string (read-line port)) + (get-string-all port))))) + +;; Write a string to the file at the given path. +(define (write-string-to-path string path) + (call-with-output-file path + (lambda (port) + (display string port))))