aboutsummaryrefslogtreecommitdiff
path: root/pearl.el
diff options
context:
space:
mode:
Diffstat (limited to 'pearl.el')
-rw-r--r--pearl.el44
1 files changed, 42 insertions, 2 deletions
diff --git a/pearl.el b/pearl.el
index bc9319d..18c65c2 100644
--- a/pearl.el
+++ b/pearl.el
@@ -1535,6 +1535,32 @@ state-name-to-keyword mapping."
"-+" "-+")))
(if (string-empty-p slug) "TODO" slug)))
+(defun pearl--label-name-to-tag (name)
+ "Slugify a Linear label NAME to an Org tag.
+Downcased, each run of characters outside [[:alnum:]_] replaced by a single
+underscore (alnum is Unicode-aware, so accented and non-Latin letters survive),
+with leading and trailing underscores trimmed. Org tags allow no hyphens or
+spaces, unlike the TODO keywords `pearl--state-name-to-keyword' builds. A name
+that slugifies to empty (all punctuation) returns the empty string; the caller
+drops it from the heading and keeps it in the `:LINEAR-LABELS:' drawer."
+ (string-trim
+ (replace-regexp-in-string "[^[:alnum:]_]+" "_" (downcase (or name "")))
+ "_+" "_+"))
+
+(defun pearl--label-tags (labels)
+ "Return the ordered, de-duplicated Org tags for a list of LABEL plists.
+Each label's `:name' is slugified via `pearl--label-name-to-tag'; empty slugs
+are dropped and duplicates removed, first occurrence winning, so two label
+names that slugify to the same tag render it once at the first's position."
+ (let ((seen (make-hash-table :test 'equal))
+ (tags '()))
+ (dolist (label labels)
+ (let ((tag (pearl--label-name-to-tag (plist-get label :name))))
+ (unless (or (string-empty-p tag) (gethash tag seen))
+ (puthash tag t seen)
+ (push tag tags))))
+ (nreverse tags)))
+
(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
@@ -1804,11 +1830,13 @@ identifier prefix -- so an unedited heading is a no-op on title sync)."
(plist-get issue :labels) ", "))
(label-ids (mapconcat (lambda (l) (or (plist-get l :id) ""))
(plist-get issue :labels) " "))
+ (tags (pearl--label-tags (plist-get issue :labels)))
+ (tag-string (if tags (concat " :" (string-join tags ":") ":") ""))
(body-org (pearl--md-to-org description)))
(concat
- (format "** %s %s%s\n" todo
+ (format "** %s %s%s%s\n" todo
(if (string-empty-p priority) "" (concat priority " "))
- heading-title)
+ heading-title tag-string)
":PROPERTIES:\n"
(format ":LINEAR-ID: %s\n" (or (plist-get issue :id) ""))
(format ":LINEAR-IDENTIFIER: %s\n" (or (plist-get issue :identifier) ""))
@@ -2747,6 +2775,16 @@ inside an issue subtree."
(org-entry-put nil "LINEAR-ASSIGNEE-ID" assignee-id)
(message "Set assignee to %s (save to push)" assignee-name))))
+(defun pearl--set-heading-label-tags (tags)
+ "Replace the Org tags on the issue heading at point with TAGS, a list of strings.
+Only the heading's tag set changes: the TODO keyword, priority cookie, title,
+identifier prefix, body, and drawer are preserved. Pearl owns the entire
+issue-heading tag set, so any hand-added tags are dropped. An empty TAGS list
+clears the heading's tags. Point must be on or inside the issue heading."
+ (save-excursion
+ (org-back-to-heading t)
+ (org-set-tags tags)))
+
;;;###autoload
(defun pearl-edit-labels (label-names)
"Set the labels of the issue at point to LABEL-NAMES in the buffer.
@@ -2774,6 +2812,8 @@ inside an issue subtree."
(org-entry-put nil "LINEAR-LABELS"
(format "[%s]" (mapconcat #'identity label-names ", ")))
(org-entry-put nil "LINEAR-LABEL-IDS" (string-join label-ids " "))
+ (pearl--set-heading-label-tags
+ (pearl--label-tags (mapcar (lambda (n) (list :name n)) label-names)))
(message "Set labels to %s (save to push)"
(if label-names (mapconcat #'identity label-names ", ") "none")))))