aboutsummaryrefslogtreecommitdiff
path: root/pearl.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-25 22:23:43 -0500
committerCraig Jennings <c@cjennings.net>2026-05-25 22:23:43 -0500
commit768c9c0122130d44e9243f22f7681677623b713a (patch)
tree81d234dde8117dafb9fd67b4b90d918108801c9d /pearl.el
parentc9ff295f02c355f889d1605853c148bbce404d52 (diff)
downloadpearl-768c9c0122130d44e9243f22f7681677623b713a.tar.gz
pearl-768c9c0122130d44e9243f22f7681677623b713a.zip
feat(render): add the pure helpers for workspace-derived TODO keywords
Two pure functions behind the coming workspace-faithful #+TODO line. pearl--state-name-to-keyword slugifies a Linear state name to an Org keyword: upcased, non-alphanumeric runs collapsed to a single hyphen, edges trimmed, Unicode letters kept (so "Ångström" survives), and an all-symbol name falls back to TODO. pearl--derive-todo-line turns an ordered state list into the "ACTIVE... | DONE..." string: completed/canceled/duplicate types go after the bar, the rest before, order preserved within each side, de-duplicated by slug, with the hardcoded default returned for an empty list. There's no caller yet -- the render integration and the gather pipeline land next. This is the derivation half of the keyword spec. The sync-back half it described is superseded by save-model-v2 (state reconciles at save) and the separate keyword-cycle task.
Diffstat (limited to 'pearl.el')
-rw-r--r--pearl.el35
1 files changed, 35 insertions, 0 deletions
diff --git a/pearl.el b/pearl.el
index f148992..b6e1e8e 100644
--- a/pearl.el
+++ b/pearl.el
@@ -1534,6 +1534,41 @@ STATE is the Linear state string."
(or (cdr (assoc state pearl-state-to-todo-mapping))
"TODO")) ; Default fallback
+(defun pearl--state-name-to-keyword (name)
+ "Slugify a Linear state NAME to an Org TODO keyword.
+Upcased, each run of non-alphanumerics replaced by a single hyphen (alnum is
+Unicode-aware, so accented and non-Latin letters survive), with leading and
+trailing hyphens trimmed. An all-symbol name (empty slug) falls back to
+\"TODO\". This is the workspace-faithful replacement for the static
+`pearl-state-to-todo-mapping' lookup."
+ (let ((slug (string-trim
+ (replace-regexp-in-string "[^[:alnum:]]+" "-" (upcase (or name "")))
+ "-+" "-+")))
+ (if (string-empty-p slug) "TODO" slug)))
+
+(defun pearl--derive-todo-line (states)
+ "Build the `#+TODO:' keyword string from an ordered STATES list.
+Each state is a plist with at least :name and :type. States whose :type is
+completed / canceled / duplicate go after the `|' (the done side); the rest go
+before it. The caller's order is preserved within each side; keywords are
+slugified (`pearl--state-name-to-keyword') and de-duplicated by slug, first-seen
+winning. An empty STATES list returns the hardcoded default so the rendered
+file always has a valid `#+TODO:' line."
+ (if (null states)
+ "TODO IN-PROGRESS IN-REVIEW BACKLOG BLOCKED | DONE"
+ (let ((active '()) (done '()) (seen (make-hash-table :test 'equal)))
+ (dolist (s states)
+ (let ((kw (pearl--state-name-to-keyword (plist-get s :name))))
+ (unless (gethash kw seen)
+ (puthash kw t seen)
+ (if (member (plist-get s :type) '("completed" "canceled" "duplicate"))
+ (push kw done)
+ (push kw active)))))
+ (let ((active-str (string-join (nreverse active) " "))
+ (done-str (string-join (nreverse done) " ")))
+ (concat active-str " |"
+ (if (string-empty-p done-str) "" (concat " " done-str)))))))
+
(defun pearl--get-todo-states-pattern ()
"Return the regex pattern matching the Org TODO states.
Built from the org keywords in `pearl-state-to-todo-mapping' and