Created
May 16, 2025 12:26
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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