* [FR] org-babel-n-tangle
@ 2024-07-12 7:11 Phil
2024-07-12 11:23 ` Ihor Radchenko
0 siblings, 1 reply; 4+ messages in thread
From: Phil @ 2024-07-12 7:11 UTC (permalink / raw)
To: emacs-orgmode
Hi,
The ability to tangle to multiple destinations is a very convenient way
to manage cluster configurations. No, it's more than that: it's an
*awesome* way to deploy and keep clusters configs and repros well
organized.
The following *org-babel-n-tangle*, is just a small derivation
of *org-babel-tangle*. It's displayed here as a diff not
with the intent to be applied as a patch, but to show the very
little differences required in order to get this working.
#+begin_src diff
diff -u ~/.emacs.d/repos/org/lisp/ob-tangle.el ~/tmp/ob-ntangle.el
--- ~/.emacs.d/repos/org/lisp/ob-tangle.el
+++ ~/tmp/ob-ntangle.el
@@ -238,8 +238,12 @@
(org-babel-tangle-file filename)))
;;;###autoload
-(defun org-babel-tangle (&optional arg target-file lang-re)
- "Write code blocks to source-specific files.
+(defun org-babel-n-tangle (&optional arg target-file lang-re)
+ "Write code blocks to source-specific files
+located into the directories designated by the :n-tangle parameter
+then onto subsequent directory and file of the :tangle parameter.
+Performs like `org-babel-tangle' adding an extra iteration over
+a list of directories, potentially different hosts and protocols
Extract the bodies of all source code blocks from the current
file into their own source-specific files. Return the list of files.
With one universal prefix argument, only tangle the block at point.
@@ -268,12 +272,19 @@
(tangle-file
(when (equal arg '(16))
(or (cdr (assq :tangle (nth 2 (org-babel-get-src-block-info 'no-eval))))
- (user-error "Point is not in a source code block"))))
+ (user-error "Point is not in a source code block"))))
+ (targets (or (cadr (assoc (cdr
+ (assoc :n-tangle (nth 2 (org-babel-get-src-block-info))))
+ org-babel-ntangle-destinations))
+ '(nil))) ; iterate on one local target
path-collector
(source-file buffer-file-name))
+
+ (dolist (target targets) ;; iterate the n-tangle group
+ (progn
(mapc ;; map over file-names
(lambda (by-fn)
- (let ((file-name (car by-fn)))
+ (let ((file-name (concat target (car by-fn))))
(when file-name
(let ((lspecs (cdr by-fn))
(fnd (file-name-directory file-name))
@@ -354,6 +365,7 @@
(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
#+end_src
In order to use this *:n-tangle* parameter, the destinations are
declared in groups of host and/or root folders.
#+begin_src elisp
(setq org-babel-ntangle-destinations
'(("test-1"
("/tmp/test/host-A"
"/tmp/test/host-B" ))
("hosts-A&B/tmp"
("/-:hostA:/tmp/"
"/-:hostB:/tmp/"))))
#+end_src
Calling *org-babel-n-tangle* with the universal argument
runs the tangle processor, not on the entire file, but
for the current block. The tangled output goes into the
designated group.
#+begin_example
#+begin_src elisp :n-tangle "hosts-A&B/tmp" :tangle /x/y :mkdirp t
(org-babel-n-tangle '(4))
#+end_src
#+end_example
In the above example the tangled outputs goes to
*hostA:/tmp/x/y* and *hostB:/tmp/x/y* using a default protocol.
In the absence of *:n-tangle* or when
*org-babel-ntangle-destinations* is nil.
*org-babel-n-tangle* behaves like *org-babel-tangle*
What do you think ?
Phil
/"Oh what a tangled web we weave..."/
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [FR] org-babel-n-tangle
2024-07-12 7:11 [FR] org-babel-n-tangle Phil
@ 2024-07-12 11:23 ` Ihor Radchenko
2024-07-15 5:12 ` Phil
0 siblings, 1 reply; 4+ messages in thread
From: Ihor Radchenko @ 2024-07-12 11:23 UTC (permalink / raw)
To: Phil; +Cc: emacs-orgmode
Phil <pe@7d.nz> writes:
> The ability to tangle to multiple destinations is a very convenient way
> to manage cluster configurations. No, it's more than that: it's an
> *awesome* way to deploy and keep clusters configs and repros well
> organized.
> ...
> #+begin_example
> #+begin_src elisp :n-tangle "hosts-A&B/tmp" :tangle /x/y :mkdirp t
> (org-babel-n-tangle '(4))
> #+end_src
> #+end_example
>
> In the above example the tangled outputs goes to
> *hostA:/tmp/x/y* and *hostB:/tmp/x/y* using a default protocol.
>
> In the absence of *:n-tangle* or when
> *org-babel-ntangle-destinations* is nil.
> *org-babel-n-tangle* behaves like *org-babel-tangle*
>
> What do you think ?
This sounds like a logic extension of the existing tangle mechanism.
Although, I feel that the semantics is a bit cumbersome.
IMHO, a more natural approach would be (1) Introduce :tangle-directory
parameter that defines relative directory to be used as tangle target;
this directory, if defined, will be used instead of the Org file
directory to expand the tangle target; (2) Allow :tangle-directory and
:tangle-file to be a list of targets to write.
Then, we can modify `org-babel-effective-tangled-filename' to account
for :tangle directory and modify `org-babel-tangle' (as you did) to
write to multiple targets.
--
Ihor Radchenko // yantar92,
Org mode contributor,
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>
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [FR] org-babel-n-tangle
2024-07-12 11:23 ` Ihor Radchenko
@ 2024-07-15 5:12 ` Phil
2024-07-15 14:28 ` Ihor Radchenko
0 siblings, 1 reply; 4+ messages in thread
From: Phil @ 2024-07-15 5:12 UTC (permalink / raw)
To: Ihor Radchenko; +Cc: emacs-orgmode
[-- Attachment #1: Type: text/plain, Size: 2059 bytes --]
* [2024-07-12 13:23] Ihor Radchenko:> Phil <pe@7d.nz> writes:
>> To tangle to multiple destinations
>
> a logic extension of the existing tangle mechanism.
> 1) Introduce :tangle-directory parameter that defines
> relative directory to be used as tangle target; this
> directory, if defined, will be used instead of the Org
> file directory to expand the tangle target;
> 2) Allow :tangle-directory and :tangle-file to be a list
> of targets to write.
>
> Then, we can modify `org-babel-effective-tangled-filename'
> to account for :tangle directory and modify
> `org-babel-tangle' (as you did) to write to multiple
> targets.
All right. So I have it working for single blocks by
modifying only `org-babel-tangle' with :tangle-directory
accepting a single string or a list, e.g.
:tangle-directory '("dir1" "/ssh:host1:/dir2" "/-::/etc")
The option is ignored for file-wide tangle.
What do you think of, instead of adding :tangle-directory,
modifying :tangle to make it accept also a list?
Since I may not get back to this in the next weeks,
I'm saving the following note and a patch as a current
status of the function for later.
=:tangle-directory dir= is set
- Tangle globally
|--------+------------------+--------------|
| tangle | expected result | current |
|--------+------------------+--------------|
| yes | ignore dir |
| no | ignore dir |
| file | block → dir/file?| block → file |
|--------+------------------+--------------|
- Tangle a single block
|--------+------------------+---------------------------|
| tangle | expected result | current |
|--------+------------------+---------------------------|
| yes | ? | dir/[org-folder/org-file] |
| no | error? | dir (as file or folder) |
| file | tangle to TD+dir | tangle to TD+dir |
|--------+------------------+---------------------------|
Cheers,
Phil
[-- Attachment #2: 0001-add-header-arg-tangle-directory.patch --]
[-- Type: text/x-patch, Size: 5461 bytes --]
From 360938b43a9c6a731114840c9b6db7c79f786116 Mon Sep 17 00:00:00 2001
From: Phil Estival <pe@7d.nz>
Date: Sat, 13 Jul 2024 14:46:08 +0200
Subject: [PATCH] add header-arg :tangle-directory declares a directory or a
list of directories as parent(s) to the :tangle argument
---
lisp/ob-tangle.el | 16 ++++++--
testing/lisp/test-ob-tangle.el | 72 +++++++++++++++++++++++++++++++++-
2 files changed, 84 insertions(+), 4 deletions(-)
diff --git a/lisp/ob-tangle.el b/lisp/ob-tangle.el
index c89763efa..c494571dc 100644
--- a/lisp/ob-tangle.el
+++ b/lisp/ob-tangle.el
@@ -269,11 +269,20 @@ matching a regular expression."
(when (equal arg '(16))
(or (cdr (assq :tangle (nth 2 (org-babel-get-src-block-info 'no-eval))))
(user-error "Point is not in a source code block"))))
+ (dirs (cdr (assq :tangle-directory (nth 2 (org-babel-get-src-block-info)))))
path-collector
- (source-file buffer-file-name))
- (mapc ;; map over file-names
+ (source-file buffer-file-name))
+
+ (setq dirs (cl-case (type-of dirs)
+ (string (list dirs))
+ (cons dirs)
+ (symbol '(nil))))
+
+ (dolist (dir dirs) ; iterate the n-tangle group
+ (progn
+ (mapc ; map over directories
(lambda (by-fn)
- (let ((file-name (car by-fn)))
+ (let ((file-name (concat dir (car by-fn))))
(when file-name
(let ((lspecs (cdr by-fn))
(fnd (file-name-directory file-name))
@@ -354,6 +363,7 @@ matching a regular expression."
(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
diff --git a/testing/lisp/test-ob-tangle.el b/testing/lisp/test-ob-tangle.el
index e13bca0cb..a725cdb14 100644
--- a/testing/lisp/test-ob-tangle.el
+++ b/testing/lisp/test-ob-tangle.el
@@ -27,6 +27,7 @@
(require 'subr-x)
(require 'ob-tangle)
+(require 'find-file)
(require 'org)
;; TODO
@@ -660,7 +661,13 @@ another block
#+begin_src emacs-lisp :tangle ~/../../tmp/absolute.el
\"H2: :tangle ~/../../tmp/absolute.el\"
-#+end_src"
+#+end_src
+
+#+begin_src emacs-lisp :tangle-directory '(\"/tmp/a/\" \"tmp/b/\") :tangle multiple.el
+\"H2: :tangle /tmp/multiple.el\"
+#+end_src
+
+"
`((?a . ,el-file-abs)
(?r . ,el-file-rel))))
;; We check the collected blocks to tangle by counting equal
@@ -699,6 +706,7 @@ another block
(should (equal
(funcall normalize-expected-targets-alist
`(("/tmp/absolute.el" . 4)
+ ("/tmp/multiple.el" . 1)
("relative.el" . 5)
;; Default :tangle header now also
;; points to the file name derived from the name of
@@ -707,6 +715,68 @@ another block
(funcall count-blocks-in-target-files
(org-babel-tangle-collect-blocks)))))))))
+(ert-deftest ob-tangle/directory ()
+ "Test if ob-tangle/directory works correctly for one directory."
+ (should
+ (equal '("1")
+ (let* (
+ (dir (make-temp-file "org-tangle-dir-test-" t))
+ (filename (md5(format "%s" (current-time))))
+ (file (concat dir "/" filename))
+ )
+ (unwind-protect
+ (progn
+ (org-test-with-temp-text-in-file
+ (format "
+#+begin_src elisp :tangle-directory %s :tangle /%s <point>
+1
+#+end_src
+" dir filename)
+ (let ((org-babel-noweb-error-all-langs nil)
+ (org-babel-noweb-error-langs nil))
+ (org-babel-tangle '(4))))
+
+ (with-temp-buffer
+ (insert-file-contents file)
+ (org-split-string (buffer-string))))
+
+ (delete-file file)))
+ )))
+
+
+(ert-deftest ob-tangle/multiple-directories ()
+ "Test if ob-tangle/directory works correctly for multiple directory."
+ (should
+ (equal '("1" "1")
+ (let* (
+ (dir1 (make-temp-file "org-tangle-dir-test-" t))
+ (dir2 (make-temp-file "org-tangle-dir-test-" t))
+ (filename (md5(format "%s" (current-time))))
+ (file1 (concat dir1 "/" filename))
+ (file2 (concat dir2 "/" filename))
+ )
+ (unwind-protect
+ (progn
+ (org-test-with-temp-text-in-file
+ (format "
+#+begin_src elisp :tangle-directory '(\"%s\" \"%s\") :tangle /%s <point>
+1
+#+end_src
+" dir1 dir2 filename)
+ (let ((org-babel-noweb-error-all-langs nil)
+ (org-babel-noweb-error-langs nil))
+ (org-babel-tangle '(4))))
+
+ (with-temp-buffer
+ (insert-file-contents file1)
+ (insert-file-contents file2)
+ (org-split-string (buffer-string))))
+ (progn
+ (delete-file file1)
+ (delete-file file2))))
+ )))
+
+
(provide 'test-ob-tangle)
;;; test-ob-tangle.el ends here
--
2.39.2
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [FR] org-babel-n-tangle
2024-07-15 5:12 ` Phil
@ 2024-07-15 14:28 ` Ihor Radchenko
0 siblings, 0 replies; 4+ messages in thread
From: Ihor Radchenko @ 2024-07-15 14:28 UTC (permalink / raw)
To: Phil; +Cc: emacs-orgmode
Phil <pe@7d.nz> writes:
> All right. So I have it working for single blocks by
> modifying only `org-babel-tangle' with :tangle-directory
> accepting a single string or a list, e.g.
>
> :tangle-directory '("dir1" "/ssh:host1:/dir2" "/-::/etc")
>
> The option is ignored for file-wide tangle.
>
> What do you think of, instead of adding :tangle-directory,
> modifying :tangle to make it accept also a list?
I am ok with making :tangle accept multiple values, but I think that
having a separate :tangle-directory argument is still useful.
Consider something like
#+PROPERTY: header-args :tangle-directory '("/path/to/project1" "/path/to/project2")
#+begin_src :tangle file1
...
#+end_src
#+begin_src :tangle file2
...
#+end_src
> Since I may not get back to this in the next weeks,
> I'm saving the following note and a patch as a current
> status of the function for later.
No problem. We have no deadlines here.
--
Ihor Radchenko // yantar92,
Org mode contributor,
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>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2024-07-15 14:27 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-07-12 7:11 [FR] org-babel-n-tangle Phil
2024-07-12 11:23 ` Ihor Radchenko
2024-07-15 5:12 ` Phil
2024-07-15 14:28 ` Ihor Radchenko
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).