Development notes

Some miscellaneous notes I took while working on jorge.

Jekyll and Hugo

I’d been blogging with Jekyll for a few years now; first on GitHub Pages, from a fork of the beautiful-jekyll theme, then on a VPS with a design I wrote from scratch. It worked well for me; I was only attempting to write my own site generator because it made a good Go learning project. So, when designing jorge, I defaulted to whatever Jekyll did, except for the few parts that I felt added friction to my blogging workflow:

  • I would support org-mode files in addition to Markdown, assuming org by default.
  • I would generate sites from a src/ directory instead of the root of the project. This would remove the need to explicitly exclude files in the configuration or by prefixing them with an underscore (and the risk of inadvertently serving them), with the added benefit that the contents of the src/ directory would better represent the final website structure.
  • I would remove date information from URLs, preferring, for instance, /blog/code-is-run-more-than-read to /2023-11-30-code-is-run-more-than-read/1. In addition to better readability, this would reduce the need for file renaming to adjust the date. I would similarly rely on front-matter metadata rather than a dedicated directory to mark posts as drafts.

Since I would be writing a static site generator in Go, I also had Hugo on my radar as a reference. I hadn’t used Hugo but, from skimming through the documentation, I got the impression that it was more complex than Jekyll, certainly more than what I was planning to build. I used it occasionally to have an additional point of view for design choices, as well as a reference when looking for Go libraries to solve specific problems (the command-line interface, file-watching, etc.).

Liquid

One decision I took early on was to use liquid as the templating language (as Jekyll does) rather than Go’s html/template package (as Hugo does). I did so because:

  • I was already familiar with the liquid syntax;
  • I had a Jekyll site that I wanted to use for testing and ultimately port to jorge, so sticking with liquid would be more productive;
  • I got the impression that html/template was better suited for Go programmers than for users of programs built with Go. I suspected that using these templates would have required extra work to make them user-friendly for non-Go programmers.

I could use the osteele/liquid library (part of a Go port of Jekyll) for parsing and rendering the templates. I later realized that some of the template tags and filters I was using in my website weren’t native liquid but rather Jekyll extensions; since I didn’t want to add the entire gojekyll project as a dependency —which felt like it would defeat the purpose of building a site generator—, I ported and adapted the few filters I needed.

CLI

The Go standard library has reasonable support for building command-line programs —not as flexible as Python’s argparse but good enough for most purposes. I implemented the entire jorge interface out of a few basic programs from Go by Example. But before releasing the project I wanted to add some standard features like version and help flags, usage documentation, and user-friendly errors; things better served by a specialized library.

I assumed, from previous experience, that all CLI libraries would be more or less equivalent, but the most popular Go ones turned out not to be flexible enough to accommodate the usage patterns I had already implemented:

  • cobra commands rely on a Run function that doesn’t return errors, so using it would have required me to add extra error handling past input argument validations.
  • urfave/cli required much manual tweaking to produce the usage text I wanted for my program.
  • Both seem to lack support for required/named positional arguments, so it would have required extra work to express commands like jorge init <dir> or jorge post <title>.

kong, on the other hand, allowed me to define my CLI concisely and declaratively, preserving the code structure and user experience I already had in place, and handling input validations for me. Once I adapted my code to use kong, further refactoring opportunities opened up.

Smartypants

In my old blogging workflow, I wrote posts as org-mode files, manually exported them to Markdown, and then passed them to Jekyll, since this produced better-formed documents than using org-html-export directly. One benefit I was inadvertently getting from this setup, which I didn’t notice until I started comparing the jorge output HTML with my online blog, was “smart quote” replacements: where my website showed “Joe’s Garage”, my jorge posts would render "Joe's Garage". Once I started noticing the difference, I couldn’t un-see those dumb apostrophes, so I started researching how to get that feature into jorge.

  • The technique was originally implemented in Perl by John Gruber (also author of Markdown). There’s a well-documented Python port, but not a general-purpose one for Go. The algorithm is tricky and regex-y enough that I wouldn’t dare try to implement it myself, even if I somewhat understood the code.
  • Most Markdown libraries do their own smart quotes replacement, including Golang’s blackfriday and goldmark (the one I use in jorge). But processing Markdown input wasn’t enough; I needed something that I could apply to any HTML file, regardless of its source.
  • I found out that there’s an org-export option for smart quotes, but it wasn’t supported by go-org.
  • Jekyll has a smartify filter (also available in gojekyll), but it requires manual application by the user.
  • The relevant blackfriday module is also available as a standalone package, but I found that it doesn’t work well on HTML documents.

I was fixated on getting this feature —I didn’t want the jorge port of my website to feel like a downgrade in any way— but none of the options was usable as it was. What I ended up doing was extracting the text-replacement logic from gojekyll’s smartify filter —since it was the shortest and simplest of the lot, even if potentially slower— and used it in my own HTML traversal code, making sure to skip preformatted tags (pre, code, script, etc). The result is here.

CSS

One of the reasons why I decided to work on a command-line application was that it wouldn’t require building and polishing a graphical user interface. Although jorge init would generate a website, CSS included, I planned to just copy the styles from my home page. That didn’t go as planned, though: as soon as I started making minor tweaks to the page contents, I found myself struggling between CSS syntax nuances, browser quirks, and my own limitations.

I can tell what I like from what I don’t, aesthetically speaking; I occasionally get ideas to improve the look of my website, and I can Google my way into making them happen. But I am no designer; I don’t have the training to reason from first principles and think holistically about design as I can with program code —not to mention getting accessibility requirements right. I may get a site to look as I want but the CSS turns out to be brittle; any change may break things that were previously working and what looks good on my machine may not on my cellphone or a different browser. HTML and CSS have come a long way since the jQuery days, but I get war flashbacks whenever I see that iOS Firefox displays a completely different thing from Firefox Desktop because it’s just Safari under the hood, and then Safari Desktop’s responsive mode doesn’t match iOS Safari, either2.

One place where things got hairy was trying to honor the browser preferences for light/dark mode (through color-scheme and prefers-color-scheme media queries) while doing syntax highlighting of code blocks (with a library that’s unaware of color preferences) but without forcing the same highlighting theme on all generated sites.

Notes


1

I know you can get the same behavior in Jekyll by changing the configuration. As with other options, I wanted the jorge configuration to meet my preferences by default.

2

This WebKit quirk was especially annoying.