branch: externals/org
commit e55fdd953dc1e6ea89e3deeda2dbf8d9a41eef70
Author: Morgan Smith <[email protected]>
Commit: Morgan Smith <[email protected]>

    org-habit.el: Rewrite using element API
    
    * lisp/org-habit.el
    (org-habit-repeater-to-days): New function.
    (org-habit-duration-to-days): Use `org-habit-repeater-to-days'.
    (org-habit-parse-todo): Accept an element as an argument.  Use
    org-element getters.  Move `save-excursion' to only be around section
    that modifies point.
    
    Co-authored-by: ApollonDeParnasse <[email protected]>
---
 lisp/org-habit.el | 147 ++++++++++++++++++++++++++++++------------------------
 1 file changed, 83 insertions(+), 64 deletions(-)

diff --git a/lisp/org-habit.el b/lisp/org-habit.el
index 8d0108639f6..bf52e50e86c 100644
--- a/lisp/org-habit.el
+++ b/lisp/org-habit.el
@@ -159,13 +159,23 @@ means of creating calendar-based reminders."
   :group 'org-habit
   :group 'org-faces)
 
+(defun org-habit-repeater-to-days (value unit)
+  (floor (* value
+            (cdr (or
+                  (assoc unit
+                         '((day . 1)
+                           (week . 7)
+                           (month . 30.4)
+                           (year . 365.25)))
+                  (error "Unsupported duration unit: %s" unit))))))
+
 (defun org-habit-duration-to-days (ts)
   (if (string-match "\\([0-9]+\\)\\([dwmy]\\)" ts)
       ;; lead time is specified.
-      (floor (* (string-to-number (match-string 1 ts))
-               (cdr (assoc (match-string 2 ts)
-                           '(("d" . 1)    ("w" . 7)
-                             ("m" . 30.4) ("y" . 365.25))))))
+      (org-habit-repeater-to-days (string-to-number (match-string 1 ts))
+                                  (cdr (assoc (match-string 2 ts)
+                                              '(("d" . day)    ("w" . week)
+                                                ("m" . month) ("y" . year)))))
     (error "Invalid duration string: %s" ts)))
 
 (defun org-is-habit-p (&optional epom)
@@ -173,77 +183,86 @@ means of creating calendar-based reminders."
 EPOM is an element, marker, or buffer position."
   (string= "habit" (org-entry-get epom "STYLE" 'selective)))
 
-(defun org-habit-parse-todo (&optional pom)
-  "Parse the TODO surrounding point for its habit-related data.
+(defun org-habit-parse-todo (&optional epom)
+  "Parse the TODO EPOM for its habit-related data.
+EPOM is an element, marker, or buffer position.
+
 Returns a list with the following elements:
 
   0: Scheduled date for the habit (may be in the past)
   1: \".+\"-style repeater for the schedule, in days
   2: Optional deadline (nil if not present)
   3: If deadline, the repeater for the deadline, otherwise nil
-  4: A list of all the past dates this todo was mark closed
+  4: A list of all the past dates this todo was marked done
   5: Repeater type as a string
 
 This list represents a \"habit\" for the rest of this module."
-  (save-excursion
-    (if pom (goto-char pom))
-    (cl-assert (org-is-habit-p (point)))
-    (let* ((scheduled (org-get-scheduled-time (point)))
-          (scheduled-repeat (org-get-repeat (org-entry-get (point) 
"SCHEDULED")))
-          (end (org-entry-end-position))
-          (habit-entry (org-no-properties (nth 4 (org-heading-components))))
-          closed-dates deadline dr-days sr-days sr-type)
-      (if scheduled
-         (setq scheduled (time-to-days scheduled))
-       (error "Habit %s has no scheduled date" habit-entry))
-      (unless scheduled-repeat
-       (error
-        "Habit `%s' has no scheduled repeat period or has an incorrect one"
-        habit-entry))
-      (setq sr-days (org-habit-duration-to-days scheduled-repeat)
-           sr-type (progn (string-match "[\\.+]?\\+" scheduled-repeat)
-                          (match-string-no-properties 0 scheduled-repeat)))
+  (let ((todo (org-element-at-point epom)))
+    (cl-assert (org-is-habit-p todo))
+    (let* ((habit-entry (org-no-properties (org-element-property :title todo)))
+           (scheduled
+            (or (org-element-property :scheduled todo)
+                (error "Habit %s has no scheduled date" habit-entry)))
+           (scheduled-repeat-value (org-element-property :repeater-value 
scheduled))
+           (scheduled-repeat-unit (org-element-property :repeater-unit 
scheduled))
+           (scheduled-repeat-deadline-value (org-element-property 
:repeater-deadline-value scheduled))
+           (scheduled-repeat-deadline-unit (org-element-property 
:repeater-deadline-unit scheduled))
+           (sr-type (pcase (org-element-property :repeater-type scheduled)
+                      (`cumulate "+") (`catch-up "++") (`restart ".+")))
+           closed-dates deadline dr-days sr-days )
+      (setq scheduled (time-to-days (org-timestamp-to-time scheduled)))
+      (unless (and scheduled-repeat-value scheduled-repeat-unit)
+        (error
+         "Habit `%s' has no scheduled repeat period or has an incorrect one"
+         habit-entry))
+      (setq sr-days (org-habit-repeater-to-days scheduled-repeat-value 
scheduled-repeat-unit))
       (unless (> sr-days 0)
-       (error "Habit %s scheduled repeat period is less than 1d" habit-entry))
-      (when (string-match "/\\([0-9]+[dwmy]\\)" scheduled-repeat)
-       (setq dr-days (org-habit-duration-to-days
-                      (match-string-no-properties 1 scheduled-repeat)))
-       (if (<= dr-days sr-days)
-           (error "Habit %s deadline repeat period is less than or equal to 
scheduled (%s)"
-                  habit-entry scheduled-repeat))
-       (setq deadline (+ scheduled (- dr-days sr-days))))
-      (org-back-to-heading t)
+        (error "Habit %s scheduled repeat period is less than 1d" habit-entry))
+      (when (and scheduled-repeat-deadline-value 
scheduled-repeat-deadline-unit)
+        (setq dr-days (org-habit-repeater-to-days
+                       scheduled-repeat-deadline-value
+                       scheduled-repeat-deadline-unit))
+        (if (<= dr-days sr-days)
+            (error "Habit %s deadline repeat period is less than or equal to 
scheduled repeat"
+                   habit-entry))
+        (setq deadline (+ scheduled (- dr-days sr-days))))
       (let* ((maxdays (+ org-habit-preceding-days org-habit-following-days))
-            (reversed org-log-states-order-reversed)
-            (search (if reversed 're-search-forward 're-search-backward))
-            (limit (if reversed end (point)))
-            (count 0)
-            (re (format
-                 "^[ \t]*-[ \t]+\\(?:State \"%s\".*%s%s\\)"
-                 (regexp-opt org-done-keywords)
-                 org-ts-regexp-inactive
-                 (let ((value (cdr (assq 'done org-log-note-headings))))
-                   (if (not value) ""
-                     (concat "\\|"
-                             (org-replace-escapes
-                              (regexp-quote value)
-                              `(("%d" . ,org-ts-regexp-inactive)
-                                ("%D" . ,org-ts-regexp)
-                                ("%s" . "\"\\S-+\"")
-                                ("%S" . "\"\\S-+\"")
-                                ("%t" . ,org-ts-regexp-inactive)
-                                ("%T" . ,org-ts-regexp)
-                                ("%u" . ".*?")
-                                ("%U" . ".*?")))))))))
-       (unless reversed (goto-char end))
-       (while (and (< count maxdays) (funcall search re limit t))
-         (push (time-to-days
-                (org-time-string-to-time
-                 (or (match-string-no-properties 1)
-                     (match-string-no-properties 2))))
-               closed-dates)
-         (setq count (1+ count))))
-      (list scheduled sr-days deadline dr-days closed-dates sr-type))))
+             (reversed org-log-states-order-reversed)
+             (search (if reversed 're-search-forward 're-search-backward))
+             (start (if reversed
+                        (org-element-contents-begin todo)
+                      (org-element-contents-end todo)))
+             (limit (if reversed
+                        (org-element-contents-end todo)
+                      (org-element-contents-begin todo)))
+             (count 0)
+             (re (format
+                  "^[ \t]*-[ \t]+\\(?:State \"%s\".*%s%s\\)"
+                  (regexp-opt org-done-keywords)
+                  org-ts-regexp-inactive
+                  (let ((value (cdr (assq 'done org-log-note-headings))))
+                    (if (not value) ""
+                      (concat "\\|"
+                              (org-replace-escapes
+                               (regexp-quote value)
+                               `(("%d" . ,org-ts-regexp-inactive)
+                                 ("%D" . ,org-ts-regexp)
+                                 ("%s" . "\"\\S-+\"")
+                                 ("%S" . "\"\\S-+\"")
+                                 ("%t" . ,org-ts-regexp-inactive)
+                                 ("%T" . ,org-ts-regexp)
+                                 ("%u" . ".*?")
+                                 ("%U" . ".*?")))))))))
+        (save-excursion
+          (goto-char start)
+          (while (and (< count maxdays) (funcall search re limit t))
+            (push (time-to-days
+                   (org-time-string-to-time
+                    (or (match-string-no-properties 1)
+                        (match-string-no-properties 2))))
+                  closed-dates)
+            (setq count (1+ count))))
+        (list scheduled sr-days deadline dr-days closed-dates sr-type)))))
 
 (defsubst org-habit-scheduled (habit)
   (nth 0 habit))

Reply via email to