~bzg/org-mode

66e307b411eb74409a2acb82d3450e9702e64c23 — Morgan Smith a month ago ead3f99
lisp/org-element.el: Add repeater-deadline support to org-element

* lisp/org-element.el (org-element-timestamp-parser,
org-element-timestamp-interpreter): Add support for repeater
deadlines.  Adds two new properties: ':repeater-deadline-value' and
':repeater-deadline-unit'.

* testing/lisp/test-org-element.el (test-org-element/timestamp-parser,
test-org-element/timestamp-interpreter): Test support for repeater
deadlines.

* etc/ORG-NEWS: Add relevant news.
3 files changed, 100 insertions(+), 22 deletions(-)

M etc/ORG-NEWS
M lisp/org-element.el
M testing/lisp/test-org-element.el
M etc/ORG-NEWS => etc/ORG-NEWS +16 -0
@@ 512,6 512,22 @@ timestamp object.  Possible values: ~timerange~, ~daterange~, ~nil~.
~org-element-timestamp-interpreter~ takes into account this property
and returns an appropriate timestamp string.

**** New properties =:repeater-deadline-value= and =:repeater-deadline-unit= for org-element timestamp object

~org-element-timestamp-parser~ now adds =:repeater-deadline-value= and
=:repeater-deadline-unit= properties to each timestamp object that has
a repeater deadline.  For example, in =<2012-03-29 Thu ++1y/2y>=, =2y=
is the repeater deadline with a value of =2= and unit of =y=.  See
"5.3.3 Tracking your habits" section in the manual.

Possible values for =:repeater-deadline-value=: ~positive integer~, ~nil~.

Possible values for =:repeater-deadline-unit=: ~hour~, ~day~, ~week~,
~month~, ~year~.

~org-element-timestamp-interpreter~ takes into account these properties
and returns an appropriate timestamp string.

**** =org-link= store functions are passed an ~interactive?~ argument

The ~:store:~ functions set for link types using

M lisp/org-element.el => lisp/org-element.el +48 -20
@@ 4288,12 4288,13 @@ Assume point is at the target."
  "Parse time stamp at point, if any.

When at a time stamp, return a new syntax node of `timestamp' type
containing `:type', `:range-type', `:raw-value', `:year-start', `:month-start',
`:day-start', `:hour-start', `:minute-start', `:year-end',
`:month-end', `:day-end', `:hour-end', `:minute-end',
containing `:type', `:range-type', `:raw-value', `:year-start',
`:month-start', `:day-start', `:hour-start', `:minute-start',
`:year-end', `:month-end', `:day-end', `:hour-end', `:minute-end',
`:repeater-type', `:repeater-value', `:repeater-unit',
`:warning-type', `:warning-value', `:warning-unit', `:begin', `:end'
and `:post-blank' properties.  Otherwise, return nil.
`:repeater-deadline-value', `:repeater-deadline-unit', `:warning-type',
`:warning-value', `:warning-unit', `:begin', `:end' and `:post-blank'
properties.  Otherwise, return nil.

Assume point is at the beginning of the timestamp."
  (when (looking-at-p org-element--timestamp-regexp)


@@ 4326,20 4327,38 @@ Assume point is at the beginning of the timestamp."
                          (date-end 'daterange)
                          (time-range 'timerange)
                          (t nil)))
	     (repeater-props
	      (and (not diaryp)
		   (string-match "\\([.+]?\\+\\)\\([0-9]+\\)\\([hdwmy]\\)"
				 raw-value)
		   (list
		    :repeater-type
		    (let ((type (match-string 1 raw-value)))
		      (cond ((equal "++" type) 'catch-up)
			    ((equal ".+" type) 'restart)
			    (t 'cumulate)))
		    :repeater-value (string-to-number (match-string 2 raw-value))
		    :repeater-unit
		    (pcase (string-to-char (match-string 3 raw-value))
		      (?h 'hour) (?d 'day) (?w 'week) (?m 'month) (_ 'year)))))
             (repeater-props
              (and (not diaryp)
                   (string-match
                    (rx
                     (group-n 1 (or "+" "++" ".+"))
                     (group-n 2 (+ digit))
                     (group-n 3 (any "hdwmy"))
                     (optional
                      "/"
                      (group-n 4 (+ digit))
                      (group-n 5 (any "hdwmy"))))
                    raw-value)
                   (nconc
                    (list
                     :repeater-type
                     (let ((type (match-string 1 raw-value)))
                       (cond ((equal "++" type) 'catch-up)
                             ((equal ".+" type) 'restart)
                             (t 'cumulate)))
                     :repeater-value (string-to-number (match-string 2 raw-value))
                     :repeater-unit
                     (pcase (string-to-char (match-string 3 raw-value))
                       (?h 'hour) (?d 'day) (?w 'week) (?m 'month) (_ 'year)))

                    (let ((repeater-deadline-value (match-string 4 raw-value))
                          (repeater-deadline-unit (match-string 5 raw-value)))
                      (when (and repeater-deadline-value repeater-deadline-unit)
                        (list
                         :repeater-deadline-value (string-to-number repeater-deadline-value)
                         :repeater-deadline-unit
                         (pcase (string-to-char repeater-deadline-unit)
                           (?h 'hour) (?d 'day) (?w 'week) (?m 'month) (_ 'year))))))))
	     (warning-props
	      (and (not diaryp)
		   (string-match "\\(-\\)?-\\([0-9]+\\)\\([hdwmy]\\)" raw-value)


@@ 4407,7 4426,16 @@ Assume point is at the beginning of the timestamp."
	             (let ((val (org-element-property :repeater-value timestamp)))
	               (and val (number-to-string val)))
	             (pcase (org-element-property :repeater-unit timestamp)
	               (`hour "h") (`day "d") (`week "w") (`month "m") (`year "y"))))
	               (`hour "h") (`day "d") (`week "w") (`month "m") (`year "y"))
                     (when-let ((repeater-deadline-value
                                 (org-element-property :repeater-deadline-value timestamp))
                                (repeater-deadline-unit
                                 (org-element-property :repeater-deadline-unit timestamp)))
                       (concat
                        "/"
                        (number-to-string repeater-deadline-value)
                        (pcase repeater-deadline-unit
                          (`hour "h") (`day "d") (`week "w") (`month "m") (`year "y"))))))
                   (range-type (org-element-property :range-type timestamp))
                   (warning-string
	            (concat

M testing/lisp/test-org-element.el => testing/lisp/test-org-element.el +36 -2
@@ 3208,12 3208,19 @@ Outside list"
     (let ((timestamp (org-element-context)))
       (or (org-element-property :hour-end timestamp)
	   (org-element-property :minute-end timestamp)))))
  ;; With repeater, warning delay and both.
  ;; With repeater, repeater deadline, warning delay and combinations.
  (should
   (eq 'catch-up
       (org-test-with-temp-text "<2012-03-29 Thu ++1y>"
	 (org-element-property :repeater-type (org-element-context)))))
  (should
   (equal '(catch-up 2 year)
       (org-test-with-temp-text "<2012-03-29 Thu ++1y/2y>"
         (let ((ts (org-element-context)))
           (list (org-element-property :repeater-type ts)
                 (org-element-property :repeater-deadline-value ts)
                 (org-element-property :repeater-deadline-unit ts))))))
  (should
   (eq 'first
       (org-test-with-temp-text "<2012-03-29 Thu --1y>"
	 (org-element-property :warning-type (org-element-context)))))


@@ 3223,6 3230,14 @@ Outside list"
	    (let ((ts (org-element-context)))
	      (list (org-element-property :repeater-type ts)
		    (org-element-property :warning-type ts))))))
  (should
   (equal '(cumulate all 2 year)
          (org-test-with-temp-text "<2012-03-29 Thu +1y/2y -1y>"
            (let ((ts (org-element-context)))
              (list (org-element-property :repeater-type ts)
                    (org-element-property :warning-type ts)
                    (org-element-property :repeater-deadline-value ts)
                    (org-element-property :repeater-deadline-unit ts))))))
  ;; :range-type property
  (should
   (eq


@@ 3963,7 3978,7 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
  ;; Diary.
  (should (equal (org-test-parse-and-interpret "<%%diary-float t 4 2>")
		 "<%%diary-float t 4 2>\n"))
  ;; Timestamp with repeater interval, with delay, with both.
  ;; Timestamp with repeater interval, repeater deadline, with delay, with combinations.
  (should
   (string-match "<2012-03-29 .* \\+1y>"
		 (org-test-parse-and-interpret "<2012-03-29 thu. +1y>")))


@@ 3977,6 3992,15 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
     nil)))
  (should
   (string-match
    "<2012-03-29 .* \\+1y/2y>"
    (org-element-timestamp-interpreter
     '(timestamp
       (:type active :year-start 2012 :month-start 3 :day-start 29
              :repeater-type cumulate :repeater-value 1 :repeater-unit year
              :repeater-deadline-value 2 :repeater-deadline-unit year))
     nil)))
  (should
   (string-match
    "<2012-03-29 .* -1y>"
    (org-element-timestamp-interpreter
     '(timestamp


@@ 3992,6 4016,16 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
	      :warning-type all :warning-value 1 :warning-unit year
	      :repeater-type cumulate :repeater-value 1 :repeater-unit year))
     nil)))
  (should
   (string-match
    "<2012-03-29 .* \\+1y/2y -1y>"
    (org-element-timestamp-interpreter
     '(timestamp
       (:type active :year-start 2012 :month-start 3 :day-start 29
              :warning-type all :warning-value 1 :warning-unit year
              :repeater-type cumulate :repeater-value 1 :repeater-unit year
              :repeater-deadline-value 2 :repeater-deadline-unit year))
     nil)))
  ;; Timestamp range with repeater interval
  (should
   (string-match "<2012-03-29 .* \\+1y>--<2012-03-30 .* \\+1y>"