mirror of
https://github.com/facundoolano/jorge.git
synced 2024-11-16 07:47:40 +01:00
Revert "back to default heading levels for org files"
This reverts commit d328d00d66
.
This commit is contained in:
parent
d328d00d66
commit
7df0be12c8
9 changed files with 25 additions and 25 deletions
|
@ -44,6 +44,6 @@ If I succeeded in making a tool that I wanted to use myself, maybe other people
|
|||
|
||||
With this new goal in mind, I jumped into the work.
|
||||
|
||||
** Notes
|
||||
*** Notes
|
||||
|
||||
[fn:1] Namely: I had to define an org-publish project in my configuration to instruct Emacs how to export my org files; since the default org export produces documents of unconventional HTML structure (that, for example, are not properly parsed by browser readers), I had to export org to markdown and let Jekyll convert the markdown to HTML; I had to publish my org files separately for ~jekyll serve~ to pick up the changes; frequent operations like changing post titles and dates, and moving them out of draft required me to update two different filenames.
|
||||
|
|
|
@ -18,7 +18,7 @@ Once ~build~ and ~serve~ were out of the way, I'd be almost done with the projec
|
|||
|
||||
The beauty of the ~serve~ command was that I could start with a naive implementation and iterate towards the ideal one, keeping it functional at every step of the way. This post summarizes that process.
|
||||
|
||||
** A basic file server
|
||||
*** A basic file server
|
||||
|
||||
The simplest ~serve~ implementation consisted of building the site once and serving the target directory locally. The standard ~net/http~ package has a [[https://pkg.go.dev/net/http#FileServer][file server]] for that:
|
||||
|
||||
|
@ -76,7 +76,7 @@ This ~HTMLFileSystem~ wrapped around the standard ~http.Dir~ one I was handing t
|
|||
return http.ListenAndServe(":4001", nil)
|
||||
#+end_src
|
||||
|
||||
** Watching for changes
|
||||
*** Watching for changes
|
||||
As a next step, I needed the command to watch the project source directory and trigger new builds whenever a file changed. I found the [[https://github.com/fsnotify/fsnotify][fsnotify]] library for this exact purpose; the fact that both Hugo and gojekyll listed as a dependency suggested that it was a reasonable choice for the job.
|
||||
|
||||
Following [[https://github.com/fsnotify/fsnotify/blob/c94b93b0602779989a9af8c023505e99055c8fe5/README.md#usage][an example]] from the fsnotify documentation, I created a watcher and a goroutine that triggered a ~site.Build~ call every time a file-change event was received:
|
||||
|
@ -114,7 +114,7 @@ func watchProjectFiles(watcher *fsnotify.Watcher, config *config.Config) {
|
|||
}
|
||||
#+end_src
|
||||
|
||||
** Build optimizations
|
||||
*** Build optimizations
|
||||
At this point I had a useful file server, always responding with the most recent version of the site. But the responsiveness of the ~serve~ command wasn't ideal: it processed the entire website for every small edit I made on a source file. I wanted to attempt some performance improvements here, but without introducing much complexity: rather than supporting incremental or conditional builds ---which would have required tracking state and dependencies between files---, I wanted to keep building the entire site on every change, only faster.
|
||||
|
||||
The first cheap optimization was obvious from looking at the command output: most of the work was copying static assets (e.g. images, static CSS files, etc.). So I changed the ~site.Build~ implementation to optionally create links instead of copying the files over to the target.
|
||||
|
@ -209,7 +209,7 @@ I was very satisfied to see a sequential piece of code turned into a concurrent
|
|||
|
||||
These couple of optimizations resulted in a good enough user experience, so I didn't need to attempt more complex ones.
|
||||
|
||||
** Live reload
|
||||
*** Live reload
|
||||
|
||||
Without having looked into their code, I presumed that the live-reloading tools I had used in the past (~jekyll serve~, [[https://github.com/shime/livedown/][livedown]]) worked by running WebSocket servers and injecting some JavaScript in the HTML files they served. I wanted to see if I could get away with implementing live reloading for ~jorge serve~ with [[https://en.wikipedia.org/wiki/Server-sent_events][Server-sent events]], a slightly simpler alternative to WebSockets that didn't require a dedicated server.
|
||||
|
||||
|
@ -378,7 +378,7 @@ func Serve(config config.Config) error {
|
|||
}
|
||||
#+end_src
|
||||
|
||||
** Handling event bursts
|
||||
*** Handling event bursts
|
||||
|
||||
The code above worked, but not consistently. A file change would occasionally cause a browser refresh to a 404 page as if the new version of the file wasn't written to the target directory yet.
|
||||
This happened because a single file edit could result in multiple writes, and those in a burst of fsnotify events (as mentioned in the [[https://github.com/fsnotify/fsnotify/blob/v1.7.0/backend_inotify.go#L108-L115][documentation]]). The solution (also suggested by [[https://github.com/fsnotify/fsnotify/blob/c94b93b0602779989a9af8c023505e99055c8fe5/cmd/fsnotify/dedup.go][an example]] in the fsnotify repository) was to de-duplicate events by introducing a delay between event arrival and response. [[https://pkg.go.dev/time#AfterFunc][~time.AfterFunc~]] helped here:
|
||||
|
|
|
@ -10,7 +10,7 @@ tags: [golang, emacs]
|
|||
|
||||
Dumping some notes I took while I was setting up my environment and reading the bare minimum to get started with Go programming.
|
||||
|
||||
** Go setup
|
||||
*** Go setup
|
||||
- I'm on Mac and I had some older version of Go already installed, so I had to ~brew uninstall go~.
|
||||
- Go 1.22 had recently come out, and it wasn't yet available in brew, so I [[https://go.dev/doc/install][downloaded it]] from the Go website.
|
||||
- I had to add this to my ~~/.zshrc~ for my shell to pick up the installation ([[https://stackoverflow.com/a/57217841/993769][source]]):
|
||||
|
@ -23,7 +23,7 @@ export PATH=$PATH:$GOROOT/bin
|
|||
export PATH=$PATH:$GOPATH/bin
|
||||
#+end_src
|
||||
|
||||
** Emacs setup
|
||||
*** Emacs setup
|
||||
In the last couple of years the Emacs LSP integration has got good enough that, other things being equal, I prefer it when I'm setting up a new language since it provides a similar out-of-the-box experience to what I'm used to, without much extra configuration.
|
||||
|
||||
- A quick search for recent Go+Emacs setup suggestions didn't yield a definite choice, but I did see a few people using [[https://github.com/dominikh/go-mode.el][go-mode]] together with LSP, so I went with that.
|
||||
|
@ -50,12 +50,12 @@ The only issues I've found so far:
|
|||
- ~lsp-describe-thing-at-point~ does give me function documentation, but only the first sentence, not the entire paragraph. I end up using ~xref-find-definitions~ and read directly from the doc comment in the code.
|
||||
- flycheck's ~next-error~ doesn't seem to work consistently in my Go setup as it does in other languages.
|
||||
|
||||
** GitHub setup
|
||||
*** GitHub setup
|
||||
|
||||
- I started with a default GitHub Actions [[https://github.com/facundoolano/jorge/blob/adb17ad9d2cb1e9929e9f9066941ccf3ac13222a/.github/workflows/test.yml][workflow to run unit tests]], which I extended to also run linters.
|
||||
- I eventually wrote [[https://github.com/facundoolano/jorge/blob/adb17ad9d2cb1e9929e9f9066941ccf3ac13222a/.github/workflows/release.yml][another workflow]] to draft a release and compile binaries for different platforms when I push a git tag.
|
||||
|
||||
** Go documentation
|
||||
*** Go documentation
|
||||
I had read [[https://www.openmymind.net/The-Little-Go-Book/][/The Little Go Book/]] a few years ago, and while I didn't do anything with what I read there and thus forgot most of it, I did remember that there wasn't anything surprising or complicated enough to require dedicated training before attempting to use the language.
|
||||
|
||||
This time I took a look a Go's [[https://go.dev/learn/][learn]] and [[https://go.dev/doc/][docs]] pages, but they didn't point me in a single obvious direction. There was the [[https://go.dev/doc/effective_go][/Effective Go/ book]], which sounded like the kind of resource I tend to favor: text form, not very long, by the language authors. But it started with a note saying it was written in 2009 and hadn't been updated since, which made it unappealing for my short-term goals. I may revisit it eventually, though, since I understand it was the canonical learning resource for many years and a good showcase of Go's idiosyncrasy.
|
||||
|
|
|
@ -8,7 +8,7 @@ tags: [project]
|
|||
#+OPTIONS: toc:nil num:nil
|
||||
#+LANGUAGE: en
|
||||
|
||||
* User interface
|
||||
** User interface
|
||||
When I'm toying with the idea for a new project, I start by picturing what it could look like from the perspective of the users: what the interface will be. For web applications, this means deciding what actions will I make available to them, what menus and buttons, and what information I need to display on a given view. Then I make some sketches to figure out how to fit all that into a web page layout[fn:1]. For command-line applications it gets much easier: I just need to come up with the right list of subcommands, some of the flags, and a couple of usage examples.
|
||||
|
||||
As soon as I [[file:why][decided to work on a static site generator]], I narrowed the commands I needed to support down to four: ~init~, ~build~, ~serve~, and ~new~. Below are some transcripts of my notebook, showing how I first imagined these commands would work (at this point I was calling the program ~golb~):
|
||||
|
@ -42,7 +42,7 @@ $ golb new note
|
|||
|
||||
The ~new~ command would be a helper to create blog posts with some of the boilerplate (e.g. the front matter) already filled in. I eventually renamed this subcommand to ~post~ and dropped most of its options.
|
||||
|
||||
* Project plan
|
||||
** Project plan
|
||||
Based on that CLI outline, I gave some thought to each of the subcommands and flags I planned to implement, trying to imagine, at a high level, which operations they should consist of, what parts seemed easy or complicated to program, which ones I wasn't yet sure how I could tackle, what work I expected to delegate to third-party libraries, and where I suspected the "unknown-unknowns" of the project could be lurking. This exercise yielded a preliminary list of tasks, that I added to my project board:
|
||||
|
||||
#+begin_src
|
||||
|
@ -81,7 +81,7 @@ Some things to note from this list:
|
|||
- The order of the tasks was influenced by the user journey I imagined (e.g. first run ~golb init~), but as soon as I started working on the project I realized I should prioritize first the "mission critical" commands (~golb build~), then the most complex to implement (~golb serve~), regardless of the user flow.
|
||||
- I captured some "nice to have" tasks that sounded interesting or fun to implement but were not required for the project to make sense or that I didn't know how feasible they were. One such case was the org-mode support; as I'll explain later, learning that there already was a Go library to parse org-mode syntax would lead me to redefine the goals of the project.
|
||||
|
||||
* Some code
|
||||
** Some code
|
||||
One nice quality of command-line programs is that the user interface tends to easily translate into the top layer of the program, each subcommand offering a good starting point for the coding. Looking at the ~main.go~ file from [[https://github.com/facundoolano/jorge/commit/16cbf1d10ea890df216b74ad9231a1b70ad102c3#diff-2873f79a86c0d8b3335cd7731b0ecf7dd4301eb19a82ef7a1cba7589b5252261][an early commit]] shows much of the initial project plan already stubbed in the code:
|
||||
|
||||
#+begin_src go
|
||||
|
|
|
@ -11,10 +11,10 @@ excerpt: I set out to write a little static site generator, but why?
|
|||
|
||||
I set out to write a little static site generator, but why?
|
||||
|
||||
** Why this project?
|
||||
*** Why this project?
|
||||
I wanted to learn the Go programming language, and my preferred way to learn a new language is to use it on some open-source project[fn:1].
|
||||
|
||||
** Why Go?
|
||||
*** Why Go?
|
||||
|
||||
In the past I studied new programming languages to "broaden my programming horizons";
|
||||
to add better, more sophisticated tools to my toolbox: new paradigms, concurrency models, type systems. Because I never used it in college and never needed it for work, and because it seemed to lack this novelty factor, I never got around to trying Go.
|
||||
|
@ -32,14 +32,14 @@ Go seemed to be an unpretentious, boring language ---and I mean that as [[https:
|
|||
|
||||
That's the preconception I had about Go, having read about it but never used it. I wanted to spend some time with it because, if these presumptions turned out to be right, Go could become the obvious default for many of my future projects ---and an accurate boring tech radar.
|
||||
|
||||
** Why a command-line application?
|
||||
*** Why a command-line application?
|
||||
Go is famously good for building server-side software, so that was the first space I turned to when looking for project ideas. I briefly considered following the [[https://pragprog.com/titles/tjgo/distributed-services-with-go/][/Distributed Services with Go/ book]], but I felt that I wouldn't get enthusiastic enough about that project to see it through to completion. More generally, I suspected that any backend-only project would turn into a useless toy I wouldn't care for. I needed something user-facing.
|
||||
|
||||
Could I use Go to implement the backend of a web application, instead? That would've made sense, but I was just [[https://olano.dev/2023-12-12-reclaiming-the-web-with-a-personal-reader/][coming out]] of working on a medium-sized web application[fn:2]; I knew too well that much of the effort in such a project would go into the front end, and I needed a break from that.
|
||||
|
||||
What else, then? Other than servers, Go is known to be good at command-line applications. I enjoy working on CLIs; they challenge me to design and reason about the user experience without most of the graphical interface struggle. A CLI app sounded promising.
|
||||
|
||||
** Why a static site generator?
|
||||
*** Why a static site generator?
|
||||
|
||||
I read somewhere that a blog is the ideal learning project for software developers: it can get as simple or as complex as you want, it exposes you to the entire web stack, from server setup to UI design, and when it's working you are encouraged to write about something (most likely about setting up a blog).
|
||||
|
||||
|
|
|
@ -13,6 +13,6 @@ jorge started as a Go learning project, aimed at streamlining my blogging workfl
|
|||
|
||||
This tutorial covers the basics of using jorge, from starting a site to deploying it. I tried to keep it accessible, but you may need to consult with [[https://jekyllrb.com/docs/][Jekyll]] or [[https://gohugo.io/documentation/][Hugo]] documentation if you never used a static site generator, want to get the finer-grained details of template syntax, etc.
|
||||
|
||||
** Notes
|
||||
*** Notes
|
||||
|
||||
[fn:1] Facundo Olano 👋
|
||||
|
|
|
@ -8,7 +8,7 @@ tags: [tutorial]
|
|||
#+OPTIONS: toc:nil num:nil
|
||||
#+LANGUAGE: en
|
||||
|
||||
* Posts and pages
|
||||
** Posts and pages
|
||||
|
||||
When jorge builds a website out of the contents of your ~src/~ directory, it distinguishes between 3 types of files:
|
||||
|
||||
|
@ -24,7 +24,7 @@ As you can see, the difference between posts and pages is subtle. Posts receive
|
|||
3. If they that declare ~tags~ in their front matter, posts are additionally included in the ~site.tags~ map.
|
||||
4. Posts expose an ~excerpt~ property with a summary of their contents. If ~excerpt~ is defined as a key in the post front matter, its value will be used; if not, the first paragraph of the post content will be used instead. Excerpts are useful for previewing posts in the blog archive, in social media links, and in RSS feeds.
|
||||
|
||||
* jorge post
|
||||
** jorge post
|
||||
Each website has its own layout so it's hard to predict what you may need to do with a page template. But blogs are different: once the site layout is in place, you more or less repeat the same steps every time you write a new post. For this reason, jorge provides the ~jorge post~ command to initialize blog post template files.
|
||||
|
||||
Let's try it out:
|
||||
|
@ -63,7 +63,7 @@ Let's look at what the command did for us:
|
|||
|
||||
With ~jorge serve~ running, you can start filling in some content on this new post and see it show up in the browser at http://localhost:4001/blog/my-own-blog-post.
|
||||
|
||||
* Customizing the post format
|
||||
** Customizing the post format
|
||||
As you may have noticed, the ~jorge post~ command makes a lot of assumptions about the post: where to put it, how to name it, and what format to use. You can control some of these decisions by redefining the ~post_format~ configuration key. The default is:
|
||||
|
||||
#+begin_src yaml
|
||||
|
@ -82,7 +82,7 @@ $ jorge post "Another kind of post"
|
|||
added src/2024-02-23-another-kind-of-post.md
|
||||
#+end_src
|
||||
|
||||
** Notes
|
||||
*** Notes
|
||||
|
||||
[fn:1] Both a blog archive and the RSS feed (technically [[https://en.wikipedia.org/wiki/Atom_(web_standard)][Atom]]) are already implemented in the default site generated by ~jorge init~.
|
||||
|
||||
|
|
|
@ -59,6 +59,6 @@ $ rsync -vPrz --delete target/ root@olano.dev:/var/www/jorge
|
|||
|
||||
And that's it!
|
||||
|
||||
** Notes
|
||||
*** Notes
|
||||
|
||||
[fn:1] [[https://github.com/facundoolano/jorge/pulls][PRs]] welcome!
|
||||
|
|
|
@ -150,8 +150,8 @@ func (templ Template) RenderWith(context map[string]interface{}, hlTheme string)
|
|||
doc := org.New().Parse(bytes.NewReader(content), templ.SrcPath)
|
||||
htmlWriter := org.NewHTMLWriter()
|
||||
|
||||
// make * -> h2, ** -> h3, etc
|
||||
htmlWriter.TopLevelHLevel = 2
|
||||
// make * -> h1, ** -> h2, etc
|
||||
htmlWriter.TopLevelHLevel = 1
|
||||
if hlTheme != NO_SYNTAX_HIGHLIGHTING {
|
||||
htmlWriter.HighlightCodeBlock = highlightCodeBlock(hlTheme)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue