From d4830cdd422258a7c91a5ed07af50f8c208a29ee Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Tue, 15 Aug 2023 23:33:17 -0500 Subject: A new start (again) --- .dir-locals.el | 4 + .gitignore | 10 +- .repl | 4 + Makefile | 15 -- boudin.egg | 49 ++++-- boudin.scm | 521 +------------------------------------------------------ boudin.sld | 7 + lib/config.sld | 45 +++++ lib/schmaltz.sld | 17 ++ lib/schmaltz.sls | 103 +++++++++++ lib/types.sld | 24 +++ lib/types.sls | 72 ++++++++ lib/util.sld | 50 ++++++ test/test.html | 19 -- 14 files changed, 379 insertions(+), 561 deletions(-) create mode 100644 .dir-locals.el create mode 100644 .repl delete mode 100644 Makefile mode change 100755 => 100644 boudin.scm create mode 100644 boudin.sld create mode 100644 lib/config.sld create mode 100644 lib/schmaltz.sld create mode 100644 lib/schmaltz.sls create mode 100644 lib/types.sld create mode 100644 lib/types.sls create mode 100644 lib/util.sld delete mode 100644 test/test.html diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..9c78ce8 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,4 @@ +;;; Directory Local Variables -*- no-byte-compile: t -*- +;;; For more information see (info "(emacs) Directory Variables") + +((scheme-mode . ((geiser-scheme-implementation . chicken)))) diff --git a/.gitignore b/.gitignore index 8e87b45..0b68fed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ boudin -*.sh -out \ No newline at end of file +*.inline +*.link +*.so +*.o +*.import.scm +*.types +*.build.sh +*.install.sh \ No newline at end of file diff --git a/.repl b/.repl new file mode 100644 index 0000000..229237b --- /dev/null +++ b/.repl @@ -0,0 +1,4 @@ +;; -*- scheme -*- +(import (beaker system)) + +(print "> (load-system \"boudin.egg\")") diff --git a/Makefile b/Makefile deleted file mode 100644 index 48999db..0000000 --- a/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -# boudin : a small static site generator - -DESTDIR = -PREFIX = $(DESTDIR)/usr - -boudin: boudin.scm - chicken-install -n - -.PHONY: install -install: - chicken-install - -.PHONY: clean -clean: - rm -f *.build.sh *.install.sh diff --git a/boudin.egg b/boudin.egg index cc163ed..e9ca2dc 100644 --- a/boudin.egg +++ b/boudin.egg @@ -1,17 +1,42 @@ -;; boudin -*- scheme +;;; boudin -*- scheme -*- -((synopsis "A small static site generator.") +((synopsis "A small tasty ssg.") (author "Case Duckworth") - (version "12044") + (version "0.0.0") (license "God Willing License") - (category fluff) - (dependencies chicanery - atom + + (dependencies chicanery r7rs utf8 html-parser - scss - srfi-37 - srfi-152 - sxml-serializer - sxpath) + srfi-152) + + (component-options + (csc-options "-X" "r7rs" "-R" "r7rs" + "-X" "utf8" "-R" "utf8" + "-no-warnings")) + (components - (program boudin))) + (program boudin + (component-dependencies boudin-lib)) + + (extension boudin-lib + (source boudin.sld) + (modules boudin) + (install-name boudin) + (component-dependencies boudin.schmaltz)) + + (extension boudin.config + (source lib/config.sld) + (component-dependencies boudin.schmaltz)) + + (extension boudin.schmaltz + (source lib/schmaltz.sld) + (source-dependencies lib/schmaltz.sls)) + + (extension boudin.types + (source lib/types.sld) + (source-dependencies lib/types.sls) + (component-dependencies boudin.config + boudin.util)) + + (extension boudin.util + (source lib/util.sld)))) diff --git a/boudin.scm b/boudin.scm old mode 100755 new mode 100644 index 28727ff..7bd741d --- a/boudin.scm +++ b/boudin.scm @@ -1,519 +1,14 @@ -#!/bin/sh -#| -*- scheme -*- -exec csi -R r7rs -s "$0" "$@" -boudin --- a little static site generator -|# +;;; (boudin) --- A small tasty ssg -(import (chicken file) - (chicken file posix) - (chicken irregex) - (chicken pathname) - (chicken port) - (chicken process-context) - (chicken random) - (chicken string) - (chicken time posix) - (chicanery) - (atom) - (html-parser) - (scss) - (srfi 37) - (srfi 152) - (sxml-serializer) - (sxpath)) +(import (boudin)) -;;; Utilities +(define foo (render-string "#,hello from Boudin!" + (interaction-environment))) -(define el sxml->html) - -;;; Transformations -;; A static site generator can be thought of largely as two sets of -;; transformations: one transforming given input content to output content, and -;; another transforming source paths to destination paths. Since both, for my -;; purposes, are strings, I have a generic function that can perform both -;; transformations. - -(define (transform str . procs) #| string (string ->string) ... -> string - Apply PROCS to STR, left-to-right, and return the result. - Each PROC will be called with its predecessor's output, and should take a - string as input and return a string. |# - (if (null? procs) - str - (apply transform ((car procs) str) (cdr procs)))) - -;;; Path transformations - -(define (indexify path) #| path -> path - Replace the PATH's extension with "/index.html". - |# - (make-pathname (pathname-strip-extension path) - "index" - "html")) - -(define (slugify path) - (transform path - normalize-pathname - ;; XXX: this should be much more robust - (lambda (p) (pathname-replace-directory p "/")) - pathname-strip-extension - (lambda (p) (string-append p "/")))) - -(define (transform-path path outdir) #| path => path - Transform PATH according to boudin's needs. |# - (transform path - normalize-pathname - (lambda (p) (pathname-replace-directory p outdir)) - indexify)) - -;;; Content transformations - -(define (split-paragraphs str) #| string -> (list string ...) - Split STR into paragraphs. - A paragraph is a contiguous series of text lines separated from other - paragraphs by at least 2 newline \n characters. This procedure collapses - inter-paragraph space. |# - (let loop ((lines (string-split str "\n")) - (par '()) - (acc '())) - (cond - ((and (null? lines) ; base case: no more lines - (null? par)) ; ... or pending paragraph - (reverse acc)) - ((null? lines) ; add the final paragraph - (loop '() '() (cons (apply string-append (reverse par)) acc))) - ((equal? (car lines) "") ; paragraph break - (loop (cdr lines) - '() - (cons (apply string-append (reverse par)) acc))) - (else ; line break - (loop (cdr lines) - (cons (string-append (car lines) "\n") par) - acc))))) - -(define (wrap-paragraphs str) #| string -> string - Wrap naked paragraphs of STR in

tags. - A 'naked' paragraph is one that doesn't begin with '<' (after optional - beginning whitespace). |# - (let loop ((pars (map string-trim (split-paragraphs str))) - (acc '())) - (cond - ((null? pars) - (apply string-append (reverse acc))) - ((zero? (string-length (car pars))) - (loop (cdr pars) - acc)) - ((eq? #\< (string-ref (car pars) 0)) - (loop (cdr pars) - (cons (car pars) - acc))) - (else - (loop (cdr pars) - (cons (string-append "

" (car pars) "

\n") - acc)))))) - -(define (preprocess-text str) #| string -> string - Preprocess STR before passing it to `expand-string'. - For example, replace all '#' with '##' unless followed by a '{' or '('. - |# - (set! str (irregex-replace/all - '(or (: #\# #\# (look-ahead (or #\{ #\())) - (: #\# (look-ahead (~ #\{ #\())) - (: #\# eos)) - str - "##")) - #; (set! str (irregex-replace/all ; XXX: missing end paren - '(: #\@ #\() - str - "#(sxml->html `(")) - ;; Return transformed string - str) - -(define (expand-string str) #| string -> string - Expand STR by passing it in a port to CHICKEN's #<# string interpolation. - Yes, this is as cursed as it sounds. - - To make it slightly less so, all # are duplicated to escape them, except for - those before ( and {. To escape /those/, double them. |# - (let* ((delim (let loop ((attempt (number->string (pseudo-random-real)))) - (if (irregex-search attempt str) - (loop (number->string (pseudo-random-real))) - attempt))) - (template (make-concatenated-port - (open-input-string (string-append "#<#" delim "\n")) - (open-input-string (preprocess-text str)) - (open-input-string (string-append "\n" delim "\n")))) - (expanded (with-output-to-string - (lambda () - (display (eval (read template) - (interaction-environment))))))) - (irregex-replace/all '(: "#" - (* whitespace)) - expanded - ""))) - -(define (transform-content content) #| string -> string - Transform CONTENT according to boudin's needs. - This is the raw html, and will still need to be processed to extract metadata - and to be further wrapped in a template. |# - (transform content - expand-string - wrap-paragraphs)) - -;;; Pages -;; A is a record type that wraps the two transformations outlined above. -;; It also includes the extracted metadata from the page for processing. - -(define-record-type - (make-page slug meta source dest source-path dest-path) - page? - (slug page-slug (setter page-slug)) - (meta page-meta (setter page-meta)) - (source page-source) - (dest page-dest (setter page-dest)) - (source-path page-source-path) - (dest-path page-dest-path (setter page-dest-path))) - -(define (page-url page) - (normalize-pathname - (make-pathname (list (site-base-url) - (page-slug page)) - #f))) - -(define (%read-port port) #| port -> string - Read PORT until it hits eof and return the results as a string. - |# - (let ((chunk-size 512)) - (let loop ((next (read-string chunk-size port)) - (blank? #f) - (acc '())) - (cond - ((or (eof-object? next) - (and blank? (equal? next ""))) - (close-input-port port) - (apply string-append (reverse acc))) - ((equal? next "") - (loop (read-string chunk-size port) - #t - (cons next acc))) - (else - (loop (read-string chunk-size port) - blank? - (cons next acc))))))) - -(define read-port #| (optional port) -> string - Read PORT completely, returning the results as a string. - PORT defaults to `current-input-port'. - |# - (case-lambda - (() (%read-port (current-input-port))) - ((p) (%read-port p)))) - -(define (file->page file) #| string -> - Convert FILE to an sxml tree after transforming it. - This procedure returns both the sxml of the transformed content, but that - page's metadata, too. |# - (let* ((source (with-input-from-file file read-port)) - (dest (html->sxml (transform-content source)))) - (make-page (slugify file) - (extract-meta dest) - source - dest - file - (transform-path file (build-directory))))) - -(define (extract-meta tree) #| sxml -> alist - Extract metadata from TREE's comments. - Returns an alist of (key . value) pairs where keys and values are strings. |# - (let loop ((tree tree) - (acc '())) - (cond - ((or (atom? tree) - (null? tree)) - (reverse acc)) - ((and (list? (car tree)) - (eq? (caar tree) '*COMMENT*)) - (loop (cdr tree) - (let* ((comment (string-trim-both (cadar tree))) - (lines (string-split comment "\n"))) - (map (lambda (l) - (let ((kv (string-split l ":"))) - (cons (string-trim-both (car kv)) - (string-trim - (string-intersperse (cdr kv) ":"))))) - lines)))) - ((list? (car tree)) - (loop (cdr tree) - (let ((subtree (loop (car tree) '()))) - (if (null? subtree) - acc - (cons subtree acc))))) - (else (loop (cdr tree) acc))))) - -(define (meta-ref meta key default) #| alist string string -> - Get KEY's value from META, or DEFAULT if it doesn't exist. - DEFAULT is required because I think it's a good idea to require it. |# - (let ((x (assoc key meta))) - (if x (cdr x) default))) - -(define (page-meta-ref page key default) #| string string -> - Get KEY's value from PAGE's meta, or DEFAULT. - |# - (let ((meta (page-meta page))) - (meta-ref meta key default))) - -;;; Time -;; Time really only matters in feeds ... but it really does matter. So I need a -;; few helper functions. - -(define publish-time ; this is a parameter so it's consistent across a run. - (make-parameter - (time->string (seconds->utc-time) "%FT%TZ"))) - -(define (page-mtime page) #| -> time-string - Grab the mtime field from PAGE's source file. |# - (let ((file (page-source-path page))) - (and file - (file-exists? file) - (time->string (seconds->utc-time - (file-modification-time - file)))))) - -(define (page-guess-updated page) #| -> time-string - Guess the "updated" property of PAGE. |# - (let ((meta-date (page-meta-ref page "date" #f))) - (if meta-date - ;; Attempt to parse the date metadata field. - (time->string (seconds->utc-time ; This double-conversion is /great/ - (local-time->seconds - (or (string->time meta-date "%Y-%m-%d") - (string->time meta-date "%Y-%m-%d%n%H:%M") - (string->time meta-date "%Y-%m-%d%n%I:%M%n%p") - ;; ... more ? - (or (page-mtime page) - (publish-time)))))) - (or (page-mtime page) - (publish-time))))) - -;;; Templating -;; Templating uses sxml to define a layout for pages and indeces (index.html, -;; feed.xml). Sxml's "stylesheets" can be used to extract metadata out of html -;; comments and to further process the document. - -;; Each template has a default, but the user can override by defining templates -;; in .config.scm (see below). All templates are function parameters that take -;; a page's sxml tree (argument PAGE) and return a string. - -(define page-template - (make-parameter - (lambda (page) - `(html (@ (lang "en")) - (head (title ,(page-meta-ref page "title" "[untitled]")) - (link (@ (href "../style.css") ; relative - (rel "stylesheet"))) - (meta (@ (name "viewport") - (content "initial-scale=1.0")))) - (body ,(let ((title (page-meta-ref page "title" #f))) - (if title `(h1 ,title) "")) - ,@(cdr (page-dest page))))))) - -(define index-template - (make-parameter - (lambda pages - `(html (@ (lang "en")) - (head (title ,(site-name)) - (link (@ (href "./style.css") ; relative - (rel "stylesheet"))) - (meta (@ (name "viewport") - (content "initial-scale=1.0")))) - (body (h1 ,(site-name)) - (ul - ,@(map (lambda (pg) - `(li (a (@ (href ,(page-slug pg))) - ,(page-meta-ref pg - "title" - (pathname-file - (page-source-path pg)))))) - ;; TODO : sort pages - pages))))))) - -(define feed-template - (make-parameter - (lambda pages - (make-atom-doc - (make-feed - title: (make-title (site-name)) - id: (site-base-url) - updated: (publish-time) ; I don't like these semantics .. - authors: (list (make-author name: (site-author) - uri: (site-base-url))) - links: (list (make-link type: 'html - uri-language: "en" - uri: (site-base-url)) - (make-link relation: "self" - type: "application/atom+xml" - uri: (make-pathname - (site-base-url) "feed" "xml"))) - rights: (make-rights (site-rights)) - generator: (make-generator "Boudin" - uri: "https://git.acdw.net/boudin" - version: "0.1.0") - entries: (map (lambda (pg) - (make-entry - title: (make-title - (page-meta-ref pg "title" "[untitled]")) - links: (list (make-link type: 'html - uri: (page-url pg))) - id: (page-url pg) - updated: (page-guess-updated pg) - ;;published: - content: (make-content - (sxml->html (page-dest pg)) - type: html) - #;`(atom:content "foo"))) - pages)))))) - -;;; Publishing - -(define (write-style) - (print-log "writing style") - (with-output-to-file (make-pathname (build-directory) "style" "css") - (lambda () (write-css (site-style))))) - -(define (write-page page) - (print-log "writing " (or (page-meta-ref page "title" #f) - (page-source-path page))) - (create-directory (pathname-directory (page-dest-path page)) 'parents) - (with-output-to-file (page-dest-path page) - (lambda () (sxml-display-as-html ((page-template) page))))) - -(define (write-index pages) - (print-log "writing index") - (with-output-to-file (make-pathname (build-directory) "index" "html") - (lambda () (sxml-display-as-html (apply (index-template) pages))))) - -(define (write-feed pages) - (print-log "writing feed") - (serialize-sxml (apply (feed-template) pages) - output: (make-pathname (build-directory) "feed" "xml") - cdata-section-elements: '(atom:content) - ns-prefixes: `((*default* . "http://www.w3.org/2005/Atom") - (*default* . "http://www.w3.org/1999/xhtml") - . ,(atom-ns-prefixes)) - allow-prefix-redeclarations: #t - )) - -;;; Configuration - -;; Build configuration - -(define build-directory - (make-parameter "out/")) - -(define build-config - (make-parameter "config.scm")) - -;; Site configuration - -(define site-name - (make-parameter "[A boudin web site]")) - -(define site-base-url - (make-parameter "https://example.com/")) - -(define site-author - (make-parameter "nobody")) - -(define site-rights - (make-parameter (string-append "(C) " (site-author)))) - -(define site-style - (make-parameter - `(css+ (body (font "20px/1.4 sans-serif") - (max-width "48em") - (padding "1em") - (margin auto))))) - -;;; Options & Operands (SRFI 37) - -(define (print-log . xs) - (with-output-to-port (current-error-port) - (lambda () (apply print xs)))) - -(define (die error-code . xs) - (apply print-log xs) - (exit error-code)) - -(define usage - (string-append - "Usage: boudin [OPTIONS] FILE ...\n" - "Options:\n" - "-h, --help show this help and exit\n" - "-o DIR, --output DIR build site to DIR (default: out/)\n" - "Operands:\n" - "FILE ... files to build\n")) - -(define opt/help - (option '(#\h "help") ; Names - #f ; Required arg? - #f ; Optional arg? - (lambda _ ; Option proc (opt name arg seeds ...) - (die 0 usage)))) - -(define opt/build-directory - (option '(#\o "output") #t #f - (lambda (opt name arg seeds) - (build-directory arg) - seeds))) - -(define opt/build-config - (option '(#\c "config") #t #f - (lambda (opt name arg seeds) - (build-config arg) - seeds))) - -(define (process-args args) - (let ((pages '())) - (args-fold args - ;; Options - (list opt/help - opt/build-directory - opt/build-config) - ;; Unrecognized option proc (option name arg seeds ...) - (lambda (_ name _ _) - (die 1 "Unrecognized option: -" name "\n" usage)) - ;; Operand proc (operand seeds ...) - (lambda (name seeds) - (if (file-exists? name) - (set! pages (cons name pages)) - (die 2 "File not found: " name)) - seeds) - ;; Seeds - '()) - pages)) - -;;; Main entry point - -(define pages (make-parameter #f)) - -(define (main args) - (parameterize ((pages (process-args args))) - (unless (pages) - (die 0 "No pages to process. Abort.")) - ;; Check for files, create directories - (if (file-exists? (build-config)) - (load (build-config)) - (print-log "No config.scm found; using default config")) - (create-directory (build-directory) 'parents) - ;; Convert pages to sxml. This needs to be done here because config.scm - ;; might define functions used by the pages. - (pages (map file->page (pages))) - ;; Build the site - (write-style) ; TODO: copy static assets (?) - (for-each write-page (pages)) - (write-index (pages)) - (write-feed (pages)))) +(define (main . args) + (print foo) + (for-each print args)) (cond-expand - ((or chicken-script compiling) - (main (command-line-arguments))) + (compiling (apply main (command-line))) (else)) diff --git a/boudin.sld b/boudin.sld new file mode 100644 index 0000000..8129659 --- /dev/null +++ b/boudin.sld @@ -0,0 +1,7 @@ +(define-library (boudin) + (import (scheme base) + (scheme repl) + (boudin schmaltz)) + (export hello) + (begin + (define hello "Hi"))) diff --git a/lib/config.sld b/lib/config.sld new file mode 100644 index 0000000..bdd6ef5 --- /dev/null +++ b/lib/config.sld @@ -0,0 +1,45 @@ +;;; (boudin config) --- default values for configuration options +;; +;; To change these, make a `config.scm' in your site's root directory and change +;; these. They're all parameters so .. change em like that. + +(define-library (boudin config) + (import (scheme base) + (boudin schmaltz) + ;; not portable + (chicken pathname) + (html-parser) + ) + + (export site-url site-dest + page-path-transformers page-text-transformers + page-template index-template feed-template) + + (begin + ;; Site information + (define site-url + (make-parameter "example.com")) + (define site-dest + (make-parameter "out/")) + ;; Transformers + (define page-path-transformers + (make-parameter + (list (lambda (path) (make-pathname (site-dest) path))))) + (define page-text-transformers + (make-parameter + (list wrap-paragraphs + render-string + html->sxml))) + ;; Templates --- note that we use quote but include unquote forms here. + ;; This is to simplify the configuration and to avoid a cyclical dependency + ;; with (boudin types). + (define page-template + (make-parameter + '(html (@ (lang "en-us")) + (head (title (or (page-ref pg "title") "[untitled]"))) + (body ,@(page-sxml pg))))) + (define index-template + (make-parameter 'todo)) + (define feed-template + (make-parameter 'todo)) + )) diff --git a/lib/schmaltz.sld b/lib/schmaltz.sld new file mode 100644 index 0000000..d54d53e --- /dev/null +++ b/lib/schmaltz.sld @@ -0,0 +1,17 @@ +(define-library (boudin schmaltz) + (export render + render-string + wrap-paragraphs) + + (import (scheme base) + (scheme case-lambda) ; case-lambda + (scheme eval) ; eval + (scheme read) ; read + (scheme repl) ; interaction-environment + (scheme write) ; display + (only (html-parser) + sxml->html) + (only (srfi 152) + string-split string-trim)) + + (include "lib/schmaltz.sls")) diff --git a/lib/schmaltz.sls b/lib/schmaltz.sls new file mode 100644 index 0000000..623191f --- /dev/null +++ b/lib/schmaltz.sls @@ -0,0 +1,103 @@ +;;; (boudin schmaltz) --- transform almost-html plus scheme into html + +;;; Embedded scheme code + +(define (->string x) + (call-with-port (open-output-string) + (lambda (port) + (display x port) + (get-output-string port)))) + +(define render-string + (case-lambda + ((s) (render-string s (interaction-environment))) + ((s env) + (call-with-port (open-input-string s) + (lambda (port) + (render port env)))))) + +(define (render port env) + ;; A few rough edges: + ;; #, x will try to render x + (define (burn-char) + ;; Throw a character away. I've defined this for clarity below. + (read-char port)) + + (let loop ((ch (read-char port)) + (acc '())) + (define (proceed) (loop (read-char port) (cons ch acc))) + (cond + ((not ch) + (loop (read-char port) acc)) + ((eof-object? ch) + (list->string (reverse acc))) + ((eq? ch #\#) ; special processing to come + (case (peek-char port) + ((#\\) ; inhibit processing of the next char + (burn-char) + (loop (read-char port) (cons ch acc))) + ((#\,) ; scheme eval expansion + (burn-char) + (loop #f + (append (let ((s (->string + (eval (read port) + env)))) + (cond + ((equal? s "#") ; XXX NOT PORTABLE + '()) + ((equal? s "#!eof") ; XXX NOT PORTABLE + '(#\, #\#)) + (else (reverse (string->list s))))) + acc))) + ((#\@) ; embedded sxml + (burn-char) + (loop #f + (append (let ((h (eval `(sxml->html ,(list 'quasiquote + (read port))) + env))) + (cond + ((equal? h "#!eof") ; XXX NOT PORTABLE + '(#\@ #\#)) + (else (reverse (string->list h))))) + acc))) + (else (proceed)))) + (else (proceed))))) + +;;; Wrap paragraphs + +(define (split-paragraphs str) + (let loop ((lines (string-split str "\n")) + (par '()) + (acc '())) + (cond + ((and (null? lines) ; base case: no more lines + (null? par)) ; ... or pending paragraph + (reverse acc)) + ((null? lines) ; add the final paragraph + (loop '() '() (cons (apply string-append (reverse par)) acc))) + ((equal? (car lines) "") ; paragraph break + (loop (cdr lines) + '() + (cons (apply string-append (reverse par)) acc))) + (else ; line break + (loop (cdr lines) + (cons (string-append (car lines) "\n") par) + acc))))) + +(define (wrap-paragraphs str) + (let loop ((pars (split-paragraphs str)) + (acc '())) + (cond + ((null? pars) + (apply string-append (reverse acc))) + ((zero? (string-length (car pars))) + (loop (cdr pars) + acc)) + ((eq? #\< (string-ref (string-trim (car pars)) 0)) + (loop (cdr pars) + (cons (car pars) + acc))) + (else + (loop (cdr pars) + (cons (string-append "

" (car pars) "

\n") + acc)))))) diff --git a/lib/types.sld b/lib/types.sld new file mode 100644 index 0000000..791ff53 --- /dev/null +++ b/lib/types.sld @@ -0,0 +1,24 @@ +(define-library (boudin types) + (import (scheme base) + (scheme case-lambda) + (scheme file) + (boudin config) + (boudin util) + ;; non-portable bits + (chicken pathname) + (html-parser) + ) + + (export + ;; pages + make-page page? + page-path page-dest page-text page-sxml page-meta + set-page-dest! set-page-text! set-page-sxml! set-page-meta! + extract-metadata page-ref page-set! + page-url page-slug + read-page write-page + ;; indeces + ;; static files + ) + + (include "lib/types.sls")) diff --git a/lib/types.sls b/lib/types.sls new file mode 100644 index 0000000..2b4da5d --- /dev/null +++ b/lib/types.sls @@ -0,0 +1,72 @@ +;;; (boudin types) --- pages, indeces, and static files + +;; All paths are relative to the site directory unless otherwise noted + +(define-record-type page + (make-page path ; Input path + dest ; Output path (rel. to output directory) + text ; Input text + sxml ; Rendered sxml + meta ; Metadata (title, etc.) + ) + page? + (path page-path) + (dest page-dest set-page-dest!) + (text page-text set-page-text!) + (sxml page-sxml set-page-sxml!) + (meta page-meta set-page-meta!)) + +(define (page-ref pg key) + (assoc-ref key (page-meta pg) (identity #f))) + +(define (page-set! pg key val) + (set-page-meta! pg (cons (cons key val) + (page-meta pg)))) + +(define (extract-metadata sxml) + #f) + +(define (*urlify path) + (normalize-pathname + (make-pathname (list (site-url) + (pathname-strip-extension path)) + "index.html"))) + +(define (page-url pg) ; foo.html => http://site.com/foo/index.html + (or (page-ref pg "url") ; memoization + (let ((url (*urlify (page-path pg)))) + (page-set! pg "url" url) + url))) + +(define (*slugify url) ; I don't love how this is written.. + (let-values (((_ _ dirs) (decompose-directory url))) + (let loop ((this (car dirs)) + (rest (cdr dirs))) + (if (null? (cdr rest)) + (make-pathname (list "/" this) #f) + (loop (car rest) + (cdr rest)))))) + +(define (page-slug pg) ; http://site.com/foo/index.html => /foo/ + (or (page-ref pg "slug") ; memoization + (let ((slug (*slugify (page-url pg)))) + (page-set! pg "slug" slug) + slug))) + +(define (read-page path) + (let ((pg (make-page path #f #f #f #f))) + (set-page-dest! pg ((apply o (page-path-transformers)) path)) + (set-page-text! pg (with-input-from-file path slurp)) + (set-page-sxml! pg ((apply o (page-text-transformers)) (page-text pg))) + (set-page-meta! pg (extract-metadata (page-sxml pg))) + pg)) + +(define write-page + (case-lambda + ((pg) (call-with-output-file (page-dest pg) + (lambda (port) (write-page pg port)))) + ((pg port) + (sxml-display-as-html ((eval/q (page-template)) pg) port)))) + + + diff --git a/lib/util.sld b/lib/util.sld new file mode 100644 index 0000000..64c633e --- /dev/null +++ b/lib/util.sld @@ -0,0 +1,50 @@ +;;; (boudin util) --- utility functions + +(define-library (boudin util) + (import (scheme base) + (scheme case-lambda) + (scheme eval)) + + (export identity + o + assoc-ref + slurp + eval/q) + + (begin + (define (identity x) x) + + (define (o . procs) ; stole from chicken core + (if (null? procs) + identity + (let loop ((procs procs)) + (let ((h (car procs)) + (t (cdr procs))) + (if (null? t) + h + (lambda (x) (h ((loop t) x)))))))) + + (define assoc-ref + (case-lambda + ((key alist) + (assoc-ref alist + key + (lambda () (error "Unrecognized key." key)))) + ((key alist failure) + (cond ((assoc key alist) => cdr) + (else (failure)))))) + + (define slurp + (case-lambda + (() (slurp (current-input-port))) + ((port) + (let loop ((ch (read-char)) + (acc '())) + (if (eof-object? ch) + (list->string (reverse acc)) + (loop (read-char) (cons ch acc))))))) + + (define (eval/q form env) ; this is probably a bad idea + (eval (list 'quasiquote form) env)) + + )) diff --git a/test/test.html b/test/test.html deleted file mode 100644 index c78e868..0000000 --- a/test/test.html +++ /dev/null @@ -1,19 +0,0 @@ - - -

some html

-

(without p tags)

- -Here is a test paragraph. example link. - -Here's another. I wonder if it'll just do the thing .. or whatever. Maybe I -should try to make it multiple lines, as well. - -
    -
  • - one plus two is #(+ 1 2). -
  • -
  • two
  • -
-- cgit 1.4.1-21-gabe81