5

Base on this, one can set a global tangled file.

However, I'd like to set one directory for each subtree, e.g. for a * Python subtree, in every code block I can set :tangle a.py or :tangle b.py instead of :tangle python/a.py and :tangle python/b.py.

Any help will be appreciated.

1 Answer 1

3

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.