Add development notes post (#28)

* outline and first section content

* new tag

* draft cli section

* draft smartypants section

* draft css notes

* fill missing parts of first section

* cleanup draft

* another cleanup round

* grammar
This commit is contained in:
Facundo Olano 2024-03-11 16:09:08 -03:00 committed by GitHub
parent 7df0be12c8
commit e874699feb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 73 additions and 2 deletions

View file

@ -3,7 +3,7 @@ title: A change of plans
date: 2024-03-04 date: 2024-03-04
layout: post layout: post
lang: en lang: en
tags: [project, emacs] tags: [project, emacs, thoughts]
excerpt: The project concept, as I originally planned it, had a few problems, but they went away when I found out I could parse org-mode files in Go. excerpt: The project concept, as I originally planned it, had a few problems, but they went away when I found out I could parse org-mode files in Go.
--- ---
#+OPTIONS: toc:nil num:nil #+OPTIONS: toc:nil num:nil

View file

@ -0,0 +1,71 @@
---
title: Development notes
date: 2024-03-11
layout: post
lang: en
tags: [project]
---
#+OPTIONS: toc:nil num:nil
#+LANGUAGE: en
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 [[https://github.com/daattali/beautiful-jekyll][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 [[file:why][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/~[fn:2]. 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 [[https://jekyllrb.com/docs/liquid/][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 [[https://github.com/osteele/liquid][osteele/liquid]] library (part of a [[https://github.com/osteele/gojekyll/][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 [[https://github.com/facundoolano/jorge/blob/7df0be12c8cdc55015c03badca9944829bbf184f/markup/filters.go][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 [[https://github.com/facundoolano/jorge/blob/HEAD/docs/src/blog/development-notes.org?plain=1#L33][argparse]] but good enough for most purposes. I implemented the entire jorge interface out of a [[https://gobyexample.com/command-line-arguments][few]] [[https://gobyexample.com/command-line-flags][basic]] [[https://gobyexample.com/command-line-subcommands][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:
- [[https://github.com/spf13/cobra][cobra]] commands rely on a ~Run~ function that [[https://github.com/spf13/cobra/issues/67][doesn't return errors]], so using it would have required me to add extra error handling past input argument validations.
- [[https://github.com/urfave/cli][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>~.
[[https://github.com/alecthomas/kong][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 ~“Joes 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 [[https://daringfireball.net/projects/smartypants/][originally implemented]] in Perl by John Gruber (also author of [[https://daringfireball.net/projects/markdown/][Markdown]]). There's a well-documented [[https://github.com/leohemsted/smartypants.py][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 [[https://github.com/russross/blackfriday/blob/4ca8c28b21a883c59eb518036a3fe45a3f281463/smartypants.go][blackfriday]] and [[https://github.com/yuin/goldmark/blob/4f3074451eda8b06654d09415768726cf170985c/extension/typographer.go][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 [[https://orgmode.org/manual/Export-Settings.html][org-export option]] for smart quotes, but it [[https://github.com/niklasfasching/go-org/issues/42][wasn't supported]] by go-org.
- Jekyll has a ~smartify~ filter (also [[https://github.com/osteele/gojekyll/blob/f1794a874890bfb601cae767a0cce15d672e9058/filters/smartify.go][available]] in gojekyll), but it requires manual application by the user.
- The relevant blackfriday module is also available as a [[https://github.com/kr/smartypants/][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 [[https://github.com/osteele/gojekyll/blob/f1794a874890bfb601cae767a0cce15d672e9058/filters/smartify.go#L3-L4][potentially slower]]--- and used it in my own HTML traversal code, making sure to skip preformatted tags (~pre~, ~code~, ~script~, etc). The result is [[https://github.com/facundoolano/jorge/blob/7df0be12c8cdc55015c03badca9944829bbf184f/markup/smartify.go][here]].
** CSS
One of [[file:why][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, either[fn:1].
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
[fn:1] [[https://stackoverflow.com/a/22417120/993769][This]] WebKit quirk was especially annoying.
[fn:2] 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.

View file

@ -3,7 +3,7 @@ title: Why?
date: 2024-02-28 date: 2024-02-28
layout: post layout: post
lang: en lang: en
tags: [project, golang] tags: [project, golang, thoughts]
excerpt: I set out to write a little static site generator, but why? excerpt: I set out to write a little static site generator, but why?
--- ---
#+OPTIONS: toc:nil num:nil #+OPTIONS: toc:nil num:nil