emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* dynamic date arithmetic in a macro or otherwise (simulating a "date counter")?
@ 2017-08-22 23:09 Matt Price
  2017-08-23  0:14 ` Sacha Chua
  0 siblings, 1 reply; 4+ messages in thread
From: Matt Price @ 2017-08-22 23:09 UTC (permalink / raw)
  To: Org Mode


[-- Attachment #1.1: Type: text/plain, Size: 2406 bytes --]

On Thu, Aug 17, 2017 at 3:25 PM, Matt Price <moptop99@gmail.com> wrote:

> I'd love to be able generate dates dynamically using the {{{n}}} org
> macro, or some other mechanism.  I don't immediately see how that would be
> possible but maybe someone can guide me.  I'd want to do something
> equivalent to this pseudo-elisp:
>
> (let ((base-date 2017-09-05))
>   (+ base-date (* 7 {{{n}}}))
>
> I haven't looked into how date objects are parsed in org-mode, though, so
> I have no idea how hard it would be to actually implement something like
> this.
>

I made some progress on this (but not much).

I defined a simple macro that adds times together and creates a timestamp
that org can read -- in fact, this one adds some text as well:

#+MACRO: w (eval (format-time-string "Week {{{n(week)}}} (<%Y-%m-%d %a>)"
(time-add (encode-time 0 0 0 17 9 2017) (days-to-time (* 7 1)))) )

This does everything that I want *except* dynamically adding time to the
previous macro call.  I tried defining a dynamic version:

#+MACRO: w (eval (format-time-string "Week {{{n(week)}}} (<%Y-%m-%d %a>)"
(time-add (encode-time 0 0 0 17 9 2017) (days-to-time (* 7 $1)))) ))

And then calling it with

{{{w({{{n}}})}}}

But unsurprisingly and appropriately, that didn't work.

I also took a look at the patch Nicolas used to implement the {{{n}}} macro
(I've reattached it here for convenience only!).  It defines
`org-macro--counter-initialize` and calls it from inside
`org-macro-templates-initialize`.  I guess I could copy that strategy but
then I'd be maintaining my own copy of org-macro-templates-initialize,
which seems like a terrible idea.

So, I'm not sure how best to proceed. For my specific use-case, something
like this would be a huge timesaver when multiplied over semesters/years.
But I also wonder whether maybe other people would enjoy being able to do
date arithmetic inside org files (and outside of tables -- I know from
Sacha's 2015 blog post  that this is possible inside a table --
http://sachachua.com/blog/2015/06/using-your-own-emacs-lisp-functions-in-org-mode-table-calculations-easier-dosage-totals/).
If other people would also use such code, I could try to hack something
together for submission. Generalizable functions will be hard for me to
write because I am sometimes a bit dense. I would love to hear suggestions
from the group...


> Thank you everyone!
>

^^ this still applies!
Matt

[-- Attachment #1.2: Type: text/html, Size: 3516 bytes --]

[-- Attachment #2: 0001-org-macro-Implement-the-n-macro.patch --]
[-- Type: text/x-patch, Size: 9636 bytes --]

From 980b713f28596c7f6486dc1ccfa82f76de7c963d Mon Sep 17 00:00:00 2001
From: Nicolas Goaziou <mail@nicolasgoaziou.fr>
Date: Mon, 8 May 2017 12:38:38 +0200
Subject: [PATCH] org-macro: Implement the "n" macro

* lisp/org-macro.el (org-macro--counter-table): New variable.
(org-macro--counter-initialize):
(org-macro--counter-increment): New functions.
(org-macro-initialize-templates): Use new functions.

* doc/org.texi (Macro replacement): Document new macro.

* testing/lisp/test-org-macro.el (test-org-macro/n):
(test-org-macro/property): New tests.
---
 etc/ORG-NEWS                   |   3 ++
 lisp/org-macro.el              |  38 +++++++++++++--
 testing/lisp/test-org-macro.el | 102 +++++++++++++++++++++++++++++++++++------
 3 files changed, 127 insertions(+), 16 deletions(-)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 3ca5b0553..b6110c412 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -197,6 +197,9 @@ contents, are now supported.
 This new function is meant to be used in back-ends supporting images
 as descriptions of links, a.k.a. image links.  See its docstring for
 details.
+**** New macro : ~{{{n}}}~
+This macro creates and increment multiple counters in a document.  See
+manual for details.
 **** Add global macros through ~org-export-global-macros~
 With this variable, one can define macros available for all documents.
 **** New keyword ~#+EXPORT_FILE_NAME~
diff --git a/lisp/org-macro.el b/lisp/org-macro.el
index 71e917b71..f5ddb92e4 100644
--- a/lisp/org-macro.el
+++ b/lisp/org-macro.el
@@ -36,8 +36,11 @@
 
 ;; Along with macros defined through #+MACRO: keyword, default
 ;; templates include the following hard-coded macros:
-;; {{{time(format-string)}}}, {{{property(node-property)}}},
-;; {{{input-file}}} and {{{modification-time(format-string)}}}.
+;;   {{{time(format-string)}}},
+;;   {{{property(node-property)}}},
+;;   {{{input-file}}},
+;;   {{{modification-time(format-string)}}},
+;;   {{{n(counter,reset}}}.
 
 ;; Upon exporting, "ox.el" will also provide {{{author}}}, {{{date}}},
 ;; {{{email}}} and {{{title}}} macros.
@@ -129,7 +132,7 @@ function installs the following ones: \"property\",
 	    (let ((old-template (assoc (car cell) templates)))
 	      (if old-template (setcdr old-template (cdr cell))
 		(push cell templates))))))
-    ;; Install hard-coded macros.
+    ;; Install "property", "time" macros.
     (mapc update-templates
 	  (list (cons "property"
 		      "(eval (save-excursion
@@ -143,6 +146,7 @@ function installs the following ones: \"property\",
                       l)))))
         (org-entry-get nil \"$1\" 'selective)))")
 		(cons "time" "(eval (format-time-string \"$1\"))")))
+    ;; Install "input-file", "modification-time" macros.
     (let ((visited-file (buffer-file-name (buffer-base-buffer))))
       (when (and visited-file (file-exists-p visited-file))
 	(mapc update-templates
@@ -152,6 +156,10 @@ function installs the following ones: \"property\",
 				  (prin1-to-string visited-file)
 				  (prin1-to-string
 				   (nth 5 (file-attributes visited-file)))))))))
+    ;; Initialize and install "n" macro.
+    (org-macro--counter-initialize)
+    (funcall update-templates
+	     (cons "n" "(eval (org-macro--counter-increment \"$1\" \"$2\"))"))
     (setq org-macro-templates templates)))
 
 (defun org-macro-expand (macro templates)
@@ -280,6 +288,9 @@ Return a list of arguments, as strings.  This is the opposite of
     s nil t)
    "\000"))
 
+\f
+;;; Helper functions and variables for internal macros
+
 (defun org-macro--vc-modified-time (file)
   (save-window-excursion
     (when (vc-backend file)
@@ -304,6 +315,27 @@ Return a list of arguments, as strings.  This is the opposite of
 	  (kill-buffer buf))
 	date))))
 
+(defvar org-macro--counter-table nil
+  "Hash table containing counter value per name.")
+
+(defun org-macro--counter-initialize ()
+  "Initialize `org-macro--counter-table'."
+  (setq org-macro--counter-table (make-hash-table :test #'equal)))
+
+(defun org-macro--counter-increment (name &optional reset)
+  "Increment counter NAME.
+NAME is a string identifying the counter.  If optional argument
+RESET is a non-empty string, reset the counter instead."
+  (if (org-string-nw-p reset)
+      (let ((new-value (if (string-match-p "\\`[ \t]*[0-9]+[ \t]*\\'" reset)
+			   (string-to-number reset)
+			 1)))
+	(puthash name new-value org-macro--counter-table))
+    (let ((value (gethash name org-macro--counter-table)))
+      (puthash name
+	       (if (null value) 1 (1+ value))
+	       org-macro--counter-table))))
+
 
 (provide 'org-macro)
 ;;; org-macro.el ends here
diff --git a/testing/lisp/test-org-macro.el b/testing/lisp/test-org-macro.el
index 26c56745c..64b0a97cc 100644
--- a/testing/lisp/test-org-macro.el
+++ b/testing/lisp/test-org-macro.el
@@ -75,9 +75,22 @@
       (org-macro-initialize-templates)
       (org-macro-replace-all org-macro-templates)
       (buffer-string))))
-  ;; Test special "property" macro.  With only one argument, retrieve
-  ;; property from current headline.  Otherwise, the second argument
-  ;; is a search option to get the property from another headline.
+  ;; Macro expansion ignores narrowing.
+  (should
+   (string-match
+    "expansion"
+    (org-test-with-temp-text
+	"#+MACRO: macro expansion\n{{{macro}}}\n<point>Contents"
+      (narrow-to-region (point) (point-max))
+      (org-macro-initialize-templates)
+      (org-macro-replace-all org-macro-templates)
+      (org-with-wide-buffer (buffer-string))))))
+
+(ert-deftest test-org-macro/property ()
+  "Test {{{property}}} macro."
+  ;; With only one argument, retrieve property from current headline.
+  ;; Otherwise, the second argument is a search option to get the
+  ;; property from another headline.
   (should
    (equal "1"
 	  (org-test-with-temp-text
@@ -107,17 +120,80 @@
    (org-test-with-temp-text
        "* H1\n:PROPERTIES:\n:A: 1\n:END:\n* H2\n{{{property(A,*???)}}}<point>"
      (org-macro-initialize-templates)
-     (org-macro-replace-all org-macro-templates)))
-  ;; Macro expansion ignores narrowing.
+     (org-macro-replace-all org-macro-templates))))
+
+(ert-deftest test-org-macro/n ()
+  "Test {{{n}}} macro."
+  ;; Standard test with default counter.
   (should
-   (string-match
-    "expansion"
-    (org-test-with-temp-text
-	"#+MACRO: macro expansion\n{{{macro}}}\n<point>Contents"
-      (narrow-to-region (point) (point-max))
-      (org-macro-initialize-templates)
-      (org-macro-replace-all org-macro-templates)
-      (org-with-wide-buffer (buffer-string))))))
+   (equal "1 2"
+	  (org-test-with-temp-text "{{{n}}} {{{n}}}"
+	    (org-macro-initialize-templates)
+	    (org-macro-replace-all org-macro-templates)
+	    (buffer-substring-no-properties
+	     (line-beginning-position) (line-end-position)))))
+  (should
+   (equal "1 2"
+	  (org-test-with-temp-text "{{{n()}}} {{{n}}}"
+	    (org-macro-initialize-templates)
+	    (org-macro-replace-all org-macro-templates)
+	    (buffer-substring-no-properties
+	     (line-beginning-position) (line-end-position)))))
+  ;; Test alternative counters.
+  (should
+   (equal "1 1 1 2"
+	  (org-test-with-temp-text "{{{n}}} {{{n(c1)}}} {{{n(c2)}}} {{{n(c1)}}}"
+	    (org-macro-initialize-templates)
+	    (org-macro-replace-all org-macro-templates)
+	    (buffer-substring-no-properties
+	     (line-beginning-position) (line-end-position)))))
+  ;; Second argument set a counter to a given value.  A non-numeric
+  ;; value resets the counter to 1.
+  (should
+   (equal "9 10"
+	  (org-test-with-temp-text "{{{n(c,9)}}} {{{n(c)}}}"
+	    (org-macro-initialize-templates)
+	    (org-macro-replace-all org-macro-templates)
+	    (buffer-substring-no-properties
+	     (line-beginning-position) (line-end-position)))))
+  (should
+   (equal "9 1"
+	  (org-test-with-temp-text "{{{n(c,9)}}} {{{n(c,reset)}}}"
+	    (org-macro-initialize-templates)
+	    (org-macro-replace-all org-macro-templates)
+	    (buffer-substring-no-properties
+	     (line-beginning-position) (line-end-position)))))
+  ;; Tolerate spaces in second argument.
+  (should
+   (equal "9 10"
+	  (org-test-with-temp-text "{{{n(c, 9)}}} {{{n(c)}}}"
+	    (org-macro-initialize-templates)
+	    (org-macro-replace-all org-macro-templates)
+	    (buffer-substring-no-properties
+	     (line-beginning-position) (line-end-position)))))
+  (should
+   (equal "9 1"
+	  (org-test-with-temp-text "{{{n(c,9)}}} {{{n(c, reset)}}}"
+	    (org-macro-initialize-templates)
+	    (org-macro-replace-all org-macro-templates)
+	    (buffer-substring-no-properties
+	     (line-beginning-position) (line-end-position)))))
+  ;; Second argument also applies to default counter.
+  (should
+   (equal "9 10 1"
+	  (org-test-with-temp-text "{{{n(,9)}}} {{{n}}} {{{n(,reset)}}}"
+	    (org-macro-initialize-templates)
+	    (org-macro-replace-all org-macro-templates)
+	    (buffer-substring-no-properties
+	     (line-beginning-position) (line-end-position)))))
+  ;; An empty second argument is equivalent to no argument.
+  (should
+   (equal "2 3"
+	  (org-test-with-temp-text "{{{n(c,2)}}} {{{n(c,)}}}"
+	    (org-macro-initialize-templates)
+	    (org-macro-replace-all org-macro-templates)
+	    (buffer-substring-no-properties
+	     (line-beginning-position) (line-end-position))))))
 
 (ert-deftest test-org-macro/escape-arguments ()
   "Test `org-macro-escape-arguments' specifications."
-- 
2.13.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: dynamic date arithmetic in a macro or otherwise (simulating a "date counter")?
  2017-08-22 23:09 dynamic date arithmetic in a macro or otherwise (simulating a "date counter")? Matt Price
@ 2017-08-23  0:14 ` Sacha Chua
  2017-08-23  2:40   ` Adam Porter
  2017-08-23 16:52   ` Matt Price
  0 siblings, 2 replies; 4+ messages in thread
From: Sacha Chua @ 2017-08-23  0:14 UTC (permalink / raw)
  To: Matt Price; +Cc: emacs list

[-- Attachment #1: Type: text/plain, Size: 2738 bytes --]

org-clone-subtree-with-time-shift might be a good starting point, too. It
could be interesting to be able to replace dates within text and
priorities. Good luck!

On Aug 22, 2017 7:10 PM, "Matt Price" <moptop99@gmail.com> wrote:

>
>
> On Thu, Aug 17, 2017 at 3:25 PM, Matt Price <moptop99@gmail.com> wrote:
>
>> I'd love to be able generate dates dynamically using the {{{n}}} org
>> macro, or some other mechanism.  I don't immediately see how that would be
>> possible but maybe someone can guide me.  I'd want to do something
>> equivalent to this pseudo-elisp:
>>
>> (let ((base-date 2017-09-05))
>>   (+ base-date (* 7 {{{n}}}))
>>
>> I haven't looked into how date objects are parsed in org-mode, though, so
>> I have no idea how hard it would be to actually implement something like
>> this.
>>
>
> I made some progress on this (but not much).
>
> I defined a simple macro that adds times together and creates a timestamp
> that org can read -- in fact, this one adds some text as well:
>
> #+MACRO: w (eval (format-time-string "Week {{{n(week)}}} (<%Y-%m-%d %a>)"
> (time-add (encode-time 0 0 0 17 9 2017) (days-to-time (* 7 1)))) )
>
> This does everything that I want *except* dynamically adding time to the
> previous macro call.  I tried defining a dynamic version:
>
> #+MACRO: w (eval (format-time-string "Week {{{n(week)}}} (<%Y-%m-%d %a>)"
> (time-add (encode-time 0 0 0 17 9 2017) (days-to-time (* 7 $1)))) ))
>
> And then calling it with
>
> {{{w({{{n}}})}}}
>
> But unsurprisingly and appropriately, that didn't work.
>
> I also took a look at the patch Nicolas used to implement the {{{n}}}
> macro (I've reattached it here for convenience only!).  It defines
> `org-macro--counter-initialize` and calls it from inside
> `org-macro-templates-initialize`.  I guess I could copy that strategy but
> then I'd be maintaining my own copy of org-macro-templates-initialize,
> which seems like a terrible idea.
>
> So, I'm not sure how best to proceed. For my specific use-case, something
> like this would be a huge timesaver when multiplied over semesters/years.
> But I also wonder whether maybe other people would enjoy being able to do
> date arithmetic inside org files (and outside of tables -- I know from
> Sacha's 2015 blog post  that this is possible inside a table --
> http://sachachua.com/blog/2015/06/using-your-own-emacs-
> lisp-functions-in-org-mode-table-calculations-easier-dosage-totals/).  If
> other people would also use such code, I could try to hack something
> together for submission. Generalizable functions will be hard for me to
> write because I am sometimes a bit dense. I would love to hear suggestions
> from the group...
>
>
>> Thank you everyone!
>>
>
> ^^ this still applies!
> Matt
>
>

[-- Attachment #2: Type: text/html, Size: 4077 bytes --]

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: dynamic date arithmetic in a macro or otherwise (simulating a "date counter")?
  2017-08-23  0:14 ` Sacha Chua
@ 2017-08-23  2:40   ` Adam Porter
  2017-08-23 16:52   ` Matt Price
  1 sibling, 0 replies; 4+ messages in thread
From: Adam Porter @ 2017-08-23  2:40 UTC (permalink / raw)
  To: emacs-orgmode

I just discovered this yesterday, and it seems relevant:

https://github.com/abo-abo/tiny

Apparently it can be used like this:

m\n8|**** TODO Learning from Data Week %(+ x 2) \nSCHEDULED: <%(date "Oct 7" (* x 7))> DEADLINE: <%(date "Oct 14" (* x 7))>

Which results in:

**** TODO Learning from Data Week 2
SCHEDULED: <2013-10-07 Mon> DEADLINE: <2013-10-14 Mon>
**** TODO Learning from Data Week 3
SCHEDULED: <2013-10-14 Mon> DEADLINE: <2013-10-21 Mon>
[...]

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: dynamic date arithmetic in a macro or otherwise (simulating a "date counter")?
  2017-08-23  0:14 ` Sacha Chua
  2017-08-23  2:40   ` Adam Porter
@ 2017-08-23 16:52   ` Matt Price
  1 sibling, 0 replies; 4+ messages in thread
From: Matt Price @ 2017-08-23 16:52 UTC (permalink / raw)
  Cc: emacs list

[-- Attachment #1: Type: text/plain, Size: 2027 bytes --]

On Tue, Aug 22, 2017 at 8:14 PM, Sacha Chua <sacha@sachachua.com> wrote:

> org-clone-subtree-with-time-shift might be a good starting point, too. It
> could be interesting to be able to replace dates within text and
> priorities. Good luck!
>

Interesting, I didn't know about `org-clone-subtree-with-time-shjift`.
thank you.

So, cloning isn't necessarily what I'm looking for; I'm often working with
documents that need to be largely reused with modifications. That means
that abo-abo's tiny isn't quite right for me, either.  So I've written the
following; it still feels really clumsy but it seems to work for now! It's
loosely based on some code that Jon Kitchin wrote for me on this list about
2 years ago (!!). Here it is:

#+MACRO: ts (eval (get-ts+7))


#+BEGIN_SRC emacs-lisp
    (defun get-ts+7 ()
    (interactive)
    (let ((base-date (save-excursion
                 (re-search-backward
                  (org-re-timestamp 'all))
                 (match-string 0)))
          (result nil))
      (with-temp-buffer
        (org-mode)
        (insert base-date )
        (backward-char)
        (org-timestamp-change 7 'day)
        (end-of-buffer)
        (insert "  ")
        (end-of-buffer)
        (setq result (save-excursion
                       (re-search-backward
                        (org-re-timestamp 'all))
                       (match-string 0))))
      result))
#+END_SRC

There's no error-checking, but basically you just insert the date of the
first weekly meeting and all the other dates update themselves
automatically on export.  It's pretty cool.  One next step would be to
allow more complex repetition cycles (e.g., to repeat eveyr tuesday and
thursday, or monday Wednesday Friday, etc.).

So now my headlines all just look like this:

** Week {{{n}}} ({{{ts}}}): Topic

Still working on that tree merge idea, will check back in on that other
thread if I get anywhere with it.

Thanks to both Adam and Sacha for the help! I'd love to hear any other
suggestions people might have.

Matt

[-- Attachment #2: Type: text/html, Size: 2974 bytes --]

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2017-08-23 16:52 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-08-22 23:09 dynamic date arithmetic in a macro or otherwise (simulating a "date counter")? Matt Price
2017-08-23  0:14 ` Sacha Chua
2017-08-23  2:40   ` Adam Porter
2017-08-23 16:52   ` Matt Price

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).