cripslock

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit d8216b1f0b193adb8bd76ff8a6512ab8f8ba8a91
parent 740df319cd7a429ba77a766b5870aae7809d2be6
Author: Daniel Barlow <dan@telent.net>
Date:   Mon, 21 Jul 2025 22:16:35 +0100

extract post indexing code to site.fnl

Diffstat:
MMakefile | 2+-
Mmain.fnl | 130++++++++-----------------------------------------------------------------------
Asite.fnl | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 138 insertions(+), 119 deletions(-)

diff --git a/Makefile b/Makefile @@ -2,7 +2,7 @@ FENNEL?=fennel PREFIX?=/usr/local NAME?=cripslock -MODULES=main.fnl textile.fnl +MODULES=main.fnl textile.fnl site.fnl %.lua : %.fnl $(FENNEL) --compile $< > $@ diff --git a/main.fnl b/main.fnl @@ -6,118 +6,13 @@ (local http-version (require :http.version)) (local ce (require :cqueues.errno)) (local lcmark (require :lcmark)) - + (local server (require :server)) (local textile (require :textile)) +(local site (require :site)) (local { : view } (require :fennel)) -(fn frontmatter [stream] - (var bail? false) - (collect [l (stream:lines) - &until (or (= l "") bail?)] - (let [(k v) (string.match l "(.-): *(.+)")] - (if k - (values (string.lower k) v) - (set bail? true))))) - -(define-tests :frontmatter - (with-open [stream (io.open "tests/post-with-headers.textile" :r)] - (let [headers (frontmatter stream)] - (expect= headers.title "What I miss most about Lisp") - (expect= headers.author "Daniel Barlow") - ;; headers names are downcased - (expect= headers.x-parrot "norweigan blue") - (expect= headers.date "Sat Apr 30 23:23:33 2011") - ;; leaves stream position at first body line - (expect= (stream:read "l") "first content line")))) - - -;; there are two date formats used in most of the extant posts: -;; "Fri Jun 29 14:17:56 2018" (emacs current-time-string) -;; and "Fri, 18 Oct 2013 10:18:34 +0100" (*rfc2822") - -(local months - {"jan" 1 - "feb" 2 - "mar" 3 - "apr" 4 - "may" 5 - "jun" 6 - "jul" 7 - "aug" 8 - "sep" 9 - "oct" 10 - "nov" 11 - "dec" 12 - }) - -(fn parse-rfc2822-timestamp [date-str] - (let [(s _ weekday day month year hour min sec offset) - (string.find date-str "(%a+), (%d+) (%a+) (%d+) (%d+):(%d+):(%d+) ([+-]%d+)" 1)] - (and s - (os.time { - : year - :month (. months (string.lower month)) - : day - : hour - : min - : sec - :isdst (not (= offset "+0000")) ;; massive cheating here - })))) - -(fn parse-emacs-timestamp [date-str] - (let [(s _ weekday month day hour min sec year) - (string.find date-str "(%a+) (%a+) (%d+) (%d+):(%d+):(%d+) (%d+)" 1)] - (and s - (os.time { - : year - :month (. months (string.lower month)) - : day - : hour - : min - : sec - :isdst true ; probably? - })))) - -(fn parse-timestamp [s] - (or (parse-rfc2822-timestamp s) - (parse-emacs-timestamp s))) - -(define-tests :date-parsing - (expect= (parse-rfc2822-timestamp "Fri, 18 Oct 2013 10:18:34 +0100") - 1382087914) - (expect= (parse-rfc2822-timestamp "Tue, 28 Dec 2004 21:41:24 +0000") - 1104270084) - (expect= (parse-emacs-timestamp "Sat Jul 19 23:53:21 2025") 1752965601) - (expect= (parse-rfc2822-timestamp "Sat Jul 19 23:53:21 2025") nil) - (expect= (parse-timestamp "Sat Jul 19 23:53:21 2025") 1752965601) - ) - -(fn slug [title] - (-> title - (string.gsub "[^A-Za-z0-9]+" "_") - (string.gsub "_+$" "") - string.lower)) - -(fn index-posts [path] - (collect [name (lfs.dir path)] - (let [fullname (.. path "/" name) - (attr err) (lfs.attributes fullname)] - (when (not attr) (print :err err)) - (if (and attr - (= attr.mode :file) - (or - (string.match name "%.textile$") - (string.match name "%.md$"))) - (with-open [f (io.open fullname)] - (let [fields (frontmatter f)] - (values (slug (or fields.subject fields.title)) - (doto fields - (tset :path name) - (tset :full-path fullname) - (tset :date (and fields.date (parse-timestamp fields.date))))))))))) - (local handlers [ #(if (= $2 "/") @@ -180,30 +75,29 @@ ) -(local post-index (index-posts "data/blogs/telent.blog/posts/")) -; (print (view (icollect [k v (pairs post-index)] k))) +(local post-index (site.for-pathname "data/blogs/telent.blog/posts/")) (fn dispatch [route stream] (let [s { :write #(stream:write_chunk $2) } ] (case route.command :show - (let [post (. post-index route.slug) + (let [post (post-index:get route.slug) path (. post :full-path)] (print :path path) (with-open [f (io.open path )] (when f - (let [_ (frontmatter f)] - (if (string.match path "%.textile$") - (each [_ el (ipairs (textile.parse f))] - (textile.to-html s el)) - (stream:write_chunk - (lcmark.convert (f:read "*a") "html" { :yaml_parser #($1) })) - )))) + (f:seek :set post.body-offset) + (if (string.match path "%.textile$") + (each [_ el (ipairs (textile.parse f))] + (textile.to-html s el)) + (stream:write_chunk + (lcmark.convert (f:read "*a") "html" { :yaml_parser #($1) })) + ))) (stream:write_chunk "" true)) :search (do (stream:write_chunk "<p><ul>") - (each [slug post (pairs post-index)] + (each [slug post (pairs (post-index:search))] (stream:write_chunk (string.format "<li><a href=%q>%s</a></li>" diff --git a/site.fnl b/site.fnl @@ -0,0 +1,125 @@ +(import-macros { : define-tests : expect : expect= } :assert) +(local lfs (require :lfs)) + +;; there are two date formats used in most of the extant posts: +;; "Fri Jun 29 14:17:56 2018" (emacs current-time-string) +;; and "Fri, 18 Oct 2013 10:18:34 +0100" (*rfc2822") + +(local months + {"jan" 1 + "feb" 2 + "mar" 3 + "apr" 4 + "may" 5 + "jun" 6 + "jul" 7 + "aug" 8 + "sep" 9 + "oct" 10 + "nov" 11 + "dec" 12 + }) + +(fn parse-rfc2822-timestamp [date-str] + (let [(s _ weekday day month year hour min sec offset) + (string.find date-str "(%a+), (%d+) (%a+) (%d+) (%d+):(%d+):(%d+) ([+-]%d+)" 1)] + (and s + (os.time { + : year + :month (. months (string.lower month)) + : day + : hour + : min + : sec + :isdst (not (= offset "+0000")) ;; massive cheating here + })))) + +(fn parse-emacs-timestamp [date-str] + (let [(s _ weekday month day hour min sec year) + (string.find date-str "(%a+) (%a+) (%d+) (%d+):(%d+):(%d+) (%d+)" 1)] + (and s + (os.time { + : year + :month (. months (string.lower month)) + : day + : hour + : min + : sec + :isdst true ; probably? + })))) + +(fn parse-timestamp [s] + (or (parse-rfc2822-timestamp s) + (parse-emacs-timestamp s))) + +(define-tests :date-parsing + (expect= (parse-rfc2822-timestamp "Fri, 18 Oct 2013 10:18:34 +0100") + 1382087914) + (expect= (parse-rfc2822-timestamp "Tue, 28 Dec 2004 21:41:24 +0000") + 1104270084) + (expect= (parse-emacs-timestamp "Sat Jul 19 23:53:21 2025") 1752965601) + (expect= (parse-rfc2822-timestamp "Sat Jul 19 23:53:21 2025") nil) + (expect= (parse-timestamp "Sat Jul 19 23:53:21 2025") 1752965601) + ) + + +(fn frontmatter [stream] + (var bail? false) + (collect [l (stream:lines) + &until (or (= l "") bail?)] + (let [(k v) (string.match l "(.-): *(.+)")] + (if k + (values (string.lower k) v) + (set bail? true))))) + +(define-tests :frontmatter + (with-open [stream (io.open "tests/post-with-headers.textile" :r)] + (let [headers (frontmatter stream)] + (expect= headers.title "What I miss most about Lisp") + (expect= headers.author "Daniel Barlow") + ;; headers names are downcased + (expect= headers.x-parrot "norweigan blue") + (expect= headers.date "Sat Apr 30 23:23:33 2011") + ;; leaves stream position at first body line + (expect= (stream:read "l") "first content line")))) + +(fn slug-for [title] + (-> title + (string.gsub "[^A-Za-z0-9]+" "_") + (string.gsub "_+$" "") + string.lower)) + +(fn index-posts [path] + (collect [name (lfs.dir path)] + (let [fullname (.. path "/" name) + (attr err) (lfs.attributes fullname)] + (when (not attr) (print :err err)) + (if (and attr + (= attr.mode :file) + (or + (string.match name "%.textile$") + (string.match name "%.md$"))) + (with-open [f (io.open fullname)] + (let [fields (frontmatter f) + title (or fields.subject fields.title) + slug (slug-for title)] + (values slug + (doto fields + (tset :path name) + (tset :full-path fullname) + (tset :url-path (.. "/" slug)) + (tset :title title) + (tset :body-offset (f:seek :cur 0)) + (tset :date (and fields.date (parse-timestamp fields.date))))))))))) + + +(fn for-pathname [path] + (let [posts (index-posts path)] + { + :get #(. posts $2) + :search (fn [_] posts) + :path path + :static-file (fn [name] (.. path "/" name)) + })) + +{ : for-pathname }