emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* [PATCH] function and symbol for headline and olp for org-capture-templates
@ 2024-05-13 22:53 Nafiz Islam
  2024-05-13 23:08 ` Nafiz Islam
  0 siblings, 1 reply; 18+ messages in thread
From: Nafiz Islam @ 2024-05-13 22:53 UTC (permalink / raw)
  To: emacs-orgmode


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

Dear All, This patch adds the option to provide function and symbol for
headline and olp for org-capture-templates. By that, I mean you can set the
org-capture-templates in the following manner: (org-capture-templates
`(("t" "Todo" entry (file+headline ,file (lambda () "A")) "** H1 %?")))
(org-capture-templates `(("t" "Todo" entry (file+headline ,file
test-org-capture/entry/headline) "** H1 %?"))) Where
`test-org-capture/entry/headline' is a variable/symbol.
(org-capture-templates `(("t" "Todo" entry (file+olp ,file (lambda () '("A"
"B"))) "* H1 %?"))) (org-capture-templates `(("t" "Todo" entry (file+olp
,file test-org-capture/entry/file+olp) "* H1 %?"))) Where
`test-org-capture/entry/file+olp' is a variable/symbol. I also added a few
more unit tests for the targets `file+olp' and `file+olp+datetree' to
verify my new features and existing features.

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

[-- Attachment #2: 0001-function-and-symbol-for-headline-and-olp-for-org-capture-templates.patch --]
[-- Type: text/x-patch, Size: 13164 bytes --]

From 4757bd5531663e9c85bd54e8644c4bad40709383 Mon Sep 17 00:00:00 2001
From: Nafiz Islam <nafiz.islam1001@gmail.com>
Date: Mon, 13 May 2024 17:53:02 -0400
Subject: [PATCH] function and symbol for headline and olp for org-capture-templates

* doc/org-manual.org: add template format for the function and symbol variant
* etc/ORG-NEWS: announce the updated options
* lisp/org-capture.el (org-capture-templates): update customization type for headline and olp target
* lisp/org-capture.el (org-capture-expand-function-or-symbol): define `org-capture-expand-function-or-symbol'
* lisp/org-capture.el (org-capture-expand-headline): define `org-capture-expand-headline'
* lisp/org-capture.el (org-capture-expand-olp): define `org-capture-expand-olp'
* lisp/org-capture.el (org-capture-expand-file): update to use `org-capture-expand-function-or-symbol' to handle function and symbol
* lisp/org-capture.el (org-capture-set-target-location): use `org-capture-expand-headline' to expand headline, use `org-capture-expand-olp' to expand outline path
* testing/lisp/test-org-capture.el (test-org-capture/entry): add tests for at most three different kinds of target for `file+headline', `file+olp', and `file+olp+datetree'

---
 doc/org-manual.org               | 12 ++++
 etc/ORG-NEWS                     |  7 +++
 lisp/org-capture.el              | 58 +++++++++++++++----
 testing/lisp/test-org-capture.el | 97 ++++++++++++++++++++++++++++++++
 4 files changed, 162 insertions(+), 12 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index e3a2c9b7..5ff9c5ac 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -8030,10 +8030,18 @@ Now lets look at the elements of a template definition.  Each entry in
 
   - =(file+headline "filename" "node headline")= ::
 
+  - =(file+headline "filename" function-returning-headline)= ::
+
+  - =(file+headline "filename" symbol-containing-headline)= ::
+
     Fast configuration if the target heading is unique in the file.
 
   - =(file+olp "filename" "Level 1 heading" "Level 2" ...)= ::
 
+  - =(file+olp "filename" function-returning-outline-path)= ::
+
+  - =(file+olp "filename" symbol-containing-outline-path)= ::
+
     For non-unique headings, the full path is safer.
 
   - =(file+regexp "filename" "regexp to find location")= ::
@@ -8042,6 +8050,10 @@ Now lets look at the elements of a template definition.  Each entry in
 
   - =(file+olp+datetree "filename" [ "Level 1 heading" ...])= ::
 
+  - =(file+olp+datetree "filename" function-returning-outline-path)= ::
+
+  - =(file+olp+datetree "filename" symbol-containing-outline-path)= ::
+
     This target[fn:30] creates a heading in a date tree[fn:31] for
     today's date.  If the optional outline path is given, the tree
     will be built under the node it is pointing to, instead of at top
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 87b72ad1..13c895dc 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -720,6 +720,13 @@ any more.  Run ~org-ctags-enable~ to setup hooks and advices:
 #+end_src
 
 ** New and changed options
+
+*** New customization options for ~org-capture-templates~
+
+The variable ~org-capture-templates~ accepts a target specification
+for headline (~file+headline~) and olp (~file+old~ and
+~file+olp+datetree~) as function and symbol.
+
 *** New option controlling how Org mode sorts things ~org-sort-function~
 
 Sorting of agenda items, tables, menus, headlines, etc can now be
diff --git a/lisp/org-capture.el b/lisp/org-capture.el
index da14f45c..8c1ff9e1 100644
--- a/lisp/org-capture.el
+++ b/lisp/org-capture.el
@@ -409,7 +409,13 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 				(file :tag "Literal")
 				(function :tag "Function")
 				(variable :tag "Variable")
-				(sexp :tag "Form"))))
+				(sexp :tag "Form")))
+        (olp-variants '(choice :tag "Outline path"
+                               (repeat :tag "Outline path" :inline t
+				       (string :tag "Headline"))
+			       (function :tag "Function")
+			       (variable :tag "Variable")
+			       (sexp :tag "Form"))))
     `(repeat
       (choice :value ("" "" entry (file "~/org/notes.org") "")
 	      (list :tag "Multikey description"
@@ -434,12 +440,15 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 			    (list :tag "File & Headline"
 				  (const :format "" file+headline)
 				  ,file-variants
-				  (string :tag "  Headline"))
+				  (choice :tag "Headline"
+				          (string   :tag "Headline")
+				          (function :tag "Function")
+				          (variable :tag "Variable")
+				          (sexp :tag "Form")))
 			    (list :tag "File & Outline path"
 				  (const :format "" file+olp)
 				  ,file-variants
-				  (repeat :tag "Outline path" :inline t
-					  (string :tag "Headline")))
+				  ,olp-variants)
 			    (list :tag "File & Regexp"
 				  (const :format "" file+regexp)
 				  ,file-variants
@@ -447,8 +456,7 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 			    (list :tag "File [ & Outline path ] & Date tree"
 				  (const :format "" file+olp+datetree)
 				  ,file-variants
-				  (option (repeat :tag "Outline path" :inline t
-						  (string :tag "Headline"))))
+				  ,olp-variants)
 			    (list :tag "File & function"
 				  (const :format "" file+function)
 				  ,file-variants
@@ -1012,7 +1020,7 @@ Store them in the capture property list."
 	    (org-capture-put-target-region-and-position)
 	    (goto-char position))
 	   (_ (error "Cannot find target ID \"%s\"" id))))
-	(`(file+headline ,path ,(and headline (pred stringp)))
+	(`(file+headline ,path ,headline)
 	 (set-buffer (org-capture-target-buffer path))
 	 ;; Org expects the target file to be in Org mode, otherwise
 	 ;; it throws an error.  However, the default notes files
@@ -1026,6 +1034,7 @@ Store them in the capture property list."
 	 (org-capture-put-target-region-and-position)
 	 (widen)
 	 (goto-char (point-min))
+         (setq headline (org-capture-expand-headline headline))
 	 (if (re-search-forward (format org-complex-heading-regexp-format
 					(regexp-quote headline))
 				nil t)
@@ -1036,7 +1045,7 @@ Store them in the capture property list."
 	   (forward-line -1)))
 	(`(file+olp ,path . ,(and outline-path (guard outline-path)))
 	 (let ((m (org-find-olp (cons (org-capture-expand-file path)
-				      outline-path))))
+				      (apply #'org-capture-expand-olp outline-path)))))
 	   (set-buffer (marker-buffer m))
 	   (org-capture-put-target-region-and-position)
 	   (widen)
@@ -1058,7 +1067,7 @@ Store them in the capture property list."
 	(`(file+olp+datetree ,path . ,outline-path)
 	 (let ((m (if outline-path
 		      (org-find-olp (cons (org-capture-expand-file path)
-					  outline-path))
+					  (apply #'org-capture-expand-olp outline-path)))
 		    (set-buffer (org-capture-target-buffer path))
 		    (point-marker))))
 	   (set-buffer (marker-buffer m))
@@ -1143,6 +1152,33 @@ Store them in the capture property list."
 			      (org-decrypt-entry)
 			      (and (org-back-to-heading t) (point))))))))
 
+(defun org-capture-expand-function-or-symbol (input)
+  "Expand functions and symbols. When INPUT is a
+function, call it. When it is a variable, return
+its value. In any other case, return `nil'."
+  (let* ((output (cond ((functionp input) (funcall input))
+		       ((and (symbolp input) (boundp input)) (symbol-value input))
+                       (t nil))))
+    output))
+
+(defun org-capture-expand-headline (headline)
+  "Expand functions, symbols and headline names for HEADLINE.
+When HEADLINE is a function, call it. When it is a variable,
+return its value. When it is a string, return it.  In any other
+case, return `nil'."
+  (let* ((final-headline (cond ((stringp headline) headline)
+                       (t (org-capture-expand-function-or-symbol headline)))))
+    final-headline))
+
+(defun org-capture-expand-olp (&rest olp)
+  "Expand functions, symbols and outline paths for OLP.
+When OLP is a function, call it. When it is a variable,
+return its value. When it is a list of string, return it.
+In any other case, return `nil'."
+  (let* ((final-olp (cond ((stringp (car olp)) olp)
+                          (t (org-capture-expand-function-or-symbol (car olp))))))
+    final-olp))
+
 (defun org-capture-expand-file (file)
   "Expand functions, symbols and file names for FILE.
 When FILE is a function, call it.  When it is a form, evaluate
@@ -1153,9 +1189,7 @@ string, however, return `org-default-notes-file'.  In any other
 case, raise an error."
   (let ((location (cond ((equal file "") org-default-notes-file)
 			((stringp file) (expand-file-name file org-directory))
-			((functionp file) (funcall file))
-			((and (symbolp file) (boundp file)) (symbol-value file))
-			(t nil))))
+                        (t (org-capture-expand-function-or-symbol file)))))
     (or (org-string-nw-p location)
 	(error "Invalid file location: %S" location))))
 
diff --git a/testing/lisp/test-org-capture.el b/testing/lisp/test-org-capture.el
index 0ed44c6a..ea11d8ef 100644
--- a/testing/lisp/test-org-capture.el
+++ b/testing/lisp/test-org-capture.el
@@ -223,6 +223,103 @@
 	(insert "Capture text")
 	(org-capture-finalize))
       (buffer-string))))
+  (should
+   (equal
+    "* A\n** H1 Capture text\n* B\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+headline ,file (lambda () "A")) "** H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** H1 Capture text\n* B\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+headline ,file test-org-capture/entry/headline) "** H1 %?"))))
+        (setq test-org-capture/entry/headline "A")
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize)
+        (makunbound 'test-org-capture/entry/headline))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp ,file "A" "B") "* H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp ,file (lambda () '("A" "B"))) "* H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp ,file test-org-capture/entry/file+olp) "* H1 %?"))))
+        (setq test-org-capture/entry/file+olp '("A" "B"))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize)
+        (makunbound 'test-org-capture/entry/file+olp))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp+datetree ,file "A" "B") "* H1 %?")))
+             (org-overriding-default-time 0))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp+datetree ,file (lambda () '("A" "B"))) "* H1 %?")))
+             (org-overriding-default-time 0))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp+datetree ,file test-org-capture/entry/file+olp+datetree) "* H1 %?")))
+             (org-overriding-default-time 0))
+        (setq test-org-capture/entry/file+olp+datetree '("A" "B"))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize)
+        (makunbound 'test-org-capture/entry/file+olp+datetree))
+      (buffer-string))))
   ;; Correctly save position of inserted entry.
   (should
    (equal
-- 
2.42.0


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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
  2024-05-13 22:53 Nafiz Islam
@ 2024-05-13 23:08 ` Nafiz Islam
  2024-05-17 12:48   ` Ihor Radchenko
  0 siblings, 1 reply; 18+ messages in thread
From: Nafiz Islam @ 2024-05-13 23:08 UTC (permalink / raw)
  To: emacs-orgmode

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

Oh dear, I shouldn't have used `atomic-chrome' to format the message in
Gmail. So here's a revised version.

Dear All,

This patch adds the option to provide function and symbol for headline and
olp for org-capture-templates.

By that, I mean you can set the org-capture-templates in the following
manner:

(org-capture-templates `(("t" "Todo" entry (file+headline ,file (lambda ()
"A")) "** H1 %?")))

(org-capture-templates `(("t" "Todo" entry (file+headline ,file
test-org-capture/entry/headline) "** H1 %?")))
Where `test-org-capture/entry/headline' is a variable/symbol.

(org-capture-templates `(("t" "Todo" entry (file+olp ,file (lambda () '("A"
"B"))) "* H1 %?")))

(org-capture-templates `(("t" "Todo" entry (file+olp ,file
test-org-capture/entry/file+olp) "* H1 %?")))
Where `test-org-capture/entry/file+olp' is a variable/symbol.

I also added a few more unit tests for the targets `file+olp' and
`file+olp+datetree' to verify my new features and existing features.

However, I just realized that the suggestion provided in
https://lists.gnu.org/archive/html/emacs-orgmode/2024-05/msg00216.html
might not work how I was hoping for. Basically, I wanted it to be possible
to search for headlines and even outline paths in the
buffer while in the function. But, by expanding outline-path before setting
the buffer (by calling outside of `org-find-olp') that
becomes infeasible.

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

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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
  2024-05-13 23:08 ` Nafiz Islam
@ 2024-05-17 12:48   ` Ihor Radchenko
  0 siblings, 0 replies; 18+ messages in thread
From: Ihor Radchenko @ 2024-05-17 12:48 UTC (permalink / raw)
  To: Nafiz Islam; +Cc: emacs-orgmode

Nafiz Islam <nafiz.islam1001@gmail.com> writes:

> This patch adds the option to provide function and symbol for headline and
> olp for org-capture-templates.

Thanks for the patch.
A few comments.

Firstly, your total contribution to Org mode with this patch will exceed
the maximum allowed number of LOC changes we can accept without FSF
copyright assignment. May I know if you have FSF copyright assignment?
If not, would you consider doing the paperwork? See
https://orgmode.org/worg/org-contribute.html#copyright

> -				  (string :tag "  Headline"))
> +				  (choice :tag "Headline"
> +				          (string   :tag "Headline")
> +				          (function :tag "Function")
> +				          (variable :tag "Variable")
> +				          (sexp :tag "Form")))

Please update the docstring of `org-capture-templates' as well.

> +(defun org-capture-expand-olp (&rest olp)
> +  "Expand functions, symbols and outline paths for OLP.
> +When OLP is a function, call it. When it is a variable,
> +return its value. When it is a list of string, return it.
> +In any other case, return `nil'."
> +  (let* ((final-olp (cond ((stringp (car olp)) olp)
> +                          (t (org-capture-expand-function-or-symbol (car olp))))))
> +    final-olp))

I think that we should filter out incorrect target specifications when
the function name is followed by more list elements - (cdr olp) is non-nil.

> ...
> However, I just realized that the suggestion provided in
> https://lists.gnu.org/archive/html/emacs-orgmode/2024-05/msg00216.html
> might not work how I was hoping for. Basically, I wanted it to be possible
> to search for headlines and even outline paths in the
> buffer while in the function. But, by expanding outline-path before setting
> the buffer (by calling outside of `org-find-olp') that
> becomes infeasible.

I think that we may pass the file name as an argument to outline path
function.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
@ 2024-05-19 23:34 Nafiz Islam
  2024-05-19 23:43 ` Nafiz Islam
  0 siblings, 1 reply; 18+ messages in thread
From: Nafiz Islam @ 2024-05-19 23:34 UTC (permalink / raw)
  To: yantar92; +Cc: emacs-orgmode

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

> would you consider doing the paperwork?

Yes. I sent an email to assign@gnu.org with the form.

I've also updated the docstring for `org-capture-templates', filtered out
incorrect target, and updated `org-capture-expand-olp' to take file
argument.

Additionally, I added a few test cases to ensure that
`org-capture-expand-olp' works as expected.

I've also attached the updated patch.

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

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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
  2024-05-19 23:34 Nafiz Islam
@ 2024-05-19 23:43 ` Nafiz Islam
  2024-05-20 10:53   ` Ihor Radchenko
  0 siblings, 1 reply; 18+ messages in thread
From: Nafiz Islam @ 2024-05-19 23:43 UTC (permalink / raw)
  To: yantar92, emacs-orgmode


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

Sorry. I forgot to actually attach the patch.

On Sun, May 19, 2024 at 7:34 PM Nafiz Islam <nafiz.islam1000@gmail.com>
wrote:

> > would you consider doing the paperwork?
>
> Yes. I sent an email to assign@gnu.org with the form.
>
> I've also updated the docstring for `org-capture-templates', filtered out
> incorrect target, and updated `org-capture-expand-olp' to take file
> argument.
>
> Additionally, I added a few test cases to ensure that
> `org-capture-expand-olp' works as expected.
>
> I've also attached the updated patch.
>

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

[-- Attachment #2: 0001-function-and-symbol-for-headline-and-olp-for-org-cap.patch --]
[-- Type: text/x-patch, Size: 16508 bytes --]

From b0d4a608a28eb5efddca804aa66f0098b24ccca5 Mon Sep 17 00:00:00 2001
From: Nafiz Islam <nafiz.islam1001@gmail.com>
Date: Mon, 13 May 2024 17:53:02 -0400
Subject: [PATCH] function and symbol for headline and olp for
 org-capture-templates

* doc/org-manual.org: add template format for the function and symbol variant
* etc/ORG-NEWS: announce the updated options
* lisp/org-capture.el (org-capture-templates): update customization type for headline and olp target, and update docstring
* lisp/org-capture.el (org-capture-expand-function-or-symbol): define `org-capture-expand-function-or-symbol'
* lisp/org-capture.el (org-capture-expand-headline): define `org-capture-expand-headline'
* lisp/org-capture.el (org-capture-expand-olp): define `org-capture-expand-olp'
* lisp/org-capture.el (org-capture-expand-file): update to use `org-capture-expand-function-or-symbol' to handle function and symbol
* lisp/org-capture.el (org-capture-set-target-location): use `org-capture-expand-headline' to expand headline, and use `org-capture-expand-olp' to expand outline path
* testing/lisp/test-org-capture.el (test-org-capture/entry):
- add tests for at most three different kinds of target for `file+headline', `file+olp', and `file+olp+datetree'
- add tests for `org-capture-expand-olp'

---
 doc/org-manual.org               |  12 +++
 etc/ORG-NEWS                     |   6 ++
 lisp/org-capture.el              |  73 ++++++++++++++----
 testing/lisp/test-org-capture.el | 128 +++++++++++++++++++++++++++++++
 4 files changed, 205 insertions(+), 14 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index e3a2c9b70..5ff9c5ac0 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -8030,10 +8030,18 @@ Now lets look at the elements of a template definition.  Each entry in
 
   - =(file+headline "filename" "node headline")= ::
 
+  - =(file+headline "filename" function-returning-headline)= ::
+
+  - =(file+headline "filename" symbol-containing-headline)= ::
+
     Fast configuration if the target heading is unique in the file.
 
   - =(file+olp "filename" "Level 1 heading" "Level 2" ...)= ::
 
+  - =(file+olp "filename" function-returning-outline-path)= ::
+
+  - =(file+olp "filename" symbol-containing-outline-path)= ::
+
     For non-unique headings, the full path is safer.
 
   - =(file+regexp "filename" "regexp to find location")= ::
@@ -8042,6 +8050,10 @@ Now lets look at the elements of a template definition.  Each entry in
 
   - =(file+olp+datetree "filename" [ "Level 1 heading" ...])= ::
 
+  - =(file+olp+datetree "filename" function-returning-outline-path)= ::
+
+  - =(file+olp+datetree "filename" symbol-containing-outline-path)= ::
+
     This target[fn:30] creates a heading in a date tree[fn:31] for
     today's date.  If the optional outline path is given, the tree
     will be built under the node it is pointing to, instead of at top
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 6c6fdbe2c..9cce78106 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -739,6 +739,12 @@ any more.  Run ~org-ctags-enable~ to setup hooks and advices:
 
 This new hook runs when a note has been stored.
 
+*** New customization options for ~org-capture-templates~
+
+The variable ~org-capture-templates~ accepts a target specification
+for headline (~file+headline~) and olp (~file+old~ and
+~file+olp+datetree~) as function and symbol.
+
 *** New option controlling how Org mode sorts things ~org-sort-function~
 
 Sorting of agenda items, tables, menus, headlines, etc can now be
diff --git a/lisp/org-capture.el b/lisp/org-capture.el
index 9d8f855ef..189cb7e6e 100644
--- a/lisp/org-capture.el
+++ b/lisp/org-capture.el
@@ -201,15 +201,21 @@ target       Specification of where the captured item should be placed.
                  File as child of this entry, or in the body of the entry
 
              (file+headline \"path/to/file\" \"node headline\")
+             (file+headline \"path/to/file\" function-returning-headline)
+             (file+headline \"path/to/file\" symbol-containing-headline)
                  Fast configuration if the target heading is unique in the file
 
              (file+olp \"path/to/file\" \"Level 1 heading\" \"Level 2\" ...)
+             (file+olp \"path/to/file\" function-returning-outline-path)
+             (file+olp \"path/to/file\" symbol-containing-outline-path)
                  For non-unique headings, the full outline path is safer
 
              (file+regexp  \"path/to/file\" \"regexp to find location\")
                  File to the entry matching regexp
 
              (file+olp+datetree \"path/to/file\" \"Level 1 heading\" ...)
+             (file+olp+datetree \"path/to/file\" function-returning-outline-path)
+             (file+olp+datetree \"path/to/file\" symbol-containing-outline-path)
                  Will create a heading in a date tree for today's date.
                  If no heading is given, the tree will be on top level.
                  To prompt for date instead of using TODAY, use the
@@ -409,7 +415,13 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 				(file :tag "Literal")
 				(function :tag "Function")
 				(variable :tag "Variable")
-				(sexp :tag "Form"))))
+				(sexp :tag "Form")))
+        (olp-variants '(choice :tag "Outline path"
+                               (repeat :tag "Outline path" :inline t
+				       (string :tag "Headline"))
+			       (function :tag "Function")
+			       (variable :tag "Variable")
+			       (sexp :tag "Form"))))
     `(repeat
       (choice :value ("" "" entry (file "~/org/notes.org") "")
 	      (list :tag "Multikey description"
@@ -434,12 +446,15 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 			    (list :tag "File & Headline"
 				  (const :format "" file+headline)
 				  ,file-variants
-				  (string :tag "  Headline"))
+				  (choice :tag "Headline"
+				          (string   :tag "Headline")
+				          (function :tag "Function")
+				          (variable :tag "Variable")
+				          (sexp :tag "Form")))
 			    (list :tag "File & Outline path"
 				  (const :format "" file+olp)
 				  ,file-variants
-				  (repeat :tag "Outline path" :inline t
-					  (string :tag "Headline")))
+				  ,olp-variants)
 			    (list :tag "File & Regexp"
 				  (const :format "" file+regexp)
 				  ,file-variants
@@ -447,8 +462,7 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 			    (list :tag "File [ & Outline path ] & Date tree"
 				  (const :format "" file+olp+datetree)
 				  ,file-variants
-				  (option (repeat :tag "Outline path" :inline t
-						  (string :tag "Headline"))))
+				  ,olp-variants)
 			    (list :tag "File & function"
 				  (const :format "" file+function)
 				  ,file-variants
@@ -1012,7 +1026,7 @@ Store them in the capture property list."
 	    (org-capture-put-target-region-and-position)
 	    (goto-char position))
 	   (_ (error "Cannot find target ID \"%s\"" id))))
-	(`(file+headline ,path ,(and headline (pred stringp)))
+	(`(file+headline ,path ,headline)
 	 (set-buffer (org-capture-target-buffer path))
 	 ;; Org expects the target file to be in Org mode, otherwise
 	 ;; it throws an error.  However, the default notes files
@@ -1026,6 +1040,7 @@ Store them in the capture property list."
 	 (org-capture-put-target-region-and-position)
 	 (widen)
 	 (goto-char (point-min))
+         (setq headline (org-capture-expand-headline headline))
 	 (if (re-search-forward (format org-complex-heading-regexp-format
 					(regexp-quote headline))
 				nil t)
@@ -1035,8 +1050,9 @@ Store them in the capture property list."
 	   (insert "* " headline "\n")
 	   (forward-line -1)))
 	(`(file+olp ,path . ,(and outline-path (guard outline-path)))
-	 (let ((m (org-find-olp (cons (org-capture-expand-file path)
-				      outline-path))))
+	 (let* ((expanded-file-path (org-capture-expand-file path))
+                (m (org-find-olp (cons expanded-file-path
+				       (apply #'org-capture-expand-olp expanded-file-path outline-path)))))
 	   (set-buffer (marker-buffer m))
 	   (org-capture-put-target-region-and-position)
 	   (widen)
@@ -1057,8 +1073,9 @@ Store them in the capture property list."
 		 (and (derived-mode-p 'org-mode) (org-at-heading-p)))))
 	(`(file+olp+datetree ,path . ,outline-path)
 	 (let ((m (if outline-path
-		      (org-find-olp (cons (org-capture-expand-file path)
-					  outline-path))
+		      (let ((expanded-file-path (org-capture-expand-file path)))
+                        (org-find-olp (cons expanded-file-path
+					    (apply #'org-capture-expand-olp expanded-file-path outline-path))))
 		    (set-buffer (org-capture-target-buffer path))
 		    (point-marker))))
 	   (set-buffer (marker-buffer m))
@@ -1143,6 +1160,36 @@ Store them in the capture property list."
 			      (org-decrypt-entry)
 			      (and (org-back-to-heading t) (point))))))))
 
+(defun org-capture-expand-function-or-symbol (input)
+  "Expand functions and symbols. When INPUT is a
+function, call it. When it is a variable, return
+its value. In any other case, return `nil'."
+  (let* ((output (cond ((functionp input) (funcall input))
+		       ((and (symbolp input) (boundp input)) (symbol-value input))
+                       (t nil))))
+    output))
+
+(defun org-capture-expand-headline (headline)
+  "Expand functions, symbols and headline names for HEADLINE.
+When HEADLINE is a function, call it. When it is a variable,
+return its value. When it is a string, return it.  In any other
+case, return `nil'."
+  (let* ((final-headline (cond ((stringp headline) headline)
+                       (t (org-capture-expand-function-or-symbol headline)))))
+    final-headline))
+
+(defun org-capture-expand-olp (file &rest olp)
+  "Expand functions, symbols and outline paths for OLP.
+When OLP is a function, call it. When it is a variable,
+return its value. When it is a list of string, return it.
+In any other case, return `nil'. The current buffer is
+set to the FILE before executing OLP as a function."
+  (with-current-buffer (find-file-noselect file)
+    (let* ((final-olp (cond ((stringp (car olp)) olp)
+                            ((not (cdr olp)) (org-capture-expand-function-or-symbol (car olp)))
+                            (t (error "Invalid outline path: %S" olp)))))
+      final-olp)))
+
 (defun org-capture-expand-file (file)
   "Expand functions, symbols and file names for FILE.
 When FILE is a function, call it.  When it is a form, evaluate
@@ -1153,9 +1200,7 @@ string, however, return `org-default-notes-file'.  In any other
 case, raise an error."
   (let ((location (cond ((equal file "") org-default-notes-file)
 			((stringp file) (expand-file-name file org-directory))
-			((functionp file) (funcall file))
-			((and (symbolp file) (boundp file)) (symbol-value file))
-			(t nil))))
+                        (t (org-capture-expand-function-or-symbol file)))))
     (or (org-string-nw-p location)
 	(error "Invalid file location: %S" location))))
 
diff --git a/testing/lisp/test-org-capture.el b/testing/lisp/test-org-capture.el
index 0ed44c6af..6779997b7 100644
--- a/testing/lisp/test-org-capture.el
+++ b/testing/lisp/test-org-capture.el
@@ -223,6 +223,103 @@
 	(insert "Capture text")
 	(org-capture-finalize))
       (buffer-string))))
+  (should
+   (equal
+    "* A\n** H1 Capture text\n* B\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+headline ,file (lambda () "A")) "** H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** H1 Capture text\n* B\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+headline ,file test-org-capture/entry/headline) "** H1 %?"))))
+        (setq test-org-capture/entry/headline "A")
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize)
+        (makunbound 'test-org-capture/entry/headline))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp ,file "A" "B") "* H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp ,file (lambda () '("A" "B"))) "* H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp ,file test-org-capture/entry/file+olp) "* H1 %?"))))
+        (setq test-org-capture/entry/file+olp '("A" "B"))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize)
+        (makunbound 'test-org-capture/entry/file+olp))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp+datetree ,file "A" "B") "* H1 %?")))
+             (org-overriding-default-time 0))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp+datetree ,file (lambda () '("A" "B"))) "* H1 %?")))
+             (org-overriding-default-time 0))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp+datetree ,file test-org-capture/entry/file+olp+datetree) "* H1 %?")))
+             (org-overriding-default-time 0))
+        (setq test-org-capture/entry/file+olp+datetree '("A" "B"))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize)
+        (makunbound 'test-org-capture/entry/file+olp+datetree))
+      (buffer-string))))
   ;; Correctly save position of inserted entry.
   (should
    (equal
@@ -809,5 +906,36 @@ before\nglobal-before\nafter\nglobal-after"
               (org-capture nil "t")
               (buffer-string))))))
 
+(ert-deftest test-org-capture/org-capture-expand-olp ()
+  "Test org-capture-expand-olp."
+  ;; org-capture-expand-olp accepts inlined outline path
+  (should
+   (equal
+    '("A" "B" "C")
+    (let ((file (make-temp-file "org-test")))
+      (unwind-protect
+          (org-capture-expand-olp file "A" "B" "C")
+        (delete-file file)))))
+  ;; The current buffer during the funcall of the lambda is the temporary test file
+  (should
+   (let ((file (make-temp-file "org-test")))
+     (equal
+      file
+      (unwind-protect
+          (org-capture-expand-olp file (lambda () (buffer-file-name (current-buffer))))
+        (delete-file file))))))
+
+(ert-deftest test-org-capture/org-capture-expand-olp-bad-olp ()
+  "Test org-capture-expand-olp when incorrect olp argument is passed."
+  :expected-result :failed
+  ;; org-capture-expand-olp rejects not inlined outline path
+  (should
+   (equal
+    '("A" "B" "C")
+    (let ((file (make-temp-file "org-test")))
+      (unwind-protect
+          (org-capture-expand-olp file '("A" "B" "C"))
+        (delete-file file))))))
+
 (provide 'test-org-capture)
 ;;; test-org-capture.el ends here
-- 
2.42.0


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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
  2024-05-19 23:43 ` Nafiz Islam
@ 2024-05-20 10:53   ` Ihor Radchenko
  2024-05-21 21:00     ` Nafiz Islam
  0 siblings, 1 reply; 18+ messages in thread
From: Ihor Radchenko @ 2024-05-20 10:53 UTC (permalink / raw)
  To: Nafiz Islam; +Cc: emacs-orgmode

Nafiz Islam <nafiz.islam1000@gmail.com> writes:

>> I've also updated the docstring for `org-capture-templates', filtered out
>> incorrect target, and updated `org-capture-expand-olp' to take file
>> argument.

I do not think that it does what you expect.
AFAIU, the idea was to pass the FILE and extra OLP arguments to the
custom function provided by the user. But it is not what your code does.

> +(defun org-capture-expand-function-or-symbol (input)
> +  "Expand functions and symbols. When INPUT is a
> +function, call it. When it is a variable, return
> +its value. In any other case, return `nil'."
> +  (let* ((output (cond ((functionp input) (funcall input))
> +		       ((and (symbolp input) (boundp input)) (symbol-value input))
> +                       (t nil))))
> +    output))
> ...
> +(defun org-capture-expand-olp (file &rest olp)
> +  "Expand functions, symbols and outline paths for OLP.
> +When OLP is a function, call it. When it is a variable,
> +return its value. When it is a list of string, return it.
> +In any other case, return `nil'. The current buffer is
> +set to the FILE before executing OLP as a function."
> +  (with-current-buffer (find-file-noselect file)
> +    (let* ((final-olp (cond ((stringp (car olp)) olp)
> +                            ((not (cdr olp)) (org-capture-expand-function-or-symbol (car olp)))
> +                            (t (error "Invalid outline path: %S" olp)))))
> +      final-olp)))

You are still calling `org-capture-expand-function-or-symbol' without
passing the rest of OLP there. Or was it the intention?

Also, is there any point calling `find-file-noselect' when the outline
path spec is _not_ a function?

Finally, you need to describe the calling convention and environment
when the function spec for OLP is called - that it is called with no (or
some?) arguments and that the current buffer is the FILE-visiting buffer.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
  2024-05-20 10:53   ` Ihor Radchenko
@ 2024-05-21 21:00     ` Nafiz Islam
  2024-05-22 11:11       ` Nafiz Islam
  0 siblings, 1 reply; 18+ messages in thread
From: Nafiz Islam @ 2024-05-21 21:00 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-orgmode

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

 > the idea was to pass the FILE and extra OLP arguments to the custom 
function provided by the user

I have misunderstood what you suggested. But, I wanted to maintain 
consistency with how the `file+headline' handles function as a target 
where the lambda takes no parameter and is expected to return the 
appropriate type of value while already visiting the file.


 > You are still calling `org-capture-expand-function-or-symbol' without 
passing the rest of OLP there. Or was it the intention?

It is intentional. The only other type I allow (besides a list of 
headings) is a single function, a single bound symbol and `nil'.


 > Also, is there any point calling `find-file-noselect' when the outline
 > path spec is _not_ a function?

No. I'll fix that. I ended up removing 
`org-capture-expand-function-or-symbol'in favour of explicitly testing 
for function and bound-symbol to allow more fine-grained use of 
`find-file-noselect'.


 > Finally, you need to describe the calling convention and environment
 > when the function spec for OLP is called - that it is called with no (or
 > some?) arguments and that the current buffer is the FILE-visiting buffer.

Sounds good.


I've attached the updated the patch.

[-- Attachment #2: 0001-function-and-symbol-for-headline-and-olp-for-org-cap.patch --]
[-- Type: text/x-patch, Size: 15997 bytes --]

From b26bbcb1049e0ab07923308f583228028c100f15 Mon Sep 17 00:00:00 2001
From: Nafiz Islam <nafiz.islam1001@gmail.com>
Date: Tue, 21 May 2024 16:24:26 -0400
Subject: [PATCH] function and symbol for headline and olp for
 org-capture-templates

* doc/org-manual.org: add template format for the function and symbol variant
* etc/ORG-NEWS: announce the updated options
* lisp/org-capture.el (org-capture-templates): update customization type for headline and olp target, and update docstring
* lisp/org-capture.el (org-capture-expand-headline): define `org-capture-expand-headline'
* lisp/org-capture.el (org-capture-expand-olp): define `org-capture-expand-olp'
* lisp/org-capture.el (org-capture-set-target-location): use `org-capture-expand-headline' to expand headline, and use `org-capture-expand-olp' to expand outline path
* testing/lisp/test-org-capture.el (test-org-capture/entry): add tests for at most three different kinds of target for `file+headline', `file+olp', and `file+olp+datetree'
* testing/lisp/test-org-capture.el (test-org-capture/test-org-capture/org-capture-expand-olp): add tests for `org-capture-expand-olp'
* testing/lisp/test-org-capture.el (test-org-capture/org-capture-expand-olp-not-inlined-strings): add failing test for `org-capture-expand-olp'

---
 doc/org-manual.org               |  12 +++
 etc/ORG-NEWS                     |   6 ++
 lisp/org-capture.el              |  67 +++++++++++++---
 testing/lisp/test-org-capture.el | 128 +++++++++++++++++++++++++++++++
 4 files changed, 202 insertions(+), 11 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 920114f70..e62acf0d9 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -8030,10 +8030,18 @@ Now lets look at the elements of a template definition.  Each entry in
 
   - =(file+headline "filename" "node headline")= ::
 
+  - =(file+headline "filename" function-returning-headline)= ::
+
+  - =(file+headline "filename" symbol-containing-headline)= ::
+
     Fast configuration if the target heading is unique in the file.
 
   - =(file+olp "filename" "Level 1 heading" "Level 2" ...)= ::
 
+  - =(file+olp "filename" function-returning-outline-path)= ::
+
+  - =(file+olp "filename" symbol-containing-outline-path)= ::
+
     For non-unique headings, the full path is safer.
 
   - =(file+regexp "filename" "regexp to find location")= ::
@@ -8042,6 +8050,10 @@ Now lets look at the elements of a template definition.  Each entry in
 
   - =(file+olp+datetree "filename" [ "Level 1 heading" ...])= ::
 
+  - =(file+olp+datetree "filename" function-returning-outline-path)= ::
+
+  - =(file+olp+datetree "filename" symbol-containing-outline-path)= ::
+
     This target[fn:30] creates a heading in a date tree[fn:31] for
     today's date.  If the optional outline path is given, the tree
     will be built under the node it is pointing to, instead of at top
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 071e8a3fb..a586647e0 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -740,6 +740,12 @@ any more.  Run ~org-ctags-enable~ to setup hooks and advices:
 
 This new hook runs when a note has been stored.
 
+*** New customization options for ~org-capture-templates~
+
+The variable ~org-capture-templates~ accepts a target specification
+for headline (~file+headline~) and olp (~file+old~ and
+~file+olp+datetree~) as function and symbol.
+
 *** New option controlling how Org mode sorts things ~org-sort-function~
 
 Sorting of agenda items, tables, menus, headlines, etc can now be
diff --git a/lisp/org-capture.el b/lisp/org-capture.el
index 9d8f855ef..d465738ca 100644
--- a/lisp/org-capture.el
+++ b/lisp/org-capture.el
@@ -201,15 +201,21 @@ target       Specification of where the captured item should be placed.
                  File as child of this entry, or in the body of the entry
 
              (file+headline \"path/to/file\" \"node headline\")
+             (file+headline \"path/to/file\" function-returning-headline)
+             (file+headline \"path/to/file\" symbol-containing-headline)
                  Fast configuration if the target heading is unique in the file
 
              (file+olp \"path/to/file\" \"Level 1 heading\" \"Level 2\" ...)
+             (file+olp \"path/to/file\" function-returning-outline-path)
+             (file+olp \"path/to/file\" symbol-containing-outline-path)
                  For non-unique headings, the full outline path is safer
 
              (file+regexp  \"path/to/file\" \"regexp to find location\")
                  File to the entry matching regexp
 
              (file+olp+datetree \"path/to/file\" \"Level 1 heading\" ...)
+             (file+olp+datetree \"path/to/file\" function-returning-outline-path)
+             (file+olp+datetree \"path/to/file\" symbol-containing-outline-path)
                  Will create a heading in a date tree for today's date.
                  If no heading is given, the tree will be on top level.
                  To prompt for date instead of using TODAY, use the
@@ -409,7 +415,13 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 				(file :tag "Literal")
 				(function :tag "Function")
 				(variable :tag "Variable")
-				(sexp :tag "Form"))))
+				(sexp :tag "Form")))
+        (olp-variants '(choice :tag "Outline path"
+                               (repeat :tag "Outline path" :inline t
+				       (string :tag "Headline"))
+			       (function :tag "Function")
+			       (variable :tag "Variable")
+			       (sexp :tag "Form"))))
     `(repeat
       (choice :value ("" "" entry (file "~/org/notes.org") "")
 	      (list :tag "Multikey description"
@@ -434,12 +446,15 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 			    (list :tag "File & Headline"
 				  (const :format "" file+headline)
 				  ,file-variants
-				  (string :tag "  Headline"))
+				  (choice :tag "Headline"
+				          (string   :tag "Headline")
+				          (function :tag "Function")
+				          (variable :tag "Variable")
+				          (sexp :tag "Form")))
 			    (list :tag "File & Outline path"
 				  (const :format "" file+olp)
 				  ,file-variants
-				  (repeat :tag "Outline path" :inline t
-					  (string :tag "Headline")))
+				  ,olp-variants)
 			    (list :tag "File & Regexp"
 				  (const :format "" file+regexp)
 				  ,file-variants
@@ -447,8 +462,7 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 			    (list :tag "File [ & Outline path ] & Date tree"
 				  (const :format "" file+olp+datetree)
 				  ,file-variants
-				  (option (repeat :tag "Outline path" :inline t
-						  (string :tag "Headline"))))
+				  ,olp-variants)
 			    (list :tag "File & function"
 				  (const :format "" file+function)
 				  ,file-variants
@@ -1012,7 +1026,7 @@ Store them in the capture property list."
 	    (org-capture-put-target-region-and-position)
 	    (goto-char position))
 	   (_ (error "Cannot find target ID \"%s\"" id))))
-	(`(file+headline ,path ,(and headline (pred stringp)))
+	(`(file+headline ,path ,headline)
 	 (set-buffer (org-capture-target-buffer path))
 	 ;; Org expects the target file to be in Org mode, otherwise
 	 ;; it throws an error.  However, the default notes files
@@ -1026,6 +1040,7 @@ Store them in the capture property list."
 	 (org-capture-put-target-region-and-position)
 	 (widen)
 	 (goto-char (point-min))
+         (setq headline (org-capture-expand-headline headline))
 	 (if (re-search-forward (format org-complex-heading-regexp-format
 					(regexp-quote headline))
 				nil t)
@@ -1035,8 +1050,9 @@ Store them in the capture property list."
 	   (insert "* " headline "\n")
 	   (forward-line -1)))
 	(`(file+olp ,path . ,(and outline-path (guard outline-path)))
-	 (let ((m (org-find-olp (cons (org-capture-expand-file path)
-				      outline-path))))
+	 (let* ((expanded-file-path (org-capture-expand-file path))
+                (m (org-find-olp (cons expanded-file-path
+				       (apply #'org-capture-expand-olp expanded-file-path outline-path)))))
 	   (set-buffer (marker-buffer m))
 	   (org-capture-put-target-region-and-position)
 	   (widen)
@@ -1057,8 +1073,9 @@ Store them in the capture property list."
 		 (and (derived-mode-p 'org-mode) (org-at-heading-p)))))
 	(`(file+olp+datetree ,path . ,outline-path)
 	 (let ((m (if outline-path
-		      (org-find-olp (cons (org-capture-expand-file path)
-					  outline-path))
+		      (let ((expanded-file-path (org-capture-expand-file path)))
+                        (org-find-olp (cons expanded-file-path
+					    (apply #'org-capture-expand-olp expanded-file-path outline-path))))
 		    (set-buffer (org-capture-target-buffer path))
 		    (point-marker))))
 	   (set-buffer (marker-buffer m))
@@ -1143,6 +1160,34 @@ Store them in the capture property list."
 			      (org-decrypt-entry)
 			      (and (org-back-to-heading t) (point))))))))
 
+(defun org-capture-expand-headline (headline)
+  "Expand functions, symbols and headline names for HEADLINE.
+When HEADLINE is a function, call it. When it is a variable,
+return its value. When it is a string, return it.  In any other
+case, return `nil'."
+  (let* ((final-headline (cond ((stringp headline) headline)
+                               ((functionp headline) (funcall headline))
+                               ((and (symbolp headline) (boundp headline))
+                                (symbol-value headline))
+                               (t nil))))
+    final-headline))
+
+(defun org-capture-expand-olp (file &rest olp)
+  "Expand functions, symbols and outline paths for OLP.
+When OLP is a function, call it with no arguments while
+the current buffer is the FILE-visiting buffer. When it
+is a variable, return its value. When it is a list of
+string, return it. In any other case, signal an error."
+  (let* ((first (car olp))
+         (final-olp (cond ((or (not first) (stringp first)) olp)
+                          ((and (not (cdr olp)) (functionp first))
+                           (with-current-buffer (find-file-noselect file)
+                             (funcall first)))
+                          ((and (not (cdr olp)) (symbolp first) (boundp first))
+                           (symbol-value first))
+                          (t (error "Invalid outline path: %S" olp)))))
+      final-olp))
+
 (defun org-capture-expand-file (file)
   "Expand functions, symbols and file names for FILE.
 When FILE is a function, call it.  When it is a form, evaluate
diff --git a/testing/lisp/test-org-capture.el b/testing/lisp/test-org-capture.el
index 0ed44c6af..602a3564a 100644
--- a/testing/lisp/test-org-capture.el
+++ b/testing/lisp/test-org-capture.el
@@ -223,6 +223,103 @@
 	(insert "Capture text")
 	(org-capture-finalize))
       (buffer-string))))
+  (should
+   (equal
+    "* A\n** H1 Capture text\n* B\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+headline ,file (lambda () "A")) "** H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** H1 Capture text\n* B\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+headline ,file test-org-capture/entry/headline) "** H1 %?"))))
+        (setq test-org-capture/entry/headline "A")
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize)
+        (makunbound 'test-org-capture/entry/headline))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp ,file "A" "B") "* H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp ,file (lambda () '("A" "B"))) "* H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp ,file test-org-capture/entry/file+olp) "* H1 %?"))))
+        (setq test-org-capture/entry/file+olp '("A" "B"))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize)
+        (makunbound 'test-org-capture/entry/file+olp))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp+datetree ,file "A" "B") "* H1 %?")))
+             (org-overriding-default-time 0))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp+datetree ,file (lambda () '("A" "B"))) "* H1 %?")))
+             (org-overriding-default-time 0))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n* B\n** B\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n* B\n** B"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp+datetree ,file test-org-capture/entry/file+olp+datetree) "* H1 %?")))
+             (org-overriding-default-time 0))
+        (setq test-org-capture/entry/file+olp+datetree '("A" "B"))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize)
+        (makunbound 'test-org-capture/entry/file+olp+datetree))
+      (buffer-string))))
   ;; Correctly save position of inserted entry.
   (should
    (equal
@@ -809,5 +906,36 @@ before\nglobal-before\nafter\nglobal-after"
               (org-capture nil "t")
               (buffer-string))))))
 
+(ert-deftest test-org-capture/org-capture-expand-olp ()
+  "Test org-capture-expand-olp."
+  ;; org-capture-expand-olp accepts inlined outline path
+  (should
+   (equal
+    '("A" "B" "C")
+    (let ((file (make-temp-file "org-test")))
+      (unwind-protect
+          (org-capture-expand-olp file "A" "B" "C")
+        (delete-file file)))))
+  ;; The current buffer during the funcall of the lambda is the temporary test file
+  (should
+   (let ((file (make-temp-file "org-test")))
+     (equal
+      file
+      (unwind-protect
+          (org-capture-expand-olp file (lambda () (buffer-file-name (current-buffer))))
+        (delete-file file))))))
+
+(ert-deftest test-org-capture/org-capture-expand-olp-not-inlined-strings ()
+  "Test org-capture-expand-olp when incorrect olp argument is passed."
+  :expected-result :failed
+  ;; org-capture-expand-olp rejects not inlined outline path
+  (should
+   (equal
+    '("A" "B" "C")
+    (let ((file (make-temp-file "org-test")))
+      (unwind-protect
+          (org-capture-expand-olp file '("A" "B" "C"))
+        (delete-file file))))))
+
 (provide 'test-org-capture)
 ;;; test-org-capture.el ends here
-- 
2.42.0


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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
  2024-05-21 21:00     ` Nafiz Islam
@ 2024-05-22 11:11       ` Nafiz Islam
  2024-05-22 11:15         ` Ihor Radchenko
  0 siblings, 1 reply; 18+ messages in thread
From: Nafiz Islam @ 2024-05-22 11:11 UTC (permalink / raw)
  To: Ihor Radchenko, emacs-orgmode

Would it be of org-mode's interest if I update 
`org-test-with-temp-text-in-file' to include a parameter which is a list 
of symbols to unbound after the test body is executed? Or maybe another 
macro called `org-test-with-temp-text-in-file-and-symbols'?

It's mainly because the tests I have written creates symbols and then 
later unbounds them. But, if the test fails then the symbols might not 
get unbound.



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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
  2024-05-22 11:11       ` Nafiz Islam
@ 2024-05-22 11:15         ` Ihor Radchenko
  0 siblings, 0 replies; 18+ messages in thread
From: Ihor Radchenko @ 2024-05-22 11:15 UTC (permalink / raw)
  To: Nafiz Islam; +Cc: emacs-orgmode

Nafiz Islam <nafiz.islam1000@gmail.com> writes:

> It's mainly because the tests I have written creates symbols and then 
> later unbounds them. But, if the test fails then the symbols might not 
> get unbound.

All you need to create a new symbol in local context is (let ((my-symbol value)) ...).

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
@ 2024-06-01 16:38 Nafiz Islam
  2024-06-05  9:09 ` Ihor Radchenko
  0 siblings, 1 reply; 18+ messages in thread
From: Nafiz Islam @ 2024-06-01 16:38 UTC (permalink / raw)
  To: yantar92; +Cc: emacs-orgmode

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

Craig Topham has cleared me to contribute to Emacs. I have attached the 
latest version of my patch.

 > All you need to create a new symbol in local context is (let ((my-symbol
 > value)) ...).

I don't think that will work because of lexical binding. For example:

(should
    (equal
     "* A\n* B\n** H1 Capture text\n* C\n"
     (org-test-with-temp-text-in-file "* A\n* B\n* C\n"
       (let* ((file (buffer-file-name))
          (org-capture-templates
           `(("t" "Todo" entry (file+headline ,file 
test-org-capture/entry/headline) "** H1 %?"))))
         (setq test-org-capture/entry/headline "B")
     (org-capture nil "t")
     (insert "Capture text")
     (org-capture-finalize)
         (makunbound 'test-org-capture/entry/headline))
       (buffer-string))))

In that code, if I decide to include `test-org-capture/entry/headline' 
in the `let' then the symbol would be `nil' during `org-capture' even if 
I initialize in the `let' or use `setq'.

I've been also considering testing if the lambda is actually visiting 
the target file when it is called, but that would mean 3 more tests: 
`headline', `olp' and `olp+datetree'. Curious to know if you believe 
that is worthwhile or not.

[-- Attachment #2: 0001-function-and-symbol-for-headline-and-olp-for-org-cap.patch --]
[-- Type: text/x-patch, Size: 16051 bytes --]

From bc1de480d7002a7fb02509901e2bc10f01b25060 Mon Sep 17 00:00:00 2001
From: Nafiz Islam <nafiz.islam1001@gmail.com>
Date: Tue, 21 May 2024 16:24:26 -0400
Subject: [PATCH] function and symbol for headline and olp for
 org-capture-templates

* doc/org-manual.org: add template format for the function and symbol variant
* etc/ORG-NEWS: announce the updated options
* lisp/org-capture.el (org-capture-templates): update customization type for headline, olp and olp+datetree targets, and update docstring
* lisp/org-capture.el (org-capture-expand-headline): define `org-capture-expand-headline'
* lisp/org-capture.el (org-capture-expand-olp): define `org-capture-expand-olp'
* lisp/org-capture.el (org-capture-set-target-location): use `org-capture-expand-headline' to expand headline, and use `org-capture-expand-olp' to expand outline path
* testing/lisp/test-org-capture.el (test-org-capture/entry): add tests for at most three different kinds of target for `file+headline', `file+olp', and `file+olp+datetree'
* testing/lisp/test-org-capture.el (test-org-capture/test-org-capture/org-capture-expand-olp): add tests for `org-capture-expand-olp'

---
 doc/org-manual.org               |  12 +++
 etc/ORG-NEWS                     |   6 ++
 lisp/org-capture.el              |  67 +++++++++++++---
 testing/lisp/test-org-capture.el | 130 ++++++++++++++++++++++++++++++-
 4 files changed, 201 insertions(+), 14 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 170eea506..fd23a6cf6 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -8041,10 +8041,18 @@ Now lets look at the elements of a template definition.  Each entry in
 
   - =(file+headline "filename" "node headline")= ::
 
+  - =(file+headline "filename" function-returning-headline)= ::
+
+  - =(file+headline "filename" symbol-containing-headline)= ::
+
     Fast configuration if the target heading is unique in the file.
 
   - =(file+olp "filename" "Level 1 heading" "Level 2" ...)= ::
 
+  - =(file+olp "filename" function-returning-outline-path)= ::
+
+  - =(file+olp "filename" symbol-containing-outline-path)= ::
+
     For non-unique headings, the full path is safer.
 
   - =(file+regexp "filename" "regexp to find location")= ::
@@ -8053,6 +8061,10 @@ Now lets look at the elements of a template definition.  Each entry in
 
   - =(file+olp+datetree "filename" [ "Level 1 heading" ...])= ::
 
+  - =(file+olp+datetree "filename" function-returning-outline-path)= ::
+
+  - =(file+olp+datetree "filename" symbol-containing-outline-path)= ::
+
     This target[fn:30] creates a heading in a date tree[fn:31] for
     today's date.  If the optional outline path is given, the tree
     will be built under the node it is pointing to, instead of at top
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 27712dd9a..82debb393 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -569,6 +569,12 @@ Users who do not want variable expansion can set
 
 This new hook runs when a note has been stored.
 
+*** New customization options for ~org-capture-templates~
+
+The variable ~org-capture-templates~ accepts a target specification
+for headline (~file+headline~) and olp (~file+old~ and
+~file+olp+datetree~) as function and symbol.
+
 *** New option controlling how Org mode sorts things ~org-sort-function~
 
 Sorting of agenda items, tables, menus, headlines, etc can now be
diff --git a/lisp/org-capture.el b/lisp/org-capture.el
index 6603b5e01..5d0c39b85 100644
--- a/lisp/org-capture.el
+++ b/lisp/org-capture.el
@@ -201,15 +201,21 @@ target       Specification of where the captured item should be placed.
                  File as child of this entry, or in the body of the entry
 
              (file+headline \"path/to/file\" \"node headline\")
+             (file+headline \"path/to/file\" function-returning-headline)
+             (file+headline \"path/to/file\" symbol-containing-headline)
                  Fast configuration if the target heading is unique in the file
 
              (file+olp \"path/to/file\" \"Level 1 heading\" \"Level 2\" ...)
+             (file+olp \"path/to/file\" function-returning-outline-path)
+             (file+olp \"path/to/file\" symbol-containing-outline-path)
                  For non-unique headings, the full outline path is safer
 
              (file+regexp  \"path/to/file\" \"regexp to find location\")
                  File to the entry matching regexp
 
              (file+olp+datetree \"path/to/file\" \"Level 1 heading\" ...)
+             (file+olp+datetree \"path/to/file\" function-returning-outline-path)
+             (file+olp+datetree \"path/to/file\" symbol-containing-outline-path)
                  Will create a heading in a date tree for today's date.
                  If no heading is given, the tree will be on top level.
                  To prompt for date instead of using TODAY, use the
@@ -409,7 +415,13 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 				(file :tag "Literal")
 				(function :tag "Function")
 				(variable :tag "Variable")
-				(sexp :tag "Form"))))
+				(sexp :tag "Form")))
+        (olp-variants '(choice :tag "Outline path"
+                               (repeat :tag "Outline path" :inline t
+				       (string :tag "Headline"))
+			       (function :tag "Function")
+			       (variable :tag "Variable")
+			       (sexp :tag "Form"))))
     `(repeat
       (choice :value ("" "" entry (file "~/org/notes.org") "")
 	      (list :tag "Multikey description"
@@ -434,12 +446,15 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 			    (list :tag "File & Headline"
 				  (const :format "" file+headline)
 				  ,file-variants
-				  (string :tag "  Headline"))
+				  (choice :tag "Headline"
+				          (string   :tag "Headline")
+				          (function :tag "Function")
+				          (variable :tag "Variable")
+				          (sexp :tag "Form")))
 			    (list :tag "File & Outline path"
 				  (const :format "" file+olp)
 				  ,file-variants
-				  (repeat :tag "Outline path" :inline t
-					  (string :tag "Headline")))
+				  ,olp-variants)
 			    (list :tag "File & Regexp"
 				  (const :format "" file+regexp)
 				  ,file-variants
@@ -447,8 +462,7 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 			    (list :tag "File [ & Outline path ] & Date tree"
 				  (const :format "" file+olp+datetree)
 				  ,file-variants
-				  (option (repeat :tag "Outline path" :inline t
-						  (string :tag "Headline"))))
+				  ,olp-variants)
 			    (list :tag "File & function"
 				  (const :format "" file+function)
 				  ,file-variants
@@ -1012,7 +1026,7 @@ Store them in the capture property list."
 	    (org-capture-put-target-region-and-position)
 	    (goto-char position))
 	   (_ (error "Cannot find target ID \"%s\"" id))))
-	(`(file+headline ,path ,(and headline (pred stringp)))
+	(`(file+headline ,path ,headline)
 	 (set-buffer (org-capture-target-buffer path))
 	 ;; Org expects the target file to be in Org mode, otherwise
 	 ;; it throws an error.  However, the default notes files
@@ -1026,6 +1040,7 @@ Store them in the capture property list."
 	 (org-capture-put-target-region-and-position)
 	 (widen)
 	 (goto-char (point-min))
+         (setq headline (org-capture-expand-headline headline))
 	 (if (re-search-forward (format org-complex-heading-regexp-format
 					(regexp-quote headline))
 				nil t)
@@ -1035,8 +1050,9 @@ Store them in the capture property list."
 	   (insert "* " headline "\n")
 	   (forward-line -1)))
 	(`(file+olp ,path . ,(and outline-path (guard outline-path)))
-	 (let ((m (org-find-olp (cons (org-capture-expand-file path)
-				      outline-path))))
+	 (let* ((expanded-file-path (org-capture-expand-file path))
+                (m (org-find-olp (cons expanded-file-path
+				       (apply #'org-capture-expand-olp expanded-file-path outline-path)))))
 	   (set-buffer (marker-buffer m))
 	   (org-capture-put-target-region-and-position)
 	   (widen)
@@ -1057,8 +1073,9 @@ Store them in the capture property list."
 		 (and (derived-mode-p 'org-mode) (org-at-heading-p)))))
 	(`(file+olp+datetree ,path . ,outline-path)
 	 (let ((m (if outline-path
-		      (org-find-olp (cons (org-capture-expand-file path)
-					  outline-path))
+		      (let ((expanded-file-path (org-capture-expand-file path)))
+                        (org-find-olp (cons expanded-file-path
+					    (apply #'org-capture-expand-olp expanded-file-path outline-path))))
 		    (set-buffer (org-capture-target-buffer path))
 		    (point-marker))))
 	   (set-buffer (marker-buffer m))
@@ -1143,6 +1160,34 @@ Store them in the capture property list."
 			      (org-decrypt-entry)
 			      (and (org-back-to-heading t) (point))))))))
 
+(defun org-capture-expand-headline (headline)
+  "Expand functions, symbols and headline names for HEADLINE.
+When HEADLINE is a function, call it. When it is a variable,
+return its value. When it is a string, return it.  In any other
+case, return `nil'."
+  (let* ((final-headline (cond ((stringp headline) headline)
+                               ((functionp headline) (funcall headline))
+                               ((and (symbolp headline) (boundp headline))
+                                (symbol-value headline))
+                               (t nil))))
+    final-headline))
+
+(defun org-capture-expand-olp (file &rest olp)
+  "Expand functions, symbols and outline paths for OLP.
+When OLP is a function, call it with no arguments while
+the current buffer is the FILE-visiting buffer. When it
+is a variable, return its value. When it is a list of
+string, return it. In any other case, signal an error."
+  (let* ((first (car olp))
+         (final-olp (cond ((or (not first) (stringp first)) olp)
+                          ((and (not (cdr olp)) (functionp first))
+                           (with-current-buffer (find-file-noselect file)
+                             (funcall first)))
+                          ((and (not (cdr olp)) (symbolp first) (boundp first))
+                           (symbol-value first))
+                          (t (error "Invalid outline path: %S" olp)))))
+      final-olp))
+
 (defun org-capture-expand-file (file)
   "Expand functions, symbols and file names for FILE.
 When FILE is a function, call it.  When it is a form, evaluate
diff --git a/testing/lisp/test-org-capture.el b/testing/lisp/test-org-capture.el
index 0ed44c6af..ceb6fd7e8 100644
--- a/testing/lisp/test-org-capture.el
+++ b/testing/lisp/test-org-capture.el
@@ -214,15 +214,112 @@
   ;; Do not break next headline.
   (should
    (equal
-    "* A\n** H1 Capture text\n* B\n"
-    (org-test-with-temp-text-in-file "* A\n* B\n"
+    "* A\n* B\n** H1 Capture text\n* C\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n* C\n"
       (let* ((file (buffer-file-name))
 	     (org-capture-templates
-	      `(("t" "Todo" entry (file+headline ,file "A") "** H1 %?"))))
+	      `(("t" "Todo" entry (file+headline ,file "B") "** H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n* B\n** H1 Capture text\n* C\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n* C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+headline ,file ,(lambda () "B")) "** H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n* B\n** H1 Capture text\n* C\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n* C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+headline ,file test-org-capture/entry/headline) "** H1 %?"))))
+        (setq test-org-capture/entry/headline "B")
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize)
+        (makunbound 'test-org-capture/entry/headline))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp ,file "A" "B") "* H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp ,file ,(lambda () '("A" "B"))) "* H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp ,file test-org-capture/entry/file+olp) "* H1 %?"))))
+        (setq test-org-capture/entry/file+olp '("A" "B"))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize)
+        (makunbound 'test-org-capture/entry/file+olp))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp+datetree ,file "A" "B") "* H1 %?")))
+             (org-overriding-default-time 0))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp+datetree ,file (lambda () '("A" "B"))) "* H1 %?")))
+             (org-overriding-default-time 0))
 	(org-capture nil "t")
 	(insert "Capture text")
 	(org-capture-finalize))
       (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp+datetree ,file test-org-capture/entry/file+olp+datetree) "* H1 %?")))
+             (org-overriding-default-time 0))
+        (setq test-org-capture/entry/file+olp+datetree '("A" "B"))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize)
+        (makunbound 'test-org-capture/entry/file+olp+datetree))
+      (buffer-string))))
   ;; Correctly save position of inserted entry.
   (should
    (equal
@@ -809,5 +906,32 @@ before\nglobal-before\nafter\nglobal-after"
               (org-capture nil "t")
               (buffer-string))))))
 
+(ert-deftest test-org-capture/org-capture-expand-olp ()
+  "Test org-capture-expand-olp."
+  ;; org-capture-expand-olp accepts inlined outline path
+  (should
+   (equal
+    '("A" "B" "C")
+    (let ((file (make-temp-file "org-test")))
+      (unwind-protect
+          (org-capture-expand-olp file "A" "B" "C")
+        (delete-file file)))))
+  ;; The current buffer during the funcall of the lambda is the temporary test file
+  (should
+   (let ((file (make-temp-file "org-test")))
+     (equal
+      file
+      (unwind-protect
+          (org-capture-expand-olp file (lambda () (buffer-file-name)))
+        (delete-file file)))))
+  ;; org-capture-expand-olp rejects outline path that is not inlined
+  (should-error
+   (equal
+    '("A" "B" "C")
+    (let ((file (make-temp-file "org-test")))
+      (unwind-protect
+          (org-capture-expand-olp file '("A" "B" "C"))
+        (delete-file file))))))
+
 (provide 'test-org-capture)
 ;;; test-org-capture.el ends here
-- 
2.44.1


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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
  2024-06-01 16:38 [PATCH] function and symbol for headline and olp for org-capture-templates Nafiz Islam
@ 2024-06-05  9:09 ` Ihor Radchenko
  2024-06-05 21:16   ` Bastien Guerry
                     ` (2 more replies)
  0 siblings, 3 replies; 18+ messages in thread
From: Ihor Radchenko @ 2024-06-05  9:09 UTC (permalink / raw)
  To: Nafiz Islam, bzg; +Cc: emacs-orgmode

Nafiz Islam <nafiz.islam1000@gmail.com> writes:

> Craig Topham has cleared me to contribute to Emacs.

Bastien, may you please check the FSF records?

>  > All you need to create a new symbol in local context is (let ((my-symbol
>  > value)) ...).
>
> I don't think that will work because of lexical binding. For example:
> ..

Right. You need dynamic let:

(dlet ((test-org-capture/entry/headline "B"))
 ...)

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
  2024-06-05  9:09 ` Ihor Radchenko
@ 2024-06-05 21:16   ` Bastien Guerry
       [not found]   ` <f2b85669-a0fc-40c6-891a-1319d0582fe0@gmail.com>
       [not found]   ` <3508dbb0-a8ee-4217-af21-a9fc3ac46eb9@gmail.com>
  2 siblings, 0 replies; 18+ messages in thread
From: Bastien Guerry @ 2024-06-05 21:16 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Nafiz Islam, emacs-orgmode

Ihor Radchenko <yantar92@posteo.net> writes:

> Nafiz Islam <nafiz.islam1000@gmail.com> writes:
>
>> Craig Topham has cleared me to contribute to Emacs.
>
> Bastien, may you please check the FSF records?

Yes, things are okay.  Thanks Nafiz!

-- 
 Bastien Guerry


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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
       [not found]     ` <87frtpgj2w.fsf@localhost>
@ 2024-06-09 14:59       ` Nafiz Islam
  2024-06-09 17:03         ` Ihor Radchenko
  0 siblings, 1 reply; 18+ messages in thread
From: Nafiz Islam @ 2024-06-09 14:59 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-orgmode

> I tried to run make test with your patch, and it is failing:

On my side it passes on main branch at commit 6c862699a6db3f6b76391c05380d92d9f1b3838f with patch.
    passed   444/1207  test-org-capture/entry (0.099791 sec)
    ...
    passed   447/1207  test-org-capture/org-capture-expand-olp (0.001104 sec)

Apparently, you get
`"* A\n** B\n*** 1970\n**** 1970-01 January\n***** 1970-01-01 Thursday\n****** H1 Capture text\n** C\n"'

Whereas I get the expected result of
`"* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n"'

What do you get if you evaluate `(calendar-gregorian-from-absolute (time-to-days 0))'? I get `(12 31 1969)'.
I set `org-overriding-default-time' to `0' for testing with datetree.

>> I don't have to additionally test for whether the lambda (as a target) 
>> is actually called while visiting the file right?
> May you elaborate what you mean?

So I've written a test for lambda-based target.

(should
    (equal
     "* A\n* B\n** H1 Capture text\n* C\n"
     (org-test-with-temp-text-in-file "* A\n* B\n* C\n"
       (let* ((file (buffer-file-name))
	     (org-capture-templates
	      `(("t" "Todo" entry (file+headline ,file ,(lambda () "B")) "** H1 %?"))))
	(org-capture nil "t")
	(insert "Capture text")
	(org-capture-finalize))
       (buffer-string))))

But it does not verify that the lambda `(lambda () "B")' is actually being called while
visiting the file which would allow reading the file to compute or generate a headline.
So I'm wondering if I should write a test for that too. Maybe for each new lambda target?



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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
  2024-06-09 14:59       ` Nafiz Islam
@ 2024-06-09 17:03         ` Ihor Radchenko
  0 siblings, 0 replies; 18+ messages in thread
From: Ihor Radchenko @ 2024-06-09 17:03 UTC (permalink / raw)
  To: Nafiz Islam; +Cc: emacs-orgmode

Nafiz Islam <nafiz.islam1000@gmail.com> writes:

>> I tried to run make test with your patch, and it is failing:
>
> On my side it passes on main branch at commit 6c862699a6db3f6b76391c05380d92d9f1b3838f with patch.
>     passed   444/1207  test-org-capture/entry (0.099791 sec)
>     ...
>     passed   447/1207  test-org-capture/org-capture-expand-olp (0.001104 sec)
>
> Apparently, you get
> `"* A\n** B\n*** 1970\n**** 1970-01 January\n***** 1970-01-01 Thursday\n****** H1 Capture text\n** C\n"'
>
> Whereas I get the expected result of
> `"* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n"'
>
> What do you get if you evaluate `(calendar-gregorian-from-absolute (time-to-days 0))'? I get `(12 31 1969)'.
> I set `org-overriding-default-time' to `0' for testing with datetree.

I get (1 1 1970).

It is probably more reliable to use `org-test-at-time' macro.

>>> I don't have to additionally test for whether the lambda (as a target) 
>>> is actually called while visiting the file right?
>> May you elaborate what you mean?
>
> So I've written a test for lambda-based target.
>
> (should
>     (equal
>      "* A\n* B\n** H1 Capture text\n* C\n"
>      (org-test-with-temp-text-in-file "* A\n* B\n* C\n"
>        (let* ((file (buffer-file-name))
> 	     (org-capture-templates
> 	      `(("t" "Todo" entry (file+headline ,file ,(lambda () "B")) "** H1 %?"))))
> 	(org-capture nil "t")
> 	(insert "Capture text")
> 	(org-capture-finalize))
>        (buffer-string))))
>
> But it does not verify that the lambda `(lambda () "B")' is actually being called while
> visiting the file which would allow reading the file to compute or generate a headline.
> So I'm wondering if I should write a test for that too. Maybe for each new lambda target?

You can. Maybe even as a part of the above test (and similar):

`(("t" "Todo"
   entry
   (file+headline
    ,file
    (lambda ()
      (should (equal ,file (buffer-file-name)))
      "B"))
   "** H1 %?"))

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
       [not found]     ` <874ja4ak0q.fsf@localhost>
@ 2024-06-15 21:45       ` Nafiz Islam
  2024-06-16 12:20         ` Ihor Radchenko
  0 siblings, 1 reply; 18+ messages in thread
From: Nafiz Islam @ 2024-06-15 21:45 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-orgmode

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

I've updated my tests with `org-test-at-time' and updated the commit 
message to fit within default column of 70.

[-- Attachment #2: 0001-function-and-symbol-for-headline-and-olp-for-org-cap.patch --]
[-- Type: text/x-patch, Size: 16554 bytes --]

From 25c0252c8532e498e3fce103dd44a81441d8a51a Mon Sep 17 00:00:00 2001
From: Nafiz Islam <nafiz.islam1001@gmail.com>
Date: Tue, 21 May 2024 16:24:26 -0400
Subject: [PATCH] function and symbol for headline and olp for
 org-capture-templates

* doc/org-manual.org: Add template formats for the function and symbol
variant.
* etc/ORG-NEWS: Announce the updated options.
* lisp/org-capture.el (org-capture-templates): Update customization
type for `file+headline', `file+olp' and `file+olp+datetree' targets,
and update docstring.
(org-capture-expand-headline): Define `org-capture-expand-headline'.
(org-capture-expand-olp): Define `org-capture-expand-olp'.
(org-capture-set-target-location): Use `org-capture-expand-headline'
to expand headline, and use `org-capture-expand-olp' to expand outline
path.
* testing/lisp/test-org-capture.el (test-org-capture/entry): Add tests
for at most three different kinds of target for `file+headline',
`file+olp', and `file+olp+datetree'.
(test-org-capture/org-capture-expand-olp): Add tests for
`org-capture-expand-olp'.
---
 doc/org-manual.org               |  12 +++
 etc/ORG-NEWS                     |   6 ++
 lisp/org-capture.el              |  67 +++++++++++---
 testing/lisp/test-org-capture.el | 148 ++++++++++++++++++++++++++++++-
 4 files changed, 219 insertions(+), 14 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 3973764f9..c4bcffdc3 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -8052,10 +8052,18 @@ Now lets look at the elements of a template definition.  Each entry in
 
   - =(file+headline "filename" "node headline")= ::
 
+  - =(file+headline "filename" function-returning-headline)= ::
+
+  - =(file+headline "filename" symbol-containing-headline)= ::
+
     Fast configuration if the target heading is unique in the file.
 
   - =(file+olp "filename" "Level 1 heading" "Level 2" ...)= ::
 
+  - =(file+olp "filename" function-returning-outline-path)= ::
+
+  - =(file+olp "filename" symbol-containing-outline-path)= ::
+
     For non-unique headings, the full path is safer.
 
   - =(file+regexp "filename" "regexp to find location")= ::
@@ -8064,6 +8072,10 @@ Now lets look at the elements of a template definition.  Each entry in
 
   - =(file+olp+datetree "filename" [ "Level 1 heading" ...])= ::
 
+  - =(file+olp+datetree "filename" function-returning-outline-path)= ::
+
+  - =(file+olp+datetree "filename" symbol-containing-outline-path)= ::
+
     This target[fn:30] creates a heading in a date tree[fn:31] for
     today's date.  If the optional outline path is given, the tree
     will be built under the node it is pointing to, instead of at top
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index ec28f77d8..aa45809c8 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -661,6 +661,12 @@ Users who do not want variable expansion can set
 
 This new hook runs when a note has been stored.
 
+*** New customization options for ~org-capture-templates~
+
+The variable ~org-capture-templates~ accepts a target specification
+for headline (~file+headline~) and olp (~file+olp~ and
+~file+olp+datetree~) as function and symbol.
+
 *** New option controlling how Org mode sorts things ~org-sort-function~
 
 Sorting of agenda items, tables, menus, headlines, etc can now be
diff --git a/lisp/org-capture.el b/lisp/org-capture.el
index 786b81771..1711e5b79 100644
--- a/lisp/org-capture.el
+++ b/lisp/org-capture.el
@@ -201,15 +201,21 @@ target       Specification of where the captured item should be placed.
                  File as child of this entry, or in the body of the entry
 
              (file+headline \"path/to/file\" \"node headline\")
+             (file+headline \"path/to/file\" function-returning-headline)
+             (file+headline \"path/to/file\" symbol-containing-headline)
                  Fast configuration if the target heading is unique in the file
 
              (file+olp \"path/to/file\" \"Level 1 heading\" \"Level 2\" ...)
+             (file+olp \"path/to/file\" function-returning-outline-path)
+             (file+olp \"path/to/file\" symbol-containing-outline-path)
                  For non-unique headings, the full outline path is safer
 
              (file+regexp  \"path/to/file\" \"regexp to find location\")
                  File to the entry matching regexp
 
              (file+olp+datetree \"path/to/file\" \"Level 1 heading\" ...)
+             (file+olp+datetree \"path/to/file\" function-returning-outline-path)
+             (file+olp+datetree \"path/to/file\" symbol-containing-outline-path)
                  Will create a heading in a date tree for today's date.
                  If no heading is given, the tree will be on top level.
                  To prompt for date instead of using TODAY, use the
@@ -411,7 +417,12 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 				(file :tag "Literal")
 				(function :tag "Function")
 				(variable :tag "Variable")
-				(sexp :tag "Form"))))
+				(sexp :tag "Form")))
+        (olp-variants '(choice :tag "Outline path"
+                               (repeat :tag "Outline path" :inline t
+				       (string :tag "Headline"))
+			       (function :tag "Function")
+			       (variable :tag "Variable"))))
     `(repeat
       (choice :value ("" "" entry (file "~/org/notes.org") "")
 	      (list :tag "Multikey description"
@@ -436,12 +447,14 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 			    (list :tag "File & Headline"
 				  (const :format "" file+headline)
 				  ,file-variants
-				  (string :tag "  Headline"))
+				  (choice :tag "Headline"
+				          (string   :tag "Headline")
+				          (function :tag "Function")
+				          (variable :tag "Variable")))
 			    (list :tag "File & Outline path"
 				  (const :format "" file+olp)
 				  ,file-variants
-				  (repeat :tag "Outline path" :inline t
-					  (string :tag "Headline")))
+				  ,olp-variants)
 			    (list :tag "File & Regexp"
 				  (const :format "" file+regexp)
 				  ,file-variants
@@ -449,8 +462,7 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 			    (list :tag "File [ & Outline path ] & Date tree"
 				  (const :format "" file+olp+datetree)
 				  ,file-variants
-				  (option (repeat :tag "Outline path" :inline t
-						  (string :tag "Headline"))))
+				  ,olp-variants)
 			    (list :tag "File & function"
 				  (const :format "" file+function)
 				  ,file-variants
@@ -1013,7 +1025,7 @@ Store them in the capture property list."
 	    (org-capture-put-target-region-and-position)
 	    (goto-char position))
 	   (_ (error "Cannot find target ID \"%s\"" id))))
-	(`(file+headline ,path ,(and headline (pred stringp)))
+	(`(file+headline ,path ,headline)
 	 (set-buffer (org-capture-target-buffer path))
 	 ;; Org expects the target file to be in Org mode, otherwise
 	 ;; it throws an error.  However, the default notes files
@@ -1027,6 +1039,7 @@ Store them in the capture property list."
 	 (org-capture-put-target-region-and-position)
 	 (widen)
 	 (goto-char (point-min))
+         (setq headline (org-capture-expand-headline headline))
 	 (if (re-search-forward (format org-complex-heading-regexp-format
 					(regexp-quote headline))
 				nil t)
@@ -1036,8 +1049,9 @@ Store them in the capture property list."
 	   (insert "* " headline "\n")
 	   (forward-line -1)))
 	(`(file+olp ,path . ,(and outline-path (guard outline-path)))
-	 (let ((m (org-find-olp (cons (org-capture-expand-file path)
-				      outline-path))))
+	 (let* ((expanded-file-path (org-capture-expand-file path))
+                (m (org-find-olp (cons expanded-file-path
+				       (apply #'org-capture-expand-olp expanded-file-path outline-path)))))
 	   (set-buffer (marker-buffer m))
 	   (org-capture-put-target-region-and-position)
 	   (widen)
@@ -1058,8 +1072,9 @@ Store them in the capture property list."
 		 (and (derived-mode-p 'org-mode) (org-at-heading-p)))))
 	(`(file+olp+datetree ,path . ,outline-path)
 	 (let ((m (if outline-path
-		      (org-find-olp (cons (org-capture-expand-file path)
-					  outline-path))
+		      (let ((expanded-file-path (org-capture-expand-file path)))
+                        (org-find-olp (cons expanded-file-path
+					    (apply #'org-capture-expand-olp expanded-file-path outline-path))))
 		    (set-buffer (org-capture-target-buffer path))
 		    (point-marker))))
 	   (set-buffer (marker-buffer m))
@@ -1144,6 +1159,36 @@ Store them in the capture property list."
 			      (org-decrypt-entry)
 			      (and (org-back-to-heading t) (point))))))))
 
+(defun org-capture-expand-headline (headline)
+  "Expand functions, symbols and headline names for HEADLINE.
+When HEADLINE is a function, call it. When it is a variable,
+return its value. When it is a string, return it.  In any other
+case, signal an error."
+  (let* ((final-headline (cond ((stringp headline) headline)
+                               ((functionp headline) (funcall headline))
+                               ((and (symbolp headline) (boundp headline))
+                                (symbol-value headline))
+                               (t nil))))
+    (or final-headline
+        (error "Invalid headline: %S" headline))))
+
+(defun org-capture-expand-olp (file &rest olp)
+  "Expand functions, symbols and outline paths for OLP.
+When OLP is a function, call it with no arguments while
+the current buffer is the FILE-visiting buffer. When it
+is a variable, return its value. When it is a list of
+string, return it. In any other case, signal an error."
+  (let* ((first (car olp))
+         (final-olp (cond ((not (memq nil (mapcar #'stringp olp))) olp)
+                          ((and (not (cdr olp)) (functionp first))
+                           (with-current-buffer (find-file-noselect file)
+                             (funcall first)))
+                          ((and (not (cdr olp)) (symbolp first) (boundp first))
+                           (symbol-value first))
+                          (t nil))))
+    (or final-olp
+        (error "Invalid outline path: %S" olp))))
+
 (defun org-capture-expand-file (file)
   "Expand functions, symbols and file names for FILE.
 When FILE is a function, call it.  When it is a form, evaluate
diff --git a/testing/lisp/test-org-capture.el b/testing/lisp/test-org-capture.el
index f97d08bce..1ad2825c3 100644
--- a/testing/lisp/test-org-capture.el
+++ b/testing/lisp/test-org-capture.el
@@ -214,15 +214,130 @@
   ;; Do not break next headline.
   (should
    (equal
-    "* A\n** H1 Capture text\n* B\n"
-    (org-test-with-temp-text-in-file "* A\n* B\n"
+    "* A\n* B\n** H1 Capture text\n* C\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n* C\n"
       (let* ((file (buffer-file-name))
 	     (org-capture-templates
-	      `(("t" "Todo" entry (file+headline ,file "A") "** H1 %?"))))
+	      `(("t" "Todo" entry (file+headline ,file "B") "** H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n* B\n** H1 Capture text\n* C\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n* C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t"
+                 "Todo"
+                 entry
+                 (file+headline ,file (lambda ()
+                                        (should (equal ,file (buffer-file-name)))
+                                        "B"))
+                 "** H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n* B\n** H1 Capture text\n* C\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n* C\n"
+      (dlet ((test-org-capture/entry/headline))
+        (let* ((file (buffer-file-name))
+	       (org-capture-templates
+	        `(("t" "Todo" entry (file+headline ,file test-org-capture/entry/headline) "** H1 %?"))))
+          (setq test-org-capture/entry/headline "B")
+	  (org-capture nil "t")
+	  (insert "Capture text")
+	  (org-capture-finalize)))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp ,file "A" "B") "* H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t"
+                 "Todo"
+                 entry
+                 (file+olp ,file (lambda ()
+                                    (should (equal ,file (buffer-file-name)))
+                                    '("A" "B")))
+                 "* H1 %?"))))
 	(org-capture nil "t")
 	(insert "Capture text")
 	(org-capture-finalize))
       (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (dlet ((test-org-capture/entry/file+olp))
+        (let* ((file (buffer-file-name))
+	       (org-capture-templates
+	        `(("t" "Todo" entry (file+olp ,file test-org-capture/entry/file+olp) "* H1 %?"))))
+          (setq test-org-capture/entry/file+olp '("A" "B"))
+	  (org-capture nil "t")
+	  (insert "Capture text")
+	  (org-capture-finalize)))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp+datetree ,file "A" "B") "* H1 %?"))))
+        (org-test-at-time 0
+	  (org-capture nil "t")
+	  (insert "Capture text")
+	  (org-capture-finalize)))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t"
+                 "Todo"
+                 entry
+                 (file+olp+datetree ,file (lambda ()
+                                            (should (equal ,file (buffer-file-name)))
+                                            '("A" "B")))
+                 "* H1 %?"))))
+	(org-test-at-time 0
+	  (org-capture nil "t")
+	  (insert "Capture text")
+	  (org-capture-finalize)))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 1969\n**** 1969-12 December\n***** 1969-12-31 Wednesday\n****** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (dlet ((test-org-capture/entry/file+olp+datetree))
+        (let* ((file (buffer-file-name))
+	       (org-capture-templates
+	        `(("t" "Todo" entry (file+olp+datetree ,file test-org-capture/entry/file+olp+datetree) "* H1 %?"))))
+          (setq test-org-capture/entry/file+olp+datetree '("A" "B"))
+          (org-test-at-time 0
+	    (org-capture nil "t")
+	    (insert "Capture text")
+	    (org-capture-finalize))))
+      (buffer-string))))
   ;; Correctly save position of inserted entry.
   (should
    (equal
@@ -843,5 +958,32 @@ before\nglobal-before\nafter\nglobal-after"
               (org-capture nil "t")
               (buffer-string))))))
 
+(ert-deftest test-org-capture/org-capture-expand-olp ()
+  "Test org-capture-expand-olp."
+  ;; org-capture-expand-olp accepts inlined outline path
+  (should
+   (equal
+    '("A" "B" "C")
+    (let ((file (make-temp-file "org-test")))
+      (unwind-protect
+          (org-capture-expand-olp file "A" "B" "C")
+        (delete-file file)))))
+  ;; The current buffer during the funcall of the lambda is the temporary test file
+  (should
+   (let ((file (make-temp-file "org-test")))
+     (equal
+      file
+      (unwind-protect
+          (org-capture-expand-olp file (lambda () (buffer-file-name)))
+        (delete-file file)))))
+  ;; org-capture-expand-olp rejects outline path that is not inlined
+  (should-error
+   (equal
+    '("A" "B" "C")
+    (let ((file (make-temp-file "org-test")))
+      (unwind-protect
+          (org-capture-expand-olp file '("A" "B" "C"))
+        (delete-file file))))))
+
 (provide 'test-org-capture)
 ;;; test-org-capture.el ends here
-- 
2.44.1


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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
  2024-06-15 21:45       ` Nafiz Islam
@ 2024-06-16 12:20         ` Ihor Radchenko
       [not found]           ` <c98ba108-c07e-4c17-a806-524444367d9d@gmail.com>
  0 siblings, 1 reply; 18+ messages in thread
From: Ihor Radchenko @ 2024-06-16 12:20 UTC (permalink / raw)
  To: Nafiz Islam; +Cc: emacs-orgmode

Nafiz Islam <nafiz.islam1000@gmail.com> writes:

> I've updated my tests with `org-test-at-time' and updated the commit 
> message to fit within default column of 70.

That does not help, unfortunately. My "0" time is still different from
yours.

If will be more reliable to use a specific timestamp. See other uses of
`org-test-at-time' in the tests.

Also, please rebase the patch onto the latest main.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
       [not found]             ` <87plsfa2nt.fsf@localhost>
@ 2024-06-18 12:05               ` Nafiz Islam
  2024-06-18 12:36                 ` Ihor Radchenko
  0 siblings, 1 reply; 18+ messages in thread
From: Nafiz Islam @ 2024-06-18 12:05 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-orgmode

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

I've attached the patch with all of the concerns addressed, hopefully.


 >Why (t nil)? It would be more sensible to throw an error.

I believe that was already addressed.

+ (or final-headline + (error "Invalid headline: %S" headline))))

[-- Attachment #2: 0001-org-capture-templates-Allow-headline-olp-target-to-b.patch --]
[-- Type: text/x-patch, Size: 16737 bytes --]

From 4cf99be212757ad910b8bbf680171434c0e6b06c Mon Sep 17 00:00:00 2001
From: Nafiz Islam <nafiz.islam1001@gmail.com>
Date: Tue, 21 May 2024 16:24:26 -0400
Subject: [PATCH] org-capture-templates: Allow headline/olp target to
 be function or symbol

* doc/org-manual.org: Add target spec format for function and symbol
for headline and olp.
* etc/ORG-NEWS: Announce the updated options for
`org-capture-templates'
* lisp/org-capture.el (org-capture-templates): Update customization
type for `file+headline', `file+olp' and `file+olp+datetree' targets,
and update docstring.
(org-capture-expand-headline): Define a new function that computes
headline string from target spec.
(org-capture-expand-olp): Define a new function that computes olp list
from target spec.
(org-capture-set-target-location): Use `org-capture-expand-headline'
to expand headline, and use `org-capture-expand-olp' to expand outline
path.
* testing/lisp/test-org-capture.el (test-org-capture/entry): Add tests
for at most three different kinds of target for `file+headline',
`file+olp', and `file+olp+datetree'.
(test-org-capture/org-capture-expand-olp): Add tests for
`org-capture-expand-olp'.
---
 doc/org-manual.org               |  12 +++
 etc/ORG-NEWS                     |   6 ++
 lisp/org-capture.el              |  67 +++++++++++---
 testing/lisp/test-org-capture.el | 148 ++++++++++++++++++++++++++++++-
 4 files changed, 219 insertions(+), 14 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 3973764f9..96c90eaab 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -8052,10 +8052,18 @@ Now lets look at the elements of a template definition.  Each entry in
 
   - =(file+headline "filename" "node headline")= ::
 
+  - =(file+headline "filename" function-returning-string)= ::
+
+  - =(file+headline "filename" symbol-containing-string)= ::
+
     Fast configuration if the target heading is unique in the file.
 
   - =(file+olp "filename" "Level 1 heading" "Level 2" ...)= ::
 
+  - =(file+olp "filename" function-returning-list-of-strings)= ::
+
+  - =(file+olp "filename" symbol-containing-list-of-strings)= ::
+
     For non-unique headings, the full path is safer.
 
   - =(file+regexp "filename" "regexp to find location")= ::
@@ -8064,6 +8072,10 @@ Now lets look at the elements of a template definition.  Each entry in
 
   - =(file+olp+datetree "filename" [ "Level 1 heading" ...])= ::
 
+  - =(file+olp+datetree "filename" function-returning-list-of-strings)= ::
+
+  - =(file+olp+datetree "filename" symbol-containing-list-of-strings)= ::
+
     This target[fn:30] creates a heading in a date tree[fn:31] for
     today's date.  If the optional outline path is given, the tree
     will be built under the node it is pointing to, instead of at top
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index e2cacb401..c7ce9f6b3 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -43,6 +43,12 @@ or newer.
 # adding new customizations, or changing the interpretation of the
 # existing customizations.
 
+*** Allow headline/olp target in ~org-capture-templates~ to be a function/variable
+
+The variable ~org-capture-templates~ accepts a target specification as
+function and symbol for headline (~file+headline~) and olp (~file+olp~
+and ~file+olp+datetree~).
+
 *** New =%\*N= placeholder in ~org-capture-templates~
 
 The new placeholder is like =%\N=, gives access not only to the
diff --git a/lisp/org-capture.el b/lisp/org-capture.el
index 98a43b096..c75b25c40 100644
--- a/lisp/org-capture.el
+++ b/lisp/org-capture.el
@@ -201,15 +201,21 @@ target       Specification of where the captured item should be placed.
                  File as child of this entry, or in the body of the entry
 
              (file+headline \"path/to/file\" \"node headline\")
+             (file+headline \"path/to/file\" function-returning-string)
+             (file+headline \"path/to/file\" symbol-containing-string)
                  Fast configuration if the target heading is unique in the file
 
              (file+olp \"path/to/file\" \"Level 1 heading\" \"Level 2\" ...)
+             (file+olp \"path/to/file\" function-returning-list-of-strings)
+             (file+olp \"path/to/file\" symbol-containing-list-of-strings)
                  For non-unique headings, the full outline path is safer
 
              (file+regexp  \"path/to/file\" \"regexp to find location\")
                  File to the entry matching regexp
 
              (file+olp+datetree \"path/to/file\" \"Level 1 heading\" ...)
+             (file+olp+datetree \"path/to/file\" function-returning-list-of-strings)
+             (file+olp+datetree \"path/to/file\" symbol-containing-list-of-strings)
                  Will create a heading in a date tree for today's date.
                  If no heading is given, the tree will be on top level.
                  To prompt for date instead of using TODAY, use the
@@ -410,7 +416,12 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
   (let ((file-variants '(choice :tag "Filename       "
 				(file :tag "Literal")
 				(function :tag "Function")
-				(variable :tag "Variable"))))
+				(variable :tag "Variable")))
+        (olp-variants '(choice :tag "Outline path"
+                               (repeat :tag "Outline path" :inline t
+				       (string :tag "Headline"))
+			       (function :tag "Function")
+			       (variable :tag "Variable"))))
     `(repeat
       (choice :value ("" "" entry (file "~/org/notes.org") "")
 	      (list :tag "Multikey description"
@@ -435,12 +446,14 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 			    (list :tag "File & Headline"
 				  (const :format "" file+headline)
 				  ,file-variants
-				  (string :tag "  Headline"))
+				  (choice :tag "Headline"
+				          (string   :tag "Headline")
+				          (function :tag "Function")
+				          (variable :tag "Variable")))
 			    (list :tag "File & Outline path"
 				  (const :format "" file+olp)
 				  ,file-variants
-				  (repeat :tag "Outline path" :inline t
-					  (string :tag "Headline")))
+				  ,olp-variants)
 			    (list :tag "File & Regexp"
 				  (const :format "" file+regexp)
 				  ,file-variants
@@ -448,8 +461,7 @@ you can escape ambiguous cases with a backward slash, e.g., \\%i."
 			    (list :tag "File [ & Outline path ] & Date tree"
 				  (const :format "" file+olp+datetree)
 				  ,file-variants
-				  (option (repeat :tag "Outline path" :inline t
-						  (string :tag "Headline"))))
+				  ,olp-variants)
 			    (list :tag "File & function"
 				  (const :format "" file+function)
 				  ,file-variants
@@ -1012,7 +1024,7 @@ Store them in the capture property list."
 	    (org-capture-put-target-region-and-position)
 	    (goto-char position))
 	   (_ (error "Cannot find target ID \"%s\"" id))))
-	(`(file+headline ,path ,(and headline (pred stringp)))
+	(`(file+headline ,path ,headline)
 	 (set-buffer (org-capture-target-buffer path))
 	 ;; Org expects the target file to be in Org mode, otherwise
 	 ;; it throws an error.  However, the default notes files
@@ -1026,6 +1038,7 @@ Store them in the capture property list."
 	 (org-capture-put-target-region-and-position)
 	 (widen)
 	 (goto-char (point-min))
+         (setq headline (org-capture-expand-headline headline))
 	 (if (re-search-forward (format org-complex-heading-regexp-format
 					(regexp-quote headline))
 				nil t)
@@ -1035,8 +1048,9 @@ Store them in the capture property list."
 	   (insert "* " headline "\n")
 	   (forward-line -1)))
 	(`(file+olp ,path . ,(and outline-path (guard outline-path)))
-	 (let ((m (org-find-olp (cons (org-capture-expand-file path)
-				      outline-path))))
+	 (let* ((expanded-file-path (org-capture-expand-file path))
+                (m (org-find-olp (cons expanded-file-path
+				       (apply #'org-capture-expand-olp expanded-file-path outline-path)))))
 	   (set-buffer (marker-buffer m))
 	   (org-capture-put-target-region-and-position)
 	   (widen)
@@ -1057,8 +1071,9 @@ Store them in the capture property list."
 		 (and (derived-mode-p 'org-mode) (org-at-heading-p)))))
 	(`(file+olp+datetree ,path . ,outline-path)
 	 (let ((m (if outline-path
-		      (org-find-olp (cons (org-capture-expand-file path)
-					  outline-path))
+		      (let ((expanded-file-path (org-capture-expand-file path)))
+                        (org-find-olp (cons expanded-file-path
+					    (apply #'org-capture-expand-olp expanded-file-path outline-path))))
 		    (set-buffer (org-capture-target-buffer path))
 		    (point-marker))))
 	   (set-buffer (marker-buffer m))
@@ -1143,6 +1158,36 @@ Store them in the capture property list."
 			      (org-decrypt-entry)
 			      (and (org-back-to-heading t) (point))))))))
 
+(defun org-capture-expand-headline (headline)
+  "Expand functions, symbols and headline names for HEADLINE.
+When HEADLINE is a function, call it. When it is a variable,
+return its value. When it is a string, return it.  In any other
+case, signal an error."
+  (let* ((final-headline (cond ((stringp headline) headline)
+                               ((functionp headline) (funcall headline))
+                               ((and (symbolp headline) (boundp headline))
+                                (symbol-value headline))
+                               (t nil))))
+    (or final-headline
+        (error "Invalid headline: %S" headline))))
+
+(defun org-capture-expand-olp (file &rest olp)
+  "Expand functions, symbols and outline paths for OLP.
+When OLP is a function, call it with no arguments while
+the current buffer is the FILE-visiting buffer. When it
+is a variable, return its value. When it is a list of
+string, return it. In any other case, signal an error."
+  (let* ((first (car olp))
+         (final-olp (cond ((not (memq nil (mapcar #'stringp olp))) olp)
+                          ((and (not (cdr olp)) (functionp first))
+                           (with-current-buffer (find-file-noselect file)
+                             (funcall first)))
+                          ((and (not (cdr olp)) (symbolp first) (boundp first))
+                           (symbol-value first))
+                          (t nil))))
+    (or final-olp
+        (error "Invalid outline path: %S" olp))))
+
 (defun org-capture-expand-file (file)
   "Expand functions, symbols and file names for FILE.
 When FILE is a function, call it.  When it is a form, evaluate
diff --git a/testing/lisp/test-org-capture.el b/testing/lisp/test-org-capture.el
index f97d08bce..f969e6c62 100644
--- a/testing/lisp/test-org-capture.el
+++ b/testing/lisp/test-org-capture.el
@@ -214,15 +214,130 @@
   ;; Do not break next headline.
   (should
    (equal
-    "* A\n** H1 Capture text\n* B\n"
-    (org-test-with-temp-text-in-file "* A\n* B\n"
+    "* A\n* B\n** H1 Capture text\n* C\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n* C\n"
       (let* ((file (buffer-file-name))
 	     (org-capture-templates
-	      `(("t" "Todo" entry (file+headline ,file "A") "** H1 %?"))))
+	      `(("t" "Todo" entry (file+headline ,file "B") "** H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n* B\n** H1 Capture text\n* C\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n* C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t"
+                 "Todo"
+                 entry
+                 (file+headline ,file (lambda ()
+                                        (should (equal ,file (buffer-file-name)))
+                                        "B"))
+                 "** H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n* B\n** H1 Capture text\n* C\n"
+    (org-test-with-temp-text-in-file "* A\n* B\n* C\n"
+      (dlet ((test-org-capture/entry/headline))
+        (let* ((file (buffer-file-name))
+	       (org-capture-templates
+	        `(("t" "Todo" entry (file+headline ,file test-org-capture/entry/headline) "** H1 %?"))))
+          (setq test-org-capture/entry/headline "B")
+	  (org-capture nil "t")
+	  (insert "Capture text")
+	  (org-capture-finalize)))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp ,file "A" "B") "* H1 %?"))))
+	(org-capture nil "t")
+	(insert "Capture text")
+	(org-capture-finalize))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t"
+                 "Todo"
+                 entry
+                 (file+olp ,file (lambda ()
+                                    (should (equal ,file (buffer-file-name)))
+                                    '("A" "B")))
+                 "* H1 %?"))))
 	(org-capture nil "t")
 	(insert "Capture text")
 	(org-capture-finalize))
       (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (dlet ((test-org-capture/entry/file+olp))
+        (let* ((file (buffer-file-name))
+	       (org-capture-templates
+	        `(("t" "Todo" entry (file+olp ,file test-org-capture/entry/file+olp) "* H1 %?"))))
+          (setq test-org-capture/entry/file+olp '("A" "B"))
+	  (org-capture nil "t")
+	  (insert "Capture text")
+	  (org-capture-finalize)))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 2024\n**** 2024-06 June\n***** 2024-06-16 Sunday\n****** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t" "Todo" entry (file+olp+datetree ,file "A" "B") "* H1 %?"))))
+        (org-test-at-time "2024-06-16"
+	  (org-capture nil "t")
+	  (insert "Capture text")
+	  (org-capture-finalize)))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 2024\n**** 2024-06 June\n***** 2024-06-16 Sunday\n****** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (let* ((file (buffer-file-name))
+	     (org-capture-templates
+	      `(("t"
+                 "Todo"
+                 entry
+                 (file+olp+datetree ,file (lambda ()
+                                            (should (equal ,file (buffer-file-name)))
+                                            '("A" "B")))
+                 "* H1 %?"))))
+	(org-test-at-time "2024-06-16"
+	  (org-capture nil "t")
+	  (insert "Capture text")
+	  (org-capture-finalize)))
+      (buffer-string))))
+  (should
+   (equal
+    "* A\n** B\n*** 2024\n**** 2024-06 June\n***** 2024-06-16 Sunday\n****** H1 Capture text\n** C\n"
+    (org-test-with-temp-text-in-file "* A\n** B\n** C\n"
+      (dlet ((test-org-capture/entry/file+olp+datetree))
+        (let* ((file (buffer-file-name))
+	       (org-capture-templates
+	        `(("t" "Todo" entry (file+olp+datetree ,file test-org-capture/entry/file+olp+datetree) "* H1 %?"))))
+          (setq test-org-capture/entry/file+olp+datetree '("A" "B"))
+          (org-test-at-time "2024-06-16"
+	    (org-capture nil "t")
+	    (insert "Capture text")
+	    (org-capture-finalize))))
+      (buffer-string))))
   ;; Correctly save position of inserted entry.
   (should
    (equal
@@ -843,5 +958,32 @@ before\nglobal-before\nafter\nglobal-after"
               (org-capture nil "t")
               (buffer-string))))))
 
+(ert-deftest test-org-capture/org-capture-expand-olp ()
+  "Test org-capture-expand-olp."
+  ;; org-capture-expand-olp accepts inlined outline path
+  (should
+   (equal
+    '("A" "B" "C")
+    (let ((file (make-temp-file "org-test")))
+      (unwind-protect
+          (org-capture-expand-olp file "A" "B" "C")
+        (delete-file file)))))
+  ;; The current buffer during the funcall of the lambda is the temporary test file
+  (should
+   (let ((file (make-temp-file "org-test")))
+     (equal
+      file
+      (unwind-protect
+          (org-capture-expand-olp file (lambda () (buffer-file-name)))
+        (delete-file file)))))
+  ;; org-capture-expand-olp rejects outline path that is not inlined
+  (should-error
+   (equal
+    '("A" "B" "C")
+    (let ((file (make-temp-file "org-test")))
+      (unwind-protect
+          (org-capture-expand-olp file '("A" "B" "C"))
+        (delete-file file))))))
+
 (provide 'test-org-capture)
 ;;; test-org-capture.el ends here
-- 
2.44.1


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

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
  2024-06-18 12:05               ` Nafiz Islam
@ 2024-06-18 12:36                 ` Ihor Radchenko
  0 siblings, 0 replies; 18+ messages in thread
From: Ihor Radchenko @ 2024-06-18 12:36 UTC (permalink / raw)
  To: Nafiz Islam; +Cc: emacs-orgmode

Nafiz Islam <nafiz.islam1000@gmail.com> writes:

>  >Why (t nil)? It would be more sensible to throw an error.
>
> I believe that was already addressed.

> + (or final-headline + (error "Invalid headline: %S" headline))))

Right, I was only looking at pcase, which did not appear to be changed.

Applied, onto main, after some minor copyedits.
https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=5265153fc

Thanks for your contribution!

You are now added to Org contributor list:
https://git.sr.ht/~bzg/worg/commit/a46b598e

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


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

end of thread, other threads:[~2024-06-18 12:36 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-06-01 16:38 [PATCH] function and symbol for headline and olp for org-capture-templates Nafiz Islam
2024-06-05  9:09 ` Ihor Radchenko
2024-06-05 21:16   ` Bastien Guerry
     [not found]   ` <f2b85669-a0fc-40c6-891a-1319d0582fe0@gmail.com>
     [not found]     ` <87frtpgj2w.fsf@localhost>
2024-06-09 14:59       ` Nafiz Islam
2024-06-09 17:03         ` Ihor Radchenko
     [not found]   ` <3508dbb0-a8ee-4217-af21-a9fc3ac46eb9@gmail.com>
     [not found]     ` <874ja4ak0q.fsf@localhost>
2024-06-15 21:45       ` Nafiz Islam
2024-06-16 12:20         ` Ihor Radchenko
     [not found]           ` <c98ba108-c07e-4c17-a806-524444367d9d@gmail.com>
     [not found]             ` <87plsfa2nt.fsf@localhost>
2024-06-18 12:05               ` Nafiz Islam
2024-06-18 12:36                 ` Ihor Radchenko
  -- strict thread matches above, loose matches on Subject: below --
2024-05-19 23:34 Nafiz Islam
2024-05-19 23:43 ` Nafiz Islam
2024-05-20 10:53   ` Ihor Radchenko
2024-05-21 21:00     ` Nafiz Islam
2024-05-22 11:11       ` Nafiz Islam
2024-05-22 11:15         ` Ihor Radchenko
2024-05-13 22:53 Nafiz Islam
2024-05-13 23:08 ` Nafiz Islam
2024-05-17 12:48   ` Ihor Radchenko

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).