Thanks Ihor, Phil

I updated the patch to address your comments.

>> You are dropping this logic completely in the patch. Without this part 
>> :tangle no may not work.
I restored the logic in the new patch.

>> With this change, ONLY-THIS-BLOCK = nil is ignored. This will cause problems.
I intended to remove the ONLY-THIS-BLOCK part since it will no longer
be used in the official code. However, after searching on GitHub, I
realized that org-babel-tangle-single-block is widely used by many
users, so I restored it in the new patch. However, in my opinion, it
should eventually be abandoned if the patch gets accepted.

llcc

On Wed, Mar 12, 2025 at 4:38 AM Phil Estival <p...@7d.nz> wrote:
>
> References: 
> <caljq+lyfvwgog+ck3p2oel2pdt4gu31xf13x3tgkp9mdj8-...@mail.gmail.com>
>         <caljq+lyqc7gqvvm7j0ycodinapzpru2rhngz9y08g1k272h...@mail.gmail.com>
> User-Agent: mu4e 1.12.8; emacs 30.1
> Date: Tue, 11 Mar 2025 21:38:00 +0100
>
> * [2025-03-09 15:12 +0800] Lei Zhe <lzhe...@gmail.com>:
>
> Hello Lei,
>
> > It'll make the tangle system more flexible, and I would be glad to
> > see it included in the next release.
>
> I would be glad too.
>
> > I wanted to check in to see if you’re still planning to complete the
> > patch. If not, I am more than happy to take over and finish it.
>
> Yes, please, do. I don't think I will, I don't intend to at the moment.
> Sorry for this late answer. I don't have enough time to properly follow
> the list these days (there should only be a little left to provide the
> patch to ob-sql in the next weeks, as I'm using it again).
>
> > This patch is the first step in resolving the feature for tangling
> > source blocks to multiple targets.
> > I would appreciate any feedback.
>
> I'll add to Ihor answer that writing a new test suite
> (test-ob-n-tangle.el) would be useful.
>
> --
> Phil
From 99c76cdeaf232115b3d556a17b5a0ebed9151771 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 | 79 ++++++++++++++++++++++++++++++++---------------
 1 file changed, 54 insertions(+), 25 deletions(-)

diff --git a/lisp/ob-tangle.el b/lisp/ob-tangle.el
index 38cad78ab..361ee521d 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.
@@ -550,6 +546,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 +609,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 +645,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 +657,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