Home
Projects
Sitemap
About

Maintaining a website with org-mode

Actually, putting up a website with org-mode is a bit of work. However, once setup, you can forget about fiddling with the setup (or maybe not, whatever floats your boat). This site is also made from org-mode, and here are some tricks to automate some common actions. Note: This is based on the excellent howto from here.

Automatically inserting a template

Using skeletons and some clever elisp, it is possible to automatically generate a header line like this

   #+TITLE: Website with org-mode
   #+SETUPFILE: ../../export-config-1.org
   #+INCLUDE: ../../export-config-1.org

The setupfile and include directives are used as in the aforementioned tutorial. INCLUDE is used to include the header box you see at the top.

And here's the code:


(setq seeger-website-base-dir (expand-file-name "~/pim/web/src/")) ;; Contains the base directory where the website source is kept.

(defvar seeger-website-base-dir "/home/jeeger/pim/web/src/" "Base directory of website")
(defvar seeger-website-config-name "export-config-")

(defun repeat-string (str times)
  (with-output-to-string
    (dotimes (i times)
      (princ str))))

(defun seeger-get-nesting-depth (filename basename)
  (with-temp-buffer
    (insert (file-relative-name filename basename))
    (goto-line 0)
    (count-matches "/")))

   
(defun seeger-make-website-conf-path (filename)
  (let ((nesting-level (seeger-get-nesting-depth filename seeger-website-base-dir)))
    (concat (repeat-string "../" (+ nesting-level 1)) seeger-website-config-name (int-to-string nesting-level) ".org")))

(define-skeleton org-website-skeleton "" "Enter Title: "
  "#+TITLE: " str ?\n
  "#+SETUPFILE: " 
  (seeger-make-website-conf-path (buffer-file-name)) ?\n
  "#+INCLUDE: "
  (seeger-make-website-conf-path (buffer-file-name)) ?\n ?\n
  "* " _ )

(defun seeger-org-website-insert-skeleton-maybe ()
  (if (and (buffer-file-name)
           (not (file-remote-p (buffer-file-name))) 
           (string-match (concat "^" seeger-website-base-dir) 
                         (expand-file-name (buffer-file-name))))
      (org-website-skeleton)))

(add-hook 'find-file-hook 'auto-insert)
(setq auto-insert-query nil)
(setq auto-insert-alist '((org-mode . seeger-org-website-insert-skeleton-maybe))) ;; To autoinsert the skeleton without asking

That's certainly a handful. What it does is test if you are editing a buffer below the website directory , and if yes, insert the correct headers. This assumes that the setup files are exactly one level below the source directory, like this, for example:

     /home/jeeger/pim/web:
  insgesamt 24
  drwxr-xr-x 3 jeeger users 4096 21. Feb 13:17 .
  drwxr-xr-x 8 jeeger users 4096 24. Jan 16:17 ..
  -rw-r--r-- 1 jeeger users  484 10. Feb 14:27 export-config-0.org
  -rw-r--r-- 1 jeeger users  473 10. Feb 14:35 export-config-1.org
  -rw-r--r-- 1 jeeger users  422  9. Feb 21:10 export-config-2.org
  drwxr-xr-x 4 jeeger users 4096 23. Feb 13:17 src

  /home/jeeger/pim/web/src:
  insgesamt 52
  drwxr-xr-x 4 jeeger users 4096 23. Feb 13:17 .
  drwxr-xr-x 3 jeeger users 4096 21. Feb 13:17 ..
  -rw-r--r-- 1 jeeger users  663 10. Feb 15:27 about.org
  drwxr-xr-x 2 jeeger users 4096 23. Feb 13:16 css
  -rw-r--r-- 1 jeeger users  179 10. Feb 14:29 index.org
  -rw-r--r-- 1 jeeger users  511 24. Jan 17:27 index.org~
  -rw-r--r-- 1 jeeger users 9055 10. Feb 12:43 me.jpg
  drwxr-xr-x 2 jeeger users 4096 23. Feb 13:18 projects
  -rw-r--r-- 1 jeeger users  154 23. Feb 13:17 sitemap.org
  -rw-r--r-- 1 jeeger users   82 25. Jan 11:59 sitemap.org~
  -rw-r--r-- 1 jeeger users  442 25. Jan 11:36 test.org~

Automatically publishing the website

This assumes that you use git for version control. I do, and so the correct method to update the site was a post-update git hook. It looks like this:

   rm -rf ~/temp/*
   git archive HEAD web/ | tar x --strip-components 1 -C ~/temp/
   emacs --batch -l ~/.emacs.d/publish-website

This just extracts the freshest revision of the `web/` directory and extracts it to temp. --strip-components is needed because otherwise, the files would be saved in ~/tmp/web/src/ etc. Then we run this little elisp:

   ;; org settings copied from the original file
   ;; with paths changed to publish from ~/temp/src/ to /var/www/etc...
   (org-publish (assoc "website" org-publish-project-alist))

And bing! we have it. When pushing, the source is automatically published to the web directory.

Instantly testing your site

So now it's on the server. However, for testing the site, you don't want to commit, git-push and browse. You want instant feedback. Can do.

Simply use org-publish-project-alist to publish to a local scratch directory and look at it with a local browser. To do that even more quickly, I use this snippet:

(defun seeger-org-website-maybe-bind-f5 ()
  (if (and buffer-file-name
           (string-match (concat "^" seeger-website-base-dir)
                         (expand-file-name (buffer-file-name))))
      (local-set-key [f5] (lambda () (org-publish-all t)))))

That way, I can simply press F5 whenever editing a website file, and the website gets published to a local folder so I can look at it.

Making font-locking work on the remote server

In the writing of this page, I had the problem that font-locking with htmlize (#+begin_src emacs-lisp and similar) worked only in interactive mode. But Carsten has (again) thought of everything, and after some mailing list scrounging, I found this:

Generate a css file (I call it font-lock.css) with org-export-htmlize-generate-css, put it into your css directory. Then set org-export-htmlize-output-type to css. Also, add an additional #+STYLE to your configuration files so the CSS file gets included. This should generate the same font locking on the server as in the interactive emacs instance.

Conclusion

I hope these tips help anyone trying to get org-mode to the web. More tricks will be added as I find them. For feedback, send me a mail to here.

Update

Since the include file template is not immediately findable in the worg tutorial, I'll just add it here:


   #+ AUTHOR: <AUTHOR>
   #+EMAIL: <MAIL>
   #+LANGUAGE: de
   #+STYLE: <link rel="stylesheet" type="text/css" href="css/main.css"/>
   #+STYLE: <link rel="stylesheet" type="text/css" href="css/font-lock.css"/>
   #+STARTUP: hidestars

   #+BEGIN_HTML
<div class="header">
     <div class="headerbox"><a href="index.html">Home</a></div>
     <div class="headerbox"><a href="projects.html">Projects</a></div>
     <div class="headerbox"><a href="sitemap.html">Sitemap</a></div>
     <div class="headerbox"><a href="about.html">About</a></div>
</div>
   #+END_HTML

And, because including org directives inside an org mode file is not really possible, you need to remove the spaces before org config directives. And remember to create different config files for the different nesting levels by adding the correct number of dots.

Autor: Jan Seeger <jan.seeger@thenybble.de>

Datum: 2010-09-08 12:49:24 CEST

HTML generated by org-mode 6.19b in emacs 22