Astro/MDX Directory Tree Component
Tue Nov 07 2023
I got tired of copying and pasting weird ASCII characters in order to write out directory trees so I wrote an Astro component to convert YAML into a format that looks like the output of the Linux tree
command!
I’ll show you how to get from this:
project:
- config:
- __init__.py
- asgi.py
- settings.py
- urls.py
- wsgi.py
- manage.py
- Pipfile
- Pipfile.lock
to this:
{
"project": [
{
"config": [
"__init__.py",
"asgi.py",
"settings.py",
"urls.py",
"wsgi.py"
]
},
"manage.py",
"Pipfile",
"Pipfile.lock"
]
}
to this:
└── project/ ├── config/ │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py ├── Pipfile └── Pipfile.lock
This is the result of running the command
django-admin startproject config .
inside a directory called project.
I won’t go into too much detail about how the code for this works. There are plenty of great resources on how to pretty print directory trees.
- This Real Python article is very thorough if not a bit overkill.
- This Stack Overflow thread has a bunch of great information as well.
All I really did was rewrite the general function outline in TypeScript and adapt it to take in an object as input instead of a filepath.
Step One: Convert YAML String to JavaScript Object
I used the JavaScript package js-yaml to convert a YAML-formatted string to an object. Unfortunately, the load
function from this package returns an object of type unknown
so I had to define and use my own types.
import { load as loadYaml } from "js-yaml";
// union of all possible *individual* node types
type SingleNode = string | number | boolean | undefined | TreeNode;
// recursive type for tree of nodes
interface TreeNode {
[key: string]: SingleNode[] | null;
}
const yamlString = `
root:
- sub1:
- file.txt
- sub2:
- file.txt
`;
const yamlObj = loadYaml(yamlString) as TreeNode;
Step Two: Use Object to Create Tree Representation
This isn’t meant to be an introduction to recursive functions, so I won’t walk through each line of the code. Hopefully the included comments are enough for folks newer to recursion to follow along.
const createTree = (yamlObj: TreeNode): string => {
// "header" strings
const PIPE = "│ ";
const BLANK = " ";
// node "prefix" strings
const ELBOW = "└── ";
const TEE = "├── ";
const res: string[] = [];
// inner recursive function to build tree strings
const traverseTree = (node: SingleNode, header = "", last = true): void => {
// is the node an object?
if (typeof node === "object") {
// all object nodes should have a single key
for (const key in node) {
res.push(`${header}${last ? ELBOW : TEE}${key}/`);
const children = node[key]; // SingleNode[] | null
// check that children !== null
if (children) {
for (let i = 0; i < children.length; i++) {
const child = children[i];
// build up header string
const h = header + (last ? BLANK : PIPE);
// is this node the last child of a parent?
const l = i === children.length - 1;
traverseTree(child, h, l);
}
}
}
/**
* else if node is (a):
* - string
* - number
* - boolean
* - undefined
*/
} else {
res.push(`${header}${last ? ELBOW : TEE}${node}`);
}
}
traverseTree(yamlObj);
return res.join("\n");
};
This might save me some time in the long run, but honestly it was just fun to build. If you or a loved one ever wants to display a directory tree, consider using a free tool someone else already built!
View the MDX for this page or submit an issue if you noticed any errors!