Last active
October 20, 2025 17:22
-
-
Save akashpal-21/b9d07fb981dd245b1defd87bb42e4d0d to your computer and use it in GitHub Desktop.
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
| ;; README — org-roam-sequence | |
| ;; | |
| ;; Helpers | |
| ;; ------- | |
| ;; org-roam-sequence-id-split (id) | |
| ;; > split "1a2" -> (1 "a" 2). | |
| ;; | |
| ;; org-roam-sequence-id-join (parts) | |
| ;; > join (1 "a" 2) -> "1a2". | |
| ;; | |
| ;; org-roam-sequence--alpha-inc (s) | |
| ;; > bijective base-26 increment: a->b, z->aa, az->ba. | |
| ;; | |
| ;; org-roam-sequence--fetch-progeny (prefix expected-parts) | |
| ;; > query DB for ids with given prefix. | |
| ;; > return only direct children tails (no grandchildren). | |
| ;; | |
| ;; org-roam-sequence--next-id (parts &optional child) | |
| ;; > compute next suffix by scanning existing tails. | |
| ;; > numeric -> max+1, alpha -> longest+lexicographic then alpha-inc. | |
| ;; | |
| ;; Interfaces | |
| ;; ---------- | |
| ;; org-roam-sequence-next-sibling-id (id) | |
| ;; > compute next sibling id. | |
| ;; | |
| ;; org-roam-sequence-next-child-id (id) | |
| ;; > compute next child id. | |
| ;; | |
| ;; org-roam-sequence-add-child | |
| ;; org-roam-sequence-add-sibling | |
| ;; > interactive commands, prompt for title, capture node. | |
| ;; | |
| ;; Control flow | |
| ;; ------------ | |
| ;; - Split id into parts. | |
| ;; - Decide prefix: parent for sibling, self for child. | |
| ;; - Fetch progeny of prefix. | |
| ;; - If last part numeric -> find max numeric tail -> +1. | |
| ;; - If last part alpha -> find best alpha tail -> alpha-inc. | |
| ;; - Join prefix + new suffix. | |
| ;; Helpers and API | |
| (defun org-roam-sequence-id-split (id) | |
| "Split alphanumeric ID into alternating parts. | |
| Example: \"1a2\" -> (1 \"a\" 2)." | |
| (let ((i 0) parts) | |
| (while (< i (length id)) | |
| (cond | |
| ((string-match "\\`[0-9]+" (substring id i)) | |
| (push (string-to-number (match-string 0 (substring id i))) parts) | |
| (setq i (+ i (length (match-string 0 (substring id i)))))) | |
| ((string-match "\\`[a-z]+" (substring id i)) | |
| (push (match-string 0 (substring id i)) parts) | |
| (setq i (+ i (length (match-string 0 (substring id i)))))))) | |
| (nreverse parts))) | |
| (defun org-roam-sequence-id-join (parts) | |
| "Join PARTS back into an alphanumeric ID." | |
| (mapconcat (lambda (p) (if (numberp p) (number-to-string p) p)) parts "")) | |
| (defun org-roam-sequence--alpha-inc (s) | |
| "Increment bijective base-26 string S: a->b, z->aa, az->ba." | |
| (let* ((chars (string-to-list (downcase s))) | |
| (n (length chars)) | |
| (carry 1)) | |
| (dotimes (i n) | |
| (let* ((idx (- n i 1)) | |
| (c (nth idx chars))) | |
| (if (= carry 1) | |
| (if (= c ?z) | |
| (setf (nth idx chars) ?a | |
| carry 1) | |
| (setf (nth idx chars) (1+ c) | |
| carry 0))))) | |
| (if (= carry 1) | |
| (concat "a" (apply #'string chars)) | |
| (apply #'string chars)))) | |
| (defun org-roam-sequence--fetch-progeny (prefix expected-parts) | |
| "Return list of tail strings that are direct children of PREFIX. | |
| Drops all instances of grandchildren. | |
| Assumption: No orphan.Ex: !exist 3c3 w/o exist 3c." | |
| (let* ((sql (concat "select id from nodes where id like '\"" prefix "%%\"';")) | |
| (rows (org-roam-db-query sql))) | |
| (delq nil | |
| (mapcar (lambda (row) | |
| (let* ((id (car row)) | |
| (parts (mapcar (lambda (p) (if (numberp p) (number-to-string p) p)) | |
| (org-roam-sequence-id-split id))) | |
| (tail (car (last parts)))) | |
| (when (and (= (length parts) (1+ expected-parts)) | |
| (or (string-match-p "\\`[0-9]+\\'" tail) | |
| (string-match-p "\\`[a-z]+\\'" (downcase tail)))) | |
| tail))) | |
| rows)))) | |
| (defun org-roam-sequence--next-id (parts &optional child) | |
| "Compute next id from PARTS. | |
| If CHILD is non-nil compute next child, | |
| otherwise compute available sibling." | |
| (let* ((last (car (last parts))) | |
| (prefix-parts (if child parts | |
| (butlast parts))) | |
| (expected (length prefix-parts)) | |
| (prefix (org-roam-sequence-id-join prefix-parts)) | |
| (tails (org-roam-sequence--fetch-progeny prefix expected)) | |
| (numericp (numberp last))) | |
| (if numericp | |
| (let ((maxn 0)) | |
| (dolist (tail tails) | |
| (when (string-match-p "\\`[0-9]+\\'" tail) | |
| (setq maxn (max maxn (string-to-number tail))))) | |
| (org-roam-sequence-id-join (append prefix-parts (list (number-to-string (1+ maxn)))))) | |
| (let ((best nil)) | |
| (dolist (tail tails) | |
| (when (and (stringp tail) (string-match-p "\\`[a-z]+\\'" tail)) | |
| (when (or (null best) | |
| (> (length tail) (length best)) | |
| (and (= (length tail) (length best)) (string> tail best))) | |
| (setq best tail)))) | |
| (org-roam-sequence-id-join | |
| (append prefix-parts (list (if best (org-roam-sequence--alpha-inc best) "a")))))))) | |
| (defun org-roam-sequence-next-sibling-id (id) | |
| "Return next available sibling id for ID." | |
| (org-roam-sequence--next-id (org-roam-sequence-id-split id) nil)) | |
| (defun org-roam-sequence-next-child-id (id) | |
| "Return next available child id for ID." | |
| (org-roam-sequence--next-id (org-roam-sequence-id-split id) 'child)) | |
| ;; HHHH------------------------------ | |
| ;; Interactive fns | |
| (defun org-roam-sequence-add-child () | |
| "Capture a new child node under the current Org-roam node. | |
| Prompts the user for a title." | |
| (interactive) | |
| (let* ((current-node (org-roam-node-at-point)) | |
| (current-id (org-roam-node-id current-node)) | |
| (child-id (org-roam-sequence-next-child-id current-id)) | |
| (child-title (read-string "Child title: ")) | |
| (child-node (org-roam-node-create :id child-id :title child-title))) | |
| (org-roam-capture- :node child-node | |
| :props '(:immediate-finish nil)))) | |
| (defun org-roam-sequence-add-sibling () | |
| "Capture a new sibling node to the current Org-roam node. | |
| Prompts the user for a title." | |
| (interactive) | |
| (let* ((current-node (org-roam-node-at-point)) | |
| (current-id (org-roam-node-id current-node)) | |
| (sibling-id (org-roam-sequence-next-sibling-id current-id)) | |
| (sibling-title (read-string "Sibling title: ")) | |
| (sibling-node (org-roam-node-create :id sibling-id :title sibling-title))) | |
| (org-roam-capture- :node sibling-node | |
| :props '(:immediate-finish nil)))) | |
| ;; HHHH------------------------------ | |
| ;; Ignore next line unless loading file | |
| (provide 'org-roam-sequence) | |
| ;; End of file |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment