I do almost all my note-taking in Emacs org-mode, so naturally I also prefer to write my blog posts in it. As for my ox-hugoblogging flow, I use the less preferred method: one org file per post, the consequence is I cannot just copy-paste the org capture setup provided by the doc site. I also setup my posts in a subdirectory beneath the HUGO_BASE_DIR:
First off, org-capture-templates is just like its name; it is a template for creation of new entries. It is used by org-mode, which is a killer feature of Emacs. Initially it was design to capture notes with little interruption1. But since it was all Emacs Lisp, we can modify it with ease.
(setqorg-capture-templates'(("t""Todo"entry(file+headline"~/org/gtd.org""Tasks")"* TODO %?\n %i\n %a")("j""Journal"entry(file+datetree"~/org/journal.org")"* %?\nEntered on %U\n %i\n %a")))
Code Snippet 2:
from the manual
org-capture-templates is a list of:
keys
in the example, t is for todo entry and j is for journal.
description
usually a one-liner describing what kind of capture the key is.
type
the type of the entry, here entry is an org node with a headline.
So I need to tweak it a bit in order to automatically create a file within a subdirectory in my blog content (using the same name to make it easier). Not only that, ox-hugo use an org meta-data for hugo front-matter3. Each new files created must be started with these metadata (at minimal):
1
2
3
4
5
6
7
8
9
10
11
12
#+options: ':nil -:nil ^:{} num:nil toc:nil#+author: Kristian Alexander P#+creator: Emacs 29.2 (Org mode 9.6.15 + ox-hugo)#+hugo_section: posts#+hugo_base_dir: ../../#+date: <2024-03-03 Sun>#+title: Hugo blog org-capture-templates#+description: My blogging workflow#+hugo_tags: hugo emacs org#+hugo_categories: emacs#+hugo_auto_set_lastmod: t#+startup: inlineimages
Code Snippet 3:
ox-hugo metadata
Some metadata will be different for each capture; title, description, hugo_tags, and hugo_categories. dates should be set as the capture date, the other will be needing a user input, including the filename, for the org-capture process.
Those will be set as the template part. As for the target, I’m using a simple Emacs Lisp function:
1
2
3
4
5
(defun+config/create-new-blog-post()"Create new blog post path."(interactive)(let((name(read-string"Filename: ")))(concat+config/blog-directory"/content-org/"(format"%s"name)"/"(format"%s.org"name))))
Code Snippet 4:
my targeting function
This function is just a basic input/output; it will ask for a file name, and then it will concatenate it as a valid file path, here subtituted as a variable +config/blog-directory
Basically it will look for a directory named “alexforsale.github.io” (it’s my github-page repository) inside the variable org-directory, which is also should be set.
Before pushing each commits to my github repository, I’d view my blog in my local machine, With hugo this can be done by running:
1
hugo server --buildDrafts --navigateToChanged
Code Snippet 8:
hugo server
from within the HUGO_BASE_DIR directory. This can be done from a terminal emulator, or, since I’m using Emacs, I can run it using async-shell-command, which is actually shell-command, but adds a & at the end of the command to run it asynchronously.
1
(async-shell-command"hugo server --buildDrafts --navigateToChanged &""*hugo*""*hugo-error*")
Code Snippet 9:
running hugo server within emacs
The *hugo* argument is the output-buffer, and *hugo-error* is the error-buffer. But since the hugo server command has many other flags, I use an external package called transient5 to toggle each flags.
Hugo provides its own webserver which builds and serves the site.
While hugo server is high performance, it is a webserver with limited options.
'hugo server' will by default write and server files from disk, but you can
render to memory by using the '--renderToMemory' flag. This can be faster
in some cases, but it will consume more memory.
By default hugo will also watch your files for any changes you make and
automatically rebuild the site. It will then live reload any open browser pages
and push the latest content to them. As most Hugo sites are built in a fraction
of a second, you will be able to save and see your changes nearly instantly.
Usage:
hugo server [command] [flags]
hugo server [command]
Aliases:
server, serve
Available Commands:
trust Install the local CA in the system trust store.
Flags:
--appendPort append port to baseURL (default true)
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
--bind string interface to which the server will bind (default "127.0.0.1")
-D, --buildDrafts include content marked as draft
-E, --buildExpired include expired content
-F, --buildFuture include content with publishdate in the future
--cacheDir string filesystem path to cache directory
--cleanDestinationDir remove files from destination not found in static directories
-c, --contentDir string filesystem path to content directory
--disableBrowserError do not show build errors in the browser
--disableFastRender enables full re-renders on changes
--disableKinds strings disable different kind of pages (home, RSS etc.)
--disableLiveReload watch without enabling live browser reload on rebuild
--enableGitInfo add Git revision, date, author, and CODEOWNERS info to the pages
--forceSyncStatic copy all files when static is changed.
--gc enable to run some cleanup tasks (remove unused cache files) after the build
-h, --help help for server
--ignoreCache ignores the cache directory
-l, --layoutDir string filesystem path to layout directory
--liveReloadPort int port for live reloading (i.e. 443 in HTTPS proxy situations) (default -1)
--minify minify any supported output format (HTML, XML etc.)
--navigateToChanged navigate to changed content file on live browser reload
--noBuildLock don't create .hugo_build.lock file
--noChmod don't sync permission mode of files
--noHTTPCache prevent HTTP caching
--noTimes don't sync modification time of files
--panicOnWarning panic on first WARNING log
--poll string set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes
-p, --port int port on which the server will listen (default 1313)
--pprof enable the pprof server (port 8080)
--printI18nWarnings print missing translations
--printMemoryUsage print memory usage to screen at intervals
--printPathWarnings print warnings on duplicate target paths etc.
--printUnusedTemplates print warnings on unused templates.
--renderStaticToDisk serve static files from disk and dynamic files from memory
--templateMetrics display metrics about template executions
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics
-t, --theme strings themes to use (located in /themes/THEMENAME/)
--tlsAuto generate and use locally-trusted certificates.
--tlsCertFile string path to TLS certificate file
--tlsKeyFile string path to TLS key file
--trace file write trace to file (not useful in general)
-w, --watch watch filesystem for changes and recreate as needed (default true)
Global Flags:
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
--config string config file (default is hugo.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
-d, --destination string filesystem path to write files to
-e, --environment string build environment
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
--logLevel string log level (debug|info|warn|error)
--quiet build in quiet mode
--renderToMemory render to memory (mostly useful when running the server)
-s, --source string filesystem path to read files relative from
--themesDir string filesystem path to themes directory
-v, --verbose verbose output
Use "hugo server [command] --help" for more information about a command.
Then I just use transient-define-prefix to create the command.
(use-packagetransient:config(transient-define-prefix+config/transient-hugo-server()"Run hugo server with `transient'.":man-page"hugo-server"["Options"("q""quit"transient-quit-all)("-D""Build drafts""--buildDrafts")("-E""Build expired""--buildExpired")("-F""Build future""--buildFuture")("-d""Debug""--debug")("-B""Disable build errors on browser""--disableBrowserError")("-c""Clean destination dir""--cleanDestinationDir")("-e""Enable Git info""--enableGitInfo")("-F""enable full re-renders on changes""--disableFastRender")("-f""Force sync static files""--forceSyncStatic")("-g""enable to run some cleanup tasks""--gc")("-m""Minify any supported output format""--minify")("-C""No chmod""--noChmod")("-T""Don't sync modification time of files""--noTimes")("-I""Print missing translation""--printI18nWarnings")("-M""Print memory usage""--printMemoryUsage")("-P""Print warning on duplicate target path""--printPathWarnings")("-q""Quiet""--quiet")("-v""Verbose""--verbose")("-w""Watch filesystem for changes""--watch")]["Action"("s""hugo server"+config/start-hugo-server)]))
Code Snippet 11:
install transient with use-package and define our hugo server command, see their showcase to learn how to use transient. I use this heavily in the past.
Note that the “Action” (s) is +config/start-hugo-server which we need to define:
1
2
3
4
5
6
7
8
(defun+config/start-hugo-server(args)"Start hugo server in `+config/blog-directory'."(interactive(list(transient-args'+config/transient-hugo-server)))(if(not(executable-find"hugo"))(message"hugo executable not found")(let((default-directory+config/blog-directory)(command"hugo server"))(async-shell-command(mapconcat#'identity`(,command,@args)" ")"*hugo*""*hugo-error*"))))
Code Snippet 12:
the function
This function will run hugo server, with additional args which will be provided by the transient command.
the mapconcat will apply the first argument to each element of the second arguments, for example:
1
(mapconcat#'identity'("abc""def""ghi")". ")
Code Snippet 13:
mapconcat example
1
"abc. def. ghi"
the identity simply returns the arguments unchanged.