My Emacs Blog

Table of Contents

What Is This

I fell into the Udemy trap: buying courses and not completing them. Or arguably worse: completing a course and jumping straight into the next one. I found myself in tutorial hell, going through certain motions expecting a job to appear out of thin air. One of those motions was maintaining a blog to "share my learning". Looking back at some of those blogs, I found myself paralyzed with second hand embarrassment. So this is a redo pretending my previous abomination of a blog never existed.

The Previous Blog

The previous blog was powered by WordPress with Bluehost taking care of the hosting. I used Next.js to create a front end that was much uglier than any of the WordPress templates I could have chosen. An important lesson I learned looking back at my "minimalist" front end: reinventing the wheel isn't always the best learning method. Sometimes, gluing together things that already work can teach you just as well. The point is, I didn't need to use Next.js. At least, not in the way that I used it. If I had taken the time to style a template that presented better than the already available WordPress templates, then Next.js would have made sense. But that's not what I did. I just used Next.js for the sake of using it.

The content of the blog was robotic. I was just highlighting basic things I learned. The information I presented could easily be found elsewhere and with better presentation. Nothing that I posted offered any unique insight. It was just a hollow attempt to show that I was being productive. Whenever I prepared to write a blog, it felt forced. I had blog posts on HTML, CSS, and React. When W3Schools, MDN Docs, CSS Tricks, and React docs exit, there's no reason to clutter the internet with the same information.

So, What is This?

This is where I hope to write about the things I've built, if I feel like it. Writing is something I enjoy, so when it occurs naturally and makes sense, it may live here. I've also been wanting to maximize the utility of my raspberry pi and dig deeper into some shiny objects I've been chasing. All of those ideas have converged into the creation of this blog.

Raspberry Pi

My pi had been relegated as my hub for git. I used the pi with git to organize and version control my notes and trading journal. We can have an argument here asking, "Why not just GitHub?" Because I didn't want all my random thoughts on GitHub. So I created my PiGitHub. Since it had performed admirably, it proved ready for the added responsibility of serving my blog posts. Instead of paying Bluehost, I could use my pi and self host for free. The trade off being the availability of my blog would be dependent on my internet rather than on Bluehost. For my personal blog, this appeared to be a fair trade.

My next step invloved chasing some more shiny objects: HTMX and Go. I had this planned out for a while. Because I already had my WordPress blog, I set this plan aside. As my Bluehost subscription was expiring, the Pi/HTMX/Go blog was on the verge of materializing. But just as I needlessly used Next.js with WordPress, I abandoned my note taking tool Obsidian. I didn't know it at the time, but that decision would leave HTMX and Go sidelined.

Emacs

I have the flaw of repeating the same mistakes disguising them as different mistakes. I learned Emacs to take over as my note taking tool. Why not just stick with Obsidian? Because procrastination can disguise itself as learning. Which is another lesson I've learned: chasing shiny objects is a form of procrastination. So whether I'm chasing a shiny object or trying to solve a problem that's already been solved, what I'm really doing is procrastinating. On the surface, chasing Emacs was no different than using Next.js without a good reason. But I think present me and future me will be using Emacs for a very long time, so maybe the effort was worthwhile. Having said all that, I didn't just bring up Emacs to say "I use Emacs by the way." The reason I bring up Emacs is because this redo blog is powered by Emacs and org-publish.

Org-Publish

I learned Emacs the same way I learned Neovim (I use Neovim by the way). I learned them both using their built in tutorials. I avoided plugins for the first three months. I still use Emacs without any plugins, but I've since evolved my Neovim experience into a lazy.nvim setup with around 20 plugins. I "vibe coded" an Emacs trading journal using Neovim, and learned a little elisp along the way. While looking into other things I could do with Emacs, I discovered org mode has a publishing management system: org-publish. All I have to do is continue writing and let Emacs convert my org files into HTML.

Emacs Config

Working on my trading journal, I learned I can make Emacs bend to my will. Could I have done the same with Obsidian? Maybe. But I've converted completely to Emacs, and I don't see myself taking notes any other way. This workflow also naturally leans into the process of writing blogs. All I had to do was follow the pattern of my trading journal and create a config file for the blog. Using org-publish docs and llm chatbots, the initial version produced HTML and CSS I was happy with:

(setq org-publish-project-alist
      `(("blog-org"
         :base-directory ,my/blog-org-dir
         :base-extension "org"
         :publishing-directory ,my/blog-public-dir
         :publishing-function org-html-publish-to-html
         :with-tags t
         :recursive t

         :auto-sitemap t
         :sitemap-filename "index.org"
         :sitemap-title "Blog Posts"
         :sitemap-sort-files anti-chronologically

         :section-numbers nil
         :with-toc nil
         :html-head "<link rel=\"stylesheet\" 
                href=\"/css/style.css\" 
                type=\"text/css\" />"
         :html-preamble nil
         :html-postamble nil)

        ("blog-static"
         :base-directory ,my/blog-static-dir
         :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf"
         :publishing-directory ,my/blog-public-dir
         :recursive t
         :publishing-function org-publish-attachment)

        ("blog" :components ("blog-org" "blog-static"))))

To learn more, the use of C-h v <name of variable> and C-h f <name of function> can explain each property. For example, C-h v org-publish-project-alist RET provides a ton of information starting with:

org-publish-project-alist is a variable defined in ‘ox-publish.el’.

Its value is shown below.

Association list to control publishing behavior.

Each element of the alist is a publishing project. The car of each element is a string, uniquely identifying the project. The cdr of each element is in one of the following forms: …

The Workflow

Edit in Notes

Along with the config, I have functions facilitating my workflow. The workflow involves editing the rough draft which lives in my version controlled notes. When I want to do something, I call the corresponding function. The my/copy-from-notes() function copies the rough draft from my notes to the blog directory:

(defun my/copy-from-notes()
  (interactive)
  (let* ((src (buffer-file-name))
        (dest (expand-file-name (file-name-nondirectory src) my/blog-org-dir)))
    (unless src
      (user-error "Current buffer is not visiting a file"))
    (unless (file-directory-p my/blog-org-dir)
      (make-directory my/blog-org-dir t))
    (copy-file src dest t)
    (message "Copied to blog: %s" (file-name-nondirectory src))))

Paths and Publish

Next, I wanted to be able to preview what the blog would look like as I edited the blog. I wanted to be able to do this both in my notes directory and the blog directory. The first set of functions determine the proper paths and publish the current blog:

(defun my/blog-org-path ()
  (let ((filename (file-name-nondirectory (buffer-file-name))))
    (expand-file-name filename my/blog-org-dir)))

(defun my/blog-html-path ()
  (let* ((filename (file-name-nondirectory (buffer-file-name)))
         (html-name (concat (file-name-sans-extension filename) ".html")))
    (expand-file-name html-name my/blog-public-dir)))

(defun my/open-blog-copy ()
  (interactive)
  (find-file (my/blog-org-path)))

(defun my/publish-current-blog-file ()
  (interactive)
  (org-publish-current-file t))

Blog Server

I learned that if I ever planned to use custom CSS, then I would only be able to preview it by running a local server. These functions start and stop the server:

(defun my/start-blog-server()
  (unless (and my/blog-server-process
               (process-live-p my/blog-server-process))
    (setq my/blog-server-process
          (start-process "blog-server" "*blog-server*"
                         "python3" "-m" "http.server" "8080"
                         "--directory" my/blog-public-dir))
    (sleep-for 1)
    (message "Blog server started")))

(defun my/stop-blog-server()
  (interactive)
  (when (and my/blog-server-process
             (process-live-p my/blog-server-process))
    (delete-process my/blog-server-process)
    (setq my/blog-server-process nil)
    (message "Blog server stopped")))

Preview

Gluing it all together, I can preview the blog from my notes or from the blog directory

(defun my/open-blog-html ()
  (interactive)
  (let* ((html-path (my/blog-html-path))
        (html-url (concat "http://localhost:8080/"
                          (file-name-nondirectory html-path))))
    (unless (file-exists-p html-path)
      (user-error "Published HTML does not exist. Publish first."))
    (my/start-blog-server)
    (message "Opening: %s" html-url)
    (browse-url html-url)))

(defun my/preview-post-from-notes ()
  (interactive)
  (my/copy-from-notes)
  (my/open-blog-copy)
  (my/publish-current-blog-file)
  (my/open-blog-html))

(defun my/preview-post-from-source ()
  (interactive)
  (my/publish-current-blog-file)
  (my/open-blog-html))

(defun my/in-blog-source-p ()
  (and (buffer-file-name)
       (file-in-directory-p
        (buffer-file-name)
        my/blog-org-dir)))

(defun my/preview-post ()
  (interactive)
  (if (my/in-blog-source-p)
      (my/preview-post-from-source)
    (my/preview-post-from-notes)))

Flaw in the Workflow

The flaw in my workflow is that I shouldn't edit files in the blog org directory. This is because it will be out of sync with the blog in my version controlled notes. If for some reason I forget that I made changes in the blog directory and start making changes in the notes, the copy function will always overwrite the blog directory. So I have to remember to always edit the blog in my notes.

This becomes an issue when I preview a blog. The displayed buffer will switch to the blog directory, and I've already caught myself editing the "wrong" file forgetting to switch to the notes buffer. There is much room for improvement.

Grappling with CSS

Learning web development taught me that I hate CSS. The only way I can articulate the hatred is by explaining I enjoy trying to implement logic, running into a wall, and eventually crashing through the wall. The logic can be some kind of frontend user interaction or some type of backend logic. I don't experience this same form of satisfaction when I finally center a div. The level headed approach would involve becoming more intimate with CSS.

I choose the lazy approach. Using solutions like tailwind or bootstrap still involves understanding CSS. Because this blog isn't trying to win any beauty contests and prefers speed, I went with Pico.css. It's more bloated than Water.css or Simple.css, but it offers robust customization if I want to go down that path in the future. At the time of writing, this blog uses the classeless approach. It provides more styling than org-publish gives out of the box.

nginx

While the initial plan was to learn and use Go for my server, my llm companions convinced me that nginx was worth learning and had great synergy with the Emacs blog workflow. Trying to adjust to my new way of thinking: glue together things that already work, nginx was the perfect choice. Also, I have many projects planned where it will be Go time. But for this project, all I had to do was install nginx on the raspberry pi and configure /etc/nginx/sites-avaiable/default.

Going Forward

I'm not sure there will be any more blogs. This could be it. For now, hello world.