Posts tagged "programming":
Personalized embark actions
I adopted Embark in 2025. To be honest, it was more because it seemed to be the typical companion for vertico + consul, than because I really understood what I could do with it.
Besides some very basic use (which I routinely forget) I didn’t bother to explore it in more detail.
Until I noticed that Embark could give me a fundamental component for a mechanism I had been trying to implement in the past.
First, some context. For my job, I often have to navigate through a quite large dbt project. If I were less picky about text editors I’d just use VS Code and the PowerUser package. But, it's no mistery, I’m fond of staying inside Emacs. Where, quite incredibly, there is no dbt support package available.
One simple thing I'd particulary like is the ability of hopping from one file to another following dbt references.
A reference, in dbt, is a Jinja expression that can be used in place of a table or view name, to inform dbt of a dependency between two models (a model is, simplifying, a named SQL query, saved in a file with that name).
For example, you can write a file called us_customers.sql like this:
select * from {{ ref('mart_customers') }}
where country = 'US'
and dbt knows that the mart_customers model (contained in a file
mart_customers.sql, in some directory of the project) is an upstream
dependency of us_customers.
This is useful because then you can invoke dbt like this (notice the +
sign):
dbt run -m mart_customers+
and dbt knows that it needs to refresh mart_sustomers
PLUS its downstream dependencies, including us_customers.
A typical dbt project may contain hundreds of interdependent models, organized in a tree of directories. So, references are very convenient.
Now to my specific Emacs problem. What I'd like to be able to do is, when the cursor is on a reference, to visit the file that contains the definition of the referenced model, without having to navigate the directories to find it.
The first useful thing that Embark does is figuring out what's the
identifier I'm on with the cursor. In the example above, that would be
the string mart_customers. In Embark (and Emacs) parlance, that is
called the target at point. More precisely, in this case, that's the
identifier at point.
When I invoke embark-act (it's customary to map this function to
C-.), Embark figures what's the identifier at point, and I get a
contextual list of actions that can be applied.
Now, I need to add my personalized action to that list.
For my needs, that is just a function that, given a reference name
(the identifier at point) puts .sql at the end of the string, then
uses the result as an argument for project-root-find-file. Model files
are guaranteed to be unique across all the project (otherwise dbt does
not work), so there is no ambiguity about the file I want to visit.
Here the implementation, and how it was integrated in the Embark's identifier target list.
(defun larsen/embark-dbt-navigate-reference (reference &rest args)
"Opens the model source code for the dbt reference at point"
(interactive "sReference: ")
(project-root-find-file (string-join (list reference ".sql"))))
(use-package embark
:ensure t
:bind (("C-." . embark-act)
:map embark-identifier-map
("D" . larsen/embark-dbt-navigate-reference)))
Extending image-dired
Emacs' image-dired is a companion package to dired that makes it easier to browse images and to do some very basic image editing. It is a perfect example of how Emacs can be extended to accomplish tasks that go beyond text manipulation.
I will not go through the details of how to use it, but I will illustrate a couple of things I recently added to it in my configuration.
I typically use image-dired to manipulate pictures that accompany blog posts and other stuff I publish on my personal website. These pictures are often exported from Google Photos, and when that is the case their size is usually not suitable for web.
So, I want to be able to know their resolution and, when it is too high, to quickly resize them.
More details in the header line
Let's tackle the first problem first. The image-dired header line only displays the name and the size of the the image file.
Unfortunately, image-dired does not provide a direct way to customize the header line format, so I have to recur to advicing.
The general idea of function advicing is to change the behavior of an
existing function without altering its original code. More
specifically, it allows to provide a new function that is called
:before or :after each call to the original function, or even to
replace the original function into a new one that can intercept the
function call and completely control what is returned (this function
composition method is called :around). Refer to the manual for further
options and instructions.
In general, I tend to avoid functions advicing because it creates an invisible layer of indirection between the function definition and its behaviour; in other words, it makes it impossible to know the real result of a function call by just looking at the code of its implementation.
Anyway, here my code:
(defun my/image-size (file)
(with-temp-buffer
(call-process "identify" nil t nil "-format" "%wx%h" file )
(buffer-string)))
(defun my/image-dired-enrich-properties (orig-fun buf file image-count props comment)
(let ((orig-str (apply orig-fun (list buf file image-count props comment)))
(size (my/image-size file)))
(concat orig-str " " size)))
(advice-add 'image-dired-format-properties-string :around #'my/image-dired-enrich-properties)
The original function is image-dired-format-properties-string. I
encapsulated a call to the external tool identify (part of
ImageMagick) into my/image-size, which I then call to add that
particular bit of information to the header line string. In what is a
typical :around composition pattern, my advice function calls the
original function, then adds extra information to the original input.
Here an example of the results:
Interactively resizing images
The mechanism here is even simpler. We just need to provide a function that can resize an image (again, using an utility courtesy of ImageMagick), and wrap it in a interactive function we can call when we are in the image-dired buffer.
(defun my/resize-image (file size)
"Resize the image in FILE to the specified SIZE (interpreted as a percentage). "
(call-process "magick" nil t nil file "-resize" (format "%d%%" size) file))
(defun image-dired-thumbnail-resize-image ()
"Resize the image at point. The size is specified at the prompt as a percentage of the original size."
(interactive nil image-dired-thumbnail-mode)
(if (not (image-dired-image-at-point-p))
(message "No thumbnail at point")
(let* ((file (image-dired-original-file-name))
(defdir default-directory))
(with-temp-buffer
(setq default-directory defdir)
(if (eq 0 (my/resize-image file (read-number "New size (%): " 50)))
(message "Successfully resized image")
(error "Could not resize image: %s"
(string-replace "\n" "" (buffer-string))))))))
If we want, we can even map this function to a keystroke, manipulating its specific keymap.
(keymap-set image-dired-thumbnail-mode-map "Z" 'image-dired-thumbnail-resize-image)