aboutsummaryrefslogtreecommitdiff
path: root/pearl.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-01 23:40:11 -0500
committerCraig Jennings <c@cjennings.net>2026-06-01 23:40:11 -0500
commit8bae955521cdf3e2f8d5ff015b3ae36b5b8a75e8 (patch)
tree2d765a0aba944e0af418f0dbf736e10d07eef73d /pearl.el
parent5ef986c880fa6fdfafe3f11943524119065bd044 (diff)
downloadpearl-8bae955521cdf3e2f8d5ff015b3ae36b5b8a75e8.tar.gz
pearl-8bae955521cdf3e2f8d5ff015b3ae36b5b8a75e8.zip
feat(filter): :state accepts a list of state names
I made :state polymorphic, mirroring :state-type. A string still compiles to state.name.eq. A list now compiles to state.name.in, so (:state ("Todo" "In Review")) matches issues in either state. The reverse-compile inverts state.name.in back to a scalar (one name) or a list (several), so copy-down no longer refuses a Linear view filtering on a set of named states. The validator accepts a string or a list of non-empty names, the same shape it already enforces for :labels, and a list-valued :state keeps its precedence over :state-type and :open. Phase 1 of docs/multi-state-filter-spec.org. Suite, compile, and lint green.
Diffstat (limited to 'pearl.el')
-rw-r--r--pearl.el36
1 files changed, 30 insertions, 6 deletions
diff --git a/pearl.el b/pearl.el
index 0ecc03a..9aebcef 100644
--- a/pearl.el
+++ b/pearl.el
@@ -849,13 +849,15 @@ PRIORITY is an integer 0-4 or a symbol in `pearl--priority-symbols'."
(defun pearl--compile-state-filter (plist)
"Return the state sub-filter alist for PLIST, or nil when no state key is set.
-An explicit `:state' (name) or `:state-type' (one type or a list) takes
-precedence over `:open', the broad not-closed predicate."
+An explicit `:state' (a name or a list of names) or `:state-type' (one type or
+a list) takes precedence over `:open', the broad not-closed predicate."
(let ((state (plist-get plist :state))
(state-type (plist-get plist :state-type))
(open (plist-get plist :open)))
(cond
- (state (list (cons "name" (pearl--eq state))))
+ (state (list (cons "name" (if (listp state)
+ (pearl--in state)
+ (pearl--eq state)))))
(state-type (list (cons "type"
(pearl--in (if (listp state-type)
state-type
@@ -984,8 +986,18 @@ multi-value filter would change which issues match."
((or (assq 'and node) (assq 'or node))
(pearl--rc-refuse "state uses OR logic Pearl can't represent"))
((assq 'name node)
- (let ((r (pearl--rc-scalar (cdr (assq 'name node)) "state.name")))
- (if (eq (car r) 'ok) (cons 'ok (list :state (cdr r))) r)))
+ ;; :state is plural-capable (a name or a list), like :state-type:
+ ;; name.eq / name.in[one] -> scalar, name.in[many] -> list.
+ (let ((op (cdr (assq 'name node))))
+ (cond
+ ((assq 'eq op) (cons 'ok (list :state (cdr (assq 'eq op)))))
+ ((assq 'in op)
+ (let ((vals (append (cdr (assq 'in op)) nil)))
+ (cond
+ ((null vals) (pearl--rc-refuse "state.name has an empty `in'"))
+ ((= 1 (length vals)) (cons 'ok (list :state (car vals))))
+ (t (cons 'ok (list :state vals))))))
+ (t (pearl--rc-refuse "state.name uses an operator Pearl can't represent")))))
((assq 'type node)
(let ((op (cdr (assq 'type node))))
(cond
@@ -1126,7 +1138,7 @@ needs team context) is resolved upstream, not here."
(let ((order (plist-get plist :order)))
(when (and order (not (memq order '(asc desc))))
(user-error ":order must be `asc' or `desc', got %S" order)))
- (dolist (key '(:state :project :team :cycle :assignee-id :label-id))
+ (dolist (key '(:project :team :cycle :assignee-id :label-id))
(let ((val (plist-get plist key)))
(when (and (stringp val) (string-empty-p val))
(user-error "%s must not be an empty string" key))))
@@ -1136,6 +1148,18 @@ needs team context) is resolved upstream, not here."
(cl-some (lambda (x) (not (and (stringp x) (not (string-empty-p x)))))
labels)))
(user-error ":labels must be a list of non-empty strings")))
+ ;; :state is a single name OR a list of non-empty names (mirrors :labels).
+ (let ((state (plist-get plist :state)))
+ (cond
+ ((null state))
+ ((stringp state)
+ (when (string-empty-p state)
+ (user-error ":state must not be an empty string")))
+ ((listp state)
+ (when (cl-some (lambda (x) (not (and (stringp x) (not (string-empty-p x)))))
+ state)
+ (user-error ":state must be a name or a list of non-empty names")))
+ (t (user-error ":state must be a name or a list of names, got %S" state))))
t)
;;; Issue Model Normalization