From 26f3d32f0ea35bd7d0c2f10153963dcf67fb8168 Mon Sep 17 00:00:00 2001 From: Arun Isaac Date: Sun, 6 Sep 2026 00:42:50 +0100 Subject: Evaluate ennum expressions lazily. Construct ennum expressions into an expression tree, and lazily evaluate them later at the time of publishing. --- ennum.el | 143 +++++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 97 insertions(+), 46 deletions(-) diff --git a/ennum.el b/ennum.el index 93923d6..cfcc0ef 100644 --- a/ennum.el +++ b/ennum.el @@ -240,10 +240,33 @@ respectively by - and _, and the pad character = is optional." ((file-executable-p file) executable-file-mode) (t file-mode))))) -(defmacro ennum-exp (&rest body) - `(ennum-eval (lambda () ,@body))) +(cl-defstruct (ennum-exp (:constructor ennum-make-exp) + (:copier nil)) + function sub-expressions) -(defun ennum-eval (closure) +(defmacro ennum-exp (&rest body) + "Wrap BODY in an ennum-exp." + (pcase body + (`(,expression) + `(ennum-make-exp :sub-expressions (list (lambda () + ,expression)))) + (`(,function . ,sub-expressions) + `(ennum-make-exp :function #',function + :sub-expressions (list ,@sub-expressions))))) + +(defun ennum-exp-apply (function arguments) + "Return ennum-exp applying FUNCTION to ARGUMENTS list." + (ennum-make-exp :function function + :sub-expressions arguments)) + +(defun ennum--evaluate-closure (closure) + "Evaluate CLOSURE writing its output to the store. + +CLOSURE is evaluated in a temporary directory with the expectation that +it writes its output to paths relative to that temporary directory. If +CLOSURE returns successfully, its output is moved to the store. The +resulting store item is prefixed with the hash of the serialization of +the closure." (let ((output (expand-file-name (with-temp-buffer @@ -273,17 +296,40 @@ respectively by - and _, and the pad character = is optional." (ennum--set-file-modes-recursively output #o555 #o444 #o555)) output)) +(defun ennum-eval (exp) + "Evaluate EXP, an ennum-exp." + (cond + ;; Naked closure; just evaluate it. + ((closurep exp) + (ennum--evaluate-closure exp)) + ;; Compound expression with several sub-expressions to be evaluated + ;; and passed to function + ((ennum-exp-function exp) + (ennum--evaluate-closure + (apply-partially #'apply + (ennum-exp-function exp) + (seq-map #'ennum-eval + (ennum-exp-sub-expressions exp))))) + ;; Simple expression with only a single sub-expression; evaluate + ;; the sub-expression and return the result. + (t + (pcase (ennum-exp-sub-expressions exp) + (`(,sub-expression) + (ennum-eval sub-expression)))))) + (defun ennum-store-item-union (items) "Return a store item that is the union of ITEMS." - (ennum-exp - (dolist (item items) - (dolist (destination (reverse (ennum-directory-files item nil t))) - ;; TODO: Print warning about overwriting files? - (let ((source (expand-file-name destination item))) - (unless (file-exists-p destination) - (if (file-directory-p source) - (make-directory destination) - (copy-file source destination t)))))))) + (ennum-exp-apply + (lambda (&rest items) + (dolist (item items) + (dolist (destination (reverse (ennum-directory-files item nil t))) + ;; TODO: Print warning about overwriting files? + (let ((source (expand-file-name destination item))) + (unless (file-exists-p destination) + (if (file-directory-p source) + (make-directory destination) + (copy-file source destination t))))))) + items)) (defmacro ennum-make-functional-setter (name copier accessor) `(defun ,name (object new-value) @@ -607,9 +653,11 @@ result as a string." (defun ennum-publish-index-home (first-index-page-output filename-prefix tongue) (ennum-exp - (ennum-copy (ennum--file-join first-index-page-output - (ennum-index-filename filename-prefix tongue "html" 1)) - (ennum-add-tongue-suffix (format "%s.html" filename-prefix) tongue)))) + (lambda (first-index-page-output) + (ennum-copy (ennum--file-join first-index-page-output + (ennum-index-filename filename-prefix tongue "html" 1)) + (ennum-add-tongue-suffix (format "%s.html" filename-prefix) tongue))) + first-index-page-output)) (defun ennum-publish-index (filename-prefix title posts-per-page tongue posts) (let* ((number-of-pages (ceiling (length posts) posts-per-page)) @@ -648,24 +696,25 @@ result as a string." (ennum-intern (ennum-post-filename post))) posts))) (ennum-exp - (message "Writing %s" feed-file) - (when (file-name-directory feed-file) - (ennum-mkdir-p (file-name-directory feed-file))) - (with-temp-file feed-file - (insert - (xmlgen - `(feed :xmlns "http://www.w3.org/2005/Atom" - (id ,(ennum--absolute-uri "")) - (title ,title) - (updated ,(ennum--atom-date (ennum-post-date (seq-first posts)))) - (link :rel "self" :href ,(ennum--absolute-uri feed-file)) - (generator - ,(format "Emacs %d.%d Org-mode %s ennum %s" - emacs-major-version emacs-minor-version (org-version) ennum-version)) - (rights ,rights) - ,@(seq-mapn (lambda (post interned-post-file) - (ennum--feed-entry post interned-post-file)) - posts interned-post-files)))))))) + (progn + (message "Writing %s" feed-file) + (when (file-name-directory feed-file) + (ennum-mkdir-p (file-name-directory feed-file))) + (with-temp-file feed-file + (insert + (xmlgen + `(feed :xmlns "http://www.w3.org/2005/Atom" + (id ,(ennum--absolute-uri "")) + (title ,title) + (updated ,(ennum--atom-date (ennum-post-date (seq-first posts)))) + (link :rel "self" :href ,(ennum--absolute-uri feed-file)) + (generator + ,(format "Emacs %d.%d Org-mode %s ennum %s" + emacs-major-version emacs-minor-version (org-version) ennum-version)) + (rights ,rights) + ,@(seq-mapn (lambda (post interned-post-file) + (ennum--feed-entry post interned-post-file)) + posts interned-post-files))))))))) (defun ennum--feed-entry (post interned-post-file) (let ((link (url-encode-url @@ -738,16 +787,17 @@ result as a string." (defun ennum-publish-image (image width) (ennum-exp - (message "Resizing %s to width %s" image width) - (ennum-mkdir-p (ennum-setting :images-directory)) - (ennum-image-optimize-image - (ennum-image-resize-image - image - (ennum--file-join - (ennum-setting :images-directory) - (ennum-image-output-filename - (file-name-nondirectory image) width)) - width)))) + (progn + (message "Resizing %s to width %s" image width) + (ennum-mkdir-p (ennum-setting :images-directory)) + (ennum-image-optimize-image + (ennum-image-resize-image + image + (ennum--file-join + (ennum-setting :images-directory) + (ennum-image-output-filename + (file-name-nondirectory image) width)) + width))))) (defun ennum-publish-copy (file) (let ((interned-file (ennum-intern file))) @@ -788,8 +838,8 @@ recognized as a directory, it should end in a slash. See (defun ennum--find-image (path) "Find store item for image with PATH." (or (seq-find 'file-exists-p - (seq-map (apply-partially 'expand-file-name - path) + (seq-map (lambda (exp) + (expand-file-name path (ennum-eval exp))) (ennum-setting :generated-image-paths))) (ennum-intern (ennum--file-join (ennum-setting :images-directory) path)))) @@ -902,7 +952,8 @@ as keys. Keys are compared using `equal'." (when (file-exists-p (ennum-setting :output-directory)) (ennum--set-file-modes-recursively (ennum-setting :output-directory) #o755 #o644 #o755) (delete-directory (ennum-setting :output-directory) t)) - (copy-directory result (ennum-setting :output-directory)) + (copy-directory (ennum-eval result) + (ennum-setting :output-directory)) (message "Ennum published to %s" (expand-file-name (ennum-setting :output-directory)))))) ;;; Server -- cgit 1.4.1