Thanks.
I realized that it's challenging to handle the error properly if the
return value of org-babel-tangle-single-block remains unchanged.
Consider your code block:
#+begin_src emacs-lisp :tangle '("1.el" "2.el")
(+ 1 2)
#+end_src
In the org-babel-tangle--unbracketed-link function, if
org-babel-tangle-use-relative-file-links is non-nil, the expression
(file-relative-name "/tmp/test.org::heading" '("1.el" "2.el")) will
raise the error. That's why I used ignore-errors in the first patch
within org-babel-tangle-single-block.
If I handle this properly using a loop and return a list of relative
file links, the return value of org-babel-tangle-single-block must
also be adjusted to a nested list accordingly. Otherwise, it would be
meaningless and could lead to potential errors.
Since this change would be a breaking one, I'd like to hear your
thoughts. Do you have any suggestions for handling this more
effectively?
llcc
On Tue, Mar 18, 2025 at 1:59 AM Ihor Radchenko <[email protected]> wrote:
>
> Lei Zhe <[email protected]> writes:
>
> >>>As for the patch, I tried to run it with simple example and got an error.
> > I tested on my side and didn't see any failures. Would you mind
> > sharing the error details?
>
> 1. git clone ...
> 2. cd /path/to/org/source
> 3. apply the patch
> 4. make repro
> 5. C-x C-f /tmp/test.org
> 6. Insert
> #+begin_src emacs-lisp :tangle '("1.el" "2.el")
> (+ 1 2)
> #+end_src
> 7. C-c C-v C-t
> 8. Observe
>
> Debugger entered--Lisp error: (wrong-type-argument stringp ("1.el" "2.el"))
> file-name-directory(("1.el" "2.el"))
> (file-relative-name (substring bare (match-end 0)) (file-name-directory
> (cdr (assq :tangle params))))
> (concat "file:" (file-relative-name (substring bare (match-end 0))
> (file-name-directory (cdr (assq :tangle params)))))
> (if (and org-babel-tangle-use-relative-file-links (string-match
> org-link-types-re bare) (string= (match-string 1 bare) "file")) (concat
> "file:" (file-relative-name (substring bare (match-end 0))
> (file-name-directory (cdr (assq :tangle params))))) bare)
> (progn (if (and org-babel-tangle-use-relative-file-links (string-match
> org-link-types-re bare) (string= (match-string 1 bare) "file")) (concat
> "file:" (file-relative-name (substring bare (match-end 0))
> (file-name-directory (cdr (assq :tangle params))))) bare))
> (if bare (progn (if (and org-babel-tangle-use-relative-file-links
> (string-match org-link-types-re bare) (string= (match-string 1 bare) "file"))
> (concat "file:" (file-relative-name (substring bare (match-end 0))
> (file-name-directory (cdr (assq :tangle params))))) bare)))
> (let* ((l (org-no-properties (org-store-link nil))) (bare (and l
> (string-match org-link-bracket-re l) (match-string 1 l)))) (if bare (progn
> (if (and org-babel-tangle-use-relative-file-links (string-match
> org-link-types-re bare) (string= (match-string 1 bare) "file")) (concat
> "file:" (file-relative-name (substring bare (match-end 0))
> (file-name-directory (cdr ...)))) bare))))
> (progn (let* ((l (org-no-properties (org-store-link nil))) (bare (and l
> (string-match org-link-bracket-re l) (match-string 1 l)))) (if bare (progn
> (if (and org-babel-tangle-use-relative-file-links (string-match
> org-link-types-re bare) (string= (match-string 1 bare) "file")) (concat
> "file:" (file-relative-name (substring bare ...) (file-name-directory ...)))
> bare)))))
> (unwind-protect (progn (let* ((l (org-no-properties (org-store-link nil)))
> (bare (and l (string-match org-link-bracket-re l) (match-string 1 l)))) (if
> bare (progn (if (and org-babel-tangle-use-relative-file-links (string-match
> org-link-types-re bare) (string= ... "file")) (concat "file:"
> (file-relative-name ... ...)) bare))))) (set-match-data saved-match-data t))
> (let ((saved-match-data (match-data))) (unwind-protect (progn (let* ((l
> (org-no-properties (org-store-link nil))) (bare (and l (string-match
> org-link-bracket-re l) (match-string 1 l)))) (if bare (progn (if (and
> org-babel-tangle-use-relative-file-links ... ...) (concat "file:" ...)
> bare))))) (set-match-data saved-match-data t)))
> (if (string= "no" (cdr (assq :comments params))) nil (let
> ((saved-match-data (match-data))) (unwind-protect (progn (let* ((l
> (org-no-properties ...)) (bare (and l ... ...))) (if bare (progn (if ... ...
> bare))))) (set-match-data saved-match-data t))))
> org-babel-tangle--unbracketed-link(((:colname-names) (:rowname-names)
> (:result-params "replace") (:result-type . value) (:results . "replace")
> (:exports . "code") (:tangle "1.el" "2.el") (:lexical . "no") (:hlines .
> "no") (:noweb . "no") (:cache . "no") (:session . "none")))
> (let* ((info (org-babel-get-src-block-info)) (start-line (save-restriction
> (widen) (+ 1 (line-number-at-pos (point))))) (file (buffer-file-name
> (buffer-base-buffer))) (src-lang (nth 0 info)) (params (nth 2 info)) (extra
> (nth 3 info)) (coderef (nth 6 info)) (cref-regexp (org-src-coderef-regexp
> coderef)) (link (org-babel-tangle--unbracketed-link params)) (source-name (or
> (nth 4 info) (format "%s:%d" (or (condition-case nil (progn ...) (error nil))
> "No heading") block-counter))) (expand-cmd (intern (concat
> "org-babel-expand-body:" src-lang))) (assignments-cmd (intern (concat
> "org-babel-variable-assignments:" src-lang))) (body (let ((body (if
> (org-babel-noweb-p params :tangle) (if ... ... ...) (nth 1 info)))) (let
> ((temp-buffer (generate-new-buffer " *temp*" t))) (save-current-buffer
> (set-buffer temp-buffer) (unwind-protect (progn ... ... ... ...) (and ...
> ...)))))) (comment (if (or (string= "both" (cdr (assq :comments params)))
> (string= "org" (cdr (assq :comments params)))) (progn (funcall
> org-babel-process-comment-text (buffer-substring (max ... ...) (point))))))
> (result (list start-line (if org-babel-tangle-use-relative-file-links
> (file-relative-name file) file) link source-name params (if
> (org-src-preserve-indentation-p) (org-trim body t) (org-trim
> (org-remove-indentation body))) comment))) (if only-this-block (let*
> ((file-names (org-babel-tangle--concat-targets file info))) (mapcar #'(lambda
> (file-name) (cons file-name (list ...))) file-names)) result))
> org-babel-tangle-single-block(1 t)
> (let* ((block (org-babel-tangle-single-block counter t)) (src-file (car
> block)) (src-lang (car (car block)))) (if (or (not src-file) (and (not
> src-lang) src-file) (and tangle-file (not (equal tangle-file src-file))) (and
> lang-re (or (not src-lang) (not (string-match-p lang-re src-lang))))) nil
> (setq blocks (mapcar #'(lambda (group) (cons (car group) (apply ... ...)))
> (seq-group-by #'car (append block blocks))))))
> (if (or (org-in-commented-heading-p) (org-in-archived-heading-p)) nil (let*
> ((block (org-babel-tangle-single-block counter t)) (src-file (car block))
> (src-lang (car (car block)))) (if (or (not src-file) (and (not src-lang)
> src-file) (and tangle-file (not (equal tangle-file src-file))) (and lang-re
> (or (not src-lang) (not (string-match-p lang-re src-lang))))) nil (setq
> blocks (mapcar #'(lambda (group) (cons ... ...)) (seq-group-by #'car (append
> block blocks)))))))
> (let ((full-block (match-string 0)) (beg-block (match-beginning 0))
> (end-block (match-end 0)) (lang (match-string 2)) (beg-lang (match-beginning
> 2)) (end-lang (match-end 2)) (switches (match-string 3)) (beg-switches
> (match-beginning 3)) (end-switches (match-end 3)) (header-args (match-string
> 4)) (beg-header-args (match-beginning 4)) (end-header-args (match-end 4))
> (body (match-string 5)) (beg-body (match-beginning 5)) (end-body (match-end
> 5))) (ignore full-block beg-block end-block lang beg-lang end-lang switches
> beg-switches end-switches header-args beg-header-args end-header-args body
> beg-body end-body) (let ((current-heading-pos (or (org-element-begin
> (org-element-lineage (org-element-at-point) 'headline t)) 1))) (if (eq
> last-heading-pos current-heading-pos) (setq counter (1+ counter)) (setq
> counter 1) (setq last-heading-pos current-heading-pos))) (if (or
> (org-in-commented-heading-p) (org-in-archived-heading-p)) nil (let* ((block
> (org-babel-tangle-single-block counter t)) (src-file (car block)) (src-lang
> (car (car block)))) (if (or (not src-file) (and (not src-lang) src-file) (and
> tangle-file (not (equal tangle-file src-file))) (and lang-re (or (not
> src-lang) (not ...)))) nil (setq blocks (mapcar #'(lambda ... ...)
> (seq-group-by #'car (append block blocks))))))) (goto-char end-block))
> (progn (goto-char (match-beginning 0)) (let ((full-block (match-string 0))
> (beg-block (match-beginning 0)) (end-block (match-end 0)) (lang (match-string
> 2)) (beg-lang (match-beginning 2)) (end-lang (match-end 2)) (switches
> (match-string 3)) (beg-switches (match-beginning 3)) (end-switches (match-end
> 3)) (header-args (match-string 4)) (beg-header-args (match-beginning 4))
> (end-header-args (match-end 4)) (body (match-string 5)) (beg-body
> (match-beginning 5)) (end-body (match-end 5))) (ignore full-block beg-block
> end-block lang beg-lang end-lang switches beg-switches end-switches
> header-args beg-header-args end-header-args body beg-body end-body) (let
> ((current-heading-pos (or (org-element-begin (org-element-lineage ... ... t))
> 1))) (if (eq last-heading-pos current-heading-pos) (setq counter (1+
> counter)) (setq counter 1) (setq last-heading-pos current-heading-pos))) (if
> (or (org-in-commented-heading-p) (org-in-archived-heading-p)) nil (let*
> ((block (org-babel-tangle-single-block counter t)) (src-file (car block))
> (src-lang (car (car block)))) (if (or (not src-file) (and (not src-lang)
> src-file) (and tangle-file (not ...)) (and lang-re (or ... ...))) nil (setq
> blocks (mapcar #'... (seq-group-by ... ...)))))) (goto-char end-block)))
> (if (org-babel-active-location-p) (progn (goto-char (match-beginning 0))
> (let ((full-block (match-string 0)) (beg-block (match-beginning 0))
> (end-block (match-end 0)) (lang (match-string 2)) (beg-lang (match-beginning
> 2)) (end-lang (match-end 2)) (switches (match-string 3)) (beg-switches
> (match-beginning 3)) (end-switches (match-end 3)) (header-args (match-string
> 4)) (beg-header-args (match-beginning 4)) (end-header-args (match-end 4))
> (body (match-string 5)) (beg-body (match-beginning 5)) (end-body (match-end
> 5))) (ignore full-block beg-block end-block lang beg-lang end-lang switches
> beg-switches end-switches header-args beg-header-args end-header-args body
> beg-body end-body) (let ((current-heading-pos (or (org-element-begin ...)
> 1))) (if (eq last-heading-pos current-heading-pos) (setq counter (1+
> counter)) (setq counter 1) (setq last-heading-pos current-heading-pos))) (if
> (or (org-in-commented-heading-p) (org-in-archived-heading-p)) nil (let*
> ((block (org-babel-tangle-single-block counter t)) (src-file (car block))
> (src-lang (car ...))) (if (or (not src-file) (and ... src-file) (and
> tangle-file ...) (and lang-re ...)) nil (setq blocks (mapcar ... ...)))))
> (goto-char end-block))))
> (while (re-search-forward org-babel-src-block-regexp nil t) (if
> (org-babel-active-location-p) (progn (goto-char (match-beginning 0)) (let
> ((full-block (match-string 0)) (beg-block (match-beginning 0)) (end-block
> (match-end 0)) (lang (match-string 2)) (beg-lang (match-beginning 2))
> (end-lang (match-end 2)) (switches (match-string 3)) (beg-switches
> (match-beginning 3)) (end-switches (match-end 3)) (header-args (match-string
> 4)) (beg-header-args (match-beginning 4)) (end-header-args (match-end 4))
> (body (match-string 5)) (beg-body (match-beginning 5)) (end-body (match-end
> 5))) (ignore full-block beg-block end-block lang beg-lang end-lang switches
> beg-switches end-switches header-args beg-header-args end-header-args body
> beg-body end-body) (let ((current-heading-pos (or ... 1))) (if (eq
> last-heading-pos current-heading-pos) (setq counter (1+ counter)) (setq
> counter 1) (setq last-heading-pos current-heading-pos))) (if (or
> (org-in-commented-heading-p) (org-in-archived-heading-p)) nil (let* ((block
> ...) (src-file ...) (src-lang ...)) (if (or ... ... ... ...) nil (setq blocks
> ...)))) (goto-char end-block)))))
> (progn (if file (progn (find-file file))) (setq to-be-removed
> (current-buffer)) (goto-char (point-min)) (while (re-search-forward
> org-babel-src-block-regexp nil t) (if (org-babel-active-location-p) (progn
> (goto-char (match-beginning 0)) (let ((full-block (match-string 0))
> (beg-block (match-beginning 0)) (end-block (match-end 0)) (lang (match-string
> 2)) (beg-lang (match-beginning 2)) (end-lang (match-end 2)) (switches
> (match-string 3)) (beg-switches (match-beginning 3)) (end-switches (match-end
> 3)) (header-args (match-string 4)) (beg-header-args (match-beginning 4))
> (end-header-args (match-end 4)) (body (match-string 5)) (beg-body
> (match-beginning 5)) (end-body (match-end 5))) (ignore full-block beg-block
> end-block lang beg-lang end-lang switches beg-switches end-switches
> header-args beg-header-args end-header-args body beg-body end-body) (let
> ((current-heading-pos ...)) (if (eq last-heading-pos current-heading-pos)
> (setq counter ...) (setq counter 1) (setq last-heading-pos
> current-heading-pos))) (if (or (org-in-commented-heading-p)
> (org-in-archived-heading-p)) nil (let* (... ... ...) (if ... nil ...)))
> (goto-char end-block))))))
> (unwind-protect (progn (if file (progn (find-file file))) (setq
> to-be-removed (current-buffer)) (goto-char (point-min)) (while
> (re-search-forward org-babel-src-block-regexp nil t) (if
> (org-babel-active-location-p) (progn (goto-char (match-beginning 0)) (let
> ((full-block ...) (beg-block ...) (end-block ...) (lang ...) (beg-lang ...)
> (end-lang ...) (switches ...) (beg-switches ...) (end-switches ...)
> (header-args ...) (beg-header-args ...) (end-header-args ...) (body ...)
> (beg-body ...) (end-body ...)) (ignore full-block beg-block end-block lang
> beg-lang end-lang switches beg-switches end-switches header-args
> beg-header-args end-header-args body beg-body end-body) (let (...) (if ...
> ... ... ...)) (if (or ... ...) nil (let* ... ...)) (goto-char end-block))))))
> (set-window-configuration wconfig))
> (let ((wconfig (current-window-configuration))) (unwind-protect (progn (if
> file (progn (find-file file))) (setq to-be-removed (current-buffer))
> (goto-char (point-min)) (while (re-search-forward org-babel-src-block-regexp
> nil t) (if (org-babel-active-location-p) (progn (goto-char (match-beginning
> 0)) (let (... ... ... ... ... ... ... ... ... ... ... ... ... ... ...)
> (ignore full-block beg-block end-block lang beg-lang end-lang switches
> beg-switches end-switches header-args beg-header-args end-header-args body
> beg-body end-body) (let ... ...) (if ... nil ...) (goto-char end-block))))))
> (set-window-configuration wconfig)))
> (let* ((case-fold-search t) (file (buffer-file-name)) (visited-p (or (null
> file) (get-file-buffer (expand-file-name file)))) (point (point))
> to-be-removed) (let ((wconfig (current-window-configuration)))
> (unwind-protect (progn (if file (progn (find-file file))) (setq to-be-removed
> (current-buffer)) (goto-char (point-min)) (while (re-search-forward
> org-babel-src-block-regexp nil t) (if (org-babel-active-location-p) (progn
> (goto-char ...) (let ... ... ... ... ...))))) (set-window-configuration
> wconfig))) (if visited-p nil (kill-buffer to-be-removed)) (goto-char point))
> (let ((counter 0) (buffer-fn (buffer-file-name (buffer-base-buffer)))
> last-heading-pos blocks) (let* ((case-fold-search t) (file
> (buffer-file-name)) (visited-p (or (null file) (get-file-buffer
> (expand-file-name file)))) (point (point)) to-be-removed) (let ((wconfig
> (current-window-configuration))) (unwind-protect (progn (if file (progn
> (find-file file))) (setq to-be-removed (current-buffer)) (goto-char
> (point-min)) (while (re-search-forward org-babel-src-block-regexp nil t) (if
> (org-babel-active-location-p) (progn ... ...)))) (set-window-configuration
> wconfig))) (if visited-p nil (kill-buffer to-be-removed)) (goto-char point))
> (mapcar #'(lambda (b) (cons (car b) (nreverse (cdr b)))) (nreverse blocks)))
> org-babel-tangle-collect-blocks(nil nil)
> (if (equal arg '(4)) (org-babel-tangle-single-block 1 t)
> (org-babel-tangle-collect-blocks lang-re tangle-file))
> (mapc #'(lambda (by-fn) (let ((file-name (car by-fn))) (if file-name (progn
> (let (... ... modes make-dir she-banged lang) (let ... ...)))))) (if (equal
> arg '(4)) (org-babel-tangle-single-block 1 t)
> (org-babel-tangle-collect-blocks lang-re tangle-file)))
> (let ((block-counter 0) (org-babel-default-header-args (if target-file
> (org-babel-merge-params org-babel-default-header-args (list (cons :tangle
> target-file))) org-babel-default-header-args)) (tangle-file (if (equal arg
> '(16)) (progn (or (cdr (assq :tangle ...)) (user-error "Point is not in a
> source code block"))))) path-collector (source-file buffer-file-name)) (mapc
> #'(lambda (by-fn) (let ((file-name (car by-fn))) (if file-name (progn (let
> ... ...))))) (if (equal arg '(4)) (org-babel-tangle-single-block 1 t)
> (org-babel-tangle-collect-blocks lang-re tangle-file))) (message "Tangled %d
> code block%s from %s" block-counter (if (= block-counter 1) "" "s")
> (file-name-nondirectory (buffer-file-name (or (buffer-base-buffer)
> (current-buffer) (and (org-src-edit-buffer-p) (org-src-source-buffer))))))
> (if org-babel-post-tangle-hook (progn (mapc #'(lambda (file) (let* (... ...
> temp-result temp-file) (org-babel-find-file-noselect-refresh temp-path) (setq
> temp-file ...) (save-current-buffer ... ...) (if visited-p nil ...)
> temp-result)) path-collector))) (run-hooks 'org-babel-tangle-finished-hook)
> path-collector)
> (save-excursion (if (equal arg '(4)) (progn (let ((head
> (org-babel-where-is-src-block-head))) (if head (goto-char head) (user-error
> "Point is not in a source code block"))))) (let ((block-counter 0)
> (org-babel-default-header-args (if target-file (org-babel-merge-params
> org-babel-default-header-args (list (cons :tangle target-file)))
> org-babel-default-header-args)) (tangle-file (if (equal arg '(16)) (progn (or
> (cdr ...) (user-error "Point is not in a source code block")))))
> path-collector (source-file buffer-file-name)) (mapc #'(lambda (by-fn) (let
> ((file-name ...)) (if file-name (progn ...)))) (if (equal arg '(4))
> (org-babel-tangle-single-block 1 t) (org-babel-tangle-collect-blocks lang-re
> tangle-file))) (message "Tangled %d code block%s from %s" block-counter (if
> (= block-counter 1) "" "s") (file-name-nondirectory (buffer-file-name (or
> (buffer-base-buffer) (current-buffer) (and (org-src-edit-buffer-p)
> (org-src-source-buffer)))))) (if org-babel-post-tangle-hook (progn (mapc
> #'(lambda (file) (let* ... ... ... ... ... temp-result)) path-collector)))
> (run-hooks 'org-babel-tangle-finished-hook) path-collector))
> (save-restriction (save-excursion (if (equal arg '(4)) (progn (let ((head
> (org-babel-where-is-src-block-head))) (if head (goto-char head) (user-error
> "Point is not in a source code block"))))) (let ((block-counter 0)
> (org-babel-default-header-args (if target-file (org-babel-merge-params
> org-babel-default-header-args (list ...)) org-babel-default-header-args))
> (tangle-file (if (equal arg '...) (progn (or ... ...)))) path-collector
> (source-file buffer-file-name)) (mapc #'(lambda (by-fn) (let (...) (if
> file-name ...))) (if (equal arg '(4)) (org-babel-tangle-single-block 1 t)
> (org-babel-tangle-collect-blocks lang-re tangle-file))) (message "Tangled %d
> code block%s from %s" block-counter (if (= block-counter 1) "" "s")
> (file-name-nondirectory (buffer-file-name (or (buffer-base-buffer)
> (current-buffer) (and ... ...))))) (if org-babel-post-tangle-hook (progn
> (mapc #'(lambda ... ...) path-collector))) (run-hooks
> 'org-babel-tangle-finished-hook) path-collector)))
> org-babel-tangle(nil)
> funcall-interactively(org-babel-tangle nil)
> command-execute(org-babel-tangle)
>
>
> >>> I recommend running make test to check the patch.
>
> > I will do that. After reviewing the `ob-tangle/collect-blocks', I
> > found the following test cases.
> >
> > #+begin_src emacs-lisp :tangle %r
> > "H2: relative org-file.lang-ext :tangle %r"
> > #+end_src
> >
> > #+begin_src emacs-lisp :tangle %a
> > "H1: absolute org-file.lang-ext :tangle %a\"
> > #+end_src
> >
> > %r and %a are not mentioned in either the org manual or ob-tangle.el.
> > Where is their logic implemented in ob-tangle.el?
>
> %a and %r are the placeholders for `format-spec', which see.
>
> --
> Ihor Radchenko // yantar92,
> Org mode maintainer,
> 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>