#+TITLE: Blogging with org-mode #+SUBTITLE: Meta post about this blog #+AUTHOR: Yann Esposito #+EMAIL: yann@esposito.host #+DATE: [2019-07-27] #+KEYWORDS: programming, blog, org-mode, meta #+OPTIONS: auto-id:t #+begin_quote /tl;dr/: [[#full-solution][Go to the full solution]] for the impatients. #+end_quote As the first article of my new blogging system, let's have a meta blog post. Once in a while, I like to change how I blog. A long time ago, I used PHP for my first website using my personal space at my university. And as of today, a clond of my university website still exists: http://yann.esposito.free.fr It contains all details about my old research activities and teaching resources for my students. Then I used [[http://nanoc.ws][nanoc]], a ruby static website generator. Then I switched to [[https://jaspervdj.be/hakyll/][hakyll]] because I wanted to switch to a Haskell's written tool. This time I'll try to use [[http://orgmode.org][org-mode]] directly with [[https://orgmode.org/worg/org-tutorials/org-publish-html-tutorial.html][org-publish]]. I used vim a lot, but recently I switched to emacs. The main reason were: 1. You use LISP as plugin language while for Vim it is vimscript which is a quite terrible language. 2. Many comment on the Internet about people telling that [[https://github.com/emacs-evil/evil][evil]] (vim keybdings in emacs) was really really good. And I can confirm it is. 3. The [[http://spacemacs.org][spacemacs]] project made it easier to handle the emacs configuratin complexity for a newbie. And by doing the switch, I discovered a lot of great emacs packages. Now I really do not regret the switch. In particular, [[http://orgmode.org][org-mode]] has been a game changer. I used to write markdown before. But now I use the org format. It is superior for my usage(s). * How? :PROPERTIES: :CUSTOM_ID: how :END: You can easily follow the org-mode doc to make your own website. But I have added a few niceties. First, the code to export is provided in a local file. I've got a system to auto-load elisp file when entering in a new project. It is quite safe as it uses PGP and check if I trust the person who encrypted the file. See [[file:project-el/index.org][my other post about it]]. ** Configuration variables :PROPERTIES: :CUSTOM_ID: configuration-variables :END: #+begin_src elisp ;; CONFIGURATION VARIABLES (setq domainname "https://her.esy.fun") (setq base-dir (concat (projectile-project-root) "src")) (setq publish-dir (concat (projectile-project-root) "_site")) (setq assets-dir (concat base-dir "/")) (setq publish-assets-dir (concat publish-dir "/")) (setq rss-dir base-dir) (setq rss-title "Subscribe to articles") (setq publish-rss-dir publish-dir) (setq css-path "/css/minimalist.css") (setq author-name "Yann Esposito") (setq author-email "yann@esposito.host") #+end_src * Full Solution :PROPERTIES: :CUSTOM_ID: full-solution :END: #+begin_src elisp (require 'org) (require 'ox-publish) (require 'ox-html) (require 'org-element) (require 'ox-rss) (setq org-link-file-path-type 'relative) (defun org-blog-prepare (project-plist) "With help from `https://github.com/howardabrams/dot-files'. Touch `index.org' to rebuilt it. Argument `PROJECT-PLIST' contains information about the current project." (let* ((base-directory (plist-get project-plist :base-directory)) (buffer (find-file-noselect (expand-file-name "index.org" base-directory) t))) (with-current-buffer buffer (set-buffer-modified-p t) (save-buffer 0)))) (defvar org-blog-head (concat "" "" "" "")) (defun menu (lst) "Blog menu" (concat "" (mapconcat 'identity (append '("Home" "Posts" "About") lst) " | ") "")) (defun str-time-to-year-float (date-str) (/ (float-time (apply 'encode-time (mapcar (lambda (x) (if (null x) 0 x)) (parse-time-string date-str)))) (* 365.25 24 60 60))) (defvar blog-creation-date "2019-07-01") (defun y-date (date-str) "Number of year since the begining of this blog" (let ((y (- (str-time-to-year-float date-str) (str-time-to-year-float blog-creation-date)))) (format "∆t=%.2f" y))) (defun org-blog-preamble (info) "Pre-amble for whole blog." (concat "
" (menu '("↓ bottom ↓")) "

" (format "%s" (car (plist-get info :title))) "

" (when-let ((date (get-from-info info :date))) (format "%s" date)) (when-let ((subtitle (car (plist-get info :subtitle)))) (format "

%s

" subtitle)) "
")) (defun get-from-info (info k) (let ((i (car (plist-get info k)))) (when (and i (stringp i)) i))) (defun rand-obfs (c) (let ((r (% (random) 20))) (cond ;; ((eq 0 r) (format "%c" c)) ((<= 0 r 10) (format "&#%d;" c)) (t (format "&#x%X;" c))))) (defun obfuscate-html (txt) (apply 'concat (mapcar 'rand-obfs txt))) (defun org-blog-postamble (info) "Post-amble for whole blog." (concat "
" ;; TODO install a comment system ;; (let ((url (format "%s%s" domainname (replace-regexp-in-string base-dir "" (plist-get info :input-file))))) ;; (format "comment" ;; (url-hexify-string url))) "" (menu '("↑ Top ↑")) "
")) (defun org-blog-sitemap-format-entry (entry _style project) "Return string for each ENTRY in PROJECT." (when (s-starts-with-p "posts/" entry) (format "@@html:@@ %s: @@html:@@ [[file:%s][%s]] @@html:@@" (format-time-string "%Y-%m-%d" (org-publish-find-date entry project)) entry (org-publish-find-title entry project)))) (defun org-blog-sitemap-function (title list) "Return sitemap using TITLE and LIST returned by `org-blog-sitemap-format-entry'." (concat "#+TITLE: " title "\n" "#+AUTHOR: " author-name "\n" "#+EMAIL: " author-email "\n" "\n#+begin_archive\n" (mapconcat (lambda (li) (format "@@html:
  • @@ %s @@html:
  • @@" (car li))) (seq-filter #'car (cdr list)) "\n") "\n#+end_archive\n")) (defun org-blog-publish-to-html (plist filename pub-dir) "Same as `org-html-publish-to-html' but modifies html before finishing." (let ((file-path (org-html-publish-to-html plist filename pub-dir))) (with-current-buffer (find-file-noselect file-path) (goto-char (point-min)) (search-forward "") (insert (mapconcat 'identity '("" "" "
    " "
    " "" " / " "" "
    " "
    " "
    ") "\n")) (goto-char (point-max)) (search-backward "") (insert "\n
    \n") (save-buffer) (kill-buffer)) file-path)) (setq org-publish-project-alist `(("orgfiles" :base-directory ,base-dir :exclude ".*drafts/.*" :base-extension "org" :publishing-directory ,publish-dir :recursive t :preparation-function org-blog-prepare :publishing-function org-blog-publish-to-html :with-toc nil :with-title nil :with-date t :section-numbers nil :html-doctype "html5" :html-html5-fancy t :html-head-include-default-style nil :html-head-include-scripts nil :htmlized-source t :html-head-extra ,org-blog-head :html-preamble org-blog-preamble :html-postamble org-blog-postamble :auto-sitemap t :sitemap-filename "archive.org" :sitemap-title "Blog Posts" :sitemap-style list :sitemap-sort-files anti-chronologically :sitemap-format-entry org-blog-sitemap-format-entry :sitemap-function org-blog-sitemap-function) ("assets" :base-directory ,assets-dir :base-extension ".*" :exclude ".*\.org$" :publishing-directory ,publish-assets-dir :publishing-function org-publish-attachment :recursive t) ("rss" :base-directory ,rss-dir :base-extension "org" :html-link-home ,domainname :html-link-use-abs-url t :rss-extension "xml" :publishing-directory ,publish-rss-dir :publishing-function (org-rss-publish-to-rss) :exclude ".*" :include ("archive.org") :section-numbers nil :table-of-contents nil) ("blog" :components ("orgfiles" "assets" "rss")))) ;; add target=_blank and rel="noopener noreferrer" to all links by default (defun my-org-export-add-target-blank-to-http-links (text backend info) "Add target=\"_blank\" to external links." (when (and (org-export-derived-backend-p backend 'html) (string-match "href=\"http[^\"]+" text) (not (string-match "target=\"" text)) (not (string-match (concat "href=\"" domainname "[^\"]*") text))) (string-match "