joegame

rss

bluesky

Bootstrapping the joegame blog

Some things can just be simple. I don't know about you, but my eyes glaze over when I peruse through this. The current blog is built like this

(require 'org)

(setq
 org-confirm-babel-evaluate nil
 org-publish-use-timestamps-flag nil
 org-publish-timestamp-directory "./.org-timestamps/")

;; rss feed
(add-to-list 'load-path (expand-file-name "./org-publish-rss/"))
(require 'org-publish-rss)
(defun joegame-site-filter-from-rss (filename)
  (and
   (not (bb/org-is-draft (bb/org-get-keywords filename)))
   (not (equal (file-name-nondirectory filename) "index.org"))))



;; shared utilities
(defun bb/org-get-keywords (blog-file)
  (with-temp-buffer
    (insert-file blog-file)
    (append (list (cons "PATH" (format "./%s" (file-relative-name blog-file))))
            (org-element-map (org-element-parse-buffer)
                'keyword
              (lambda (it)
                (cons
                 (org-element-property :key it)
                 (org-element-property :value it)))))))

(defun bb/org-is-draft (keywords)
  (equal "t" (alist-get "DRAFT" keywords "t" nil #'equal)))
(defun bb/org-keyword-value (keywords key)
  (alist-get key keywords nil nil #'equal))


;; projects
(setq
 org-publish-project-alist '(("orgfiles"
                              :base-directory "./src"
                              :recursive t
                              :publishing-directory "./public"
                              :html-head "<link rel=\"stylesheet\" href=\"/style.css\" type=\"text/css\"/>"
                              :html-postamble t
                              :auto-sitemap nil
                              :html-postamble-format (( "en" "<footer> <p>&copy; 2026 Your Company. All rights reserved.</p></footer>"))
                              :html-preamble-format (("en" "<header> <h2 style=\"flex-grow:1 \"><a href=\"/\">joegame</a></h2> <img class=\"gif-image\" src=\"/images/kangaroo_east.gif\"</img> <h4><a href=\"/rss.xml\">rss</a></h4></header>"))
                              :html-home/up-format ""
                              :base-extension "org"
                              :headline-levels 3
                              :section-numbers nil
                              :with-toc nil
                              :with-todo-keywords nil
                              :org-html-html5-fancy t
                              :org-html-head-include-default-style nil
                              :auto-rss t
                              :rss-title "joegame.xyz Posts"
                              :rss-description "Writeups from Joegame studios."
                              :rss-with-content all
                              :html-link-home "joegame.xyz"
                              :completion-function org-publish-rss
                              :rss-filter-function joegame-site-filter-from-rss
                              )

                             ("static"
                              :base-directory "./src"
                              :recursive t
                              :base-extension "jpg\\|gif\\|png\\|css\\|svg\\|webp\\|html"
                              :include ("rss.xml")
                              :publishing-directory "./public"
                              :publishing-function org-publish-attachment)

                             ("site" :components ("orgfiles" "static"))))

Most of this is just using org-mode's publish feature, along with our one external dependency in org-publish-rss, and finally a few extra things we will get to. This is saved in a site.el file at the root of the website repo, and we can build the site with a makefile rule:

build:
        emacs -q --script ./site.el --eval '(org-publish "site")'

So far, the extra stuff we are doing is just related to generating a list of posts. Also in the basedir of the site is a lib.org file, acting as our own personal library of babel. It contains helper functions like this:

* gen post list
#+name: gen-post-list
#+begin_src elisp :results output raw :exports results
  (defun joegame-site-gen-blog-entries ()
    (dolist (to-pub
             (sort (cl-remove-if #'bb/org-is-draft
                                 (cl-map 'list #'bb/org-get-keywords
                                         (directory-files "./blog/" t "\\.*.org")))
                   (lambda (a b)
                     (string> (or (bb/org-keyword-value a "DATE") "")
                              (or (bb/org-keyword-value b "DATE") "")))))
      (princ (format "- ,[,[%s][%s]] %s\n"
                     (bb/org-keyword-value to-pub "PATH")
                     (bb/org-keyword-value to-pub "TITLE")
                     (bb/org-keyword-value to-pub "DATE")))))

    (joegame-site-gen-blog-entries)

#+end_src


Note especially how we are generating raw org markdown (:results output raw), which is then turned into html by the actual publishing process.

We use it like so (on the homepage):

#+call: ../lib.org:gen-post-list()

Another helper function looks like this, for images

#+name: image
#+begin_src elisp :results output raw :exports results :var image="slgj-day1-end.png" caption="A caption"
  (princ (format "#+CAPTION: %s\n" caption))
  (princ (format "#+ATTR_HTML: :alt %s :class figure\n" caption))
  (princ (format "file:%s\n" image))
#+end_src

Which we can call like so

#+call: ../../lib.org:image("../images/slgj-day1-end.png", "The game at the end of day 1. The source cursor is blue and the target one is green" )