A new Clojure Terminal REPL under development
A complete migration of all Practicalli websites to Zensical is underway, now all the plugins are available.
I was not that familiar with the TOML syntax (although I am now), so time was spend wrangling the right syntax for zensical.toml configuration. I occasionally used DuckDuckGo Search Assistant (via web browser) to find working examples (which it did about 75% of the time, sometimes after telling the assistant it had made things up).
A quick spike test with the Clojure CLI REPL was carried out using a simple Practicalli project, comparing the experience with my current preference of Rebel Readine. Potential is there, but a few more things to be added before I could use it regularly. A great start though and it is a new project.
Trying out the whalebird Mastodon desktop clients for Debian Linux, which was installed from a Debian package in the GitHub release (via DRA).
I needed to create a new Mastodon account for Practicalli: mastodon.social/@practicalli_johnny. Functional.cafe is closed to new accounts and clj.social was down (need to check if this domain is still active).
This week I am listening to an audio book version of "The Truth" by "Terry Pratchett". It is a satire on the evolution of the first printing press with many familiar Discworld characters and a few new ones too.
Ungovernd Produce channel has a few Terry Pratchett books that they read as their own Audiobook version. Curiously, I was thinking of creating my own audio books on my walk in the park yesterday.
Mastodon¶
I am trying out the whalebird desktop client for Mastodon social network.
A debian linux install script was created and added to the other post-install scripts in Practicalli Dotfiles.
#!/usr/bin/env bash
echo ""
echo "# ---------------------------------------"
echo "Whalebird Mastodon Desktop UI"
# install the nvim.appimage (automatic only installs nvim and not runtime)
# dra download --automatic --install --output ~/.local/bin h3poteto/whalebird-desktop
sudo dra download --select "*.deb" --install --output ~/.local/bin h3poteto/whalebird-desktop
echo "# ---------------------------------------"
echo ""
This installs Whalebird using the Debian package in the projects GitHub release artifacts.
The desktop launcher was used to start Whalebird.
Click the + icon to start adding an account.
Sign in to the server where the account was created by specifying the server domain, i.e. mastodon.social
The desktop app opens a browser page to confirm access to the domain and access account details. I assume a login is required if not already logged into the server.
A quick test post from Whalebird shows that its working okay.
Zensical¶
With the release of blog, rss, and social support I can complete the migration of all Practicalli sites from mkdocs to Zensical.
The speed of Zensical is excellent and the feedback from zensical serve alone has made my content quality greatly increase.
For simplicity I've used uvx to run zensical as a tool, both locally and via GitHub CI workflow (this also includes the zensical-catppuccin plugin for a really great theme).
TOML syntax¶
TOML consist of key-value pairs, tables, and arrays. Keys and values are separated by an equals sign.
Use double quotes for basic strings, single quotes for literal strings, and triple quotes for multiline strings.
basic = "A string with \"quotes\" and \\backslashes\\"
literal = 'No escape needed: \n stays as-is'
multiline = """This string
spans multiple lines."""
Boolean values are written as true or false.
TOML supports RFC 3339 format for date and time.
A dotted key is a sequence of keys joined by dots, creating nested tables implicitly.
Arrays hold values of the same type (homogeneous). Separate elements with commas and use square brackets.
fruits = ["apple", "oranges", "peaches"]
fibbonacci = [1, 2, 3, 5, 8, 13]
complex = [[1, 2], [3, 4]]
mappings = [{key1 = "string"}, {key1 = "string"}] # Zensical navigation
A table header defined as [table] starts a table and defines where subsequent key/value pairs are stored.
A dotted table header, [table.name.option], defines nested tables using dot-separated name segments.
A Table is used to add a plugin to the Zensical configuration. Each plugin can override the default plugin options by adding the respective keys and values.
Key names are case-sensitive.
Tables are better for Multiple related settings, providing greater readability for complex configurations
An array of tables uses [[name]] to append a new table element to an array under name.
The Social links in the footer of Zensical are an example of adding several icon links using an array of tables, all adding to the same project.extra.social table.
[[project.extra.social]]
icon = "fontawesome/brands/github"
link = "https://github.com/practicalli/journal"
[[project.extra.social]]
icon = "material/web"
link = "https://practical.li/"
[[project.extra.social]]
icon = "fontawesome/brands/linkedin"
link = "https://www.linkedin.com/in/jr0cket/"
[[project.extra.social]]
icon = "fontawesome/brands/slack"
link = "https://clojurians.slack.com/messages/practicalli"
[[project.extra.social]]
icon = "fontawesome/brands/mastodon"
link = "https://mastodon.social/@practicalli_johnny"
[[project.extra.social]]
icon = "fontawesome/brands/github"
link = "https://github.com/practicalli"
[[project.extra.social]]
icon = "fontawesome/brands/docker"
link = "https://hub.docker.com/u/practicalli"
NOTE: the array of tables syntax is used for the different pallette modes in the zensical-catppuccin theme config.
An Inline Table allows an option to have nested options.
Inline tables are used for simple one-off groupings when compactness is preferred
TOML reserves characters and it is not allow to unescaped them in keys or strings:
.(dot) separates nested keys#comment"or'define a string
Toml Configuration¶
zensical will build a site using an existing mkdocs.yml configuration and use the blog, rss and social configurations I already included.
I decided to migrate to the zensical.toml configuration as eventually there are features that are Zensical specific, e.g. using zensical-catppuccin theme (and future development of zensical plugins).
The three plugins are added as tables
Each plugin can be configured by adding keys and there respective values.
Practicalli Zensical Configuration for Blog site with RSS Feed and Social Cards
# ----------------------------------------------------------------------------
# Blog Website Support (blog, rss, social)
[project.plugins.blog]
blog_dir = "." # root directory of blog posts (used if not blog/posts/)
post_url_format = "{date}/{slug}" # url for post (slug is title of post)
draft = false # build drafts
draft_on_serve = true # show drafts in local server (default true)
draft_if_future_date = true # draft not published, but not marked as draft in local serve
# Only build with allowed categories
categories_allowed = [
"100daysofcode",
"clojure",
"clojurists-together",
"debian",
"git",
"github",
"hardware",
"hyprland",
"journal",
"megalinter",
"neovim",
"practicalli",
]
[project.plugins.rss]
image = "https://github.com/practicalli/graphic-design/blob/live/logos/practicalli-logo.png?raw=true"
match_path = "posts/.*" # path to posts that should be included in the rss feed
categories = ["categories", "tags",] # Included in feed summary
length = 100 # articles to include (Default 20)
# Use date from the front matter of each post
# date_from_meta = {as_creation = "date"} (simple format)
# date_from_meta = { as_creation = "date.created" } # date: created: format
# date_from_meta = { as_update = "date.updated" } # date: updated : format
# Set multiple values from the front matter (or use the expanded TOML below)
# date_from_meta = [
# { as_creation = "date.created", as_update = "date.updated" },
# ]
# Use date from the front matter of each post
[project.plugins.rss.date_from_meta]
as_creation = "date.created"
as_update = "date.updated"
# https://squidfunk.github.io/mkdocs-material/plugins/social/#social-cards
[project.plugins.social]
cards_layout_options = { font_family = "Ubuntu" }
# ----------------------------------------------------------------------------
Clojure CLI REPL¶
I had a quick play with the Clojure CLI REPL, a new project to provide a rich command line REPL support, using Java Jline.
Clojure CLI REPL has an interesting Inspector feature and the ability to evaluate part of an expression. A config file is required to define key mappings for these features (no key mappings by default).
There is an example config and docs to help and there is a lot more to the config examples I didnt have time to investigate.
There is a lot of promise to the Clojure CLI REPL and once tab completion of namespace and function names it ready its worth testing as a potential replacement or alternative for Rebel Readline.
NOTE: I had to set up auto-require configuration so I could use unqualified functions from
clojure.core(it seems strange that clojure.core isnt there by default).
For day to day work I will continue using Rebel Readline as my Terminal REPL prompt
Practicalli Rebel Readline Terminal REPL prompt
Features comparable to Rebel Readlin include multi-line editing, structural editing, doc lookup, and Vim or Emacs editing modes
Unique features include:
- inline eval
- a data inspector
- configurable prompts
- custom keybindings
I've added aliases to Practicalli Clojure CLI Config using my standard naming convention:
:repl/clistart nREPL server and rich REPL client:repl/servestart nREPL server only:repl/attachstart a client and attach to an existing nREPL server
The nREPL server creates an .nrepl.port file containing the local port the nREPL server is listening too
Startup¶
I have Java v25.0.4 installed and Clojure CLI 1.12.4.1602, running on Debian Linux.
Aliases to run the tool were added to the Clojure user level configuration $XDG_CONFIG_HOME/clojure/deps.edn using Practicalli Clojure CLI Config.
The XDG_CONFIG_HOME environment variable was checked that it still points to the correct location.
Run the nREPL server and client using the Clojure CLI tool.
The command proceeds to download the internet (I regularly clear the local Maven cache so this took a few seconds).
Warnings were shown at the end of the startup output regarding jline calling java.lang.System::load
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by org.jline.nativ.JLineNativeLoader in an unnamed module (file:/home/practicalli/.m2/repository/org/jline/jline/4.3.1/jline-4.3.1.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
The default prompt was showing at the end, just after the message stating the port nREPL was listening too. I believe this is a Java version 25 warning (I also get this with Rebel Readline).
The Clojure CLI REPL honours the Clojure CLI location. It did not try to create a separate $HOME/.clojure directory, which is good as I dont use that classic approach to dotfiles. I checked as the Clojure CLI REPL docs only mention ~/.clojure as the config directory.
The CLojure CLI REPL did not create a config file, but did create a history buried deep in the path $XDG_CONFIG_HOME/clojure/.cljconf/org.clojure/clojure-cli.repl/history
My assumption about the deep path is because there is a bigger tree for all the code configuration. Not sure I want to maintain a bunch of code in order to configure a REPL UI. Maybe there are bigger plans that would benefit from more customisation.
NOTE: it felt confusing to have a
.cljconfdotfile within the parent Clojure CLI dotfile, especially when the Clojure CLI config is in$HOME/.config/clojure/.cljconf. I missed that.cljconfhad been created at first glance.The
.cljconfname could easily be mistaken for something related to Clojure CLI rather than Clojure CLI REPL.
First use¶
I could evaluate expressions and multi-line typing worked.
History of expressions is accessed via the Up arrow Up or via Ctrl r and typing a pattern (just like the usual shell history search)
I did not get docs after pausing at the end of a function name. I didnt realise initially that a configuration file with key mappings is required to make some features work.
Because the key mappings were not at hand, I didnt know how to do structural editing, editing a multi-line expression, call docs or inspect a result). The docs seem clear enough though, just have to think about key mappings I would actually use.
Ctrl d to exit the REPL prompt back to the shell prompt. I first evaluated (quit), (exit), :repl/quit, :exit to see if they would exit the REPL, but without success.
A simple project¶
I created a simple project using Practicalli Project Templates
Then ran the Clojure REPL CLI from the root of that project, calling the -main function from the practicalli.playground namespace.
No support is provided for namespace of function name completion at present (at least not when I tried), an obvious feature missing compared to Rebel Readline.
UPDATE: this is actively being worked on.
~/temp/playground v25.0.4 ❯ clojure -M:repl/cli
Downloading: org/clojure/clojure/1.12.3/clojure-1.12.3.pom from central
Downloading: com/brunobonacci/mulog/0.9.0/mulog-0.9.0.pom from clojars
Downloading: org/clojure/clojure/1.12.3/clojure-1.12.3.jar from central
Downloading: com/brunobonacci/mulog/0.9.0/mulog-0.9.0.jar from clojars
nREPL server listening on port 33423
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by org.jline.nativ.JLineNativeLoader in an unnamed module (file:/home/practicalli/.m2/repository/org/jline/jline/4.3.1/jline-4.3.1.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
connected to nREPL on 33423
user => (require 'practicalli.playground)
nil
user => (practicalli.playground/-main)
practicalli playground service developed by the secret engineering team
nil
user => (practicalli.playground/-main {:team-name "clojure cli repl"})
practicalli playground service developed by the clojure cli repl team
nil
user => (in-ns 'practicalli.playground)
#object[clojure.lang.Namespace 0x52e8cc95 "practicalli.playground"]
practicalli.playground => (-main)
practicalli playground service developed by the secret engineering team
nil
practicalli.playground =>
Configuration file¶
A configuration file is required to define key mappings (as currently there seem to be no default mappings).
I copied the .edn file from the example configuration to the hidden path the project specifies. It seemed strange to add all the code too, but that seems to be were all the additional customisation can be done.
{:prompt dev.full-width-prompt/prompt :middleware [dev.timing/middleware dev.heap/middleware] :keybindings dev.keybindings/install :print-hook dev.hooks/pprint-values :bracket-pairs false :eval-form-at-cursor "M-e" :doc-at-cursor "M-d" :inspect "M-i"}
Staring the REPL failed when running with this configuration in the user level location.
~/temp/playground v25.0.4 ❯ clojure -M:repl/cli
Execution error (FileNotFoundException) at clojure-cli.repl.hooks/resolve-hook (hooks.clj:24).
Could not locate dev/hooks__init.class, dev/hooks.clj or dev/hooks.cljc on classpath.
Full report at:
/tmp/clojure-5814065469117925004.edn
Looking at the full report my first assumption is that dev is not on the path when Clojure CLI REPL. As the example has several dev.,,, namespaces mentioned, then that is probably why it failed.
I used only a small part of the example config
I had limited time and just wanted to see what was out of the box. The example had a config and a separate namespace which I contained more customisations (in Clojure code).
I didnt have time to go through the code examples (and didnt initiall understand why they were neccessary).
Trying again after commenting the keywords that have dev.,,, namespaces as their values
~/temp/playground v25.0.4 ❯ clojure -M:repl/cli
nREPL server listening on port 46857
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by org.jline.nativ.JLineNativeLoader in an unnamed module (file:/home/practicalli/.m2/repository/org/jline/jline/4.3.1/jline-4.3.1.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
connected to nREPL on 46857
user => (require
clojure.core/require
[& args]
Loads libs, skipping any that are already loaded. Each argument is
either a libspec that identifies a lib, a prefix list that identifies
multiple libs whose names share a common prefix, or a flag that modifies
how all the identified libs are loaded. Use :require in the ns macro
in preference to calling this directly.
Libs
A 'lib' is a named set of resources in classpath whose contents define a
library of Clojure code. Lib names are symbols and each lib is associated
with a Clojure namespace and a Java package that share its name. A lib's
name also locates its root directory within classpath using Java's
package name to classpath-relative path mapping. All resources in a lib
should be contained in the directory structure under its root directory.
All definitions a lib makes should be in its associated namespace.
... doc key again for the full doc
Auto-require namespaces¶
To make it easy to use some f the functions from clojure.repl when changing to any other namespace but user the config needs to add and auto-require.
Practicalli declarative config for Clojure CLI REPL (so far)
Now I can use convenience functions from clojure.repl unqualified in any namespace
Inspector¶
I started by evaluating a basic expression
Then use M-i to inspect the result
The inspector shows each index and value from the sequence that was returned as the result of the evaluation.
Results that return a nested map are much more interesting though.
Things that didn't work as expected¶
Not including resources directory when defined in the project deps.edn file
I have a file I slurp that resides in the root of the resources directory.
When evaluating code in the namespace that uses the file the code fails to evaluate as the file is not found.
{:paths ["src" "resources"]
:deps
{org.clojure/clojure {:mvn/version "1.12.5"}
;; Logic
org.clojure/core.match {:mvn/version "1.1.1"}
;; Date and time
clojure.java-time/clojure.java-time {:mvn/version "1.4.3"}
;; HTML generation
hiccup/hiccup {:mvn/version "2.0.0"}}
:aliases
{};; Aliases from practicalli/clojure-deps-edn included via .dir-locals
#_()}
I ran out of time to look into this further.
Debian Linux¶
After running sudo apt update I noticed a new version of Firefox and Chromium. These usually have security fixes, so good to keep up with new releases.
No obvious large or breaking changes in any of the packages, so should be safe to upgrade without causing any maintenance issues.
Apt package update - 30 September 2026
❯ sudo apt upgrade
The following package was automatically installed and is no longer required:
chromium-sandbox
Use 'sudo apt autoremove' to remove it.
Upgrading:
at-spi2-common heif-thumbnailer libgs10 libvlc-bin
at-spi2-core imagemagick libgs10-common libvlc5
base-files imagemagick-7-common libheif-plugin-aomenc libvlccore9
bash imagemagick-7.q16 libheif-plugin-dav1d libwbclient0
bind9-dnsutils jq libheif-plugin-libde265 linux-image-amd64
bind9-host libasound2-data libheif-plugin-x265 linux-libc-dev
bind9-libs libasound2-dev libheif1 locales
busybox libasound2t64 libimage-magick-perl locales-all
chromium libatk-adaptor libimage-magick-q16-perl logsave
chromium-common libatk-bridge2.0-0t64 libjq1 openssl
chromium-sandbox libatk1.0-0t64 libldb2 openssl-provider-legacy
code libatopology2t64 libmagickcore-7.q16-10 perl
curl libatspi2.0-0t64 libmagickcore-7.q16-10-extra perl-base
dcmtk-data libaudit-common libmagickwand-7.q16-10 perl-modules-5.40
dhcpcd-base libaudit1 libmbedcrypto16 python3.13
dirmngr libc-bin libmbedtls21 python3.13-dev
dnsmasq-base libc-dev-bin libmbedx509-7 python3.13-minimal
e2fsprogs libc-l10n libnfs14 python3.13-venv
exim4-base libc6 libodbc2 samba-libs
exim4-config libc6-dev libodbccr2 socat
exim4-daemon-light libc6-i386 libodbcinst2 tzdata
firefox libcap2 libpcre2-16-0 unixodbc-common
firefox-l10n-en-gb libcap2-bin libpcre2-32-0 vlc
ghostscript libcom-err2 libpcre2-8-0 vlc-bin
gimp libcurl3t64-gnutls libpcre2-dev vlc-data
gimp-data libcurl4t64 libpcre2-posix3 vlc-l10n
gir1.2-atk-1.0 libdcmtk19 libperl5.40 vlc-plugin-access-extra
gir1.2-atspi-2.0 libevent-2.1-7t64 libpython3.13 vlc-plugin-base
gir1.2-gimp-3.0 libevent-core-2.1-7t64 libpython3.13-dev vlc-plugin-notify
gir1.2-glib-2.0 libext2fs2t64 libpython3.13-minimal vlc-plugin-qt
girepository-tools libfluidsynth3 libpython3.13-stdlib vlc-plugin-samba
gnupg-utils libgimp-3.0-0 libraw23t64 vlc-plugin-skins2
google-chrome-stable libgio-2.0-dev libsmbclient0 vlc-plugin-video-output
gpg libgio-2.0-dev-bin libsqlite3-0 vlc-plugin-video-splitter
gpg-agent libgirepository-2.0-0 libss2 vlc-plugin-visualization
gpg-wks-client libglib2.0-0t64 libssh2-1t64 xdg-dbus-proxy
gpgconf libglib2.0-bin libssl3t64 xserver-common
gpgsm libglib2.0-data libtalloc2 xserver-xephyr
gpgv libglib2.0-dev libtdb1 xserver-xorg-core
gzip libglib2.0-dev-bin libtevent0t64 xserver-xorg-legacy
heif-gdk-pixbuf libgs-common libunbound8 zsh
Installing dependencies:
linux-image-6.12.111+deb13-amd64
Suggested packages:
firmware-linux-free linux-doc-6.12 debian-kernel-handbook
Summary:
Upgrading: 164, Installing: 1, Removing: 0, Not Upgrading: 0
Download size: 0 B / 845 MB
Space needed: 125 MB / 933 MB available
There was a Linux Kernel package update, so a reboot is required at some point today to pick up the new version.
Linux Kernel image package update
Setting up linux-image-6.12.111+deb13-amd64 (6.12.111-1) ...
I: /vmlinuz.old is now a symlink to boot/vmlinuz-6.12.107+deb13-amd64
I: /initrd.img.old is now a symlink to boot/initrd.img-6.12.107+deb13-amd64
I: /vmlinuz is now a symlink to boot/vmlinuz-6.12.111+deb13-amd64
I: /initrd.img is now a symlink to boot/initrd.img-6.12.111+deb13-amd64
/etc/kernel/postinst.d/initramfs-tools:
update-initramfs: Generating /boot/initrd.img-6.12.111+deb13-amd64
/etc/kernel/postinst.d/zz-update-grub:
Generating grub configuration file ...
Found background image: /usr/share/images/desktop-base/desktop-grub.png
Found linux image: /boot/vmlinuz-6.12.111+deb13-amd64
Found initrd image: /boot/initrd.img-6.12.111+deb13-amd64
Found linux image: /boot/vmlinuz-6.12.107+deb13-amd64
Found initrd image: /boot/initrd.img-6.12.107+deb13-amd64
Found linux image: /boot/vmlinuz-6.12.101+deb13-amd64
Found initrd image: /boot/initrd.img-6.12.101+deb13-amd64
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
Adding boot menu entry for UEFI Firmware Settings ...
done
Thank you.