(import (chicken base) utf8 spiffy nrepl srfi-18 (chicken file) (chicken io) (chicken port) (chicken format) (chicken pretty-print) (chicken string) lowdown sxml-transforms matchable (chicken irregex) srfi-13 intarweb uri-common (chicken process) (chicken process-context)) (define *PORT* (string->number (or (get-environment-variable "PORT") "8080"))) (define *WEBROOT* (or (get-environment-variable "WEBROOT") "dist")) (define *SRCROOT* (or (get-environment-variable "SRCROOT") "pages")) (define *TEMPLATE* (or (get-environment-variable "TEMPLATE") "template.html")) (define (pipe x . fns) (match fns [() x] [(fn . rest) (apply pipe (fn x) rest)])) ;; Convenience macro (@ expr ...) that expands to (lambda (x) (expr ... x)) (define-syntax @ (syntax-rules () ((_ expr ...) (lambda (x) (expr ... x))))) (define-syntax thunk (syntax-rules () ((_ expr ...) (lambda () (expr ...))))) (define (replace from to str) (irregex-replace/all from str to)) (define (html-unescape html) (pipe html (@ replace """ "\"") (@ replace "'" "'") (@ replace "&" "&") (@ replace "<" "<") (@ replace ">" ">"))) (define (highlight ext code) (define tmp-file-path (create-temporary-file ext)) (with-output-to-file tmp-file-path (lambda () (display code))) (define result (receive (input _ _) (process (format "highlight ~A -O html -f" tmp-file-path)) (read-string #f input))) (define result (capture ,(format "highlight ~A -O html -f" tmp-file-path))) (delete-file tmp-file-path) result) (define (replace-code-block content return) (define irx (irregex "^%lang (\\S+)%\\n?" 's)) (define lang-match (irregex-search irx content)) (if (not lang-match) (return content)) (define lang (irregex-match-substring lang-match 1)) (pipe content (@ replace irx "") (@ html-unescape) (@ highlight lang) (@ string-trim-both))) (define (convert-md-to-html filepath) (with-output-to-string (lambda () (markdown->html (open-input-file filepath))))) (define (highlight-code-blocks html) (define (highlight-match m) (format "~A" (call/cc (@ replace-code-block (irregex-match-substring m 1))))) (irregex-replace/all (irregex "(.*?)" 's) html highlight-match)) (define (insert-to-page-tmpl tmpl-path html) (format (read-string #f (open-input-file tmpl-path)) html)) (define (compile-file filepath prev-result) (define (inner return) (define initial-srcroot (string-append "^" *SRCROOT*)) (define replaced-filepath (replace initial-srcroot *WEBROOT* filepath)) (if (directory-exists? filepath) (begin (print (format "Creating corresponding directory: ~A" filepath)) (create-directory replaced-filepath #t) (return (void)))) (if (not (irregex-search "\\.md$" filepath)) (begin (print (format "Copying non-md file: ~A" filepath)) (copy-file filepath replaced-filepath) (return (void)))) (print (format "Compiling markdown: ~A" filepath)) (define out-path (pipe filepath (@ replace "\\.md" ".html") (@ replace initial-srcroot *WEBROOT*))) (define out-content (pipe filepath (@ convert-md-to-html) (@ highlight-code-blocks) (@ insert-to-page-tmpl *TEMPLATE*))) (with-output-to-file out-path (thunk display out-content))) (call/cc inner)) (define (recompile) (create-directory "dist" #t) (define (delete-path path) (if (directory-exists? path) (delete-directory path #t) (delete-file path))) (for-each delete-path (glob (string-append *WEBROOT* "/*"))) (find-files "pages" action: compile-file)) (thread-start! (lambda () (receive (input _ _) (process (format "fswatch -o ~A" *SRCROOT*)) (let loop () (print (format "Monitored directory has changed (# of changes: ~A)" (read-line input))) (recompile) (loop))))) (thread-start! (lambda () (print "starting nrepl on port 1234") (nrepl-prompt (lambda () (display "#;0> "))) (nrepl 1234))) (recompile) (define (map-last proc lst) (if (null? (cdr lst)) (list (proc (car lst))) (cons (car lst) (map-last proc (cdr lst))))) (define (app cont) (define uri (request-uri (current-request))) (define path (uri-path uri)) (define (append-html-if-needed fragment) (define has-ext? (@ irregex-search "\\.\\S+$")) (match fragment ["" "index.html"] ;; if path ends in an extension, do nothing [(? has-ext?) fragment] ;; otherwise, add .html [_ (string-append fragment ".html")])) (define (string-intercalate delim lst) (string-intersperse lst delim)) (define static-file-path (pipe path (@ map-last append-html-if-needed) (@ cdr) (@ string-intercalate "/"))) (print (format "Serving file ~A" static-file-path)) (send-static-file static-file-path)) (print (format "Serving ~A on port ~A, using ~A as html template" *WEBROOT* *PORT* *TEMPLATE*)) (server-port *PORT*) (root-path *WEBROOT*) (vhost-map `((".*" . ,(@ app)))) (start-server)