about summary refs log tree commit diff
path: root/ennum.el
diff options
context:
space:
mode:
authorArun Isaac2026-09-07 00:48:25 +0100
committerArun Isaac2026-09-07 02:55:59 +0100
commit5b01ede563d1e5bae4d9643308f709d6fce3c329 (patch)
tree4d370b89bbdd03e33568821386329cc46990c6a4 /ennum.el
parentbc16c6424ebe124d1826bd06a6d4e65464d51b2b (diff)
downloadennum-5b01ede563d1e5bae4d9643308f709d6fce3c329.tar.gz
ennum-5b01ede563d1e5bae4d9643308f709d6fce3c329.tar.lz
ennum-5b01ede563d1e5bae4d9643308f709d6fce3c329.zip
Add gemini capsule publishing.
Diffstat (limited to 'ennum.el')
-rw-r--r--ennum.el245
1 files changed, 173 insertions, 72 deletions
diff --git a/ennum.el b/ennum.el
index b58a205..7cdf374 100644
--- a/ennum.el
+++ b/ennum.el
@@ -1,5 +1,6 @@
 ;; -*- lexical-binding: t -*-
 
+(require 'ennum-gemini)
 (require 'ennum-html)
 (require 'ennum-image)
 (require 'ennum-link)
@@ -32,10 +33,16 @@ this path.
 
 `:output-directory'
 
-Path to output directory. All output paths (`:atom-feed-file',
+Path to output HTML directory. All output paths (`:atom-feed-file',
 `:tag-directory', etc.) are interpreted relative to
 `:output-directory'.
 
+`:output-gemini-directory'
+
+Path to output gemini directory. All output paths (`:posts-directory',
+`:tag-directory', etc.) are interpreted relative to
+`:output-gemini-directory'.
+
 `:posts-directory'
 
 Source directory with blog posts to publish. All posts in this
@@ -507,19 +514,27 @@ output."
                                 (file-name-as-directory (expand-file-name directory)))
                files))))
 
-(defun ennum--org-output-filename (filename)
-  (concat (file-name-sans-extension filename) ".html"))
+(defun ennum--export-extension (backend)
+  (pcase backend
+    ((or 'ennum-html 'html) "html")
+    ((or 'ennum-gemini 'gemini) "gmi")
+    (_ (error "Unrecognized backend %s" backend))))
 
-(defun ennum-export-post (post interned-org-file &optional output-html-file body-only)
-  "Export INTERNED-ORG-FILE of POST to OUTPUT-HTML-FILE using the
-ennum-html backend. INTERNED-ORG-FILE must be an org file
-interned into the ennum store.
+(defun ennum--org-output-filename (backend filename)
+  (concat (file-name-sans-extension filename)
+          "."
+          (ennum--export-extension backend)))
+
+(defun ennum-export-post (post interned-org-file backend &optional output-file body-only)
+  "Export INTERNED-ORG-FILE of POST to OUTPUT-FILE using
+BACKEND. INTERNED-ORG-FILE must be an org file interned into the ennum
+store.
 
 When optional argument BODY-ONLY is non-nil, only return body
 code, without surrounding template. See `org-export-as'.
 
-When optional argument OUTPUT-HTML-FILE is nil, return exported
-result as a string."
+When optional argument OUTPUT-FILE is nil, return exported result as a
+string."
   (let ((system-time-locale (map-elt (ennum-setting :locale-alist)
                                      (ennum-post-language post)))
         (ext-plist (list :ennum-post post)))
@@ -542,19 +557,21 @@ result as a string."
                  (ennum-setting :open-graph-fallback-image))))))
       (ennum-with-file-contents interned-org-file
         (cond
-         (output-html-file
-          (ennum-mkdir-p (file-name-directory output-html-file))
-          (org-export-to-file 'ennum-html output-html-file nil nil nil body-only ext-plist))
-         (t (org-export-as 'ennum-html nil nil body-only ext-plist)))))))
+         (output-file
+          (ennum-mkdir-p (file-name-directory output-file))
+          (org-export-to-file backend output-file nil nil nil body-only ext-plist))
+         (t (org-export-as backend nil nil body-only ext-plist)))))))
 
-(defun ennum-publish-post (post)
+(defun ennum-publish-post (backend post)
   (let ((interned-org-file (ennum-intern (ennum-post-filename post))))
     (append
      (list
       (ennum-exp
        (ennum-export-post post
                           interned-org-file
-                          (ennum--org-output-filename (ennum-post-filename post)))))
+                          backend
+                          (ennum--org-output-filename
+                           backend (ennum-post-filename post)))))
      (when (ennum-post-tangle post)
        (list
         (ennum-exp
@@ -570,23 +587,25 @@ result as a string."
                  (ennum-copy interned-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)))))
+     (seq-mapcat (apply-partially 'ennum-publish-link
+                                  backend)
+                 (ennum-post-links post)))))
 
-(defun ennum-publish-generic (other-files-directory file)
+(defun ennum-publish-generic (other-files-directory backend file)
   (let ((interned-file (ennum-intern
                         (ennum--file-join other-files-directory
                                           file))))
     (ennum-exp
      (let ((output-file
             (pcase (file-name-extension file)
-              ("org" (ennum--org-output-filename file))
+              ("org" (ennum--org-output-filename backend 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)))
+            (org-export-to-file backend output-file)))
          (_ (ennum-copy interned-file output-file)))))))
 
 (defun ennum-video-poster (video)
@@ -613,68 +632,98 @@ result as a string."
        (concat filename-prefix extension))
      tongue)))
 
-(defun ennum-publish-index-page (filename-prefix title last-page posts page-number tongue output-file)
+(defun ennum-publish-index-page (backend filename-prefix title last-page posts page-number tongue output-file)
   (ennum-exp
    (let ((system-time-locale
           (map-elt (ennum-setting :locale-alist) tongue)))
      (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")
-       (dolist (post posts)
-         (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")))
-       (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 last-page
-         (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)))))
-
-(defun ennum-publish-index-home (first-index-page-output filename-prefix tongue)
+     (pcase backend
+       ('html
+        (with-temp-buffer
+          (insert (format "#+TITLE: %s\n" title))
+          (insert (format "#+LANGUAGE: %s\n" tongue))
+          (insert "#+OPTIONS: num:nil toc:nil\n\n")
+          (dolist (post posts)
+            (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")))
+          (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 last-page
+            (insert (format "[[./%s][Older posts]]\n"
+                            (ennum-index-filename (file-name-nondirectory filename-prefix)
+                                                  tongue nil (1+ page-number)))))
+          (org-export-to-file backend output-file)))
+       ('gemini
+        (with-temp-file output-file
+          (insert (format "# %s\n\n" title))
+          (dolist (post posts)
+            (insert (format "=> %s.gmi %s: %s\n\n"
+                            (url-encode-url
+                             (expand-file-name* (ennum-post-slug post)
+                                                (ennum-setting :posts-directory)))
+                            (format-time-string "%b %e, %Y"
+                                                (ennum-post-date post))
+                            (ennum-post-title post)))
+            (when-let ((summary (ennum-post-summary post)))
+              (insert summary)
+              (insert "\n\n")))
+          (unless (= page-number 1)
+            (insert (format "=> ./%s.gmi Newer posts\n"
+                            (ennum-index-filename (file-name-nondirectory filename-prefix)
+                                                  tongue nil (1- page-number)))))
+          (unless last-page
+            (insert (format "=> ./%s.gmi Older posts\n"
+                            (ennum-index-filename (file-name-nondirectory filename-prefix)
+                                                  tongue nil (1+ page-number)))))))))))
+
+(defun ennum-publish-index-home (first-index-page-output backend filename-prefix tongue)
   (ennum-exp
    (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)))
+     (let ((extension (ennum--export-extension backend)))
+       (ennum-copy (ennum--file-join first-index-page-output
+                                     (ennum-index-filename filename-prefix
+                                                           tongue
+                                                           extension
+                                                           1))
+                   (ennum-add-tongue-suffix (format "%s.%s" filename-prefix extension)
+                                            tongue))))
    first-index-page-output))
 
-(defun ennum-publish-index (filename-prefix title posts-per-page tongue posts)
+(defun ennum-publish-index (backend filename-prefix title posts-per-page tongue posts)
   (let* ((number-of-pages (ceiling (length posts) posts-per-page))
          (page-numbers (number-sequence 1 number-of-pages))
          (output-files (seq-map (apply-partially
-                                 'ennum-index-filename filename-prefix tongue "html")
+                                 'ennum-index-filename filename-prefix tongue
+                                 (ennum--export-extension backend))
                                 page-numbers))
          (outputs (seq-mapn (lambda (post page-number output-file)
                               (ennum-publish-index-page
-                               filename-prefix title
+                               backend filename-prefix title
                                (= page-number number-of-pages)
                                post page-number tongue output-file))
                             (seq-partition posts posts-per-page)
                             page-numbers
                             output-files)))
-    (cons (ennum-publish-index-home (seq-first outputs) filename-prefix tongue)
+    (cons (ennum-publish-index-home (seq-first outputs) backend filename-prefix tongue)
           outputs)))
 
 (defun ennum--absolute-uri (path)
@@ -720,7 +769,7 @@ result as a string."
 (defun ennum--feed-entry (post interned-post-file)
   (let ((link (url-encode-url
                (ennum--absolute-uri (ennum--org-output-filename
-                                     (ennum-post-filename post))))))
+                                     'ennum-html (ennum-post-filename post))))))
     `(entry (id ,link)
             (title :xml:lang ,(ennum-post-language post) ,(ennum-post-title post))
             (updated ,(ennum--atom-date (ennum-post-date post)))
@@ -729,7 +778,7 @@ result as a string."
                    (name ,(ennum-post-author post))
                    (email ,user-mail-address))))
             (content :type "html" :xml:lang ,(ennum-post-language post)
-                     ,(ennum-export-post post interned-post-file nil t))
+                     ,(ennum-export-post post interned-post-file 'ennum-html nil t))
             (summary :xml:lang ,(ennum-post-language post)
                      ,(ennum-post-summary post))
             (link :rel "alternate" :href ,link)
@@ -739,9 +788,9 @@ result as a string."
 (defun ennum-setting (property)
   (pcase property
     ((or :blog-domain :blog-license :blog-title
-         :images-directory :output-directory :posts-directory
-         :static-directory :videos-directory :working-directory
-         :open-graph-fallback-image)
+         :images-directory :output-directory :output-gemini-directory
+         :posts-directory :static-directory :videos-directory
+         :working-directory :open-graph-fallback-image)
      (or (plist-get ennum-blog property)
          (user-error "Property %s not defined" property)))
     ((or :atom-feed-number-of-posts :atom-feed-file
@@ -845,14 +894,15 @@ recognized as a directory, it should end in a slash. See
       (ennum-intern (ennum--file-join (ennum-setting :images-directory)
                                       path))))
 
-(defun ennum-publish-link (link)
+(defun ennum-publish-link (backend link)
   (pcase (ennum-link-type link)
     ('image
      (seq-map (lambda (width)
                 (ennum-publish-image (ennum--find-image (ennum-link-path link))
                                      width))
-              (list (ennum-setting :image-width)
-                    (ennum-setting :image-link-width))))
+              (cons (ennum-setting :image-link-width)
+                    (and (eq backend 'ennum-html)
+                         (list (ennum-setting :image-width))))))
     ((or 'pdf 'static)
      (list
       (ennum-publish-copy
@@ -913,7 +963,9 @@ as keys. Keys are compared using `equal'."
             (ennum-store-item-union
              (append
               ;; Publish posts
-              (seq-mapcat 'ennum-publish-post posts)
+              (seq-mapcat (apply-partially 'ennum-publish-post
+                                           'ennum-html)
+                          posts)
               ;; Publish feeds
               (seq-map
                (pcase-lambda (`(,tongue . ,posts))
@@ -927,7 +979,7 @@ as keys. Keys are compared using `equal'."
               ;; Publish indices
               (seq-mapcat
                (pcase-lambda (`(,tongue . ,posts))
-                 (ennum-publish-index "index" blog-title posts-per-page tongue posts))
+                 (ennum-publish-index 'html "index" blog-title posts-per-page tongue posts))
                (seq-group-by 'ennum-post-language posts))
               ;; Publish tag indices
               (seq-mapcat
@@ -935,6 +987,7 @@ as keys. Keys are compared using `equal'."
                  (seq-mapcat
                   (pcase-lambda (`(,tongue . ,posts))
                     (ennum-publish-index
+                     'html
                      (ennum--file-join (ennum-setting :tag-directory) tag)
                      tag posts-per-page tongue posts))
                   (seq-group-by 'ennum-post-language posts)))
@@ -945,7 +998,9 @@ as keys. Keys are compared using `equal'."
                                               (ennum-setting :image-thumbnail-width)))
                        (ennum--filter-map 'ennum-post-thumbnail posts))
               ;; Publish other files
-              (seq-map (apply-partially 'ennum-publish-generic other-files-directory)
+              (seq-map (apply-partially 'ennum-publish-generic
+                                        other-files-directory
+                                        'html)
                        (ennum-with-current-directory (ennum-setting :other-files-directory)
                          (ennum-directory-files)))
               (ennum-setting :other-items)))))
@@ -957,6 +1012,52 @@ as keys. Keys are compared using `equal'."
                       (ennum-setting :output-directory))
       (message "Ennum published to %s" (expand-file-name (ennum-setting :output-directory))))))
 
+(defun ennum-gemini-publish ()
+  (interactive)
+  (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 (apply-partially 'ennum-publish-post
+                                           'ennum-gemini)
+                          posts)
+              ;; Publish indices
+              (seq-mapcat
+               (pcase-lambda (`(,tongue . ,posts))
+                 (ennum-publish-index 'gemini "index" blog-title posts-per-page tongue posts))
+               (seq-group-by 'ennum-post-language posts))
+              ;; Publish tag indices
+              (seq-mapcat
+               (pcase-lambda (`(,tag . ,posts))
+                 (seq-mapcat
+                  (pcase-lambda (`(,tongue . ,posts))
+                    (ennum-publish-index
+                     'gemini
+                     (ennum--file-join (ennum-setting :tag-directory) tag)
+                     tag posts-per-page tongue posts))
+                  (seq-group-by 'ennum-post-language posts)))
+               (ennum-many-to-many-group-by 'ennum-post-tags posts))
+              ;; Publish other files
+              (seq-map (apply-partially 'ennum-publish-generic
+                                        other-files-directory
+                                        'gemini)
+                       (ennum-with-current-directory (ennum-setting :other-files-directory)
+                         (ennum-directory-files)))
+              (ennum-setting :other-items)))))
+      ;; Replace old output directory
+      (when (file-exists-p (ennum-setting :output-gemini-directory))
+        (ennum--set-file-modes-recursively (ennum-setting :output-gemini-directory)
+                                           #o755 #o644 #o755)
+        (delete-directory (ennum-setting :output-gemini-directory) t))
+      (copy-directory (ennum-eval result)
+                      (ennum-setting :output-gemini-directory))
+      (message "Ennum published to %s" (expand-file-name (ennum-setting :output-gemini-directory))))))
+
 ;;; Server
 ;;;
 ;;; Test HTTP server to serve the blog locally