Hi Ihor, I apologize for the delay. I've been busy these last few days and also encountered some technical difficulties with my device.
On Sun, May 11 2025, Ihor Radchenko wrote: > Kristoffer Balintona <krisbalint...@gmail.com> writes: > >>> Why not just using nil value as you initially suggested? >> >> Currently, both `org-capture-expand-headline' and >> `org-capture-expand-olp' error when the expanded headline or olp, >> respectively, return nil. I wanted to preserve that assuming there was a >> good reason behind this decision. Do you think interpreting a nil value >> in this way is more appropriate? > > There is no need to preserve the behavior of > `org-capture-expand-headline'/`org-capture-expand-olp'. Their purpose is > basically parsing allowed values of olp/headline target. You are > proposing to extend the allowed values, so it is 100% fine to handle nil > without error - I will be a valid value now. The error should only be > thrown when the user supplies invalid target. Thank you for the clarification. WDYT of the patches attached? >>> We used to have file+datetree target, and a number of others. They have >>> been consolidated into a single file+olp+datetree in Org 9.1, 8 years ago. >>> See >>> https://list.orgmode.org/orgmode/CADn3Z2+AanLPvUO9ENhZ_2tkAvsmdnq9ewR3sFasn=zv--s...@mail.gmail.com/ >> >> Should we do the same with file and file+olp? Or would that be too >> backwards incompatible? > > Backwards compatibility per se is not a problem. For example, > file+datetree target is still working. It is just deprecated. > However, forcing users to change their setup (even just because they > start seeing deprecation warning) without an obvious counter-benefit is > not a good idea. I do not see any significant gain from consolidating > file+olp and file targets. I see. That's reasonable. I am not accustomed to the kinds of considerations a package as big as org has, so I appreciate the insight. -- With appreciation, Kristoffer
From 6268653bb8ce866742401612e2fe8b4353884d6b Mon Sep 17 00:00:00 2001 From: Kristoffer Balintona <krisbalint...@gmail.com> Date: Fri, 9 May 2025 11:40:13 -0500 Subject: [PATCH 1/2] lisp/org-capture.el: Accept optional and nil olp for the file+olp+datetree and file+olp specifications * lisp/org-capture.el (org-capture-expand-olp): When the supplied olp is nil, org-capture-expand-olp now returns nil. (org-capture-set-target-location): The file+olp+datetree and file+olp target specifications now accept a nil or omitted olp. In either case, insert the capture entry at the end of the buffer, on top level. Update docstring to reflect these new behaviors. * testing/lisp/test-org-capture.el (org-capture-expand-olp): Add test case for org-capture-expand-olp now accepting a nil olp. --- lisp/org-capture.el | 68 +++++++++++++++++++++----------- testing/lisp/test-org-capture.el | 11 +++++- 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/lisp/org-capture.el b/lisp/org-capture.el index 6d395406c..3be1cf57c 100644 --- a/lisp/org-capture.el +++ b/lisp/org-capture.el @@ -207,20 +207,30 @@ target Specification of where the captured item should be placed. (file+headline <file-spec> symbol-containing-string) Fast configuration if the target heading is unique in the file + (file+olp <file-spec>) (file+olp <file-spec> \"Level 1 heading\" \"Level 2\" ...) (file+olp <file-spec> function-returning-list-of-strings) (file+olp <file-spec> symbol-containing-list-of-strings) - For non-unique headings, the full outline path is safer + For non-unique headings, the full outline path is + safer. If no headings are given, or if + function-returning-list-of-strings or + symbol-containing-list-of-strings return nil, the entry + will be inserted at the end of <file-spec> on top + level. (file+regexp <file-spec> \"regexp to find location\") File to the entry matching regexp + (file+olp+datetree <file-spec>) (file+olp+datetree <file-spec> \"Level 1 heading\" ...) (file+olp+datetree <file-spec> function-returning-list-of-strings) (file+olp+datetree <file-spec> 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 + The tree will be inserted at the end of <file-spec> on + top level if no heading is given or if + function-returning-list-of-strings or + symbol-containing-list-of-strings return nil. To + prompt for date instead of using TODAY, use the :time-prompt property. To create a week-tree, use the :tree-type property. @@ -1065,8 +1075,13 @@ Store them in the capture property list." (forward-line -1))) (`(file+olp ,path . ,(and outline-path (guard 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))))) + (expanded-olp (apply #'org-capture-expand-olp expanded-file-path outline-path)) + ;; If expanded-olp is nil, then create the datetree at + ;; the end of the file specified by expanded-file-path + (m (if expanded-olp + (org-find-olp (cons expanded-file-path expanded-olp)) + (set-buffer (org-capture-target-buffer expanded-file-path)) + (save-restriction (widen) (point-max-marker))))) (set-buffer (marker-buffer m)) (org-capture-put-target-region-and-position) (widen) @@ -1086,12 +1101,16 @@ Store them in the capture property list." (setq target-entry-p (and (derived-mode-p 'org-mode) (org-at-heading-p))))) (`(file+olp+datetree ,path . ,outline-path) - (let ((m (if 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)))) + (let* ((expanded-file-path (org-capture-expand-file path)) + (expanded-olp (apply #'org-capture-expand-olp expanded-file-path outline-path)) + ;; If expanded-olp is nil, then file-level-datetree-p + ;; is non-nil and the datetree should be created at + ;; the end of the file specified by expanded-file-path + (file-level-datetree-p (not expanded-olp)) + (m (if (not file-level-datetree-p) + (org-find-olp (cons expanded-file-path expanded-olp)) + (set-buffer (org-capture-target-buffer expanded-file-path)) + (save-restriction (widen) (point-max-marker))))) (set-buffer (marker-buffer m)) (org-capture-put-target-region-and-position) (widen) @@ -1152,7 +1171,7 @@ Store them in the capture property list." (org-today)))) ;; the following is the keep-restriction argument for ;; org-datetree-find-date-create - (when outline-path 'subtree-at-point)))) + (unless file-level-datetree-p 'subtree-at-point)))) (`(file+function ,path ,(and function (pred functionp))) (set-buffer (org-capture-target-buffer path)) (org-capture-put-target-region-and-position) @@ -1201,20 +1220,21 @@ an error." (defun org-capture-expand-olp (file &rest olp) "Expand functions, symbols and outline paths in FILE for OLP. +Return a list of strings representing an outline path (OLP) in FILE. + 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 "org-capture: Invalid outline path target: %S" olp)))) +value. When it is a list of strings, return it. When OLP is nil, +return nil. In any other case, signal an error." + (let* ((first (car olp))) + (cond ((and (= 1 (length olp)) (null first)) nil) + ((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 (error "org-capture: Invalid outline path target: %S" olp))))) (defun org-capture-expand-file (file) "Expand functions, symbols and file names for FILE. diff --git a/testing/lisp/test-org-capture.el b/testing/lisp/test-org-capture.el index 7673347ed..a30549846 100644 --- a/testing/lisp/test-org-capture.el +++ b/testing/lisp/test-org-capture.el @@ -1010,7 +1010,7 @@ before\nglobal-before\nafter\nglobal-after" (buffer-string)))))) (ert-deftest test-org-capture/org-capture-expand-olp () - "Test org-capture-expand-olp." + "Test `org-capture-expand-olp'." ;; `org-capture-expand-olp' accepts inlined outline path. (should (equal @@ -1019,6 +1019,15 @@ before\nglobal-before\nafter\nglobal-after" (unwind-protect (org-capture-expand-olp file "A" "B" "C") (delete-file file))))) + ;; `org-capture-expand-olp' should return nil if the outline path is + ;; nil + (should + (equal + nil + (let ((file (make-temp-file "org-test"))) + (unwind-protect + (org-capture-expand-olp file nil) + (delete-file file))))) ;; The current buffer during the funcall of the lambda is the temporary ;; test file. (should -- 2.49.0
From 4eecde72b939a871dc8076d13b6617e9e7d22bf9 Mon Sep 17 00:00:00 2001 From: Kristoffer Balintona <krisbalint...@gmail.com> Date: Fri, 9 May 2025 11:40:13 -0500 Subject: [PATCH 2/2] lisp/org-capture.el: Accept optional or nil headline for the file+headline specification * lisp/org-capture.el (org-capture-expand-headline): When the supplied headline is nil, org-capture-expand-olp now returns nil. (org-capture-set-target-location): The file+headline target specification now accepts a nil or omitted headline. In either case, insert the capture entry at the end of the buffer, on top level. Update docstring to reflect these new behaviors. * testing/lisp/test-org-capture.el (test-org-capture/org-capture-expand-headline): Add new test for org-capture-expand-headline that tests these new behaviors as well as several previously existing ones. --- lisp/org-capture.el | 54 +++++++++++++++++++------------- testing/lisp/test-org-capture.el | 42 +++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 22 deletions(-) diff --git a/lisp/org-capture.el b/lisp/org-capture.el index 3be1cf57c..eb7f8394e 100644 --- a/lisp/org-capture.el +++ b/lisp/org-capture.el @@ -202,10 +202,15 @@ target Specification of where the captured item should be placed. (id \"id of existing Org entry\") File as child of this entry, or in the body of the entry + (file+headline <file-spec>) (file+headline <file-spec> \"node headline\") (file+headline <file-spec> function-returning-string) (file+headline <file-spec> symbol-containing-string) - Fast configuration if the target heading is unique in the file + Fast configuration if the target heading is unique in + the file. If no heading is given, or if + function-returning-string or symbol-containing-string + return nil, the entry will be inserted at the end of + <file-spec> on top level. (file+olp <file-spec>) (file+olp <file-spec> \"Level 1 heading\" \"Level 2\" ...) @@ -1050,7 +1055,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 ,headline) + (`(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 @@ -1064,16 +1069,20 @@ 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) - (forward-line 0) - (goto-char (point-max)) - (unless (bolp) (insert "\n")) - (insert "* " headline "\n") - (forward-line -1))) - (`(file+olp ,path . ,(and outline-path (guard outline-path))) + (setq headline (org-capture-expand-headline (car headline))) + (pcase headline + ((pred not) (goto-char (point-max))) + ((pred stringp) + (re-search-forward (format org-complex-heading-regexp-format + (regexp-quote headline)) + nil t) + (forward-line 0)) + (_ + (goto-char (point-max)) + (unless (bolp) (insert "\n")) + (insert "* " headline "\n") + (forward-line -1)))) + (`(file+olp ,path . ,outline-path) (let* ((expanded-file-path (org-capture-expand-file path)) (expanded-olp (apply #'org-capture-expand-olp expanded-file-path outline-path)) ;; If expanded-olp is nil, then create the datetree at @@ -1207,16 +1216,17 @@ Store them in the capture property list." (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 "org-capture: Invalid headline target: %S" headline)))) +Return a string representing a headline. + +When HEADLINE is a function, call it. When it is a variable, return its +value. When it is a string, return it. When headline is nil, return +nil. In any other case, signal an error." + (cond ((null headline) nil) + ((stringp headline) headline) + ((functionp headline) (funcall headline)) + ((and (symbolp headline) (boundp headline)) + (symbol-value headline)) + (t (error "org-capture: Invalid headline target: %S" headline)))) (defun org-capture-expand-olp (file &rest olp) "Expand functions, symbols and outline paths in FILE for OLP. diff --git a/testing/lisp/test-org-capture.el b/testing/lisp/test-org-capture.el index a30549846..c518bfa1d 100644 --- a/testing/lisp/test-org-capture.el +++ b/testing/lisp/test-org-capture.el @@ -1009,6 +1009,48 @@ before\nglobal-before\nafter\nglobal-after" (org-capture nil "t") (buffer-string)))))) +(ert-deftest test-org-capture/org-capture-expand-headline () + "Test `org-capture-expand-headline'." + ;; `org-capture-expand-headline' should return nil when headline is + ;; nil + (should + (equal + nil + (let ((file (make-temp-file "org-test"))) + (unwind-protect + (org-capture-expand-headline nil) + (delete-file file))))) + ;; `org-capture-expand-headline' should return headline if it is a + ;; string + (should + (equal + "A" + (let ((file (make-temp-file "org-test"))) + (unwind-protect + (org-capture-expand-headline "A") + (delete-file file))))) + ;; `org-capture-expand-headline' should evaluate headline if it is a + ;; function and return its value + (should + (equal + "A" + (let ((file (make-temp-file "org-test"))) + (unwind-protect + (org-capture-expand-headline (lambda () "A")) + (delete-file file))))) + ;; `org-capture-expand-headline' should return the value of headline + ;; if it is a symbol + (should + (equal + "A" + (let ((file (make-temp-file "org-test"))) + (unwind-protect + (progn + (setq temp "A") + (org-capture-expand-headline 'temp)) + (makunbound 'temp) + (delete-file file)))))) + (ert-deftest test-org-capture/org-capture-expand-olp () "Test `org-capture-expand-olp'." ;; `org-capture-expand-olp' accepts inlined outline path. -- 2.49.0