Static Websites Management Functions

Menu

Introduction

These functions complement org-mode publishing features by adding a local preview and a deploy function using rsync:

  • website-server-start (and corresponding stop function)
  • website-deploy

Projects managed with these functions need to be declared in a association list using:

  • website-add

Project Specification Association List

Previewing and deploying are managed with emacs-lisp functions which allows to select the project to preview or publish.

The following code builds an association list with all the information required to preview and publish a website, namely:

( project-name (:port project-port :dir project-dir :deploy-dir deploy-cmd :no-sources no-sources) )

Note that since project-name is a reference to a project defined in org-publish-project-alist (and, as such, it allows us to get information about source directory, etc.)

    (defvar website-alist nil "List of websites managed with functions defined in this file")

    (defun website-add (name preview-port local-dest deploy-dest &optional no-sources)
      "Add a website specification to the list of managed websites.
    Arguments: 
    - name (name of project)
    - preview-port (local port used for previewing)
    - local-dest (directory in which html files are placed)
    - deploy-dest (directory where html files are deployed)"
      (setq website-alist
            (cons (cons name `(:port ,preview-port :dir ,local-dest :deploy-dir ,deploy-dest :no-sources ,no-sources))
                  website-alist)))

    (defun website-remove (name)
      "Delete website specification =name= from the list of managed websites.
Useful if you want to reload a configuration."
      (setq website-alist (assoc-delete-all name website-alist)))

    ;; (boundp (quote website-alist))

Building

This is syntactic sugar, since org-mode already provides these functions.

(defun website-build (project &optional force async)
  "Build a website (invoke org-publish)"
  (interactive
   (list (assoc (completing-read "Publish project: "
                                 website-alist nil t)
                website-alist)
         current-prefix-arg))
  (org-publish (car project) force async))

Previewing

Define two functions which start and stop a webserver serving website whose specification is stored in website-alist. The elnode tutorial provides the example code for starting and stopping the server.

These functions can be safely moved to the Emacs initialization file, especially when there is more than one project:

(require 'elnode nil t)

(defun website-server-start ()
  "Ask for a project name and start previewing it"
  (interactive)
  (let* ( (project-name (completing-read "Website to start previewing: " (mapcar (lambda (x) (car x)) website-alist))) )
    (website-server-start-ll project-name)))

(defalias 'website-preview 'website-server-start)

(defun website-server-start-ll (project-name)
  "Start previewing a project whose name is passed as argument"
  (let ( (port (plist-get (cdr (assoc project-name website-alist)) :port))
         (dir (plist-get (cdr (assoc project-name website-alist)) :dir)) )
    (progn
      (elnode-start 
       (elnode-webserver-handler-maker dir)
       :port port 
       :host "localhost")
      (message "Started serving directory %s on port %s" dir port))))

(defun website-server-stop ()
  "Stop previewing a project"
  (interactive)
  (let* ( (project-name (completing-read "Website to stop previewing: " (mapcar (lambda (x) (car x)) website-alist))) )
    (website-server-stop-ll project-name)))

(defun website-server-stop-ll (project-name)
  "Stop previewing a project passed as argument"
  (let ( (port (plist-get (cdr (assoc project-name website-alist)) :port)) )
    (elnode-stop port)))

Deploying

Define a function which invokes the deploy command for the project. All project specifications are stored in website-alist.

The code for deploying has been taken from: https://stackoverflow.com/questions/1453956/which-shell-command-in-emacs-lisp. A more structured solution probably uses call-process, but I did not want to have to manage shell expansions.

This function can be safely moved to the Emacs initialization file, especially when there is more than one project:

(defun website-deploy ()
  (interactive)
  (let* ( (webserver (completing-read "Website to deploy: " (mapcar (lambda (x) (car x)) website-alist)))
          (local-dir (plist-get (cdr (assoc webserver website-alist)) :dir))
          (deploy-dir (plist-get (cdr (assoc webserver website-alist)) :deploy-dir))
          (exclude-sources (plist-get (cdr (assoc webserver website-alist)) :no-sources))
          (buffer (get-buffer-create (concat "*rsync-buffer for " webserver "*"))) )
    (if deploy-dir
        (progn
          (display-buffer buffer)
          (start-process "process-name"
                         buffer
                         "/usr/bin/rsync"
                         "-crvz"
                         "--exclude=*~"
                         "--exclude=.git"
                         "--exclude=_*"
                         (if exclude-sources "--exclude=*.org" "--include=*.org")
                         "--delete"
                         "--delete-excluded"
                         (file-name-as-directory local-dir) ; add a final slash (otherwise local-dir might be created on the server instead)
                         deploy-dir))
          (message "No deployment command specified for %s" webserver))))

What do we provide?

(provide 'website)