Created
August 2, 2025 14:32
-
-
Save fdmysterious/65454f1b087e8db7743ff134dd962a64 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| Quick, dirty and weird script to print a file tree with a separator to write doc after it | |
| - Florian Dupeyron ([email protected]) | |
| - August 2025 | |
| Change the TREE variable to change the file tree to display | |
| Example output tree: | |
| -------------------- | |
| <root> ∙ (you can add your documentation here) | |
| ├─ docs ∙ | |
| ├─ web/ ∙ | |
| │ ├─ landing-page/ ∙ | |
| │ └─ documentation/ ∙ | |
| │ ∙ | |
| ├─ platform/ ∙ | |
| │ ├─ observability/ → compose.yml ∙ | |
| │ ├─ proxy/ → compose.yml ∙ | |
| │ ├─ websrv/ → compose.yml ∙ | |
| │ ├─ analytics/ → compose.yml ∙ | |
| │ │ ∙ | |
| │ ├─ Justfile ∙ | |
| │ └─ compose.yml ∙ | |
| │ ∙ | |
| └─ Justfile ∙ | |
| """ | |
| from __future__ import annotations | |
| ########################################### | |
| INDENT_AMT = 1 # Indentation space count, change how long is bar before file name | |
| PADDING_AMT = 0 # Number of skip lines between each level | |
| OFFSET_TXT = 1 # Offset between the item name and start of next arrow | |
| TOTAL_LENGTH = 38 # Total length before right bar | |
| ########################################### | |
| def print_indent(lvlstack: tuple[bool]) -> str: | |
| it_lvl = iter(lvlstack) | |
| try: | |
| next(it_lvl) | |
| except StopIteration: | |
| pass | |
| s_indent = (" " * OFFSET_TXT if lvlstack else "") + "".join( | |
| ("│" if not x else " ") + (" " * (INDENT_AMT + OFFSET_TXT)) | |
| for x in it_lvl | |
| ) | |
| return s_indent | |
| def print_item_txt(lvlstack: tuple[bool], leaf: bool, txt: str) -> str: | |
| # Print start of line | |
| if txt: | |
| s_start = ("├" + "─" * INDENT_AMT + " " if not leaf else "└" + "─" * INDENT_AMT + " ") if lvlstack else "" | |
| else: | |
| s_start = "" if leaf else "│" | |
| s_indent = print_indent(lvlstack) | |
| s_line = f"{s_indent}{s_start}{txt}" | |
| l_line = len(s_line) | |
| if l_line < TOTAL_LENGTH: | |
| s_line += " " * (TOTAL_LENGTH - l_line) | |
| s_line += " ∙ " | |
| return s_line | |
| def iterate_last(x): | |
| """Utility iterator that returns all items in an iterator with a boolean | |
| that indicates if it's the last element | |
| """ | |
| try: | |
| cur = next(x) # Can throw StopIteration if empty | |
| try: | |
| while True: | |
| nxt = next(x) | |
| yield cur, False | |
| cur = nxt | |
| except StopIteration: | |
| yield cur, True | |
| except StopIteration: | |
| pass | |
| def process_tree(tree, lvlstack: tuple[bool] = None, leaf: bool = True) -> str: | |
| lvlstack = lvlstack or tuple() | |
| if isinstance(tree, str): | |
| item_name, item_children = tree, None | |
| else: | |
| item_name, item_children = tree | |
| s_start = print_item_txt(lvlstack, leaf, item_name) | |
| if item_children: | |
| s_content = "\n" + "\n".join(map( | |
| lambda x: process_tree(x[0], lvlstack + (leaf,), x[1]), | |
| iterate_last(iter(item_children)) | |
| )) | |
| else: | |
| s_content = "" | |
| if leaf and lvlstack: | |
| s_content += ("\n" + print_indent(lvlstack)) * PADDING_AMT | |
| return s_start + s_content | |
| ########################################### | |
| TREE = ("<root>", ( | |
| "docs", | |
| ("web/", ( | |
| "landing-page/", | |
| "documentation/" | |
| )), | |
| "", | |
| ("platform/", ( | |
| "observability/ → compose.yml", | |
| "proxy/ → compose.yml", | |
| "websrv/ → compose.yml", | |
| "analytics/ → compose.yml", | |
| "", | |
| "Justfile", | |
| "compose.yml", | |
| )), | |
| "", | |
| "Justfile", | |
| )) | |
| print(process_tree(iter(TREE))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment