Overview
This answer uses Emacs Org Mode with its noweb syntax and
tangling to generate this answer. Included below is the original
Org mode file text to use as a starting point.
This answer was tangled using Emacs version 26.3 and
Org Mode version 9.3.6.
Define the org-in-tangle-dir Elisp function
Define the following function in your init.el
file1 so
that it is reusable in multiple Org files upon the next time you
reinvoke Emacs2:
(defun org-in-tangle-dir (sub-path)
"Expand the SUB-PATH into the directory given by the tangle-dir
property if that property exists, else use the
`default-directory'."
(expand-file-name sub-path
(or
(org-entry-get (point) "tangle-dir" 'inherit)
(default-directory))))
You can define that function interactively by marking the entire
function definition, and typing:
M-x eval-region
Use org-in-tangle-dir
Elisp function with the tangle
header argument
Now that the org-in-tangle-dir
Elisp function is defined in your Emacs,
you can use the function to provide the value to the tangle
header
argument in your source blocks in multiple Org files. The
documentation states:
FILENAME
Export the code block to source file whose file name is derived
from any string passed to the ‘tangle’ header argument. Org
derives the file name as being relative to the directory of the Org
file’s location. Example: ‘:tangle FILENAME’.
The key here is to not hardcode the FILENAME
but generate it's
value dynamically during tangling via the call to org-in-tangle-dir
.
Each heading can optionally define its own tangle-dir
property. If
it is not defined within the property drawer of that heading,
org-in-tangle-dir
searches all property drawers in each parent heading
for the property. If the property is not found in any parent heading,
it will use the current working directory of the Org file (which would
have the same effect as not specifying tangle
header argument).
You can set properties into the current heading property drawer using
C-c C-x p
(see org-set-property), and you can enter in
tangle-dir
followed by the path to the tangle directory.
Once you have defined all tangle-dir
properties you would like to
define, type in the C-c C-v C-t
which calls org-babel-tangle
to
tangle the buffer into the directories given by the tangle-dir
properties.
It generates the main tangle-dir
in the top-most heading, and a few
subdirectories within that due to the subheadings below it. There is
no particular reason why they have to be all within the same directory
hierarchy; if you were to put them into other directories you could do
so provided your user account has write-access to them.
Here are the resulting files. You would have to "tangle" twice (with
C-c C-v C-t
) to see this output, as the first time generates it and
the second time would see that existing output (chicken-and-egg-problem):
/tmp/emacs.stackexchange.com.answer
├── answer.txt
├── some.dir.1
│ ├── HelloWorld.py
│ └── some-text-file1.txt
└── some.dir.2
├── some-bash-script2.sh
└── some-text-file2.txt
2 directories, 5 files
Up to 10 lines of each generated file are shown below:
FILE: /tmp/emacs.stackexchange.com.answer/answer.txt
1 # Overview
2
3 This answer uses Emacs Org with its [noweb syntax][1] and [tangling][2] to
4 generate this answer. Included below is the original Org mode file
5 text to use as a starting point.
6
7 This answer was tangled using Emacs version 26.3 and
8 Org Mode version 9.3.6.
9
10 ## Define the org-in-tangle-dir Elisp function
FILE: /tmp/emacs.stackexchange.com.answer/some.dir.1/HelloWorld.py
1 """This is a hello world example document"""
2 print("Hello World from Python!")
FILE: /tmp/emacs.stackexchange.com.answer/some.dir.1/some-text-file1.txt
1 This is the python script to be generated into a text file inside yet another directory
2
3 """This is a hello world example document"""
4 print("Hello World from Python!")
5
6 Executing that script gives the expected output:
7
8 Hello World from Python!
FILE: /tmp/emacs.stackexchange.com.answer/some.dir.2/some-bash-script2.sh
1 #!/bin/bash
2 echo "A Bash script is executing."
FILE: /tmp/emacs.stackexchange.com.answer/some.dir.2/some-text-file2.txt
1 This is the bash script to be generated into a text file inside yet another directory
2
3 echo "A Bash script is executing."
4
5 Executing that script gives the expected output:
6
7 A Bash script is executing.
Example Org file
The Org file used to create this answer is shown below.
* TODO Example of per-heading tangled parent directories
:PROPERTIES:
:tangle-dir: /tmp/emacs.stackexchange.com.answer
:ID: bcdcf445-b38b-453b-8531-e6377c673b5d
:END:
Here is the answer text to tangle into the directory given by
`tangle-dir` of this Org heading, into the filename given in the call
to `org-in-tangle-dir` below (notice we are using the `:mkdirp` to ensure
that the output directory is created before writing to it; errors will
occur otherwise):
#+BEGIN_SRC text :noweb yes :tangle (org-in-tangle-dir "answer.txt") :exports none :mkdirp yes
# Overview
This answer uses Emacs Org Mode with its [noweb syntax][1] and
[tangling][2] to generate this answer. Included below is the original
Org mode file text to use as a starting point.
This answer was tangled using Emacs version <<emacs-version()>> and
Org Mode version <<org-mode-version()>>.
## Define the org-in-tangle-dir Elisp function
Define the following function in your `init.el` file<sup>1</sup> so
that it is reusable in multiple Org files upon the next time you
reinvoke Emacs<sup>2</sup>:
<!-- language: lang-el -->
<<org-in-tangle-dir>>
You can define that function interactively by marking the entire
function definition, and typing:
M-x eval-region
## Use `org-in-tangle-dir` Elisp function with the `tangle` header argument
Now that the `org-in-tangle-dir` Elisp function is defined in your Emacs,
you can use the function to provide the value to the `tangle` header
argument in your source blocks in multiple Org files. The
[documentation][2] states:
> FILENAME
> Export the code block to source file whose file name is derived
> from any string passed to the ‘tangle’ header argument. Org
> derives the file name as being relative to the directory of the Org
> file’s location. Example: ‘:tangle FILENAME’.
The **key** here is to not hardcode the `FILENAME` but generate it's
value dynamically during tangling via the call to `org-in-tangle-dir`.
Each heading can optionally define its own `tangle-dir` property. If
it is not defined within the property drawer of that heading,
`org-in-tangle-dir` searches all property drawers in each parent heading
for the property. If the property is not found in any parent heading,
it will use the current working directory of the Org file (which would
have the same effect as not specifying `tangle` header argument).
You can set properties into the current heading property drawer using
`C-c C-x p` (see [org-set-property][3]), and you can enter in
`tangle-dir` followed by the path to the tangle directory.
Once you have defined all `tangle-dir` properties you would like to
define, type in the `C-c C-v C-t` which calls `org-babel-tangle` to
tangle the buffer into the directories given by the `tangle-dir`
properties.
It generates the main `tangle-dir` in the top-most heading, and a few
subdirectories within that due to the subheadings below it. There is
no particular reason why they have to be all within the same directory
hierarchy; if you were to put them into other directories you could do
so provided your user account has write-access to them.
Here are the resulting files. You would have to "tangle" twice (with
`C-c C-v C-t`) to see this output, as the first time generates it and
the second time would see that existing output (chicken-and-egg-problem):
<<show-directory()>>
# Example Org file
The Org file used to create this answer is shown below.
<<this-org-file()>>
<sup>1</sup> https://emacs.stackexchange.com/questions/12938/how-can-i-evaluate-elisp-in-an-orgmode-file-when-it-is-opened/12958#12958
describes using File Local variables to define functions like as done
above, but they are considered by some to be a security risk. Even if
it was not a security risk, you may still find them annoying if you need to
use them in multiple Org files. Also, because they only work at the
end of the file which tends to get appended to with new child
headings, leaving the File Local Variables in the middle of the file
where they are likely to not be missed by Emacs when it loads the
file the next time.
<sup>2</sup>The `org-in-tangle-dir` function could be included into
the Org mode itself in some manner.
[1]: https://orgmode.org/manual/Noweb-Reference-Syntax.html#Noweb-Reference-Syntax
[2]: https://orgmode.org/manual/Extracting-Source-Code.html#Extracting-Source-Code
[3]: https://orgmode.org/manual/Property-Syntax.html#Property-Syntax
#+END_SRC
See child headings below that demonstrate using different `tangle-dir` properties.
** Child heading 1
:PROPERTIES:
:tangle-dir: /tmp/emacs.stackexchange.com.answer/some.dir.1
:ID: 5957bdfc-26f1-43ba-807e-0088b7a86073
:END:
Reusing a trimmed down example from http://ehneilsen.net/notebook/orgExamples/org-examples.html#sec-25-1 :
#+NAME: the-python-script
#+BEGIN_SRC python :noweb yes :tangle :tangle (org-in-tangle-dir "HelloWorld.py") :exports none :mkdirp yes
"""This is a hello world example document"""
print("Hello World from Python!")
#+END_SRC
Because some may have a Conda python in my $PATH that is not in
/usr/bin, this calls the unqualified python to execute it. This reuses
`org-in-tangle-dir` again to locate it:
#+NAME: bashrun-the-python-script
#+BEGIN_SRC bash :results output :exports none :var script=(org-in-tangle-dir "HelloWorld.py")
python $script 2>&1
true # Ignore non-zero exit codes from any bugs in the script
#+END_SRC
Tangle a text file that shows the script and its resulting output:
#+BEGIN_SRC text :noweb yes :tangle (org-in-tangle-dir "some-text-file1.txt") :exports none :mkdirp yes
This is the python script to be generated into a text file inside yet another directory
<<the-python-script>>
Executing that script gives the expected output:
<<bashrun-the-python-script()>>
#+END_SRC
** Child heading 2
:PROPERTIES:
:tangle-dir: /tmp/emacs.stackexchange.com.answer/some.dir.2
:ID: 928b3f4f-b0e4-4647-93f7-11753911319d
:END:
Define a bash script:
#+NAME: the-bash-script
#+BEGIN_SRC bash :results output :noweb yes :tangle :tangle (org-in-tangle-dir "some-bash-script2.sh") :exports none :mkdirp yes :shebang "#!/bin/bash"
echo "A Bash script is executing."
#+END_SRC
Tangle a text file that shows the script and its resulting output:
#+BEGIN_SRC text :noweb yes :tangle (org-in-tangle-dir "some-text-file2.txt") :exports none :mkdirp yes
This is the bash script to be generated into a text file inside yet another directory
<<the-bash-script>>
Executing that script gives the expected output:
<<the-bash-script()>>
#+END_SRC
** Elisp definition of org-in-tangle-dir
This is the Elisp definition of org-in-tangle-dir:
#+NAME: org-in-tangle-dir
#+BEGIN_SRC emacs-lisp :results value
(defun org-in-tangle-dir (sub-path)
"Expand the SUB-PATH into the directory given by the tangle-dir
property if that property exists, else use the
`default-directory'."
(expand-file-name sub-path
(or
(org-entry-get (point) "tangle-dir" 'inherit)
(default-directory))))
#+END_SRC
** this-org-file block
The this-org-file block reads the current Org file:
#+NAME: this-org-file
#+BEGIN_SRC emacs-lisp :var ref-file=(buffer-file-name)
(with-temp-buffer
(insert-file-contents (expand-file-name ref-file))
(buffer-string))
#+END_SRC
** emacs-version block
#+NAME: emacs-version
#+BEGIN_SRC emacs-lisp
;; (emacs-version) function returns a lot of extra info whereas I just need the version number in the emacs-version variable:
emacs-version
#+END_SRC
** org-mode-version block
#+NAME: org-mode-version
#+BEGIN_SRC emacs-lisp
;; (org-version) only returns the version number
(org-version)
#+END_SRC
** show-directory block
:PROPERTIES:
:ID: 019f982f-399c-4c79-b6dd-2b897481563c
:END:
Notice how the value of tangle-dir this returns is not from this
heading (which actually does not have a properties drawer), but from
its parent heading, yeilding the top-most directory:
# #+NAME: show-directory
# #+BEGIN_SRC bash :results output :noweb yes :tangle :tangle (org-in-tangle-dir "show-directory.sh") :exports none :mkdirp yes :shebang "#!/bin/bash" :var tangle_dir=(org-entry-get (point) "tangle-dir" 'inherit) :var reffile=(buffer-file-name)
# echo tangle_dir $tangle_dir
# echo reffile $reffile
# #+END_SRC
#+NAME: show-directory
#+BEGIN_SRC bash :results output :exports none :var tangle_dir=(org-entry-get (point) "tangle-dir" 'inherit)
head_count=10
tree $tangle_dir
echo
echo "Up to $head_count lines of each generated file are shown below:"
echo
for file in $(find $tangle_dir -type f | sort)
do
echo
echo "FILE: $file"
cat -n $file | head -$head_count
done
#+END_SRC
1 How can I evaluate elisp in an orgmode file when it is opened?
describes using File Local variables to define functions like as done
above, but they are considered by some to be a security risk. Even if
it was not a security risk, you may still find them annoying if you need to
use them in multiple Org files. Also, because they only work at the
end of the file which tends to get appended to with new child
headings, leaving the File Local Variables in the middle of the file
where they are likely to not be missed by Emacs when it loads the
file the next time.
2The org-in-tangle-dir
function could be included into
the Org mode itself in some manner.