From cb070712c9326a97c750c9ccb5958fa0fa53e41b Mon Sep 17 00:00:00 2001 From: Arun Isaac Date: Fri, 31 Jul 2020 03:32:44 +0530 Subject: Switch from ennum-operation to ennum-exp. ennum-exp is a simple embedded domain specific language to express blog publishing in a more expression oriented form. * ennum.el (ennum-operation): Delete type. (ennum--do-operation): Delete function. (ennum-intern, ennum--hash, ennum-file-hash, ennum--file-hash, ennum--set-file-modes-recursively, ennum--rewrite-inputs, ennum-eval, ennum-store-item-union): New functions. (ennum-exp): New macro. (ennum-setting): Introduce :store setting. (ennum-publish-post, ennum-publish-generic, ennum-publish-index, ennum-publish-feed, ennum--feed-entry, ennum-publish-image, ennum-publish-copy, ennum-publish-link, ennum-publish): Switch from ennum-operation to ennum-exp. --- ennum.el | 542 ++++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 312 insertions(+), 230 deletions(-) diff --git a/ennum.el b/ennum.el index a1fac83..adba985 100644 --- a/ennum.el +++ b/ennum.el @@ -15,15 +15,126 @@ (defvar ennum-blog nil "Property list specifying ennum publish settings") -(cl-defstruct (ennum-post (:constructor ennum-make-post) +;; TODO: Should the store have an absolute path to deal with directory +;; changes? Yes, since we ask for an absolute working directory, we +;; should. +(defun ennum-intern (filename) + (let* ((store-item + (expand-file-name (ennum-file-hash filename) + (ennum-setting :store))) + (interned-path (expand-file-name (file-name-nondirectory filename) + store-item))) + (unless (file-exists-p store-item) + (ennum-copy filename interned-path) + (ennum--set-file-modes-recursively store-item #o555 #o444 #o555)) + interned-path)) + +(defun ennum--hash () + "Return SHA256 hash of buffer contents encoded using +filename-safe base64 encoding. See RFC 4648ยง5. Briefly, this is a +variant of base64 encoding where characters + and / are replaced +respectively by - and _, and the pad character = is optional." + (replace-regexp-in-string + (rx (any ?+ ?/ ?=)) + (lambda (str) + (pcase str + ("+" "-") + ("/" "_") + ("=" ""))) + (base64-encode-string + (secure-hash 'sha256 (current-buffer) nil nil t)))) + +(defun ennum-file-hash (file) + (ennum--file-hash file (file-attribute-modification-time + (file-attributes file)))) + +(defmemoize ennum--file-hash (file last-modified) + (with-temp-buffer + ;; TODO: Use ennum-with-file-contents + (set-buffer-multibyte nil) + (insert-file-contents-literally file) + (ennum--hash))) + +(defun ennum--set-file-modes-recursively (directory directory-mode file-mode executable-file-mode) + (chmod directory directory-mode) + (seq-do (lambda (file) + (cond + ((file-directory-p file) (chmod file directory-mode)) + ((file-executable-p file) (chmod file executable-file-mode)) + (t (chmod file file-mode)))) + (ennum-directory-files directory t t))) + +(cl-defstruct (ennum-exp (:constructor ennum-make-exp) (:copier nil)) + inputs proc) + +(defun ennum--rewrite-inputs (exp) + (pcase exp + (`(ennum-input ,arg) + (let ((input-identifier (gensym "input"))) + (vector input-identifier (list arg) (list input-identifier)))) + (`(,parent . ,childern) + (seq-reduce (pcase-lambda (`[,result ,inputs ,input-identifiers] node) + (pcase (ennum--rewrite-inputs node) + (`[,modified-tree ,new-inputs ,new-input-identifiers] + (vector (append result (list modified-tree)) + (append inputs new-inputs) + (append input-identifiers new-input-identifiers))))) + exp + (vector nil nil nil))) + (leaf (vector exp nil nil)))) + +(defmacro ennum-exp (&rest body) + (let ((raw-expression `(progn ,@body))) + (pcase (ennum--rewrite-inputs raw-expression) + (`[,rewritten-body ,inputs ,input-identifiers] + `(ennum-eval + (ennum-make-exp + :inputs (list ,@inputs) + :proc (lambda ,input-identifiers ,rewritten-body))))))) + +(defun ennum-eval (exp) + (let ((output + (expand-file-name + (with-temp-buffer + (let ((print-length nil) + (print-level nil)) + (print exp (current-buffer))) + (ennum--hash)) + (ennum-setting :store)))) + ;; Create store if it doesn't exist + (ennum-mkdir-p (ennum-setting :store)) + ;; Create store item if it doesn't already exist + (if (file-exists-p output) + (message "Skipping build of %s" output) + (message "Building %s" output) + (ennum-with-temporary-directory temporary-directory + (ennum-with-current-directory temporary-directory + (apply (ennum-exp-proc exp) + (ennum-exp-inputs exp))) + (rename-file temporary-directory output)) + (ennum--set-file-modes-recursively output #o555 #o444 #o555)) + output)) + +(defun ennum-store-item-union (items) + "Return a store item that is the union of ITEMS." + (ennum-exp + (seq-do (lambda (item) + (seq-do (lambda (destination) + ;; 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))))) + (reverse (ennum-directory-files item nil t)))) + (ennum-input items)))) + +(cl-defstruct (ennum-post (:constructor ennum-make-post) + (:copier nil)) filename slug author date language links tangle summary tags thumbnail title translation-group video-posters) -(cl-defstruct (ennum-operation (:constructor ennum-make-operation) - (:copier nil)) - inputs outputs publish) - (defun ennum-posts (posts-directory) (sort (ennum--filter-map (lambda (file) (when (string= (file-name-extension file) "org") @@ -126,63 +237,56 @@ non-nil, include directories in the output." (concat (file-name-sans-extension filename) ".html")) (defun ennum-publish-post (posts) - (let ((link-publish-operations - (seq-mapcat 'ennum-publish-link (seq-mapcat 'ennum-post-links posts))) - (input-post-files (seq-map 'ennum-post-filename posts))) - (append - (list - (ennum-make-operation - :inputs (append input-post-files - (seq-mapcat 'ennum-operation-inputs link-publish-operations)) - :outputs (seq-map 'ennum--org-output-filename input-post-files) - :publish - (lambda (&rest output-files) - (seq-mapn - (lambda (post output-file) - (let ((system-time-locale (map-elt (ennum-setting :locale-alist) - (ennum-post-language post) nil 'string=))) - (ennum-with-file-contents (ennum-post-filename post) - (org-export-to-file - 'ennum-html output-file nil nil nil nil - (list :ennum-translations (seq-remove (apply-partially 'equal post) posts) - :ennum-video-posters (ennum-post-video-posters post)))))) - posts - output-files)))) - (ennum--filter-map - (lambda (post) - (when (ennum-post-tangle post) - (ennum-make-operation - :inputs (list (ennum-post-filename post)) - :outputs (seq-map (lambda (tangle-output) - (ennum--expand-relative tangle-output - (ennum-setting :static-directory))) - (ennum-post-tangle post)) - :publish (lambda (&rest output-files) - ;; TODO: Handle tangle outputs that are nested - ;; into directories, and when each tangle output - ;; is nested into a different directory. - (let ((post-file-copy (concat - (file-name-directory (first output-files)) - (file-name-nondirectory (ennum-post-filename post))))) - (copy-file (ennum-post-filename post) post-file-copy) - (org-babel-tangle-file post-file-copy) - (delete-file post-file-copy)))))) - posts) - link-publish-operations))) + (seq-mapcat (lambda (post) + (append + (list + (ennum-exp + (ennum-input (seq-map (lambda (post) + (ennum-intern (ennum-post-filename post))) + posts)) + (let ((input-org-file (ennum-input (ennum-intern (ennum-post-filename post)))) + (output-file + (ennum--org-output-filename (ennum-post-filename post)))) + (ennum-mkdir-p (file-name-directory output-file)) + (let ((system-time-locale (map-elt (ennum-setting :locale-alist) + (ennum-post-language post) nil 'string=))) + (ennum-with-file-contents input-org-file + ;; TODO: Centralize these with feed generation + (org-export-to-file + 'ennum-html output-file nil nil nil nil + (list :ennum-translations (seq-remove (apply-partially 'equal post) posts) + :ennum-video-posters (ennum-post-video-posters post)))))))) + (when (ennum-post-tangle post) + (list + (ennum-exp + ;; TODO: Handle tangle outputs that are nested + ;; into directories, and when each tangle output + ;; is nested into a different directory. + (let* ((input-org-file (ennum-input (ennum-intern (ennum-post-filename post)))) + (post-file-copy + (expand-file-name + (file-name-nondirectory input-org-file) + (ennum-setting :static-directory)))) + (ennum-copy input-org-file post-file-copy) + (org-babel-tangle-file post-file-copy) + (delete-file post-file-copy))))) + (seq-mapcat 'ennum-publish-link (ennum-post-links post)))) + posts)) (defun ennum-publish-generic (other-files-directory file) - (ennum-make-operation - :inputs (list (ennum--expand-relative file other-files-directory)) - :outputs - (list (pcase (file-name-extension file) - ("org" (ennum--org-output-filename file)) - (_ file))) - :publish (lambda (output-file) - (pcase (file-name-extension file) - ("org" (ennum-with-file-contents file - (org-export-to-file 'html output-file))) - (_ (ennum-copy (ennum--expand-relative file other-files-directory) - output-file)))))) + (ennum-exp + (let ((interned-file (ennum-input (ennum-intern (ennum--expand-relative file other-files-directory)))) + (output-file + (pcase (file-name-extension file) + ("org" (ennum--org-output-filename file)) + (_ file)))) + (pcase (file-name-extension interned-file) + ("org" + (when (file-name-directory output-file) + (ennum-mkdir-p (file-name-directory output-file))) + (ennum-with-file-contents interned-file + (org-export-to-file 'html output-file))) + (_ (ennum-copy interned-file output-file)))))) (defun ennum-video-poster (video) (or (seq-find (lambda (file) @@ -211,51 +315,55 @@ non-nil, include directories in the output." (let* ((tongue (ennum-post-language (first posts))) (number-of-pages (ceiling (length posts) posts-per-page)) (page-numbers (number-sequence 1 number-of-pages))) - (ennum-make-operation - :inputs (seq-map 'ennum-post-filename posts) - :outputs (cons (ennum-add-tongue-suffix (format "%s.html" filename-prefix) tongue) - (seq-map (apply-partially 'ennum-index-filename filename-prefix tongue "html") - page-numbers)) - :publish - (lambda (home-page &rest output-files) - (let ((system-time-locale (map-elt (ennum-setting :locale-alist) tongue nil 'string=))) - (seq-mapn - (lambda (posts page-number output-file) - (with-temp-buffer - (insert (format "#+TITLE: %s\n" title)) - (insert (format "#+LANGUAGE: %s\n" tongue)) - (insert "#+OPTIONS: num:nil toc:nil\n\n") - (seq-do (lambda (post) - (insert (format "* [[post:%s][%s]]\n" (ennum-post-slug post))) - (insert (format-time-string "/%b %e, %Y/\n\n" (ennum-post-date post))) - (when-let ((thumbnail (ennum-post-thumbnail post))) - (insert (format "[[thumbnail:%s]]\n\n" thumbnail))) - (when-let ((summary (ennum-post-summary post))) - (insert summary) - (insert "\n\n")) - (when-let ((tags (ennum-post-tags post))) - (insert "Tags: ") - (insert - (string-join - (seq-map (lambda (tag) - (format "[[tag:%s][%s]]" (ennum-add-tongue-suffix tag tongue) tag)) - tags) - ", ")) - (insert "\n\n"))) - posts) - (unless (= page-number 1) - (insert (format "[[./%s][Newer posts]]\n\n" - (ennum-index-filename (file-name-nondirectory filename-prefix) - tongue nil (1- page-number))))) - (unless (= page-number number-of-pages) - (insert (format "[[./%s][Older posts]]\n" - (ennum-index-filename (file-name-nondirectory filename-prefix) - tongue nil (1+ page-number))))) - (org-export-to-file 'html output-file))) - (seq-partition posts posts-per-page) - page-numbers - output-files)) - (copy-file (first output-files) home-page))))) + (ennum-exp + (ennum-input (seq-map (lambda (post) + (ennum-intern (ennum-post-filename post))) + posts)) + (let ((output-files (seq-map (apply-partially 'ennum-index-filename filename-prefix tongue "html") + page-numbers)) + (system-time-locale (map-elt (ennum-setting :locale-alist) tongue nil 'string=))) + (seq-mapn + (lambda (posts page-number output-file) + (when (file-name-directory output-file) + (ennum-mkdir-p (file-name-directory output-file))) + (with-temp-buffer + (insert (format "#+TITLE: %s\n" title)) + (insert (format "#+LANGUAGE: %s\n" tongue)) + (insert "#+OPTIONS: num:nil toc:nil\n\n") + (seq-do (lambda (post) + (insert (format "* [[post:%s][%s]]\n" + (ennum-post-slug post) + (ennum-post-title post))) + (insert (format-time-string "/%b %e, %Y/\n\n" (ennum-post-date post))) + (when-let ((thumbnail (ennum-post-thumbnail post))) + (insert (format "[[thumbnail:%s]]\n\n" thumbnail))) + (when-let ((summary (ennum-post-summary post))) + (insert summary) + (insert "\n\n")) + (when-let ((tags (ennum-post-tags post))) + (insert "Tags: ") + (insert + (string-join + (seq-map (lambda (tag) + (format "[[tag:%s][%s]]" (ennum-add-tongue-suffix tag tongue) tag)) + tags) + ", ")) + (insert "\n\n"))) + posts) + (unless (= page-number 1) + (insert (format "[[./%s][Newer posts]]\n\n" + (ennum-index-filename (file-name-nondirectory filename-prefix) + tongue nil (1- page-number))))) + (unless (= page-number number-of-pages) + (insert (format "[[./%s][Older posts]]\n" + (ennum-index-filename (file-name-nondirectory filename-prefix) + tongue nil (1+ page-number))))) + (org-export-to-file 'html output-file))) + (seq-partition posts posts-per-page) + page-numbers + output-files) + (copy-file (first output-files) + (ennum-add-tongue-suffix (format "%s.html" filename-prefix) tongue)))))) (defun ennum--absolute-uri (path) (format "%s://%s/%s" @@ -267,26 +375,30 @@ non-nil, include directories in the output." (format-time-string "%Y-%m-%dT%H:%M:%SZ" date)) (defun ennum-publish-feed (feed-file title rights posts) - (ennum-make-operation - :inputs (seq-map 'ennum-post-filename posts) - :outputs (list feed-file) - :publish - (lambda (output-file) - (with-temp-file output-file - (insert - (xmlgen - `(feed :xmlns "http://www.w3.org/2005/Atom" - (id ,(ennum--absolute-uri "")) - (title ,title) - (updated ,(ennum--atom-date (ennum-post-date (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-map 'ennum--feed-entry posts)))))))) - -(defun ennum--feed-entry (post) + (ennum-exp + ;; TODO: Create ennu-mkdir-p-for-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 (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 + (ennum-input (seq-map (lambda (post) + (ennum-intern (ennum-post-filename post))) + posts))))))))) + +(defun ennum--feed-entry (post interned-post-file) (let ((link (ennum--absolute-uri (ennum--org-output-filename (ennum-post-filename post))))) `(entry (id ,link) @@ -316,7 +428,7 @@ non-nil, include directories in the output." :blog-scheme :default-image-width :image-link-width :index-posts-per-page :locale-alist :other-files-directory - :tag-directory :thumbnail-image-width) + :store :tag-directory :thumbnail-image-width) (plist-get (org-combine-plists (list :atom-feed-number-of-posts 12 :atom-feed-file "blog.atom" @@ -325,6 +437,7 @@ non-nil, include directories in the output." :image-link-width 1024 :index-posts-per-page 12 :locale-alist '(("en" . "C")) + :store ".ennum" :tag-directory "tag" :thumbnail-image-width 320) ennum-blog) @@ -339,23 +452,22 @@ non-nil, include directories in the output." (defun ennum--expand-relative (name directory) (concat (file-name-as-directory directory) name)) -(defun ennum-publish-image (widths image) - (ennum-make-operation - :inputs (list image) - :outputs (seq-map (apply-partially 'ennum-image-output-filename image) - widths) - :publish - (lambda (&rest output-files) - (seq-mapn (lambda (output-file width) - (ennum-image-optimize-image - (ennum-image-resize-image image output-file width))) - output-files widths)))) +(defun ennum-publish-image (image width) + (ennum-exp + (let ((input-image (ennum-input (ennum-intern image)))) + (ennum-mkdir-p (ennum-setting :images-directory)) + (ennum-image-optimize-image + (ennum-image-resize-image + input-image + (ennum--expand-relative + (ennum-image-output-filename + (file-name-nondirectory input-image) width) + (ennum-setting :images-directory)) + width))))) (defun ennum-publish-copy (file) - (ennum-make-operation - :inputs (list file) - :outputs (list file) - :publish (apply-partially 'ennum-copy file))) + (ennum-exp + (ennum-copy (ennum-input (ennum-intern file)) file))) (defun newest-file (files) (pcase files @@ -380,55 +492,25 @@ if DESTINATION already exists." (defun ennum--filter-map (function sequence) (seq-filter 'identity (seq-map function sequence))) -;; TODO: What if a file was removed from the inputs? Detect that -;; change as well. - -;; Two separate problems -;; - tracking of list of inputs -;; - depending on a function of inputs, or equivalently intermediate files - -;; Solve both problems with an "ennum store" -(defun ennum--do-operation (temporary-directory operation) - ;; TODO: Check all outputs were created correctly. - (let* ((expand (lambda (directory file) - (expand-file-name file directory))) - (inputs (ennum-operation-inputs operation)) - (outputs (ennum-operation-outputs operation)) - (absolute-outputs - (seq-map (apply-partially expand temporary-directory) - outputs)) - (previous-outputs - (seq-map (apply-partially expand (ennum-setting :output-directory)) - outputs))) - (cond - ((and (seq-every-p 'file-exists-p previous-outputs) - (file-newer-than-file-p (newest-file previous-outputs) - (newest-file inputs))) - (message "Skipping publishing %s to %s" inputs outputs) - (seq-mapn 'ennum-copy previous-outputs absolute-outputs)) - (t (message "Publishing %s to %s" inputs outputs) - (seq-do 'ennum-mkdir-p - (seq-uniq - (seq-map 'file-name-directory absolute-outputs))) - (apply (ennum-operation-publish operation) absolute-outputs))))) - - (defun ennum-publish-link (link) (pcase link (`("image" . ,path) - (list - (ennum-publish-image - (list (ennum-setting :default-image-width) - (ennum-setting :image-link-width)) - (ennum--expand-relative path (ennum-setting :images-directory))))) + (seq-map (lambda (width) + (ennum-publish-image + (ennum--expand-relative path (ennum-setting :images-directory)) + width)) + (list (ennum-setting :default-image-width) + (ennum-setting :image-link-width)))) (`("static" . ,path) (list - (ennum-publish-copy (ennum--expand-relative path (ennum-setting :static-directory))))) + (ennum-publish-copy + (ennum--expand-relative path (ennum-setting :static-directory))))) (`("video" . ,path) - (list - (ennum-publish-copy (ennum--expand-relative path (ennum-setting :video-directory))) - (ennum-publish-copy (ennum--expand-relative (ennum-video-poster path) - (ennum-setting :images-directory))))))) + (seq-map 'ennum-publish-copy + (list + (ennum--expand-relative path (ennum-setting :video-directory)) + (ennum--expand-relative (ennum-video-poster path) + (ennum-setting :images-directory))))))) (defmacro ennum-with-current-directory (directory &rest body) "Change to DIRECTORY, evaluate BODY and restore the current @@ -469,53 +551,53 @@ as keys. Keys are compared using `equal'." (defun ennum-publish () (interactive) - (let ((make-backup-files nil) - (blog-title (ennum-setting :blog-title)) - (posts-per-page (ennum-setting :index-posts-per-page))) - (ennum-with-current-directory (ennum-setting :working-directory) - (ennum-with-temporary-directory temporary-directory - (seq-do - (apply-partially 'ennum--do-operation temporary-directory) - (append - (let ((posts (ennum-posts (ennum-setting :posts-directory)))) - (append - ;; Publish posts - (seq-mapcat (pcase-lambda (`(,translation-group . ,posts)) - (ennum-publish-post posts)) - (seq-group-by 'ennum-post-translation-group posts)) - ;; Publish feed - (list (ennum-publish-feed (ennum-setting :atom-feed-file) - blog-title - (ennum-setting :blog-license) - (seq-take posts (ennum-setting :atom-feed-number-of-posts)))) - ;; Publish indices - (seq-map - (pcase-lambda (`(,tongue . ,posts)) - (ennum-publish-index "index" blog-title posts-per-page posts)) - (seq-group-by 'ennum-post-language posts)) - (seq-mapcat - (pcase-lambda (`(,tag . ,posts)) - (seq-map - (pcase-lambda (`(,tongue . ,posts)) - (ennum-publish-index - (ennum--expand-relative tag (ennum-setting :tag-directory)) - tag posts-per-page posts)) - (seq-group-by 'ennum-post-language posts))) - (ennum-many-to-many-group-by 'ennum-post-tags posts)) - ;; Publish thumbnails - (seq-map - (apply-partially 'ennum-publish-image (list (ennum-setting :thumbnail-image-width))) + (ennum-with-current-directory (ennum-setting :working-directory) + (let* ((blog-title (ennum-setting :blog-title)) + (posts (ennum-posts (ennum-setting :posts-directory))) + (posts-per-page (ennum-setting :index-posts-per-page)) + (other-files-directory (ennum-setting :other-files-directory)) + (result + (ennum-store-item-union + (append + ;; Publish posts + (seq-mapcat (pcase-lambda (`(,translation-group . ,posts)) + (ennum-publish-post posts)) + (seq-group-by 'ennum-post-translation-group posts)) + ;; Publish feed + (list (ennum-publish-feed (ennum-setting :atom-feed-file) + blog-title + (ennum-setting :blog-license) + (seq-take posts (ennum-setting :atom-feed-number-of-posts)))) + ;; Publish indices + (seq-map + (pcase-lambda (`(,tongue . ,posts)) + (ennum-publish-index "index" blog-title posts-per-page posts)) + (seq-group-by 'ennum-post-language posts)) + ;; Publish tag indices + (seq-mapcat + (pcase-lambda (`(,tag . ,posts)) + (seq-map + (pcase-lambda (`(,tongue . ,posts)) + (ennum-publish-index + (ennum--expand-relative tag (ennum-setting :tag-directory)) + tag posts-per-page posts)) + (seq-group-by 'ennum-post-language posts))) + (ennum-many-to-many-group-by 'ennum-post-tags posts)) + ;; Publish thumbnails (seq-map (lambda (image) - (ennum--expand-relative image (ennum-setting :images-directory))) - (seq-uniq (ennum--filter-map 'ennum-post-thumbnail posts)))))) - ;; Publish other files - (when-let ((other-files-directory (ennum-setting :other-files-directory))) - (seq-map (apply-partially 'ennum-publish-generic other-files-directory) - (ennum-directory-files other-files-directory))))) - ;; Replace old output directory - (let ((output (ennum-setting :output-directory))) - (delete-directory output t) - (rename-file temporary-directory output t)))))) + (ennum-publish-image + (ennum--expand-relative image (ennum-setting :images-directory)) + (ennum-setting :thumbnail-image-width))) + (ennum--filter-map 'ennum-post-thumbnail posts)) + ;; Publish other files + (seq-map (apply-partially 'ennum-publish-generic other-files-directory) + (ennum-directory-files other-files-directory)))))) + ;; Replace old output directory + (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)) + (message "Ennum published to %s" (expand-file-name (ennum-setting :output-directory)))))) ;;; Server ;;; -- cgit v1.2.3