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; 10+ 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] 10+ messages in thread

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
  2024-05-13 22:53 [PATCH] function and symbol for headline and olp for org-capture-templates Nafiz Islam
@ 2024-05-13 23:08 ` Nafiz Islam
  2024-05-17 12:48   ` Ihor Radchenko
  0 siblings, 1 reply; 10+ 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] 10+ 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; 10+ 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] 10+ 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; 10+ 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] 10+ 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; 10+ 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] 10+ 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; 10+ 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] 10+ 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; 10+ 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] 10+ 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; 10+ 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] 10+ 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; 10+ 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] 10+ messages in thread

* Re: [PATCH] function and symbol for headline and olp for org-capture-templates
@ 2024-06-01 16:38 Nafiz Islam
  0 siblings, 0 replies; 10+ 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] 10+ messages in thread

end of thread, other threads:[~2024-06-01 16:39 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-05-13 22:53 [PATCH] function and symbol for headline and olp for org-capture-templates Nafiz Islam
2024-05-13 23:08 ` Nafiz Islam
2024-05-17 12:48   ` 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-06-01 16:38 Nafiz Islam

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