grug

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

commit c3491fa94db4ccf37ee0398a9f35790221e6b1f5
parent f01f01ed70594bc62c32c035d3f0f583bda7043c
Author: Luke Willis <lukejw@loquat.dev>
Date:   Fri,  4 Jul 2025 01:16:01 -0400

Working blog demo

Diffstat:
MMakefile.am | 3++-
Dexample/pages/index.md | 22----------------------
Aexample/posts/hello-world.md | 2++
Aexample/posts/update_situation.md | 2++
Agrug/builders.scm | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgrug/ui.scm | 76++++------------------------------------------------------------------------
6 files changed, 136 insertions(+), 95 deletions(-)

diff --git a/Makefile.am b/Makefile.am @@ -25,7 +25,8 @@ bin_SCRIPTS = \ SOURCES = \ grug/ui.scm \ - grug/utils.scm + grug/utils.scm \ + grug/builders.scm EXTRA_DIST += \ README.md \ diff --git a/example/pages/index.md b/example/pages/index.md @@ -1,22 +0,0 @@ -# Hello, world! -This is a test markdown file. - -I can use both **bold** and *italic* because `code` is super cool. -What do you think? - -## Title 2 - -### Title 3 - -#### Title 4 - -##### Title 5 (List edition) - -1. Numero uno -2. Numero dos - - "That's rascist" - - No it's not - - I can count in spanish, too (two)! -3. Numero tres - -The test is complete. diff --git a/example/posts/hello-world.md b/example/posts/hello-world.md @@ -0,0 +1,2 @@ +# Hello, post! +This is a test post. diff --git a/example/posts/update_situation.md b/example/posts/update_situation.md @@ -0,0 +1,2 @@ +# The Update Situation +Things are changing, and it might be good or bad. diff --git a/grug/builders.scm b/grug/builders.scm @@ -0,0 +1,126 @@ +(define-module (grug builders) + #:use-module (ice-9 popen) + #:use-module (ice-9 textual-ports) + #:use-module (ice-9 pretty-print) + #:use-module (htmlprag) + #:use-module (grug utils) + #:export (copy-directory + simple-pages + blog)) + +;; Reads the markdown file at path and converts it into sxml representing html ('shtml') +;; TODO: Cleanup and move to its own module +(define (markdown->shtml path) + (let* ((cmd (string-append "cmark --to html --nobreaks < " path)) + (port (open-input-pipe cmd)) + (html (get-string-all port))) + (close-port port) + (delete '*TOP* (html->shtml (string-delete #\newline html))))) + +;; Copy the given directory to the site. +;; This is good for things like CSS folders. +(define* (copy-directory directory + #:key + (prefix "site")) + (display "copy-directory\n") + + (for-each + (lambda (path) + (let* ((output-path (string-append prefix "/" path)) + (output-dir (dirname output-path))) + (format #t "\t~A -> ~A\n" path output-path) + (unless (file-exists? output-dir) (mkdir output-dir)) + (copy-file path output-path))) + (reverse (ls-recursive directory)))) + +;; TODO: Cleanup and move to its own module +(define (basic-template body) + `(*TOP* (*DECL* DOCTYPE html) + (html + (head + (meta (@ (charset "utf-8"))) + (title "Hello, world!")) + (body + (h1 "Test site") + ,@body)))) + +;; Build the pages in the given directory and copy them to the site. +;; This is good for things like 'About' or 'Contact' pages. +;; TODO: Don't assume a markdown reader +(define* (simple-pages directory + #:key + (prefix "site") + (template basic-template)) + (display "simple-pages\n") + + ;; Iterate through files in directory + (for-each + (lambda (path) + (let* ((base-shtml (markdown->shtml path)) + (built-shtml (template base-shtml)) + (output (shtml->html built-shtml)) + (output-dir + (string-append prefix + (substring (dirname path) + (string-length directory)))) + (output-path + (string-append output-dir "/" + (basename path ".md") + ".html"))) + (unless (file-exists? output-dir) (mkdir output-dir)) + (format #t "\t~A -> ~A\n" path output-path) + (call-with-output-file output-path + (lambda (port) + (display output port))))) + ;; The list is reversed so that the shortest paths are listed first. + (reverse (ls-recursive directory)))) + +;; TODO: Cleanup and move to its own module +(define (basic-collection-template posts) + `((h1 "Posts") + ,@(map (lambda (post) + `(article (h2 ,post) + (p "Description..."))) + posts))) + +;; TODO: Figure out how to get title, date and description information from posts +;; This would likely require some kind of parsing system for the first line of the file. +;; Perhaps it could be written in scheme? `((title . "My Update") (date . "07/10/2007 18:00")) +;; If I were to modify the text by removing the first line, I would have to change the parsing +;; process. I'd have to do it the hard way using ports after cutting out the string. +;; TODO: Cleanup and move to its own module +(define* (blog directory + #:key + (prefix "site") + (post-prefix "posts") + (template basic-template) + (collection-template basic-collection-template)) + (display "blog\n") + + (let ((posts (map + (lambda (path) + (let* ((base-shtml (markdown->shtml path)) + (built-shtml (template base-shtml)) + (output (shtml->html built-shtml)) + (output-dir + (string-append prefix "/" post-prefix + (substring (dirname path) + (string-length directory)))) + (output-path + (string-append output-dir "/" + (basename path ".md") + ".html"))) + (unless (file-exists? output-dir) (mkdir output-dir)) + (format #t "\t~A -> ~A\n" path output-path) + (call-with-output-file output-path + (lambda (port) + (display output port))) + output-path)) + (reverse (ls-recursive directory))))) + ;; Build index.html + (let ((output (shtml->html (template (collection-template posts)))) + (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)))))) diff --git a/grug/ui.scm b/grug/ui.scm @@ -1,80 +1,12 @@ ;;; Grug user interface. (define-module (grug ui) - #:use-module (grug utils) - #:use-module (htmlprag) - #:use-module (ice-9 popen) - #:use-module (ice-9 textual-ports) #:use-module (ice-9 pretty-print) + #:use-module (grug builders) #:export (main)) -;; Pipe the contents of a markdown file into cmark -;; TODO: Change to make compact HTML without messing up things like code blocks -(define (markdown->html path) - (let* ((cmd (string-append "cmark --to html --nobreaks < " path)) - (port (open-input-pipe cmd)) - (html (get-string-all port))) - (close-port port) - (string-delete #\newline html))) - -(define (basic-template body) - `(*TOP* (*DECL* DOCTYPE html) - (html - (head - (meta (@ (charset "utf-8"))) - (title "Hello, world! m")) - (body - (h1 "Test site") - ,@body)))) - -;; Copy the given directory to the output directory. Very simple. -;; I would use this for copying CSS files and images, for example. -(define* (copy-directory directory - #:key - (prefix "site")) - (display "copy-directory\n") - - (for-each - (lambda (path) - (let* ((output-path (string-append prefix "/" path)) - (output-dir (dirname output-path))) - (format #t "\t~A -> ~A\n" path output-path) - (unless (file-exists? output-dir) (mkdir output-dir)) - (copy-file path output-path))) - (reverse (ls-recursive directory)))) - -;; Build pages from the given directory into the output directory. -;; 'input-dir/index.md' will become 'output-dir/index.html' -;; 'input-dir/subdir/index.md' will become 'output-dir/subdir/index.html' -;; ...and so on. -(define* (simple-pages directory - #:key - (prefix "site") - (template basic-template)) - (display "simple-pages\n") - - ;; Iterate through files in directory - (for-each - (lambda (path) - (let* ((base-sxml (delete '*TOP* (html->sxml (markdown->html path)))) - (built-sxml (template base-sxml)) - (output (sxml->html built-sxml)) - (output-dir - (string-append prefix - (substring (dirname path) - (string-length directory)))) - (output-path - (string-append output-dir "/" - (basename path ".md") - ".html"))) - (unless (file-exists? output-dir) (mkdir output-dir)) - (format #t "\t~A -> ~A\n" path output-path) - (call-with-output-file output-path - (lambda (port) - (display output port))))) - ;; Reverse the list so that base directories are listed first - (reverse (ls-recursive directory)))) - (define* (main prog . args) + (pretty-print args) (simple-pages "pages") - (copy-directory "css")) + (copy-directory "css") + (blog "posts"))