From 49d0fa8306c0ea2d66b53d2ee41a26d221d880d2 Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Sun, 18 Aug 2019 22:57:38 -0500 Subject: [PATCH] Add: Timestamp selectors :on a number This ought to be pretty thorough now. --- README.org | 11 ++++++++-- org-ql.el | 35 ++++++++++++++++++++++++++++-- tests/test-org-ql.el | 51 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 90 insertions(+), 7 deletions(-) diff --git a/README.org b/README.org index 0e38d79..86ad046 100644 --- a/README.org +++ b/README.org @@ -207,15 +207,22 @@ Arguments are listed next to predicate names, where applicable. :TOC: ignore :END: -All of these selectors take optional keyword arguments ~:from~, ~:to:~, and ~:on~. If ~:from~, return non-nil if entry has a timestamp on or after ~:from~. If ~:to~, return non-nil if entry has a timestamp on or before ~:to~. If ~:on~, return non-nil if entry has a timestamp on date ~:on~. Argument values should be either ~ts~ structs, or strings parseable by ~parse-time-string~ which may omit the time value. +All of these selectors take optional keyword arguments ~:from~, ~:to:~, and ~:on~: ++ If ~:from~, return non-nil if entry has a timestamp on or after ~:from~. ++ If ~:to~, return non-nil if entry has a timestamp on or before ~:to~. ++ If ~:on~, return non-nil if entry has a timestamp on date ~:on~. + +Argument values should be either a number of days (positive to look forward, or negative to look backward), a ~ts~ struct, or a string parseable by ~parse-time-string~ which may omit the time value. + +*Selectors:* + ~ts~ :: Return non-nil if current entry has a timestamp in given period. If no arguments are specified, return non-nil if entry has any timestamp. + ~ts-active~ :: Like ~ts~, but only matches active timestamps. + ~ts-a~ :: Like ~ts~, but only matches active timestamps. + ~ts-inactive~ :: Like ~ts~, but only matches inactive timestamps. + ~ts-i~ :: Like ~ts~, but only matches inactive timestamps. -The following selectors can also take a single argument, a number, which looks backward or forward a number of days. The number can also be negative to invert the direction. +The following selectors, in addition to the keyword arguments, can also take a single argument, a number, which looks backward or forward a number of days. The number can be negative to invert the direction. *Backward-looking:* + ~clocked~ :: Return non-nil if current entry was clocked in given period. If no arguments are specified, return non-nil if entry was clocked at any time. Note: Clock entries are expected to be clocked out. Currently clocked entries (i.e. with unclosed timestamp ranges) are ignored. diff --git a/org-ql.el b/org-ql.el index a8da355..c8dd6f6 100644 --- a/org-ql.el +++ b/org-ql.el @@ -313,14 +313,45 @@ Replaces bare strings with (regexp) selectors, and appropriate (ts-apply :hour 23 :minute 59 :second 59)))) `(,pred :to ,to))) (`(,(and pred (or 'deadline 'scheduled 'planning)) - ,(and type (or :from :to :on)) + ,(and direction (or :from :to :on)) ,(and num-days (pred numberp))) (let ((target (->> (ts-now) (ts-adjust 'day num-days) (ts-apply :hour 23 :minute 59 :second 59)))) - `(,pred ,type ,target))) + `(,pred ,direction ,target))) + (`(,(and pred (or 'ts 'ts-a 'ts-i 'ts-active 'ts-inactive)) + ,(and direction (or :from :to)) + ,(and num-days (pred numberp))) + (-let* ((type (pcase-exhaustive pred + ((or 'ts-i 'ts-inactive) 'inactive) + ((or 'ts-a 'ts-active) 'active) + ('ts 'both))) + ((hour minute second) (pcase-exhaustive direction + (:from '(0 0 0)) + (:to '(23 59 59)))) + (ts (->> (ts-now) + (ts-adjust 'day num-days) + (ts-apply :hour hour :minute minute :second second)))) + `(ts :type ,type ,direction ,ts))) + (`(,(and pred (or 'ts 'ts-a 'ts-i 'ts-active 'ts-inactive)) + :on + ,(and num-days (pred numberp))) + ;; This rule is only for :on, because we must provide both :from and :to + ;; timestamps to the (ts) selector for the --query-predicate function. + (-let* ((type (pcase-exhaustive pred + ((or 'ts-i 'ts-inactive) 'inactive) + ((or 'ts-a 'ts-active) 'active) + ('ts 'both))) + (from (->> (ts-now) + (ts-adjust 'day num-days) + (ts-apply :hour 0 :minute 0 :second 0))) + (to (->> (ts-now) + (ts-adjust 'day num-days) + (ts-apply :hour 23 :minute 59 :second 59)))) + `(ts :type ,type :from ,from :to ,to))) (`(,(or 'ts-active 'ts-a) . ,rest) `(ts :type active ,@rest)) (`(,(or 'ts-inactive 'ts-i) . ,rest) `(ts :type inactive ,@rest)) + ;; Any other form: passed through unchanged. (_ element)))) (rec query))) diff --git a/tests/test-org-ql.el b/tests/test-org-ql.el index e92d4a3..05bc0f2 100644 --- a/tests/test-org-ql.el +++ b/tests/test-org-ql.el @@ -529,17 +529,32 @@ RESULTS should be a list of strings as returned by (org-ql-expect ((ts :from "2019-06-08" :type active)) nil)) + (org-ql-it ":from a number of days" + (org-ql-then + (org-ql-expect ((ts :from 5)) + '("Take over the universe" "Visit Mars" "Visit the moon" "Renew membership in supervillain club" "Internet" "Spaceship lease" "Rewrite Emacs in Common Lisp")))) + (org-ql-it ":to a timestamp" (org-ql-expect ((ts :to "2019-06-10" :type active)) '("Take over the universe" "Take over the world" "Skype with president of Antarctica" "Visit Mars" "Visit the moon" "Practice leaping tall buildings in a single bound" "Renew membership in supervillain club" "Order a pizza" "Get haircut" "Internet" "Spaceship lease" "Fix flux capacitor" "/r/emacs" "Shop for groceries" "Rewrite Emacs in Common Lisp")) (org-ql-expect ((ts :to "2017-07-04" :type active)) '("Skype with president of Antarctica"))) + (org-ql-it ":to a number of days" + (org-ql-then + (org-ql-expect ((ts :to -1)) + '("Skype with president of Antarctica")))) + (org-ql-it ":on a timestamp" (org-ql-expect ((ts :on "2017-07-05" :type active)) '("Practice leaping tall buildings in a single bound" "Order a pizza" "Get haircut" "Fix flux capacitor" "/r/emacs" "Shop for groceries" "Rewrite Emacs in Common Lisp")) (org-ql-expect ((ts :on "2019-06-09" :type active)) - nil))) + nil)) + + (org-ql-it ":on a number of days" + (org-ql-then + (org-ql-expect ((ts-active :on 2)) + '("Take over the world"))))) (describe "inactive" @@ -553,17 +568,32 @@ RESULTS should be a list of strings as returned by (org-ql-expect ((ts :from "2019-06-08" :type inactive)) nil)) + (org-ql-it ":from a number of days" + (org-ql-then + (org-ql-expect ((ts-i :from 5)) + '("Visit the moon" "Rewrite Emacs in Common Lisp")))) + (org-ql-it ":to a timestamp" (org-ql-expect ((ts :to "2019-06-10" :type inactive)) '("Test data" "Visit the moon" "Learn universal sign language" "Rewrite Emacs in Common Lisp")) (org-ql-expect ((ts :to "2017-07-04" :type inactive)) 'nil)) + (org-ql-it ":to a number of days" + (org-ql-then + (org-ql-expect ((ts-i :to 5)) + '("Test data" "Learn universal sign language")))) + (org-ql-it ":on a timestamp" (org-ql-expect ((ts :on "2017-07-05" :type inactive)) '("Test data" "Learn universal sign language")) (org-ql-expect ((ts :on "2019-06-09" :type inactive)) - nil))) + nil)) + + (org-ql-it ":on a number of days" + (org-ql-then + (org-ql-expect ((ts-inactive :on 19)) + '("Visit the moon" "Rewrite Emacs in Common Lisp"))))) (describe "both" @@ -584,6 +614,11 @@ RESULTS should be a list of strings as returned by (org-ql-expect ((ts :from "2019-06-08" :type both)) nil)) + (org-ql-it ":from a number of days" + (org-ql-then + (org-ql-expect ((ts :from -5)) + '("Test data" "Take over the universe" "Take over the world" "Skype with president of Antarctica" "Visit Mars" "Visit the moon" "Practice leaping tall buildings in a single bound" "Renew membership in supervillain club" "Learn universal sign language" "Order a pizza" "Get haircut" "Internet" "Spaceship lease" "Fix flux capacitor" "/r/emacs" "Shop for groceries" "Rewrite Emacs in Common Lisp")))) + (org-ql-it ":to a timestamp" (org-ql-expect ((ts :to "2017-07-06")) '("Test data" "Skype with president of Antarctica" "Practice leaping tall buildings in a single bound" "Learn universal sign language" "Order a pizza" "Get haircut" "Fix flux capacitor" "/r/emacs" "Shop for groceries" "Rewrite Emacs in Common Lisp")) @@ -594,6 +629,11 @@ RESULTS should be a list of strings as returned by (org-ql-expect ((ts :to "2017-07-04" :type both)) '("Skype with president of Antarctica"))) + (org-ql-it ":to a number of days" + (org-ql-then + (org-ql-expect ((ts :to 5)) + '("Test data" "Take over the world" "Skype with president of Antarctica" "Practice leaping tall buildings in a single bound" "Renew membership in supervillain club" "Learn universal sign language" "Order a pizza" "Get haircut" "Fix flux capacitor" "/r/emacs" "Shop for groceries" "Rewrite Emacs in Common Lisp")))) + (org-ql-it ":on a timestamp" (org-ql-expect ((ts :on "2017-07-05")) '("Test data" "Practice leaping tall buildings in a single bound" "Learn universal sign language" "Order a pizza" "Get haircut" "Fix flux capacitor" "/r/emacs" "Shop for groceries" "Rewrite Emacs in Common Lisp")) @@ -602,7 +642,12 @@ RESULTS should be a list of strings as returned by (org-ql-expect ((ts :on "2019-06-09")) nil) (org-ql-expect ((ts :on "2019-06-09" :type both)) - nil)))) + nil)) + + (org-ql-it ":on a number of days" + (org-ql-then + (org-ql-expect ((ts :on 5)) + '("Renew membership in supervillain club")))))) (describe "Compound queries"