Thanks for your suggestion. Or, return bare only if :tangle has
multiple targets in `org-babel-tangle--unbracketed-link', then we
can leave everything else unchanged.
please find the updated patch attached. Let me know your thoughts.

llcc


On Fri, Mar 21, 2025 at 2:48 AM Ihor Radchenko <yanta...@posteo.net> wrote:
>
> Lei Zhe <lzhe...@gmail.com> writes:
>
> > Then, for the returned value `result' of org-babel-tangle-single-block:
> > The original function returns (file-name1 ... link1 ...).
> > The proposed function returns ((file-name1 ... link1 ...)).
> >
> >  If we change the function behavior, the difference must be a breaking 
> > change.
>
> It looks like we have to change the return value one way or another.
> Either how I suggested or using your suggestion with multiple
> links/file-names specified in the return value.
> (of course, if you have better, less breaking, ideas, please share them)
>
> We can minimize breakage with the return value I am suggesting as the 
> following:
>
> 1. Introduce a new optional parameter ALLOW-MULTIPLE for 
> `org-babel-tangle-single-block'
> 2. When ALLOW-MULTIPLE is non-nil, return as I suggested
> 3. When ALLOW-MULTIPLE is nil and :tangle specifies single target, use the 
> previous return value convention
> 4. When ALLOW-MULTIPLE is nil and :tangle specifies multiple targets,
>    still use previous return value convention by returning the first
>    target only and displaying a warning
>
> Then, all the users of `org-babel-tangle-single-block' will pass non-nil
> ALLOW-MULTIPLE argument. External users will naturally pass nil, keeping
> the old convention and not breaking anything. External users used on new
> Org files that make use of the multiple tangle targets feature will get
> a runtime warning.
>
> Does it make sense?
>
> --
> 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>
From 8fb558fb080586a2c14be226999f1d6fa606f140 Mon Sep 17 00:00:00 2001
From: llcc <lzhes43@gmail.com>
Date: Sun, 9 Mar 2025 15:05:35 +0800
Subject: [PATCH] New feature: tangle org source blocks to multiple targets

1. add `:tangle-directory' to specify tangle directory.
2. `:tangle' now accepts symbols that return a path string, or a list of file path, or a single string
---
 lisp/ob-tangle.el | 80 ++++++++++++++++++++++++++++++++---------------
 1 file changed, 55 insertions(+), 25 deletions(-)

diff --git a/lisp/ob-tangle.el b/lisp/ob-tangle.el
index 38cad78ab..f0cb546e6 100644
--- a/lisp/ob-tangle.el
+++ b/lisp/ob-tangle.el
@@ -500,34 +500,30 @@ code blocks by target file."
                    (org-element-at-point)
                    'headline t))
                  1)))
-	(if (eq last-heading-pos current-heading-pos) (cl-incf counter)
-	  (setq counter 1)
-	  (setq last-heading-pos current-heading-pos)))
+	    (if (eq last-heading-pos current-heading-pos) (cl-incf counter)
+	      (setq counter 1)
+	      (setq last-heading-pos current-heading-pos)))
       (unless (or (org-in-commented-heading-p)
 		  (org-in-archived-heading-p))
-	(let* ((info (org-babel-get-src-block-info 'no-eval))
-	       (src-lang (nth 0 info))
-	       (src-tfile (cdr (assq :tangle (nth 2 info)))))
-	  (unless (or (string= src-tfile "no")
+        (let* ((block (org-babel-tangle-single-block counter t))
+               (src-file (car block))
+               (src-lang (caar block)))
+          (unless (or (not src-file)
                       ;; src block without lang
-                      (and (not src-lang) (string= src-tfile "yes"))
-		      (and tangle-file (not (equal tangle-file src-tfile)))
+                      (and (not src-lang) src-file)
+                      (and tangle-file (not (equal tangle-file src-file)))
                       ;; lang-re but either no lang or lang doesn't match
-		      (and lang-re
+                      (and lang-re
                            (or (not src-lang)
                                (not (string-match-p lang-re src-lang)))))
-	    ;; Add the spec for this block to blocks under its tangled
-	    ;; file name.
-	    (let* ((block (org-babel-tangle-single-block counter))
-                   (src-tfile (cdr (assq :tangle (nth 4 block))))
-		   (file-name (org-babel-effective-tangled-filename
-                               buffer-fn src-lang src-tfile))
-		   (by-fn (assoc file-name blocks)))
-	      (if by-fn (setcdr by-fn (cons (cons src-lang block) (cdr by-fn)))
-		(push (cons file-name (list (cons src-lang block))) blocks)))))))
+            (setq blocks
+                  (mapcar (lambda (group)
+                            (cons (car group)
+                                  (apply #'append (mapcar #'cdr (cdr group)))))
+                          (seq-group-by #'car (append block blocks))))))))
     ;; Ensure blocks are in the correct order.
     (mapcar (lambda (b) (cons (car b) (nreverse (cdr b))))
-	    (nreverse blocks))))
+	        (nreverse blocks))))
 
 (defun org-babel-tangle--unbracketed-link (params)
   "Get a raw link to the src block at point, without brackets.
@@ -541,6 +537,7 @@ The PARAMS are the 3rd element of the info for the same src block."
                         (match-string 1 l))))
         (when bare
           (if (and org-babel-tangle-use-relative-file-links
+                   (length= (cdr (assq :tangle params)) 1)
                    (string-match org-link-types-re bare)
                    (string= (match-string 1 bare) "file"))
               (concat "file:"
@@ -550,6 +547,39 @@ The PARAMS are the 3rd element of the info for the same src block."
             bare))))))
 
 (defvar org-outline-regexp) ; defined in lisp/org.el
+
+(defun org-babel-tangle--concat-targets (buffer-fn info)
+  "Return a list of tangled files based on the `:tangle'
+and `:tangle-directory' in PARAMS."
+  (let* ((params (nth 2 info))
+         (src-lang (nth 0 info))
+         (src-tdirectories (cdr (assq :tangle-directory params)))
+	 (src-tfiles (cdr (assq :tangle params)))
+         (src-tfiles (pcase (type-of src-tfiles)
+                       ('cons src-tfiles)
+                       ('symbol (eval src-tfiles))
+                       (_ (eval src-tfiles)))))
+    (unless (or (not src-tdirectories)
+                (consp src-tdirectories))
+      (setq src-tdirectories (list src-tdirectories)))
+    (unless (consp src-tfiles)
+      (setq src-tfiles
+            (list (cond ((string= src-tfiles "yes")
+                         (file-name-nondirectory
+                          (org-babel-effective-tangled-filename buffer-fn src-lang src-tfiles)))
+                        ((string= src-tfiles "no") nil)
+                        (t src-tfiles)))))
+    (when (and src-tdirectories
+               (not (equal src-tfiles '(nil))))
+      (setq src-tfiles
+            (apply 'append
+                   (mapcar (lambda (src-tdirectory)
+                             (mapcar (lambda (src-tfile)
+                                       (expand-file-name src-tfile src-tdirectory))
+                                     src-tfiles))
+                           src-tdirectories))))
+    src-tfiles))
+
 (defun org-babel-tangle-single-block (block-counter &optional only-this-block)
   "Collect the tangled source for current block.
 Return the list of block attributes needed by
@@ -580,7 +610,7 @@ non-nil, return the full association list to be used by
 	  ;; Run the tangle-body-hook.
           (let ((body (if (org-babel-noweb-p params :tangle)
                           (if (string= "strip-tangle" (cdr (assq :noweb (nth 2 info))))
-                            (replace-regexp-in-string (org-babel-noweb-wrap) "" (nth 1 info))
+                              (replace-regexp-in-string (org-babel-noweb-wrap) "" (nth 1 info))
 			    (org-babel-expand-noweb-references info))
 			(nth 1 info))))
 	    (with-temp-buffer
@@ -616,7 +646,6 @@ non-nil, return the full association list to be used by
 			 (match-end 0)
 		       (point-min))))
 	      (point)))))
-         (src-tfile (cdr (assq :tangle params)))
 	 (result
 	  (list start-line
 		(if org-babel-tangle-use-relative-file-links
@@ -629,9 +658,10 @@ non-nil, return the full association list to be used by
 		  (org-trim (org-remove-indentation body)))
 		comment)))
     (if only-this-block
-        (let* ((file-name (org-babel-effective-tangled-filename
-                           file src-lang src-tfile)))
-          (list (cons file-name (list (cons src-lang result)))))
+        (let* ((file-names (org-babel-tangle--concat-targets file info)))
+          (mapcar (lambda (file-name)
+                    (cons file-name (list (cons src-lang result))))
+                  file-names))
       result)))
 
 (defun org-babel-tangle-comment-links (&optional info)
-- 
2.37.2.windows.2

Reply via email to