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 <yanta...@posteo.net> wrote: > > Lei Zhe <lzhe...@gmail.com> 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>