# automating recursive file hierarchy (directory tree) diagrams with tikz

So, I've been trying to write a python script that will produce the TikZ/LaTeX source code for one of these filesystem tree / directory structure diagrams:

## Code

LaTeX: TikZ

\documentclass[tikz,border=10]{standalone}
\usetikzlibrary{trees}
\tikzstyle{every node}=[draw=black,thick,anchor=west]

\begin{document}

\begin{tikzpicture}[ %
grow via three points={one child at (0.5,-0.7) and two children at (0.5,-0.7) and (0.5,-1.4)},
edge from parent path={(\tikzparentnode.south)|-(\tikzchildnode.west)}]

\node {/tmp/test}
%
child{node{a}
child{node{1}}
child{node{2}}
child{node{3}}}
child [missing] {} %
child [missing] {} %
child [missing] {} %
%
child{node{b}
child{node{1}}
child{node{2}}}
child [missing] {} %
child [missing] {} %
%
child{node{c}
child{node{1}}}
child [missing] {} %
%
child{node{d}};
%
\end{tikzpicture}

\end{document}


It was supposed to be just a quick little thing, but it turns out programming programs to.. program programs can be bloody confusing. I'd like a way to do it all from within the LaTeX environment (perhaps LuaLaTeX can help?) if possible. Or alternatively some kind of macro or function that takes input in a practical format that can be generated externally and copy/pasted in. Here are a couple of examples of possible ways to generate input that might be suitable (I'm just spitballing here, they won't be perfect, but I can tune them to suit if need be):

## Additional Info:

Don't be confused by this section, the question isn't about bash or python or whatever, this is just to demonstrate a few ways to generate input, and also to show it can easily be manipulated to the appropriate format. What I need help with is writing a suitable LaTeX macro that can typeset this information properly.

### bash: find

in

find .


out

.
./a
./a/1
./a/2
./a/3
./b
./b/1
./b/2
./c
./c/1
./d


in

printf '{/tmp/test';
find /tmp/test -printf '%P, ';
printf '}\n';


out

{tmp/test, a, a/1, a/2, a/3, b, b/1, b/2, c, c/1, d, }


### python: os.walk()

in

import os
for (root,dirs,files) in os.walk('.'):
print(f'{root}\n{dirs}\n{files}')


out

.
['a', 'b', 'c', 'd']
[]
./a
[]
['1', '2', '3']
./b
[]
['1', '2']
./c
[]
['1']
./d
[]
[]


in

print('{/tmp/test',end='')
for (root,dirs,files) in os.walk('.'):
print(f"{root[2:]},[{','.join(dirs+files)}],")
print('}')


out

{/tmp/test,[a,b,c,d],
a,[1,2,3],
b,[1,2],
c,[1],
d,[],
}

• Maybe this link helps. I feel, though, that this is not necessarily a LaTeX question. If you understand how to produce the right format, you can immediately use it in forest with the folder option. – Schrödinger's cat Nov 9 at 2:20
• @Schrödinger'scat Please explain. – voices Nov 9 at 5:29
• You must have misunderstood the question. I was concerned that might happen as soon as I mentioned another language. I probably gave too much information in the interest of being thorough and providing context. I was trying to make it easier for anyone who might be able to help. It's purely a LaTeX question. Essentially: I don't want to have to draw the tree manually every time. I want to write a LaTeX macro or command that essentially does the same thing as the Unix tree utility. But rather than an ascii diagram, I want a full scalable vector SVG or PDF image. – voices Nov 9 at 5:48
• In other words I don't want to have to describe the filesystem to LaTeX. I want LaTeX to describe the filesystem to me. Ideally the macro would take a single argument (i.e., a directory. e.g., /tmp/test). But if that's too tall an order, I can make do with having to specify all the files and subdirectories, especially if it accepts them arguments in a convenient format that I can generate externally. Does that make more sense? – voices Nov 9 at 6:01
• Not really. LaTeX can't really describe the file system to you. It doesn't do that. That is, of course you can write something in TeX to do it if you really want to, but it will be a lot of work for a third-rate solution, at best. It's far better to have something else parse the file system and LaTeX typeset it. You can set it up so the something else is run as part of the compilation process (with shell escape enabled) or probably Lua can do it, but trying to do it all in TeX is just silly. (The only reason I can think would be cross-platform compatibility, but I can't see a good solution ... – cfr Nov 10 at 3:46

## 2 Answers

It is not an answer as such (but it is too long to put in a commentary). I think it's possible using -shell-escape, the tree command and a few regex (I'm more familiar with perl than python). The first thing is to be able to find a pattern to be able to build the code in tikz. Using :

[pablo@worktex ~] $pwd /home/pablo [pablo@worktex ~]$ tree ltxgit/
ltxgit/
├── build
│   ├── distrib
│   ├── doc
│   ├── local
│   ├── test
│   └── unpacked
├── build.lua
├── mybuild.lua
├── mygitcmd.txt
├── mytree.txt
├── README.md
├── sources
│   ├── README.md
│   └── testfiles
│       ├── test-nospace.tex
│       └── test-pkg.tex
└── texlive
├── texlive_install.sh
├── texlive_packages
└── texlive.profile

9 directories, 11 files


The point is to distinguish which are directories and which are just files, then we switch to:

[pablo@worktex ~] \$ tree -p ltxgit/
ltxgit/
├── [drwxrwxr-x]  build
│   ├── [drwxrwxr-x]  distrib
│   ├── [drwxrwxr-x]  doc
│   ├── [drwxrwxr-x]  local
│   ├── [drwxrwxr-x]  test
│   └── [drwxrwxr-x]  unpacked
├── [-rw-rw-r--]  build.lua
├── [-rw-rw-r--]  mybuild.lua
├── [-rw-rw-r--]  mygitcmd.txt
├── [-rw-rw-r--]  mytree.txt
├── [-rw-rw-r--]  README.md
├── [drwxrwxr-x]  sources
│   ├── [-rw-rw-r--]  README.md
│   └── [drwxrwxr-x]  testfiles
│       ├── [-rw-rw-r--]  test-nospace.tex
│       └── [-rw-rw-r--]  test-pkg.tex
└── [drwx------]  texlive
├── [-rw-rw-r--]  texlive_install.sh
├── [-rw-rw-r--]  texlive_packages
└── [-rw-rw-r--]  texlive.profile

9 directories, 11 files


Differentiate directories from files using -p. Now comes the hard part, finding regularity to pass the code to tikz. We know that the first line corresponds to the root directory and the others follow a regularity based on the beginning of the line |, the spaces and their identification [d.+?]. We can generate a routine and convert it to this:

ltxgit/
├── build/
│   ├── distrib/
│   ├── doc/
│   ├── local/
│   ├── test/
│   └── unpacked/
├── build.lua
├── mybuild.lua
├── mygitcmd.txt
├── mytree.txt
├── README.md
├── sources/
│   ├── README.md
│   └── testfiles/
│       ├── test-nospace.tex
│       └── test-pkg.tex
└── texlive/
├── texlive_install.sh
├── texlive_packages
└── texlive.profile


in terms of tikz. I think (I'm not sure about this) that it would be better to use Lua and to do it multiplatform and not depend on tree, but, if you are using python maybe you can play for a while using pythontex until you come up with the solution.

Good luck with this, it would be great to see it in action.

You could do something like the following. This relies on the tree command being present on the system, as well as bash and sed --- so this is useless on Windows, for example --- but the command is run externally, as part of compilation, provided shell escape is enabled.

\documentclass{article}
\usepackage{bashful}
\usepackage[edges]{forest}
\begin{document}
\bash[stdoutFile=mydirtree.tex]
tree -X --noreport /opt/stapler/ | sed -e "s/<tree>/\\\begin{forest}for tree={folder,grow\'=0}/" -e 's/<\/tree>/\\end{forest}/' -e 's/$$<directory name=$$\|$$<file name=$$/[/' -e's/\"$$[^\"]*$$\">/\1/' -e 's/<\/directory>/]/' -e 's/<\/file>/]/' -e '/^<?xml version/d' -e 's/_/\\_/g'
\END
\input{mydirtree}

\end{document}


Note that you may need to adjust the sed depending on which characters occur in file names on your system (including directories, which are also files, of course).

I used pdflatex -shell-escape <filename> to compile.

• I'm getting ./mydirtree.tex:4 Argument of \bracket@Parse@token has extra } <inserted text> \par l.4 \end{forest}. I'll try again. – voices Nov 10 at 12:20
• @voices Could be a version difference. Or something else. – cfr Nov 10 at 20:11
• @crf: Your idea is much cooler than mine...if you generate the output in xml, maybe you can read it directly with LuaXML? – Pablo González L Nov 10 at 23:38
• @PabloGonzálezL Feel free to steal it. I know zilch about Lua, never mind LuaXML. Or give me a hint where I should read, if you'd rather do that. As you point out tree isn't cross-platform, but I'm dubious about the utility of a cross-platform solution here since file system conventions vary so much. However, I was excited to discover the other output options of tree :). – cfr Nov 10 at 23:55
• @crf : Unfortunately my knowledge of Lua is extremely poor, I can only use a little string.gsub (which is similar to regex) but I read here in the forum that it can process an xml without problems. The idea I had proposed above is for the output you get in win 10 using tree /F ltxgit is almost identical to the linux output, just look at the pattern. Anyway, if you separate your solution by lines using \ShellEscape and WSL it still works on windows :) – Pablo González L Nov 11 at 0:36