grug

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

commit f01f01ed70594bc62c32c035d3f0f583bda7043c
parent 12e85119ad4486d702af1f0838aa2e75f410393d
Author: Luke Willis <lukejw@loquat.dev>
Date:   Thu,  3 Jul 2025 21:45:09 -0400

Working simple-pages and copy-directory builders

Diffstat:
M.gitignore | 3+--
MMakefile.am | 3++-
Aexample/css/style.css | 0
Aexample/pages/err/404.md | 1+
Aexample/pages/foo.md | 2++
Aexample/pages/index.md | 22++++++++++++++++++++++
Mgrug/ui.scm | 77++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Agrug/utils.scm | 14++++++++++++++
8 files changed, 94 insertions(+), 28 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,2 +1 @@ -index.md -*.html +example/site diff --git a/Makefile.am b/Makefile.am @@ -24,7 +24,8 @@ bin_SCRIPTS = \ scripts/grug SOURCES = \ - grug/ui.scm + grug/ui.scm \ + grug/utils.scm EXTRA_DIST += \ README.md \ diff --git a/example/css/style.css b/example/css/style.css diff --git a/example/pages/err/404.md b/example/pages/err/404.md @@ -0,0 +1 @@ +You failed. diff --git a/example/pages/foo.md b/example/pages/foo.md @@ -0,0 +1,2 @@ +# Welcome to FOO PAGE +Truly the best alternate page diff --git a/example/pages/index.md b/example/pages/index.md @@ -0,0 +1,22 @@ +# 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/grug/ui.scm b/grug/ui.scm @@ -1,6 +1,7 @@ ;;; 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) @@ -8,6 +9,7 @@ #: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)) @@ -15,39 +17,64 @@ (close-port port) (string-delete #\newline html))) -(define (test-layout body) +(define (basic-template body) `(*TOP* (*DECL* DOCTYPE html) (html (head (meta (@ (charset "utf-8"))) - (title "Hello, world!")) + (title "Hello, world! m")) (body (h1 "Test site") ,@body)))) -(define* (main prog file . args) - (format #t "Args: ~a~%" args) +;; 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") - (display "Stage 1 - parsed MD to HTML:\n") - (define raw-html (markdown->html file)) - (display raw-html) - (newline) - (newline) + (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)))) - (display "Stage 2 - converted HTML to SXML:\n") - (define base-html (delete '*TOP* (html->sxml raw-html))) - (pretty-print base-html) - (newline) +;; 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)))) - (display "Stage 3 - modified SXML:\n") - (define new-html (test-layout base-html)) - (pretty-print new-html) - (newline) - - (display "Stage 4 - converted SXML to HTML:\n") - (define final-html (sxml->html new-html)) - (display final-html) - (newline) - (call-with-output-file (string-append file ".html") - (lambda (port) - (display final-html port)))) +(define* (main prog . args) + (simple-pages "pages") + (copy-directory "css")) diff --git a/grug/utils.scm b/grug/utils.scm @@ -0,0 +1,14 @@ +;;; Utility functions for Grug. + +(define-module (grug utils) + #:use-module (ice-9 ftw) + #:export (ls-recursive)) + +;; Return a list of file paths by recursively searching a given directory. +;; This is basically like running `ls -aR`. +(define (ls-recursive directory) + (let ((enter? (lambda (path stat result) #t)) + (leaf (lambda (path stat result) (cons path result))) + (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)))