grm blog. Work is copyrighted unless otherwise stated.
2020-11-16 Mon
^

Blogging with emacs and org-mode

This blog is being managed entirely in Emacs. In this post I will explain how it works and try to keep it up to date with any future changes.

Needs

I want a blog for a few reasons:

  • "tutorials" I can refference later
  • and also share with others
  • semi-permanent stuff I want online
  • street cred

Specifications

  • posts written in org-mode
    • converted straight to html to remove complexity
  • sidebar with utilities and the latest posts
    • as well as link to my git
  • support pages for permanent stuff
    • like a resume
  • search function
    • posts are in plaintext, I want to grep them online
  • index page with all the posts shortened
    • with a read more... url
  • no js!

Org publish

The main emacs facility being used is org-publish which automates the html generation from the source org-mode files. It also allows for extra customisation like injecting my own php to accomodate the other specifications.

Everything is being exported with links between the files and other content (video/images) automatically being taken care of in a local folder which emacs then proceeds to rsync to my server.

Directory structure

pages
org-mode files converted to static pages (non searchable)
res
css, extra html & php, e.t.c.
media
images, videos e.t.c.
blog
blog posts in org-mode files
www
exported files

Sidebar

The sidebar is one of the injected code snippets I mentioned before. It contains links to tools like the search function, pages of this blog, as well as links to all the posts. It looks for blog posts in the server's file structure and prints them in a <ul> on the sidebar.

Search

Search is key if I wanted the blog to act as a refference for various SOPs or tips. I am leveraging the fact that I also publish the source files for each post, which is plain text. Thus a simple pattern matching (much like grep -i) is enough for my needs. Againg it's a dead simple php function living in it's own page.

Index

The magic happens here:

(defun blog/get-preview (filename)
  "Returns a list: '(<needs-more> <preview-string>) where
<needs-more> is t or nil, indicating whether a \"Read More...\"
link is needed."
  (with-temp-buffer
    (insert-file-contents (concat blog/project-dir "blog/" filename))
    (goto-char (point-min))
    (let ((content-start (or
                          ;; Look for the first non-keyword line
                          (and (re-search-forward "^[^#]" nil t)
                               (match-beginning 0))
                          ;; Failing that, assume we're malformed and
                          ;; have no content
                          (buffer-size)))
          (marker (or
                   (and (re-search-forward "^# read_more$" nil t)
                        (match-beginning 0))
                   (buffer-size))))
      ;; Return a pair of '(needs-more preview-string)
      (list (not (= marker (buffer-size)))
            (buffer-substring content-start marker)))))

(defun blog/sitemap (title list)
  "Generate the sitemap (Blog Main Page)"
  (concat "#+TITLE: " title "\n" "--------\n"
          (string-join (mapcar #'car (cdr list)) "\n\n")))

(defun blog/sitemap-entry (entry style project)
  "Sitemap (Blog Main Page) Entry Formatter"
  (when (not (directory-name-p entry))
    (format (string-join
             '("* [[file:%s][%s]]\n"
               "#+BEGIN_published\n"
               "%s\n"
               "#+END_published\n\n"
               "%s\n"
               "--------\n"))
            entry
            (org-publish-find-title entry project)
            (format-time-string "%A, %B %_d %Y at %l:%M %p %Z" (org-publish-find-date entry project))
            (let* ((preview (blog/get-preview entry))
                   (needs-more (car preview))
                   (preview-text (cadr preview)))
              (if needs-more
                  (format
                   (concat
                    "%s\n\n"
                    "#+BEGIN_morelink\n"
                    "[[file:%s][Read More...]]\n"
                    "#+END_morelink\n")
                   preview-text entry)
                (format "%s" preview-text))))))

TODOs

RSS

yeah…

modify random post

Add a page under res/ that redirects to a random post and add a link for this page instead of randomly adding a link each time the nav bar loads.

Changelog

1.0

Main blog skeleton

1.1

Adds search function

2.0 (latest)

Loads of changes to the point where the code listed above is no longer a reflection of what's been served, but is still a valid starting point.

new stuff:

  • tags are now visible in index
  • search now handles spaces as .* which is also used in the tags to form a search string returning posts with the tag
  • the sidebar posts are now sorted by date and the year is listed
  • a table of contents has been added for every page that needs it
  • an audio type media export option has been added (hint hint!)