[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] scratch/org-edna 37e9b58 50/72: Fixed chain-find and planning act
From: |
Ian Dunn |
Subject: |
[elpa] scratch/org-edna 37e9b58 50/72: Fixed chain-find and planning actions |
Date: |
Sun, 21 May 2017 21:11:26 -0400 (EDT) |
branch: scratch/org-edna
commit 37e9b58e8d86d61c9b43d21066122b7d6b0a5683
Author: Ian D <address@hidden>
Commit: Ian D <address@hidden>
Fixed chain-find and planning actions
* org-edna.el (org-edna-finder/chain-find): Fixed to match org-depend.el
(org-edna--handle-planning): Fix to recognize when a time was used.
* org-edna.org: Documented planning actions.
* org-edna-tests.el: Added tests for planning.
* org-edna-tests.org: Added planning test entry.
---
org-edna-tests.el | 64 ++++++++++++++++++++++
org-edna-tests.org | 5 ++
org-edna.el | 40 ++++++++++----
org-edna.org | 153 +++++++++++++++++++++++++++++++++++++++++++++--------
4 files changed, 228 insertions(+), 34 deletions(-)
diff --git a/org-edna-tests.el b/org-edna-tests.el
index 2a523c9..14c0caa 100644
--- a/org-edna-tests.el
+++ b/org-edna-tests.el
@@ -108,6 +108,10 @@
(defconst org-edna-test-file
(expand-file-name "org-edna-tests.org" org-edna-test-dir))
+;; Jan 15, 2000; chosen at random
+(defconst org-edna-test-time
+ (encode-time 0 0 0 15 1 2000))
+
;; Finders
@@ -161,8 +165,68 @@
(org-edna-action/todo nil "DONE")
(should (string-equal (org-entry-get nil "TODO") "DONE"))
(org-edna-action/todo nil "TODO")
+ (should (string-equal (org-entry-get nil "TODO") "TODO"))
+ (org-edna-action/todo nil 'DONE)
+ (should (string-equal (org-entry-get nil "TODO") "DONE"))
+ (org-edna-action/todo nil 'TODO)
(should (string-equal (org-entry-get nil "TODO") "TODO")))))
+(ert-deftest org-edna-action-scheduled/wkdy ()
+ ;; Override `current-time' so we can get a deterministic value
+ (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
+ (org-agenda-files `(,org-edna-test-file))
+ (target (org-id-find "0d491588-7da3-43c5-b51a-87fbd34f79f7" t)))
+ (org-with-point-at target
+ (org-edna-action/scheduled nil "Mon")
+ (should (string-equal (org-entry-get nil "SCHEDULED")
+ "<2000-01-17 Mon>"))
+ (org-edna-action/scheduled nil 'rm)
+ (should (not (org-entry-get nil "SCHEDULED")))
+ (org-edna-action/scheduled nil "Mon 9:00")
+ (should (string-equal (org-entry-get nil "SCHEDULED")
+ "<2000-01-17 Mon 09:00>"))
+ (org-edna-action/scheduled nil 'rm)
+ (should (not (org-entry-get nil "SCHEDULED"))))))
+
+(ert-deftest org-edna-action-scheduled/cp ()
+ ;; Override `current-time' so we can get a deterministic value
+ (let* ((org-agenda-files `(,org-edna-test-file))
+ (target (org-id-find "0d491588-7da3-43c5-b51a-87fbd34f79f7" t))
+ (source (org-id-find "97e6b0f0-40c4-464f-b760-6e5ca9744eb5" t))
+ (pairs '((cp . rm) (copy . remove) ("cp" . "rm") ("copy" .
"remove"))))
+ (org-with-point-at target
+ (dolist (pair pairs)
+ (message "Pair: %s" pair)
+ (org-edna-action/scheduled source (car pair))
+ (should (string-equal (org-entry-get nil "SCHEDULED")
+ "<2000-01-15 Sat 00:00>"))
+ (org-edna-action/scheduled source (cdr pair))
+ (should (not (org-entry-get nil "SCHEDULED")))))))
+
+(ert-deftest org-edna-action-scheduled/inc ()
+ ;; Override `current-time' so we can get a deterministic value
+ (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
+ (org-agenda-files `(,org-edna-test-file))
+ (target (org-id-find "97e6b0f0-40c4-464f-b760-6e5ca9744eb5" t)))
+ (org-with-point-at target
+ ;; Time started at Jan 15, 2000
+ ;; Increment 1 minute
+ (org-edna-action/scheduled nil "+1M")
+ (should (string-equal (org-entry-get nil "SCHEDULED")
+ "<2000-01-15 Sat 00:01>"))
+ (org-edna-action/scheduled nil "-1M")
+ (should (string-equal (org-entry-get nil "SCHEDULED")
+ "<2000-01-15 Sat 00:00>"))
+ (org-edna-action/scheduled nil "+1d")
+ (should (string-equal (org-entry-get nil "SCHEDULED")
+ "<2000-01-16 Sun 00:00>"))
+ (org-edna-action/scheduled nil "++1h")
+ (should (string-equal (org-entry-get nil "SCHEDULED")
+ "<2000-01-15 Sat 01:00>"))
+ (org-edna-action/scheduled nil "2000-01-15 Sat 00:00")
+ (should (string-equal (org-entry-get nil "SCHEDULED")
+ "<2000-01-15 Sat 00:00>")))))
+
;; Conditions
diff --git a/org-edna-tests.org b/org-edna-tests.org
index 6f3341c..666ec1e 100644
--- a/org-edna-tests.org
+++ b/org-edna-tests.org
@@ -28,6 +28,11 @@ along with this program. If not, see
<http://www.gnu.org/licenses/>.
:ID: b010cbad-60dc-46ef-a164-eb155e62cbb2
:LOGGING: nil
:END:
+** TODO ID Heading 3
+SCHEDULED: <2000-01-15 Sat 00:00>
+:PROPERTIES:
+:ID: 97e6b0f0-40c4-464f-b760-6e5ca9744eb5
+:END:
** Scheduled Headings
*** TODO Scheduled Heading 1
SCHEDULED: <2017-01-01 Sun>
diff --git a/org-edna.el b/org-edna.el
index 73d2bd0..da8747a 100644
--- a/org-edna.el
+++ b/org-edna.el
@@ -360,9 +360,15 @@ IDS are all UUIDs as understood by `org-id-find'."
(defun org-edna-finder/chain-find (&rest options)
;; sortfun - function to use to sort elements
- ;; filterufn - Function to use to filter elements
+ ;; filterfun - Function to use to filter elements
;; Both should handle positioning point
- (let (targets sortfun filterfun)
+ (let (targets
+ sortfun
+ ;; From org-depend.el:
+ ;; (and (not todo-and-done-only)
+ ;; (member (second item) org-done-keywords))
+ (filterfun (lambda (target)
+ (member (org-entry-get target "TODO")
org-done-keywords))))
(dolist (opt options)
(pcase opt
('from-top
@@ -374,13 +380,17 @@ IDS are all UUIDs as understood by `org-id-find'."
('no-wrap
(setq targets (org-edna-finder/rest-of-siblings)))
('todo-only
+ ;; Remove any entry without a TODO keyword, or with a DONE keyword
(setq filterfun
(lambda (target)
- (org-entry-get target "TODO"))))
+ (let ((kwd (org-entry-get target "TODO")))
+ (or (not kwd)
+ (member kwd org-done-keywords))))))
('todo-and-done-only
+ ;; Remove any entry without a TODO keyword
(setq filterfun
(lambda (target)
- (member (org-entry-get target "TODO") org-done-keywords))))
+ (not (org-entry-get target "TODO")))))
('priority-up
(setq sortfun
(lambda (lhs rhs)
@@ -408,7 +418,7 @@ IDS are all UUIDs as understood by `org-id-find'."
(when (and targets sortfun)
(setq targets (seq-sort sortfun targets)))
(when (and targets filterfun)
- (setq targets (seq-filter filterfun targets)))
+ (setq targets (seq-remove filterfun targets)))
(when targets
(list (seq-elt 0 targets)))))
@@ -446,24 +456,32 @@ IDS are all UUIDs as understood by `org-id-find'."
("h" . hour)
("M" . minute))))
(cond
- ((member arg '('rm 'remove "rm" "remove"))
+ ((member arg '(rm remove "rm" "remove"))
(org-add-planning-info nil nil type))
- ((member arg '('cp 'copy "cp" "copy"))
+ ((member arg '(cp copy "cp" "copy"))
+ (unless last-ts
+ (error "Tried to copy but last entry doesn't have a timestamp"))
;; Copy old time verbatim
(org-add-planning-info type last-ts))
((string-match-p "\\`[+-]" arg)
+ ;; Starts with a + or -, so assume we're incrementing a timestamp
;; We support hours and minutes, so this must be supported separately,
;; since org-read-date-analyze doesn't
- ;; Starts with a + or -, so assume we're incrementing a timestamp
(pcase-let* ((`(,n ,what-string ,def) (org-read-date-get-relative arg
this-time current))
(ts (if def current-ts this-ts))
(what (cdr (assoc-string what-string type-map))))
(org--deadline-or-schedule nil type (org-edna--mod-timestamp ts n
what))))
(t
;; For everything else, assume `org-read-date-analyze' can handle it
- (let* ((parsed-time (org-read-date-analyze arg this-time (decode-time
this-time)))
- (final-time (apply 'encode-time parsed-time))
- (new-ts (format-time-string "%F %R" final-time)))
+
+ ;; The third argument to `org-read-date-analyze' specifies the defaults
to
+ ;; use if that time component isn't specified. Since there's no way to
+ ;; tell if a time was specified, tell `org-read-date-analyze' to use nil
+ ;; if no time is found.
+ (let* ((parsed-time (org-read-date-analyze arg this-time '(nil nil nil
nil nil nil)))
+ (have-time (nth 2 parsed-time))
+ (final-time (apply 'encode-time (mapcar (lambda (e) (or e 0))
parsed-time)))
+ (new-ts (format-time-string (if have-time "%F %R" "%F")
final-time)))
(org--deadline-or-schedule nil type new-ts))))))
(defun org-edna-action/scheduled (last-entry &rest args)
diff --git a/org-edna.org b/org-edna.org
index d225adb..5144f5a 100644
--- a/org-edna.org
+++ b/org-edna.org
@@ -118,17 +118,19 @@ scheduling another task, marking another task as TODO, or
renaming a file.
:END:
#+cindex: syntax
-The basic syntax of Edna's commands is KEYWORD(ARG1,ARG2,...)
+Edna has its own language for commands, the basic form of which is
KEYWORD(ARG1 ARG2 ...)
-KEYWORD can be any valid symbol, such as key-word, KEY_WORD, or keyword?.
+KEYWORD can be any valid lisp symbol, such as key-word, KEY_WORD, or keyword?.
Each argument can be one of the following:
-- A symbol, such as arg or arg-1
-- A valid lisp form, such as (+ 1 2) or (or a b)
+- A symbol, such as arg or org-mode
- A quoted string, such as "hello" or "My name is Edna"
+- A number, such as 0.5, +1e3, or -5
+- A UUID, such as c5e30c76-879a-494d-9281-3a4b559c1a3c
-Any quotes should be escaped (e.g. "\"\"").
+Each argument takes specific datatypes as input, so be sure to read the entry
+before using it.
The parentheses can be omitted for commands with no arguments.
* Basic Features
@@ -165,6 +167,42 @@ For example:
In the above example, "Heading 5" will be blocked until "Heading 1", "Heading
3", and "Heading 4" are marked "DONE", while "Heading 2" is ignored.
+*** chain-find
+
+chain-find(OPTION OPTION...)
+
+Identical to the chain argument in org-depend, chain-find selects its single
+target using the following method:
+
+1. Creates a list of possible targets
+2. Filters the targets from Step 1
+3. Sorts the targets from Step 2
+
+After this is finished, chain-find selects the first target in the list and
+returns it.
+
+One option from each of the following three categories may be used; if more
than
+one is specified, the last will be used.
+
+<<Selection>>
+
+- from-top: Select siblings of the current headline, starting at the top
+- from-bottom: As above, but from the bottom
+- from-current: Selects siblings, starting from the headline (wraps)
+- no-wrap: As above, but without wrapping
+
+<<Filtering>>
+
+- todo-only: Select only targets with TODO state set that isn't a
DONE keyword
+- todo-and-done-only: Select all targets with a TODO state set
+
+<<Sorting>>
+
+- priority-up: Sort by priority, highest first
+- priority-down: Same, but lowest first
+- effort-up: Sort by effort, highest first
+- effort-down: Sort by effort, lowest first
+
*** children
:PROPERTIES:
:DESCRIPTION: Find all immediate children
@@ -176,7 +214,7 @@ headline.
In order to get all levels of children of the current headline, use the
[[#descendants][descendants]] keyword instead.
-*** TODO descendants
+*** descendants
:PROPERTIES:
:DESCRIPTION: Find all descendants
:CUSTOM_ID: descendants
@@ -193,8 +231,10 @@ EXAMPLE HERE
:DESCRIPTION: Find a file by name
:END:
-The ~file~ finder finds a single file. The returned target will be the minimum
-point in the file.
+file(FILE)
+
+The ~file~ finder finds a single file, specified as a string. The returned
target
+will be the minimum point in the file.
Note that with the default condition, ~file~ won't work. See
[[#conditions][conditions]] for how
to set a different condition. For example:
@@ -208,9 +248,6 @@ to set a different condition. For example:
Here, "Test" will block until myfile.org is clear of headlines.
-WARNING: Make sure to quote the file name. If not, Edna will interpret it as
-"myfile\\.org" and create that file.
-
*** first-child
:PROPERTIES:
:CUSTOM_ID: first-child
@@ -231,7 +268,7 @@ number of UUIDs may be specified. For example:
#+BEGIN_EXAMPLE
,* TODO Test
:PROPERTIES:
- :BLOCKER:
ids(62209a9a-c63b-45ef-b8a8-12e47a9ceed9,6dbd7921-a25c-4e20-b035-365677e00f30)
+ :BLOCKER: ids(62209a9a-c63b-45ef-b8a8-12e47a9ceed9
6dbd7921-a25c-4e20-b035-365677e00f30)
:END:
#+END_EXAMPLE
@@ -239,19 +276,23 @@ Here, "Test" will block until the headline with ID
62209a9a-c63b-45ef-b8a8-12e47a9ceed9 and the headline with ID
6dbd7921-a25c-4e20-b035-365677e00f30 are set to "DONE".
+Note that UUIDs need not be quoted; Edna will handle that for you.
+
*** match
:PROPERTIES:
:CUSTOM_ID: match
:DESCRIPTION: Good old tag matching
:END:
+match(MATCH-STRING SCOPE SKIP)
+
The ~match~ keyword will take any arguments that ~org-map-entries~ usually
takes.
In fact, the arguments to ~match~ are passed straight into ~org-map-entries~.
#+BEGIN_EXAMPLE
,* TODO Test
:PROPERTIES:
- :BLOCKER: match(test&mine,agenda)
+ :BLOCKER: match("test&mine" agenda)
:END:
#+END_EXAMPLE
@@ -265,14 +306,46 @@ argument.
:PROPERTIES:
:CUSTOM_ID: next-sibling
:END:
+
+The ~next-sibling~ keyword returns the next sibling of the current heading, if
+any.
+
*** olp
:PROPERTIES:
:CUSTOM_ID: olp
:END:
+
+olp(FILE OLP)
+
+Finds the heading given by OLP in FILE. Both arguments are strings.
+
+#+BEGIN_EXAMPLE
+,* TODO Test
+ :PROPERTIES:
+ :BLOCKER: olp("test.org" "path/to/heading")
+ :END:
+#+END_EXAMPLE
+
+"Test" will block if the heading "path/to/heading" in "test.org" is not DONE.
+
*** org-file
:PROPERTIES:
:CUSTOM_ID: org-file
:END:
+
+org-file("FILE")
+
+A special form of ~file~, ~org-file~ will find FILE in ~org-directory~.
+
+#+BEGIN_EXAMPLE
+,* TODO Test
+ :PROPERTIES:
+ :BLOCKER: org-file("test.org")
+ :END:
+#+END_EXAMPLE
+
+Note that the file still requires an extension.
+
*** parent
:PROPERTIES:
:CUSTOM_ID: parent
@@ -281,36 +354,70 @@ argument.
:PROPERTIES:
:CUSTOM_ID: previous-sibling
:END:
+*** rest-of-siblings
+
+Finds the remaining siblings on the same level as the current headline.
+
*** self
:PROPERTIES:
:CUSTOM_ID: self
:END:
+
+Returns the current headline.
+
*** siblings
:PROPERTIES:
:CUSTOM_ID: siblings
:END:
+*** siblings-wrap
+
+Finds the siblings on the same level as the current headline, wrapping when it
+reaches the end.
+
** Actions
Once Edna has collected its targets for a trigger, it will perform actions on
them.
*** Scheduled/Deadline
-- PLANNING(WKDY[ TIME]) -> Set PLANNING to following weekday WKDY at TIME
-- PLANNING(rm|remove) -> Remove PLANNING info
-- PLANNING([copy|cp]) -> Copy timestamp verbatim
-- PLANNING([+|-][+|-]NTHING) -> Increment(+) or decrement(-) source (double)
or current (single) PLANNING by N THINGs
+:PROPERTIES:
+:CUSTOM_ID: planning
+:END:
+
+scheduled(OPTIONS)
+deadline(OPTIONS)
+
+There are several forms that the planning keywords can take:
+
+- PLANNING("WKDY[ TIME]")
+
+ Sets PLANNING to the following weekday WKDY at TIME. If TIME is not
+ specified, only a date will be added to the target.
+
+ WKDY is a weekday or weekday abbreviation (see ~org-read-date~)
+
+ TIME is a time string HH:MM, etc.
+
+- PLANNING(rm|remove)
+
+ Remove PLANNING from all targets. The argument to this form may be either a
+ string or a symbol.
+
+- PLANNING(copy|cp)
-PLANNING is either scheduled or deadline
+ Copy PLANNING info verbatim from the current headline to all targets. The
+ argument to this form may be either a string or a symbol.
-WKDY is a weekday or weekday abbreviation (see org-read-date)
+- PLANNING("[+|-][+|-]NTHING")
-TIME is a time string HH:MM, etc.
+ Increment(+) or decrement(-) source (double) or current (single) PLANNING by
N
+ THINGs
-N is an integer
+ N is an integer
-THING is one of y (years), m (months), d (days), h (hours), or M (minutes)
+ THING is one of y (years), m (months), d (days), h (hours), or M (minutes)
Examples:
-scheduled(Mon 09:00) -> Set SCHEDULED to the following Monday at 9:00
+scheduled("Mon 09:00") -> Set SCHEDULED to the following Monday at 9:00
*** Todo State
todo(NEW-STATE)
- [elpa] scratch/org-edna ce425a3 71/72: Fixed up documentation, (continued)
- [elpa] scratch/org-edna ce425a3 71/72: Fixed up documentation, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna c62886d 55/72: Updated to new syntax, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 0444691 49/72: Fixed error in last commit, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 9573539 53/72: Added Savannah project link to documentation, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 406f8ea 67/72: Fixed ancestors example, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 69ecfe4 57/72: Added tests for conditions, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 74dae46 51/72: Added tests for finders, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna f56be02 69/72: Silenced byte-compiler, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 8c31160 59/72: Added tests for actions, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 83663fa 64/72: Cleaned up package header and added various docstrings, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 37e9b58 50/72: Fixed chain-find and planning actions,
Ian Dunn <=
- [elpa] scratch/org-edna ad0f3ef 66/72: Added in-buffer settings and new sections to Documentation, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 3167ea7 39/72: Added check and local settings to Makefile, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 50652ab 58/72: Added installation and setup instructions to documentation, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 191c938 41/72: Added copyright and licensing information to all files, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 7e1dafb 44/72: Fixed license blocks in elisp files, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 831ef13 54/72: Fixed variable-set condition, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 628869b 56/72: Fixed error reporting, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna a82892c 47/72: Fixed bugs from last commit, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna e3d2e89 61/72: Documented delete-property! action, Ian Dunn, 2017/05/21
- [elpa] scratch/org-edna 0035a7b 46/72: Various parsing fixes, Ian Dunn, 2017/05/21