Skip to content

Instantly share code, notes, and snippets.

@hkjels
Created May 16, 2025 12:26
Show Gist options
  • Save hkjels/202483d446b552ff96b2c3137665aed1 to your computer and use it in GitHub Desktop.
Save hkjels/202483d446b552ff96b2c3137665aed1 to your computer and use it in GitHub Desktop.
Align a vector of Clojure maps so that keys line up across maps like columns
(defun clojure-align--current-indent (pos)
"Return indentation (string of spaces) at POS."
(save-excursion
(goto-char pos)
(back-to-indentation)
(buffer-substring (line-beginning-position) (point))))
(defun clojure-align--lines (beg end)
"Return list of lines from region between BEG and END."
(split-string (buffer-substring-no-properties beg end) "\n"))
(defun clojure-align--extract-rows (lines)
"Extract rows of key-value pairs from map LINES."
(mapcar (lambda (line)
(let ((row '())
(pos 0))
(while (string-match ":\\(\\w+\\)\\s-*\\([^:}\n ]+\\)?" line pos)
(let ((key (match-string 1 line))
(val (match-string 2 line)))
(push (cons key (or val "")) row)
(setq pos (match-end 0))))
(reverse row)))
lines))
(defun clojure-align--all-keys (rows)
"Return list of all unique keys in ROWS, in order of appearance."
(let ((keys '()))
(dolist (row rows)
(dolist (entry row)
(let ((k (car entry)))
(unless (member k keys)
(push k keys)))))
(reverse keys)))
(defun clojure-align--column-widths (all-keys rows)
"Calculate maximum value widths for ALL-KEYS in ROWS."
(mapcar
(lambda (k)
(apply #'max
(length k)
(mapcar (lambda (row)
(length (or (cdr (assoc k row)) "")))
rows)))
all-keys))
(defun clojure-align--format-rows (rows all-keys col-widths indent)
"Format ROWS into aligned strings using ALL-KEYS and COL-WIDTHS.
It also preserves INDENT."
(mapcar
(lambda (row)
(let ((parts '()))
(cl-mapcar
(lambda (k w)
(let ((v (or (cdr (assoc k row)) "")))
(push (format ":%s %s"
(format (format "%%-%ds" (length k)) k)
(format (format "%%-%ds" w) v))
parts)))
all-keys col-widths)
(concat indent " {" (string-join (reverse parts) " ") "}")))
rows))
(defun clojure-align-maps-in-vector (beg end)
"Align a vector of Clojure maps so that keys line up across maps like columns.
If a region is active, align from BEG to END. Otherwise, align the top-level form at point."
(interactive
(if (use-region-p)
(list (region-beginning) (region-end))
(let ((bounds (bounds-of-thing-at-point 'sexp)))
(list (car bounds) (cdr bounds)))))
(save-excursion
(let* ((indent (clojure-align--current-indent beg))
(lines (clojure-align--lines beg end))
(rows (clojure-align--extract-rows lines))
(all-keys (clojure-align--all-keys rows))
(col-widths (clojure-align--column-widths all-keys rows))
(result-lines (clojure-align--format-rows rows all-keys col-widths indent)))
(delete-region beg end)
(insert (concat indent "[\n"
(mapconcat #'identity result-lines "\n")
"\n" indent "]")))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment