From 9b53537b89d83fd7ade6a8707bb932d46564519a Mon Sep 17 00:00:00 2001
From: Arun Isaac
Date: Sat, 18 Jul 2020 05:46:59 +0530
Subject: Rename ennu to ennum.
* ennu.el: Rename to ...
* ennum.el: ... this. Replace all instances of ennu with ennum.
* ennu-html.el: Rename to ...
* ennum-html.el: ... this. Replace all instances of ennu with ennum.
* ennu-image.el: Rename to ...
* ennum-image.el: ... this. Replace all instances of ennu with ennum.
---
ennu-html.el | 383 -----------------------------------------
ennu-image.el | 55 ------
ennu.el | 524 -------------------------------------------------------
ennum-html.el | 383 +++++++++++++++++++++++++++++++++++++++++
ennum-image.el | 55 ++++++
ennum.el | 534 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 972 insertions(+), 962 deletions(-)
delete mode 100644 ennu-html.el
delete mode 100644 ennu-image.el
delete mode 100644 ennu.el
create mode 100644 ennum-html.el
create mode 100644 ennum-image.el
create mode 100644 ennum.el
diff --git a/ennu-html.el b/ennu-html.el
deleted file mode 100644
index 10025bc..0000000
--- a/ennu-html.el
+++ /dev/null
@@ -1,383 +0,0 @@
-;; -*- lexical-binding: t -*-
-
-(require 'ox)
-(require 'subr-x)
-(require 'xmlgen)
-
-(defconst ennu--iso-639-1-alist
- '(("ab" . "аҧсуа бызшәа, аҧсшәа")
- ("aa" . "Afaraf")
- ("af" . "Afrikaans")
- ("ak" . "Akan")
- ("sq" . "Shqip")
- ("am" . "አማርኛ")
- ("ar" . "العربية")
- ("an" . "aragonés")
- ("hy" . "Հայերեն")
- ("as" . "অসমীয়া")
- ("av" . "авар мацӀ, магӀарул мацӀ")
- ("ae" . "avesta")
- ("ay" . "aymar aru")
- ("az" . "azərbaycan dili")
- ("bm" . "bamanankan")
- ("ba" . "башҡорт теле")
- ("eu" . "euskara, euskera")
- ("be" . "беларуская мова")
- ("bn" . "বাংলা")
- ("bh" . "भोजपुरी")
- ("bi" . "Bislama")
- ("bs" . "bosanski jezik")
- ("br" . "brezhoneg")
- ("bg" . "български език")
- ("my" . "ဗမာစာ")
- ("ca" . "català")
- ("ch" . "Chamoru")
- ("ce" . "нохчийн мотт")
- ("ny" . "chiCheŵa, chinyanja")
- ("zh" . "中文 (Zhōngwén), 汉语, 漢語")
- ("cv" . "чӑваш чӗлхи")
- ("kw" . "Kernewek")
- ("co" . "corsu, lingua corsa")
- ("cr" . "ᓀᐦᐃᔭᐍᐏᐣ")
- ("hr" . "hrvatski jezik")
- ("cs" . "čeština, český jazyk")
- ("da" . "dansk")
- ("dv" . "ދިވެހި")
- ("nl" . "Nederlands, Vlaams")
- ("dz" . "རྫོང་ཁ")
- ("en" . "English")
- ("eo" . "Esperanto")
- ("et" . "eesti, eesti keel")
- ("ee" . "Eʋegbe")
- ("fo" . "føroyskt")
- ("fj" . "vosa Vakaviti")
- ("fi" . "suomi, suomen kieli")
- ("fr" . "français, langue française")
- ("ff" . "Fulfulde, Pulaar, Pular")
- ("gl" . "galego")
- ("ka" . "ქართული")
- ("de" . "Deutsch")
- ("el" . "ελληνικά")
- ("gn" . "Avañe'ẽ")
- ("gu" . "ગુજરાતી")
- ("ht" . "Kreyòl ayisyen")
- ("ha" . "(Hausa) هَوُسَ")
- ("he" . "עברית")
- ("hz" . "Otjiherero")
- ("hi" . "हिन्दी, हिंदी")
- ("ho" . "Hiri Motu")
- ("hu" . "magyar")
- ("ia" . "Interlingua")
- ("id" . "Bahasa Indonesia")
- ("ie" . "Originally called Occidental; then Interlingue after WWII")
- ("ga" . "Gaeilge")
- ("ig" . "Asụsụ Igbo")
- ("ik" . "Iñupiaq, Iñupiatun")
- ("io" . "Ido")
- ("is" . "Íslenska")
- ("it" . "Italiano")
- ("iu" . "ᐃᓄᒃᑎᑐᑦ")
- ("ja" . "日本語 (にほんご)")
- ("jv" . "ꦧꦱꦗꦮ, Basa Jawa")
- ("kl" . "kalaallisut, kalaallit oqaasii")
- ("kn" . "ಕನ್ನಡ")
- ("kr" . "Kanuri")
- ("ks" . "कश्मीरी, كشميري")
- ("kk" . "қазақ тілі")
- ("km" . "ខ្មែរ, ខេមរភាសា, ភាសាខ្មែរ")
- ("ki" . "Gĩkũyũ")
- ("rw" . "Ikinyarwanda")
- ("ky" . "Кыргызча, Кыргыз тили")
- ("kv" . "коми кыв")
- ("kg" . "Kikongo")
- ("ko" . "한국어")
- ("ku" . "Kurdî, كوردی")
- ("kj" . "Kuanyama")
- ("la" . "latine, lingua latina")
- ("lb" . "Lëtzebuergesch")
- ("lg" . "Luganda")
- ("li" . "Limburgs")
- ("ln" . "Lingála")
- ("lo" . "ພາສາລາວ")
- ("lt" . "lietuvių kalba")
- ("lu" . "Tshiluba")
- ("lv" . "latviešu valoda")
- ("gv" . "Gaelg, Gailck")
- ("mk" . "македонски јазик")
- ("mg" . "fiteny malagasy")
- ("ms" . "bahasa Melayu, بهاس ملايو")
- ("ml" . "മലയാളം")
- ("mt" . "Malti")
- ("mi" . "te reo Māori")
- ("mr" . "मराठी")
- ("mh" . "Kajin M̧ajeļ")
- ("mn" . "Монгол хэл")
- ("na" . "Dorerin Naoero")
- ("nv" . "Diné bizaad")
- ("nd" . "isiNdebele")
- ("ne" . "नेपाली")
- ("ng" . "Owambo")
- ("nb" . "Norsk bokmål")
- ("nn" . "Norsk nynorsk")
- ("no" . "Norsk")
- ("ii" . "ꆈꌠ꒿ Nuosuhxop")
- ("nr" . "isiNdebele")
- ("oc" . "occitan, lenga d'òc")
- ("oj" . "ᐊᓂᔑᓈᐯᒧᐎᓐ")
- ("cu" . "ѩзыкъ словѣньскъ")
- ("om" . "Afaan Oromoo")
- ("or" . "ଓଡ଼ିଆ")
- ("os" . "ирон æвзаг")
- ("pa" . "ਪੰਜਾਬੀ")
- ("pi" . "पाऴि")
- ("fa" . "فارسی")
- ("pl" . "język polski, polszczyzna")
- ("ps" . "پښتو")
- ("pt" . "Português")
- ("qu" . "Runa Simi, Kichwa")
- ("rm" . "rumantsch grischun")
- ("rn" . "Ikirundi")
- ("ro" . "Română")
- ("ru" . "Русский")
- ("sa" . "संस्कृतम्")
- ("sc" . "sardu")
- ("sd" . "सिन्धी, سنڌي، سندھی")
- ("se" . "Davvisámegiella")
- ("sm" . "gagana fa'a Samoa")
- ("sg" . "yângâ tî sängö")
- ("sr" . "српски језик")
- ("gd" . "Gàidhlig")
- ("sn" . "chiShona")
- ("si" . "සිංහල")
- ("sk" . "slovenčina, slovenský jazyk")
- ("sl" . "slovenski jezik, slovenščina")
- ("so" . "Soomaaliga, af Soomaali")
- ("st" . "Sesotho")
- ("es" . "Español")
- ("su" . "Basa Sunda")
- ("sw" . "Kiswahili")
- ("ss" . "SiSwati")
- ("sv" . "svenska")
- ("ta" . "தமிழ்")
- ("te" . "తెలుగు")
- ("tg" . "тоҷикӣ, toçikī, تاجیکی")
- ("th" . "ไทย")
- ("ti" . "ትግርኛ")
- ("bo" . "བོད་ཡིག")
- ("tk" . "Türkmen, Түркмен")
- ("tl" . "Wikang Tagalog")
- ("tn" . "Setswana")
- ("to" . "faka Tonga")
- ("tr" . "Türkçe")
- ("ts" . "Xitsonga")
- ("tt" . "татар теле, tatar tele")
- ("tw" . "Twi")
- ("ty" . "Reo Tahiti")
- ("ug" . "ئۇيغۇرچە, Uyghurche")
- ("uk" . "Українська")
- ("ur" . "اردو")
- ("uz" . "Oʻzbek, Ўзбек, أۇزبېك")
- ("ve" . "Tshivenḓa")
- ("vi" . "Tiếng Việt")
- ("vo" . "Volapük")
- ("wa" . "walon")
- ("cy" . "Cymraeg")
- ("wo" . "Wollof")
- ("fy" . "Frysk")
- ("xh" . "isiXhosa")
- ("yi" . "ייִדיש")
- ("yo" . "Yorùbá")
- ("za" . "Saɯ cueŋƅ, Saw cuengh")
- ("zu" . "isiZulu")))
-
-(defun expand-file-name* (name default-directory)
- (expand-file-name name (concat "/" default-directory)))
-
-(org-export-define-derived-backend 'ennu-html 'html
- :translate-alist
- '((inner-template . ennu-html-inner-template)
- (link . ennu-html-link))
- :options-alist
- '((:summary "SUMMARY" nil nil parse)
- (:thumbnail "THUMBNAIL" nil nil t)
- (:translation-group "TRANSLATION_GROUP" nil nil t)))
-
-(defun ennu-html-inner-template (contents info)
- (concat
- ;; Table of contents
- (let ((depth (plist-get info :with-toc)))
- (when depth (org-html-toc depth info)))
- ;; Beginning of h-entry
- ""
- ;; Title
- (format "%s
\n"
- (org-export-data (plist-get info :title) info))
- ;; Author and date
- (let ((author (when (plist-get info :with-author)
- (plist-get info :author)))
- (date (when (plist-get info :with-date)
- (org-export-get-date info))))
- (when (or author date)
- (xmlgen `(p "Published"
- ,@(when author
- `(" by "
- (a :class "p-author h-card"
- :href ,(ennu--absolute-uri "")
- ,(car (plist-get info :author)))))
- ,@(when date
- `(" on "
- (time :class "dt-published"
- :datetime ,(org-export-get-date info "%Y-%m-%d 12:00:00")
- ,(org-export-get-date info "%B %d, %Y"))))))))
- ;; Interlanguage language links
- (when-let (translations (plist-get info :translations))
- (format "In other languages: %s
"
- (mapconcat
- (lambda (translation)
- (let ((lang (ennu-post-language translation))
- (slug (ennu-post-slug translation)))
- (replace-regexp-in-string
- "Tags: %s
"
- (mapconcat
- (lambda (tag)
- (replace-regexp-in-string
- "%s"
- (org-export-data (plist-get info :summary) info))
- ;; Document contents
- (format "%s
" contents)
- ;; Footnotes section
- (org-html-footnote-section info)
- ""))
-
-(defun ennu-html-link (link desc info)
- ;; We override the html link transcoder to handle image links
- ;; differently. We cannot use the `:export' property of
- ;; `org-link-parameters' since those functions cannot access the
- ;; `info' communication channel.
- (let ((path (org-element-property :path link)))
- (pcase (org-element-property :type link)
- ("image"
- ;; Convert image links to file links, get them transcoded by
- ;; `org-html-link' and then remove the file:// scheme from the
- ;; URI. Finally insert the transcoded image link in a link to a
- ;; larger image as specified by the :image-link-width setting.
- (format "%s"
- (expand-file-name*
- (ennu-image-output-filename
- path (ennu-setting :image-link-width))
- (ennu-setting :images-directory))
- (replace-regexp-in-string
- (rx (group (or "src" "data")) "=\"file://") "\\1=\""
- (org-html-link
- (org-element-put-property
- (org-element-put-property
- link :path (url-encode-url
- (expand-file-name*
- (ennu-image-output-filename
- path (ennu-setting :default-image-width))
- (ennu-setting :images-directory))))
- :type "file")
- desc info))))
- ;; Pass other link types to org-html-link
- (_ (org-html-link link desc info)))))
-
-(defmacro ennu-follow (path)
- `(ennu-with-current-directory (ennu-setting :working-directory)
- (find-file ,path)))
-
-;; TODO: Pass title through org-export-data-with-backend or something
-;; similar in order to export org syntax in title
-(defun ennu-export-post (path desc backend)
- (pcase backend
- ((or 'ennu-html 'html)
- (let ((filename (concat (expand-file-name path (ennu-setting :posts-directory))
- ".org")))
- (xmlgen `(a :href ,(url-encode-url
- (expand-file-name* path (ennu-setting :posts-directory)))
- ,(or desc (ennu-post-title (ennu-read-post filename)))))))))
-
-(defun ennu-follow-post (path)
- (ennu-follow (expand-file-name (concat path ".org")
- (ennu-setting :posts-directory))))
-
-(org-link-set-parameters
- "post"
- :export 'ennu-export-post
- :follow 'ennu-follow-post)
-
-(defun ennu-follow-image (path)
- (ennu-follow (expand-file-name path (ennu-setting :images-directory))))
-
-(org-link-set-parameters
- "image" :follow 'ennu-follow-image)
-
-(defun ennu-export-thumbnail (path desc backend)
- (pcase backend
- ((or 'ennu-html 'html)
- (xmlgen
- `(img :src ,(url-encode-url
- (expand-file-name*
- (ennu-image-output-filename
- path (ennu-setting :thumbnail-image-width))
- (ennu-setting :images-directory))))))))
-
-(org-link-set-parameters
- "thumbnail"
- :export 'ennu-export-thumbnail
- :follow 'ennu-follow-image)
-
-(defun ennu-export-video (path desc backend)
- (pcase backend
- ((or 'ennu-html 'html)
- (let ((video-directory (ennu-setting :video-directory)))
- (xmlgen
- `(video :src ,(url-encode-url (expand-file-name* path video-directory))
- :poster ,(url-encode-url
- (expand-file-name* (ennu-video-poster path) video-directory))
- :preload "none"
- :controls ""))))))
-
-(org-link-set-parameters
- "video" :export 'ennu-export-video)
-
-(defun ennu-export-static (path desc backend)
- (pcase backend
- ((or 'ennu-html 'html)
- (xmlgen
- `(a :href ,(url-encode-url
- (expand-file-name* path (ennu-setting :static-directory)))
- ,desc)))))
-
-(org-link-set-parameters
- "static" :export 'ennu-export-static)
-
-(org-link-set-parameters
- "tangle" :export 'ennu-export-static)
-
-(defun ennu-export-tag (tag desc backend)
- (pcase backend
- ((or 'ennu-html 'html)
- (xmlgen
- `(a :href ,(url-encode-url
- (expand-file-name* tag (ennu-setting :tag-directory)))
- ,(or desc tag))))))
-
-(org-link-set-parameters
- "tag" :export 'ennu-export-tag)
-
-(provide 'ennu-html)
diff --git a/ennu-image.el b/ennu-image.el
deleted file mode 100644
index 34c3e7e..0000000
--- a/ennu-image.el
+++ /dev/null
@@ -1,55 +0,0 @@
-;; -*- lexical-binding: t -*-
-
-(require 'image)
-(require 'seq)
-
-;; Check if all necessary image types are supported
-(seq-do (lambda (image-type)
- (unless (image-type-available-p image-type)
- (lwarn '(ennu) :error "`%s' image type not supported" image-type)))
- '(jpeg png svg))
-
-;; Check for existence of external image processing utilities
-(seq-do (lambda (external-program)
- (unless (executable-find external-program)
- (lwarn '(ennu) :error "`%s' not found" external-program)))
- '("convert" "identify" "jpegtran" "optipng"))
-
-(defun ennu-image-resize-image (infile-path outfile-path width)
- "A simple shell wrapper around ImageMagick's convert"
- (ennu-image--assert-file-exists infile-path)
- (cl-case (image-type infile-path)
- (svg
- (copy-file infile-path outfile-path t))
- (otherwise
- (call-process "convert" nil nil nil
- infile-path "-resize" (format "%d>" width) outfile-path)))
- outfile-path)
-
-(defun ennu-image-optimize-image (image-path)
- "A simple shell wrapper around jpegtran and optipng"
- (ennu-image--assert-file-exists image-path)
- (cl-case (image-type image-path)
- (jpeg
- (call-process "jpegtran" nil nil nil "-optimize"
- "-progressive" "-copy" "none"
- "-outfile" image-path image-path))
- (png
- (call-process "optipng" nil nil nil image-path)))
- image-path)
-
-(defun ennu-image-get-width (image-path)
- (ennu-image--assert-file-exists image-path)
- (cl-case (image-type image-path)
- (svg 1e+INF)
- (otherwise
- (with-temp-buffer
- (call-process "identify" nil t nil
- "-format" "%w" image-path)
- (string-to-number (buffer-string))))))
-
-(defun ennu-image--assert-file-exists (path)
- (unless (file-exists-p path)
- (error "File %s does not exist" path)))
-
-(provide 'ennu-image)
diff --git a/ennu.el b/ennu.el
deleted file mode 100644
index 439e1d9..0000000
--- a/ennu.el
+++ /dev/null
@@ -1,524 +0,0 @@
-;; -*- lexical-binding: t -*-
-
-(require 'ennu-html)
-(require 'ennu-image)
-(require 'ox)
-(require 'seq)
-(require 'cl)
-(require 'map)
-(require 'memoize)
-(require 'simple-httpd)
-
-(defvar ennu-version "0.1.0"
- "Ennu version string")
-
-(cl-defstruct (ennu-post (:constructor ennu-make-post)
- (:copier nil))
- filename slug author date language links tangle
- summary tags thumbnail title translation-group)
-
-(cl-defstruct (ennu-operation (:constructor ennu-make-operation)
- (:copier nil))
- inputs outputs publish)
-
-(defun ennu-posts (posts-directory)
- (sort (seq-map 'ennu-read-post
- (file-expand-wildcards
- (concat (file-name-as-directory
- (ennu-setting :posts-directory))
- "*.org")))
- 'ennu-later-post-p))
-
-(defun ennu-later-post-p (post1 post2)
- (time-less-p (ennu-post-date post2)
- (ennu-post-date post1)))
-
-(defun ennu-read-post (filename)
- (ennu--read-post
- filename (file-attribute-modification-time
- (file-attributes filename))))
-
-(defmemoize ennu--read-post (filename last-modified)
- (ennu-with-file-contents filename
- (let ((metadata (org-export-get-environment 'ennu-html))
- (export (apply-partially 'org-export-with-backend 'ennu-html)))
- (seq-do (lambda (key)
- (unless (plist-member metadata key)
- (user-error "Metadata %s not specified" key)))
- ennu-mandatory-metadata)
- (let* ((tree (org-element-parse-buffer))
- (links (org-element-map tree 'link
- (lambda (link)
- (pcase link
- (`(link ,properties . ,_)
- (let ((link-type (org-element-property :type link)))
- (when (member link-type (list "image" "static" "video"))
- (cons link-type (org-element-property :path link))))))))))
- (ennu-make-post
- :filename filename
- :slug (file-name-base filename)
- :author (when-let (author (plist-get metadata :author))
- (funcall export (first author)))
- :date (org-timestamp-to-time (first (plist-get metadata :date)))
- :language (plist-get metadata :language)
- :links links
- ;; TODO: Deal with cases when the :tangle parameter is "yes"
- :tangle (seq-uniq
- (org-element-map tree 'src-block
- (lambda (src-block)
- (pcase (org-babel-get-src-block-info nil src-block)
- (`(,_ ,_ ,arguments ,_ ,_ ,_ ,_)
- (let ((tangle-output-file (map-elt arguments :tangle)))
- (pcase tangle-output-file
- ("no" nil)
- (_ tangle-output-file))))))))
- :summary (when-let (summary (plist-get metadata :summary))
- (funcall export (first summary)))
- :tags (plist-get metadata :filetags)
- :thumbnail (or (plist-get metadata :thumbnail)
- (seq-some (lambda (link)
- (pcase link
- (`("image" . ,path) path)
- (`("video" . ,path) (ennu-video-poster path))))
- links))
- :title (funcall export (first (plist-get metadata :title)))
- :translation-group (or (plist-get metadata :translation-group)
- (file-name-base filename)))))))
-
-(defvar ennu-mandatory-metadata
- (list :title :date))
-
-(defmacro ennu-with-file-contents (file &rest body)
- "Create a temporary buffer, insert contents of FILE into that
-buffer and evaluate BODY. The value returned is the value of the
-last form in BODY."
- (declare (indent defun))
- `(with-temp-buffer
- (insert-file-contents ,file)
- ,@body))
-
-(defun ennu--org-output-filename (filename)
- (concat (file-name-sans-extension filename) ".html"))
-
-(defun ennu-publish-post (posts)
- (let ((link-publish-operations
- (seq-mapcat 'ennu-publish-link (seq-mapcat 'ennu-post-links posts)))
- (input-post-files (seq-map 'ennu-post-filename posts)))
- (append
- (list
- (ennu-make-operation
- :inputs (append input-post-files
- (seq-mapcat 'ennu-operation-inputs link-publish-operations))
- :outputs (seq-map 'ennu--org-output-filename input-post-files)
- :publish
- (lambda (&rest output-files)
- (seq-mapn
- (lambda (post output-file)
- (let ((system-time-locale (map-elt (ennu-setting :locale-alist)
- (ennu-post-language post) nil 'string=)))
- (ennu-with-file-contents (ennu-post-filename post)
- (org-export-to-file
- 'ennu-html output-file nil nil nil nil
- (list :translations (seq-remove (apply-partially 'equal post) posts))))))
- posts
- output-files))))
- (ennu--filter-map
- (lambda (post)
- (when (ennu-post-tangle post)
- (ennu-make-operation
- :inputs (list (ennu-post-filename post))
- :outputs (seq-map (lambda (tangle-output)
- (ennu--expand-relative tangle-output
- (ennu-setting :static-directory)))
- (ennu-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 (ennu-post-filename post)))))
- (copy-file (ennu-post-filename post) post-file-copy)
- (org-babel-tangle-file post-file-copy)
- (delete-file post-file-copy))))))
- posts)
- link-publish-operations)))
-
-(defun ennu-publish-generic (other-files-directory file)
- (ennu-make-operation
- :inputs (list file)
- :outputs
- (list (string-remove-prefix
- (file-name-as-directory other-files-directory)
- (pcase (file-name-extension file)
- ("org" (ennu--org-output-filename file))
- (_ file))))
- :publish (lambda (output-file)
- (pcase (file-name-extension file)
- ("org" (ennu-with-file-contents file
- (org-export-to-file 'html output-file)))
- (_ (ennu-copy file output-file))))))
-
-(defun ennu-video-poster (video)
- (pcase (directory-files (ennu-setting :images-directory) nil
- (concat (file-name-sans-extension video)
- "\\.\\(jpg\\|png\\)$"))
- (`(,poster . ,_) poster)
- (`() (user-error "Poster for %s not found" video))))
-
-(defun ennu-add-tongue-suffix (filename tongue)
- (pcase tongue
- ("en" filename)
- (_ (format "%s.%s%s"
- (file-name-sans-extension filename)
- tongue
- (file-name-extension filename t)))))
-
-(defun ennu-index-filename (filename-prefix tongue &optional extension page-number)
- (let ((extension (if extension (concat "." extension) "")))
- (ennu-add-tongue-suffix
- (if page-number
- (format "%s-%s%s" filename-prefix page-number extension)
- (concat filename-prefix extension))
- tongue)))
-
-(defun ennu-publish-index (filename-prefix title posts-per-page posts)
- (let* ((tongue (ennu-post-language (first posts)))
- (number-of-pages (ceiling (length posts) posts-per-page))
- (page-numbers (number-sequence 1 number-of-pages)))
- (ennu-make-operation
- :inputs (seq-map 'ennu-post-filename posts)
- :outputs (cons (ennu-add-tongue-suffix (format "%s.html" filename-prefix) tongue)
- (seq-map (apply-partially 'ennu-index-filename filename-prefix tongue "html")
- page-numbers))
- :publish
- (lambda (home-page &rest output-files)
- (let ((system-time-locale (map-elt (ennu-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]]\n" (ennu-post-slug post)))
- (insert (format-time-string "/%b %e, %Y/\n\n" (ennu-post-date post)))
- (when-let ((thumbnail (ennu-post-thumbnail post)))
- (insert (format "[[thumbnail:%s]]\n\n" thumbnail)))
- (when-let ((summary (ennu-post-summary post)))
- (insert summary)
- (insert "\n\n"))
- (when-let ((tags (ennu-post-tags post)))
- (insert "Tags: ")
- (insert
- (string-join
- (seq-map (lambda (tag)
- (format "[[tag:%s][%s]]" (ennu-add-tongue-suffix tag tongue) tag))
- tags)
- ", "))
- (insert "\n\n")))
- posts)
- (unless (= page-number 1)
- (insert (format "[[./%s][Newer posts]]\n"
- (ennu-index-filename (file-name-nondirectory filename-prefix)
- tongue nil (1- page-number)))))
- (unless (= page-number number-of-pages)
- (insert (format "[[./%s][Older posts]]\n"
- (ennu-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)))))
-
-(defun ennu--absolute-uri (path)
- (format "%s://%s/%s"
- (ennu-setting :blog-scheme)
- (ennu-setting :blog-domain)
- path))
-
-(defun ennu--atom-date (date)
- (format-time-string "%Y-%m-%dT%H:%M:%SZ" date))
-
-(defun ennu-publish-feed (feed-file title rights posts)
- (ennu-make-operation
- :inputs (seq-map 'ennu-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 ,(ennu--absolute-uri ""))
- (title ,title)
- (updated ,(ennu--atom-date (ennu-post-date (first posts))))
- (link :rel "self" :href ,(ennu--absolute-uri feed-file))
- (generator
- ,(format "Emacs %d.%d Org-mode %s ennu %s"
- emacs-major-version emacs-minor-version (org-version) ennu-version))
- (rights ,rights)
- ,@(seq-map 'ennu--feed-entry posts))))))))
-
-(defun ennu--feed-entry (post)
- (let ((link (ennu--absolute-uri (ennu--org-output-filename
- (ennu-post-filename post)))))
- `(entry (id ,link)
- (title :xml:lang ,(ennu-post-language post) ,(ennu-post-title post))
- (updated ,(ennu--atom-date (ennu-post-date post)))
- ,@(when org-export-with-author
- `((author
- (name ,(ennu-post-author post))
- (email ,user-mail-address))))
- (content :type "html" :xml:lang ,(ennu-post-language post)
- ,(ennu-with-file-contents (ennu-post-filename post)
- (org-export-as 'ennu-html nil nil t)))
- (link :rel "alternate" :href ,link)
- ,@(seq-map (lambda (tag) `(category :term ,tag))
- (ennu-post-tags post)))))
-
-(defun ennu-setting (property)
- (pcase property
- ((or :blog-domain :blog-license :blog-title
- :images-directory :output-directory :posts-directory
- :static-directory :tag-directory :video-directory
- :working-directory)
- (or (plist-get ennu-blog property)
- (user-error "Property %s not defined" property)))
- ((or :atom-feed-number-of-posts :atom-feed-file
- :blog-scheme :default-image-width
- :image-link-width :index-posts-per-page
- :locale-alist :other-files-directory
- :tag-directory :thumbnail-image-width)
- (plist-get (org-combine-plists
- (list :atom-feed-number-of-posts 12
- :atom-feed-file "blog.atom"
- :blog-scheme "https"
- :default-image-width 640
- :image-link-width 1024
- :index-posts-per-page 12
- :locale-alist '(("en" . "C"))
- :tag-directory "tag"
- :thumbnail-image-width 320)
- ennu-blog)
- property))
- (_ (error "Unknown property %s" property))))
-
-(defun ennu-image-output-filename (image width)
- (format "%s-%spx.%s"
- (file-name-sans-extension image)
- width (file-name-extension image)))
-
-(defun ennu--expand-relative (name directory)
- (concat (file-name-as-directory directory) name))
-
-(defun ennu-publish-image (widths image)
- (ennu-make-operation
- :inputs (list image)
- :outputs (seq-map (apply-partially 'ennu-image-output-filename image)
- widths)
- :publish
- (lambda (&rest output-files)
- (seq-mapn (lambda (output-file width)
- (ennu-image-optimize-image
- (ennu-image-resize-image image output-file width)))
- output-files widths))))
-
-(defun ennu-publish-copy (file)
- (ennu-make-operation
- :inputs (list file)
- :outputs (list file)
- :publish (apply-partially 'ennu-copy file)))
-
-(defun newest-file (files)
- (pcase files
- (`(,head . ,tail)
- (seq-reduce (lambda (file1 file2)
- (if (file-newer-than-file-p file1 file2)
- file1 file2))
- tail head))))
-
-(defun ennu-mkdir-p (directory)
- (make-directory directory t))
-
-(defun ennu-copy (source destination)
- "Copy file or directory from SOURCE to DESTINATION. Overwrite
-if DESTINATION already exists."
- (if (file-directory-p source)
- (copy-directory source destination)
- (make-directory (file-name-directory destination) t)
- (copy-file source destination t)))
-
-(defun ennu--filter-map (function sequence)
- (seq-filter 'identity (seq-map function sequence)))
-
-(defun ennu--do-operation (temporary-directory operation)
- (let* ((expand (lambda (directory file)
- (expand-file-name file directory)))
- (inputs (ennu-operation-inputs operation))
- (outputs (ennu-operation-outputs operation))
- (absolute-outputs
- (seq-map (apply-partially expand temporary-directory)
- outputs))
- (previous-outputs
- (seq-map (apply-partially expand (ennu-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 'ennu-copy previous-outputs absolute-outputs))
- (t (message "Publishing %s to %s" inputs outputs)
- (seq-do 'ennu-mkdir-p
- (seq-uniq
- (seq-map 'file-name-directory absolute-outputs)))
- (apply (ennu-operation-publish operation) absolute-outputs)))))
-
-(defun ennu-publish-static-file (file)
- (ennu-make-operation
- :inputs (list file)
- :outputs (list file)
- :publish (apply-partially 'ennu-copy file)))
-
-(defun ennu-publish-link (link)
- (pcase link
- (`("image" . ,path)
- (list
- (ennu-publish-image
- (list (ennu-setting :default-image-width)
- (ennu-setting :image-link-width))
- (ennu--expand-relative path (ennu-setting :images-directory)))))
- (`("static" . ,path)
- (list
- (ennu-publish-copy (ennu--expand-relative path (ennu-setting :static-directory)))))
- (`("video" . ,path)
- (list
- (ennu-publish-copy (ennu--expand-relative path (ennu-setting :video-directory)))
- (ennu-publish-copy (ennu--expand-relative (ennu-video-poster path)
- (ennu-setting :images-directory)))))))
-
-(defmacro ennu-with-current-directory (directory &rest body)
- "Change to DIRECTORY, evaluate BODY and restore the current
-working directory. The value returned is the value of the last
-form in BODY."
- (declare (indent defun))
- (let ((current-directory-symbol (make-symbol "current-directory")))
- `(let ((,current-directory-symbol default-directory))
- (unwind-protect (progn (cd ,directory) ,@body)
- (cd ,current-directory-symbol)))))
-
-(defmacro ennu-with-temporary-directory (temporary-directory &rest body)
- "Create temporary directory, evaluate BODY with the absolute
-path of that directory assigned to TEMPORARY-DIRECTORY and
-finally delete the temporary directory. The value returned is the
-value of the last form in BODY."
- (declare (indent defun))
- `(let ((,temporary-directory (make-temp-file "ennu" t)))
- (chmod ,temporary-directory #o755)
- (unwind-protect
- (progn ,@body)
- (delete-directory ,temporary-directory t))))
-
-(defun ennu-many-to-many-group-by (function sequence)
- "Apply FUNCTION to each element of SEQUENCE.
-Separate the elements of SEQUENCE into an alist using the results
-as keys. Keys are compared using `equal'."
- (seq-reduce
- (lambda (result element)
- (seq-do
- (lambda (key)
- (map-put result key
- (cons element (map-elt result key nil 'equal))
- 'equal))
- (funcall function element))
- result)
- (seq-reverse sequence)
- nil))
-
-(defun ennu-publish ()
- (interactive)
- (let ((make-backup-files nil)
- (blog-title (ennu-setting :blog-title))
- (posts-per-page (ennu-setting :index-posts-per-page)))
- (ennu-with-current-directory (ennu-setting :working-directory)
- (ennu-with-temporary-directory temporary-directory
- (seq-do
- (apply-partially 'ennu--do-operation temporary-directory)
- (append
- (let ((posts (ennu-posts (ennu-setting :posts-directory))))
- (append
- ;; Publish posts
- (seq-mapcat (pcase-lambda (`(,translation-group . ,posts))
- (ennu-publish-post posts))
- (seq-group-by 'ennu-post-translation-group posts))
- ;; Publish feed
- (list (ennu-publish-feed (ennu-setting :atom-feed-file)
- blog-title
- (ennu-setting :blog-license)
- (seq-take posts (ennu-setting :atom-feed-number-of-posts))))
- ;; Publish indices
- (seq-map
- (pcase-lambda (`(,tongue . ,posts))
- (ennu-publish-index "index" blog-title posts-per-page posts))
- (seq-group-by 'ennu-post-language posts))
- (seq-mapcat
- (pcase-lambda (`(,tag . ,posts))
- (seq-map
- (pcase-lambda (`(,tongue . ,posts))
- (ennu-publish-index
- (ennu--expand-relative tag (ennu-setting :tag-directory))
- tag posts-per-page posts))
- (seq-group-by 'ennu-post-language posts)))
- (ennu-many-to-many-group-by 'ennu-post-tags posts))
- ;; Publish thumbnails
- (seq-map
- (apply-partially 'ennu-publish-image (list (ennu-setting :thumbnail-image-width)))
- (seq-map (lambda (image)
- (ennu--expand-relative image (ennu-setting :images-directory)))
- (seq-uniq (ennu--filter-map 'ennu-post-thumbnail posts))))))
- ;; Publish other files
- (when-let ((other-files-directory (ennu-setting :other-files-directory)))
- (seq-map (apply-partially 'ennu-publish-generic other-files-directory)
- (seq-map (apply-partially 'string-remove-prefix
- (file-name-as-directory (expand-file-name default-directory)))
- (directory-files-recursively other-files-directory "."))))))
- ;; Replace old output directory
- (let ((output (ennu-setting :output-directory)))
- (delete-directory output t)
- (rename-file temporary-directory output t))))))
-
-;;; Server
-;;;
-;;; Test HTTP server to serve the blog locally
-
-(defun ennu-server-start ()
- (interactive)
- (setq httpd-root (expand-file-name (ennu-setting :output-directory)
- (ennu-setting :working-directory)))
- (defun httpd/ (proc uri-path query request)
- (let* ((uri-path (httpd-unhex uri-path))
- (file-path (httpd-gen-path uri-path)))
- (cond
- ;; If a HTML file other than index.html was requested, reject
- ;; that request.
- ((and (not (string= (file-name-nondirectory file-path) "index.html"))
- (string= (file-name-extension file-path) "html"))
- (httpd-error proc 404))
- ;; If the requested file was found, serve it.
- ((= (httpd-status file-path) 200)
- (httpd-serve-root proc httpd-root uri-path request))
- ;; Perhaps, this is a post or other HTML file that is being
- ;; requested. Try serving a file with a .html extension
- ;; appended.
- (t (httpd-serve-root proc httpd-root (concat uri-path ".html") request)))))
- (httpd-start)
- (message "Ennu web server listening at http://localhost:%d" httpd-port))
-
-(defun ennu-server-stop ()
- (interactive)
- (httpd-stop)
- (message "Ennu web server stopped"))
-
-(provide 'ennu)
diff --git a/ennum-html.el b/ennum-html.el
new file mode 100644
index 0000000..1475927
--- /dev/null
+++ b/ennum-html.el
@@ -0,0 +1,383 @@
+;; -*- lexical-binding: t -*-
+
+(require 'ox)
+(require 'subr-x)
+(require 'xmlgen)
+
+(defconst ennum--iso-639-1-alist
+ '(("ab" . "аҧсуа бызшәа, аҧсшәа")
+ ("aa" . "Afaraf")
+ ("af" . "Afrikaans")
+ ("ak" . "Akan")
+ ("sq" . "Shqip")
+ ("am" . "አማርኛ")
+ ("ar" . "العربية")
+ ("an" . "aragonés")
+ ("hy" . "Հայերեն")
+ ("as" . "অসমীয়া")
+ ("av" . "авар мацӀ, магӀарул мацӀ")
+ ("ae" . "avesta")
+ ("ay" . "aymar aru")
+ ("az" . "azərbaycan dili")
+ ("bm" . "bamanankan")
+ ("ba" . "башҡорт теле")
+ ("eu" . "euskara, euskera")
+ ("be" . "беларуская мова")
+ ("bn" . "বাংলা")
+ ("bh" . "भोजपुरी")
+ ("bi" . "Bislama")
+ ("bs" . "bosanski jezik")
+ ("br" . "brezhoneg")
+ ("bg" . "български език")
+ ("my" . "ဗမာစာ")
+ ("ca" . "català")
+ ("ch" . "Chamoru")
+ ("ce" . "нохчийн мотт")
+ ("ny" . "chiCheŵa, chinyanja")
+ ("zh" . "中文 (Zhōngwén), 汉语, 漢語")
+ ("cv" . "чӑваш чӗлхи")
+ ("kw" . "Kernewek")
+ ("co" . "corsu, lingua corsa")
+ ("cr" . "ᓀᐦᐃᔭᐍᐏᐣ")
+ ("hr" . "hrvatski jezik")
+ ("cs" . "čeština, český jazyk")
+ ("da" . "dansk")
+ ("dv" . "ދިވެހި")
+ ("nl" . "Nederlands, Vlaams")
+ ("dz" . "རྫོང་ཁ")
+ ("en" . "English")
+ ("eo" . "Esperanto")
+ ("et" . "eesti, eesti keel")
+ ("ee" . "Eʋegbe")
+ ("fo" . "føroyskt")
+ ("fj" . "vosa Vakaviti")
+ ("fi" . "suomi, suomen kieli")
+ ("fr" . "français, langue française")
+ ("ff" . "Fulfulde, Pulaar, Pular")
+ ("gl" . "galego")
+ ("ka" . "ქართული")
+ ("de" . "Deutsch")
+ ("el" . "ελληνικά")
+ ("gn" . "Avañe'ẽ")
+ ("gu" . "ગુજરાતી")
+ ("ht" . "Kreyòl ayisyen")
+ ("ha" . "(Hausa) هَوُسَ")
+ ("he" . "עברית")
+ ("hz" . "Otjiherero")
+ ("hi" . "हिन्दी, हिंदी")
+ ("ho" . "Hiri Motu")
+ ("hu" . "magyar")
+ ("ia" . "Interlingua")
+ ("id" . "Bahasa Indonesia")
+ ("ie" . "Originally called Occidental; then Interlingue after WWII")
+ ("ga" . "Gaeilge")
+ ("ig" . "Asụsụ Igbo")
+ ("ik" . "Iñupiaq, Iñupiatun")
+ ("io" . "Ido")
+ ("is" . "Íslenska")
+ ("it" . "Italiano")
+ ("iu" . "ᐃᓄᒃᑎᑐᑦ")
+ ("ja" . "日本語 (にほんご)")
+ ("jv" . "ꦧꦱꦗꦮ, Basa Jawa")
+ ("kl" . "kalaallisut, kalaallit oqaasii")
+ ("kn" . "ಕನ್ನಡ")
+ ("kr" . "Kanuri")
+ ("ks" . "कश्मीरी, كشميري")
+ ("kk" . "қазақ тілі")
+ ("km" . "ខ្មែរ, ខេមរភាសា, ភាសាខ្មែរ")
+ ("ki" . "Gĩkũyũ")
+ ("rw" . "Ikinyarwanda")
+ ("ky" . "Кыргызча, Кыргыз тили")
+ ("kv" . "коми кыв")
+ ("kg" . "Kikongo")
+ ("ko" . "한국어")
+ ("ku" . "Kurdî, كوردی")
+ ("kj" . "Kuanyama")
+ ("la" . "latine, lingua latina")
+ ("lb" . "Lëtzebuergesch")
+ ("lg" . "Luganda")
+ ("li" . "Limburgs")
+ ("ln" . "Lingála")
+ ("lo" . "ພາສາລາວ")
+ ("lt" . "lietuvių kalba")
+ ("lu" . "Tshiluba")
+ ("lv" . "latviešu valoda")
+ ("gv" . "Gaelg, Gailck")
+ ("mk" . "македонски јазик")
+ ("mg" . "fiteny malagasy")
+ ("ms" . "bahasa Melayu, بهاس ملايو")
+ ("ml" . "മലയാളം")
+ ("mt" . "Malti")
+ ("mi" . "te reo Māori")
+ ("mr" . "मराठी")
+ ("mh" . "Kajin M̧ajeļ")
+ ("mn" . "Монгол хэл")
+ ("na" . "Dorerin Naoero")
+ ("nv" . "Diné bizaad")
+ ("nd" . "isiNdebele")
+ ("ne" . "नेपाली")
+ ("ng" . "Owambo")
+ ("nb" . "Norsk bokmål")
+ ("nn" . "Norsk nynorsk")
+ ("no" . "Norsk")
+ ("ii" . "ꆈꌠ꒿ Nuosuhxop")
+ ("nr" . "isiNdebele")
+ ("oc" . "occitan, lenga d'òc")
+ ("oj" . "ᐊᓂᔑᓈᐯᒧᐎᓐ")
+ ("cu" . "ѩзыкъ словѣньскъ")
+ ("om" . "Afaan Oromoo")
+ ("or" . "ଓଡ଼ିଆ")
+ ("os" . "ирон æвзаг")
+ ("pa" . "ਪੰਜਾਬੀ")
+ ("pi" . "पाऴि")
+ ("fa" . "فارسی")
+ ("pl" . "język polski, polszczyzna")
+ ("ps" . "پښتو")
+ ("pt" . "Português")
+ ("qu" . "Runa Simi, Kichwa")
+ ("rm" . "rumantsch grischun")
+ ("rn" . "Ikirundi")
+ ("ro" . "Română")
+ ("ru" . "Русский")
+ ("sa" . "संस्कृतम्")
+ ("sc" . "sardu")
+ ("sd" . "सिन्धी, سنڌي، سندھی")
+ ("se" . "Davvisámegiella")
+ ("sm" . "gagana fa'a Samoa")
+ ("sg" . "yângâ tî sängö")
+ ("sr" . "српски језик")
+ ("gd" . "Gàidhlig")
+ ("sn" . "chiShona")
+ ("si" . "සිංහල")
+ ("sk" . "slovenčina, slovenský jazyk")
+ ("sl" . "slovenski jezik, slovenščina")
+ ("so" . "Soomaaliga, af Soomaali")
+ ("st" . "Sesotho")
+ ("es" . "Español")
+ ("su" . "Basa Sunda")
+ ("sw" . "Kiswahili")
+ ("ss" . "SiSwati")
+ ("sv" . "svenska")
+ ("ta" . "தமிழ்")
+ ("te" . "తెలుగు")
+ ("tg" . "тоҷикӣ, toçikī, تاجیکی")
+ ("th" . "ไทย")
+ ("ti" . "ትግርኛ")
+ ("bo" . "བོད་ཡིག")
+ ("tk" . "Türkmen, Түркмен")
+ ("tl" . "Wikang Tagalog")
+ ("tn" . "Setswana")
+ ("to" . "faka Tonga")
+ ("tr" . "Türkçe")
+ ("ts" . "Xitsonga")
+ ("tt" . "татар теле, tatar tele")
+ ("tw" . "Twi")
+ ("ty" . "Reo Tahiti")
+ ("ug" . "ئۇيغۇرچە, Uyghurche")
+ ("uk" . "Українська")
+ ("ur" . "اردو")
+ ("uz" . "Oʻzbek, Ўзбек, أۇزبېك")
+ ("ve" . "Tshivenḓa")
+ ("vi" . "Tiếng Việt")
+ ("vo" . "Volapük")
+ ("wa" . "walon")
+ ("cy" . "Cymraeg")
+ ("wo" . "Wollof")
+ ("fy" . "Frysk")
+ ("xh" . "isiXhosa")
+ ("yi" . "ייִדיש")
+ ("yo" . "Yorùbá")
+ ("za" . "Saɯ cueŋƅ, Saw cuengh")
+ ("zu" . "isiZulu")))
+
+(defun expand-file-name* (name default-directory)
+ (expand-file-name name (concat "/" default-directory)))
+
+(org-export-define-derived-backend 'ennum-html 'html
+ :translate-alist
+ '((inner-template . ennum-html-inner-template)
+ (link . ennum-html-link))
+ :options-alist
+ '((:summary "SUMMARY" nil nil parse)
+ (:thumbnail "THUMBNAIL" nil nil t)
+ (:translation-group "TRANSLATION_GROUP" nil nil t)))
+
+(defun ennum-html-inner-template (contents info)
+ (concat
+ ;; Table of contents
+ (let ((depth (plist-get info :with-toc)))
+ (when depth (org-html-toc depth info)))
+ ;; Beginning of h-entry
+ ""
+ ;; Title
+ (format "%s
\n"
+ (org-export-data (plist-get info :title) info))
+ ;; Author and date
+ (let ((author (when (plist-get info :with-author)
+ (plist-get info :author)))
+ (date (when (plist-get info :with-date)
+ (org-export-get-date info))))
+ (when (or author date)
+ (xmlgen `(p "Published"
+ ,@(when author
+ `(" by "
+ (a :class "p-author h-card"
+ :href ,(ennum--absolute-uri "")
+ ,(car (plist-get info :author)))))
+ ,@(when date
+ `(" on "
+ (time :class "dt-published"
+ :datetime ,(org-export-get-date info "%Y-%m-%d 12:00:00")
+ ,(org-export-get-date info "%B %d, %Y"))))))))
+ ;; Interlanguage language links
+ (when-let (translations (plist-get info :translations))
+ (format "In other languages: %s
"
+ (mapconcat
+ (lambda (translation)
+ (let ((lang (ennum-post-language translation))
+ (slug (ennum-post-slug translation)))
+ (replace-regexp-in-string
+ "Tags: %s"
+ (mapconcat
+ (lambda (tag)
+ (replace-regexp-in-string
+ "%s"
+ (org-export-data (plist-get info :summary) info))
+ ;; Document contents
+ (format "%s
" contents)
+ ;; Footnotes section
+ (org-html-footnote-section info)
+ ""))
+
+(defun ennum-html-link (link desc info)
+ ;; We override the html link transcoder to handle image links
+ ;; differently. We cannot use the `:export' property of
+ ;; `org-link-parameters' since those functions cannot access the
+ ;; `info' communication channel.
+ (let ((path (org-element-property :path link)))
+ (pcase (org-element-property :type link)
+ ("image"
+ ;; Convert image links to file links, get them transcoded by
+ ;; `org-html-link' and then remove the file:// scheme from the
+ ;; URI. Finally insert the transcoded image link in a link to a
+ ;; larger image as specified by the :image-link-width setting.
+ (format "%s"
+ (expand-file-name*
+ (ennum-image-output-filename
+ path (ennum-setting :image-link-width))
+ (ennum-setting :images-directory))
+ (replace-regexp-in-string
+ (rx (group (or "src" "data")) "=\"file://") "\\1=\""
+ (org-html-link
+ (org-element-put-property
+ (org-element-put-property
+ link :path (url-encode-url
+ (expand-file-name*
+ (ennum-image-output-filename
+ path (ennum-setting :default-image-width))
+ (ennum-setting :images-directory))))
+ :type "file")
+ desc info))))
+ ;; Pass other link types to org-html-link
+ (_ (org-html-link link desc info)))))
+
+(defmacro ennum-follow (path)
+ `(ennum-with-current-directory (ennum-setting :working-directory)
+ (find-file ,path)))
+
+;; TODO: Pass title through org-export-data-with-backend or something
+;; similar in order to export org syntax in title
+(defun ennum-export-post (path desc backend)
+ (pcase backend
+ ((or 'ennum-html 'html)
+ (let ((filename (concat (expand-file-name path (ennum-setting :posts-directory))
+ ".org")))
+ (xmlgen `(a :href ,(url-encode-url
+ (expand-file-name* path (ennum-setting :posts-directory)))
+ ,(or desc (ennum-post-title (ennum-read-post filename)))))))))
+
+(defun ennum-follow-post (path)
+ (ennum-follow (expand-file-name (concat path ".org")
+ (ennum-setting :posts-directory))))
+
+(org-link-set-parameters
+ "post"
+ :export 'ennum-export-post
+ :follow 'ennum-follow-post)
+
+(defun ennum-follow-image (path)
+ (ennum-follow (expand-file-name path (ennum-setting :images-directory))))
+
+(org-link-set-parameters
+ "image" :follow 'ennum-follow-image)
+
+(defun ennum-export-thumbnail (path desc backend)
+ (pcase backend
+ ((or 'ennum-html 'html)
+ (xmlgen
+ `(img :src ,(url-encode-url
+ (expand-file-name*
+ (ennum-image-output-filename
+ path (ennum-setting :thumbnail-image-width))
+ (ennum-setting :images-directory))))))))
+
+(org-link-set-parameters
+ "thumbnail"
+ :export 'ennum-export-thumbnail
+ :follow 'ennum-follow-image)
+
+(defun ennum-export-video (path desc backend)
+ (pcase backend
+ ((or 'ennum-html 'html)
+ (let ((video-directory (ennum-setting :video-directory)))
+ (xmlgen
+ `(video :src ,(url-encode-url (expand-file-name* path video-directory))
+ :poster ,(url-encode-url
+ (expand-file-name* (ennum-video-poster path) video-directory))
+ :preload "none"
+ :controls ""))))))
+
+(org-link-set-parameters
+ "video" :export 'ennum-export-video)
+
+(defun ennum-export-static (path desc backend)
+ (pcase backend
+ ((or 'ennum-html 'html)
+ (xmlgen
+ `(a :href ,(url-encode-url
+ (expand-file-name* path (ennum-setting :static-directory)))
+ ,desc)))))
+
+(org-link-set-parameters
+ "static" :export 'ennum-export-static)
+
+(org-link-set-parameters
+ "tangle" :export 'ennum-export-static)
+
+(defun ennum-export-tag (tag desc backend)
+ (pcase backend
+ ((or 'ennum-html 'html)
+ (xmlgen
+ `(a :href ,(url-encode-url
+ (expand-file-name* tag (ennum-setting :tag-directory)))
+ ,(or desc tag))))))
+
+(org-link-set-parameters
+ "tag" :export 'ennum-export-tag)
+
+(provide 'ennum-html)
diff --git a/ennum-image.el b/ennum-image.el
new file mode 100644
index 0000000..3a8aa23
--- /dev/null
+++ b/ennum-image.el
@@ -0,0 +1,55 @@
+;; -*- lexical-binding: t -*-
+
+(require 'image)
+(require 'seq)
+
+;; Check if all necessary image types are supported
+(seq-do (lambda (image-type)
+ (unless (image-type-available-p image-type)
+ (lwarn '(ennum) :error "`%s' image type not supported" image-type)))
+ '(jpeg png svg))
+
+;; Check for existence of external image processing utilities
+(seq-do (lambda (external-program)
+ (unless (executable-find external-program)
+ (lwarn '(ennum) :error "`%s' not found" external-program)))
+ '("convert" "identify" "jpegtran" "optipng"))
+
+(defun ennum-image-resize-image (infile-path outfile-path width)
+ "A simple shell wrapper around ImageMagick's convert"
+ (ennum-image--assert-file-exists infile-path)
+ (cl-case (image-type infile-path)
+ (svg
+ (copy-file infile-path outfile-path t))
+ (otherwise
+ (call-process "convert" nil nil nil
+ infile-path "-resize" (format "%d>" width) outfile-path)))
+ outfile-path)
+
+(defun ennum-image-optimize-image (image-path)
+ "A simple shell wrapper around jpegtran and optipng"
+ (ennum-image--assert-file-exists image-path)
+ (cl-case (image-type image-path)
+ (jpeg
+ (call-process "jpegtran" nil nil nil "-optimize"
+ "-progressive" "-copy" "none"
+ "-outfile" image-path image-path))
+ (png
+ (call-process "optipng" nil nil nil image-path)))
+ image-path)
+
+(defun ennum-image-get-width (image-path)
+ (ennum-image--assert-file-exists image-path)
+ (cl-case (image-type image-path)
+ (svg 1e+INF)
+ (otherwise
+ (with-temp-buffer
+ (call-process "identify" nil t nil
+ "-format" "%w" image-path)
+ (string-to-number (buffer-string))))))
+
+(defun ennum-image--assert-file-exists (path)
+ (unless (file-exists-p path)
+ (error "File %s does not exist" path)))
+
+(provide 'ennum-image)
diff --git a/ennum.el b/ennum.el
new file mode 100644
index 0000000..69a7b5b
--- /dev/null
+++ b/ennum.el
@@ -0,0 +1,534 @@
+;; -*- lexical-binding: t -*-
+
+(require 'ennum-html)
+(require 'ennum-image)
+(require 'ox)
+(require 'seq)
+(require 'cl)
+(require 'map)
+(require 'memoize)
+(require 'simple-httpd)
+
+(defvar ennum-version "0.1.0"
+ "Ennum version string")
+
+(cl-defstruct (ennum-post (:constructor ennum-make-post)
+ (:copier nil))
+ filename slug author date language links tangle
+ summary tags thumbnail title translation-group)
+
+(cl-defstruct (ennum-operation (:constructor ennum-make-operation)
+ (:copier nil))
+ inputs outputs publish)
+
+(defun ennum-posts (posts-directory)
+ (sort (seq-map 'ennum-read-post
+ (file-expand-wildcards
+ (concat (file-name-as-directory
+ (ennum-setting :posts-directory))
+ "*.org")))
+ 'ennum-later-post-p))
+
+(defun ennum-later-post-p (post1 post2)
+ (time-less-p (ennum-post-date post2)
+ (ennum-post-date post1)))
+
+(defun ennum-read-post (filename)
+ (ennum--read-post
+ filename (file-attribute-modification-time
+ (file-attributes filename))))
+
+(defmemoize ennum--read-post (filename last-modified)
+ (ennum-with-file-contents filename
+ (let ((metadata (org-export-get-environment 'ennum-html))
+ (export (apply-partially 'org-export-with-backend 'ennum-html)))
+ (seq-do (lambda (key)
+ (unless (plist-member metadata key)
+ (user-error "Metadata %s not specified" key)))
+ ennum-mandatory-metadata)
+ (let* ((tree (org-element-parse-buffer))
+ (links (org-element-map tree 'link
+ (lambda (link)
+ (pcase link
+ (`(link ,properties . ,_)
+ (let ((link-type (org-element-property :type link)))
+ (when (member link-type (list "image" "static" "video"))
+ (cons link-type (org-element-property :path link))))))))))
+ (ennum-make-post
+ :filename filename
+ :slug (file-name-base filename)
+ :author (when-let (author (plist-get metadata :author))
+ (funcall export (first author)))
+ :date (org-timestamp-to-time (first (plist-get metadata :date)))
+ :language (plist-get metadata :language)
+ :links links
+ ;; TODO: Deal with cases when the :tangle parameter is "yes"
+ :tangle (seq-uniq
+ (org-element-map tree 'src-block
+ (lambda (src-block)
+ (pcase (org-babel-get-src-block-info nil src-block)
+ (`(,_ ,_ ,arguments ,_ ,_ ,_ ,_)
+ (let ((tangle-output-file (map-elt arguments :tangle)))
+ (pcase tangle-output-file
+ ("no" nil)
+ (_ tangle-output-file))))))))
+ :summary (when-let (summary (plist-get metadata :summary))
+ (funcall export (first summary)))
+ :tags (plist-get metadata :filetags)
+ :thumbnail (or (plist-get metadata :thumbnail)
+ (seq-some (lambda (link)
+ (pcase link
+ (`("image" . ,path) path)
+ (`("video" . ,path) (ennum-video-poster path))))
+ links))
+ :title (funcall export (first (plist-get metadata :title)))
+ :translation-group (or (plist-get metadata :translation-group)
+ (file-name-base filename)))))))
+
+(defvar ennum-mandatory-metadata
+ (list :title :date))
+
+(defmacro ennum-with-file-contents (file &rest body)
+ "Create a temporary buffer, insert contents of FILE into that
+buffer and evaluate BODY. The value returned is the value of the
+last form in BODY."
+ (declare (indent defun))
+ `(with-temp-buffer
+ (insert-file-contents ,file)
+ ,@body))
+
+(defun ennum--org-output-filename (filename)
+ (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 :translations (seq-remove (apply-partially 'equal post) posts))))))
+ 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)))
+
+(defun ennum-publish-generic (other-files-directory file)
+ (ennum-make-operation
+ :inputs (list file)
+ :outputs
+ (list (string-remove-prefix
+ (file-name-as-directory other-files-directory)
+ (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 file output-file))))))
+
+(defun ennum-video-poster (video)
+ (pcase (directory-files (ennum-setting :images-directory) nil
+ (concat (file-name-sans-extension video)
+ "\\.\\(jpg\\|png\\)$"))
+ (`(,poster . ,_) poster)
+ (`() (user-error "Poster for %s not found" video))))
+
+(defun ennum-add-tongue-suffix (filename tongue)
+ (pcase tongue
+ ("en" filename)
+ (_ (format "%s.%s%s"
+ (file-name-sans-extension filename)
+ tongue
+ (file-name-extension filename t)))))
+
+(defun ennum-index-filename (filename-prefix tongue &optional extension page-number)
+ (let ((extension (if extension (concat "." extension) "")))
+ (ennum-add-tongue-suffix
+ (if page-number
+ (format "%s-%s%s" filename-prefix page-number extension)
+ (concat filename-prefix extension))
+ tongue)))
+
+(defun ennum-publish-index (filename-prefix title posts-per-page posts)
+ (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]]\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)))))
+
+(defun ennum--absolute-uri (path)
+ (format "%s://%s/%s"
+ (ennum-setting :blog-scheme)
+ (ennum-setting :blog-domain)
+ path))
+
+(defun ennum--atom-date (date)
+ (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)
+ (let ((link (ennum--absolute-uri (ennum--org-output-filename
+ (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)))
+ ,@(when org-export-with-author
+ `((author
+ (name ,(ennum-post-author post))
+ (email ,user-mail-address))))
+ (content :type "html" :xml:lang ,(ennum-post-language post)
+ ,(ennum-with-file-contents (ennum-post-filename post)
+ (org-export-as 'ennum-html nil nil t)))
+ (link :rel "alternate" :href ,link)
+ ,@(seq-map (lambda (tag) `(category :term ,tag))
+ (ennum-post-tags post)))))
+
+(defun ennum-setting (property)
+ (pcase property
+ ((or :blog-domain :blog-license :blog-title
+ :images-directory :output-directory :posts-directory
+ :static-directory :tag-directory :video-directory
+ :working-directory)
+ (or (plist-get ennum-blog property)
+ (user-error "Property %s not defined" property)))
+ ((or :atom-feed-number-of-posts :atom-feed-file
+ :blog-scheme :default-image-width
+ :image-link-width :index-posts-per-page
+ :locale-alist :other-files-directory
+ :tag-directory :thumbnail-image-width)
+ (plist-get (org-combine-plists
+ (list :atom-feed-number-of-posts 12
+ :atom-feed-file "blog.atom"
+ :blog-scheme "https"
+ :default-image-width 640
+ :image-link-width 1024
+ :index-posts-per-page 12
+ :locale-alist '(("en" . "C"))
+ :tag-directory "tag"
+ :thumbnail-image-width 320)
+ ennum-blog)
+ property))
+ (_ (error "Unknown property %s" property))))
+
+(defun ennum-image-output-filename (image width)
+ (format "%s-%spx.%s"
+ (file-name-sans-extension image)
+ width (file-name-extension image)))
+
+(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-copy (file)
+ (ennum-make-operation
+ :inputs (list file)
+ :outputs (list file)
+ :publish (apply-partially 'ennum-copy file)))
+
+(defun newest-file (files)
+ (pcase files
+ (`(,head . ,tail)
+ (seq-reduce (lambda (file1 file2)
+ (if (file-newer-than-file-p file1 file2)
+ file1 file2))
+ tail head))))
+
+(defun ennum-mkdir-p (directory)
+ (make-directory directory t))
+
+(defun ennum-copy (source destination)
+ "Copy file or directory from SOURCE to DESTINATION. Overwrite
+if DESTINATION already exists."
+ (if (file-directory-p source)
+ (copy-directory source destination)
+ (make-directory (file-name-directory destination) t)
+ (copy-file source destination t)))
+
+(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-static-file (file)
+ (ennum-make-operation
+ :inputs (list file)
+ :outputs (list file)
+ :publish (apply-partially 'ennum-copy file)))
+
+(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)))))
+ (`("static" . ,path)
+ (list
+ (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)))))))
+
+(defmacro ennum-with-current-directory (directory &rest body)
+ "Change to DIRECTORY, evaluate BODY and restore the current
+working directory. The value returned is the value of the last
+form in BODY."
+ (declare (indent defun))
+ (let ((current-directory-symbol (make-symbol "current-directory")))
+ `(let ((,current-directory-symbol default-directory))
+ (unwind-protect (progn (cd ,directory) ,@body)
+ (cd ,current-directory-symbol)))))
+
+(defmacro ennum-with-temporary-directory (temporary-directory &rest body)
+ "Create temporary directory, evaluate BODY with the absolute
+path of that directory assigned to TEMPORARY-DIRECTORY and
+finally delete the temporary directory. The value returned is the
+value of the last form in BODY."
+ (declare (indent defun))
+ `(let ((,temporary-directory (make-temp-file "ennum" t)))
+ (chmod ,temporary-directory #o755)
+ (unwind-protect
+ (progn ,@body)
+ (delete-directory ,temporary-directory t))))
+
+(defun ennum-many-to-many-group-by (function sequence)
+ "Apply FUNCTION to each element of SEQUENCE.
+Separate the elements of SEQUENCE into an alist using the results
+as keys. Keys are compared using `equal'."
+ (seq-reduce
+ (lambda (result element)
+ (seq-do
+ (lambda (key)
+ (map-put result key
+ (cons element (map-elt result key nil 'equal))
+ 'equal))
+ (funcall function element))
+ result)
+ (seq-reverse sequence)
+ nil))
+
+(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)))
+ (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)
+ (seq-map (apply-partially 'string-remove-prefix
+ (file-name-as-directory (expand-file-name default-directory)))
+ (directory-files-recursively other-files-directory "."))))))
+ ;; Replace old output directory
+ (let ((output (ennum-setting :output-directory)))
+ (delete-directory output t)
+ (rename-file temporary-directory output t))))))
+
+;;; Server
+;;;
+;;; Test HTTP server to serve the blog locally
+
+;; TODO: Why can't simple-httpd itself handle the unhexing?
+(defun ennum-server-start ()
+ (interactive)
+ (setq httpd-root (expand-file-name (ennum-setting :output-directory)
+ (ennum-setting :working-directory)))
+ (defun httpd/ (proc uri-path query request)
+ (let* ((uri-path (httpd-unhex uri-path))
+ (file-path (httpd-gen-path uri-path)))
+ (cond
+ ;; If a HTML file other than index.html was requested, reject
+ ;; that request.
+ ((and (not (string= (file-name-nondirectory file-path) "index.html"))
+ (string= (file-name-extension file-path) "html"))
+ (httpd-error proc 404))
+ ;; If the requested file was found, serve it.
+ ((= (httpd-status file-path) 200)
+ (httpd-serve-root proc httpd-root uri-path request))
+ ;; Perhaps, this is a post or other HTML file that is being
+ ;; requested. Try serving a file with a .html extension
+ ;; appended.
+ (t (httpd-serve-root proc httpd-root (concat uri-path ".html") request)))))
+ (httpd-start)
+ (message "Ennum web server listening at http://localhost:%d" httpd-port))
+
+(defun ennum-server-stop ()
+ (interactive)
+ (httpd-stop)
+ (message "Ennum web server stopped"))
+
+(provide 'ennum)
--
cgit v1.2.3