Skip to content

🏠 Home | ⚙️ API Reference | 🌳 Project Tree

Project Tree API

Reference for the utility that exports a readable project tree for docs and onboarding flows.

ProjectTreeGenerator

ProjectTreeGenerator

Source code in src/codex_core/dev/project_tree.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
class ProjectTreeGenerator:
    DEFAULT_IGNORE_DIRS: frozenset[str] = frozenset(
        {
            ".git",
            ".github",
            "venv",
            ".venv",
            "__pycache__",
            ".idea",
            ".vscode",
            "data",
            "logs",
            ".pytest_cache",
            ".mypy_cache",
            ".ruff_cache",
            ".gemini",
            "node_modules",
            "site-packages",
            "site",
        }
    )
    DEFAULT_IGNORE_EXTENSIONS: frozenset[str] = frozenset(
        {
            ".pyc",
            ".png",
            ".jpg",
            ".jpeg",
            ".gif",
            ".svg",
            ".db",
            ".sqlite3",
            ".ico",
            ".woff",
            ".woff2",
        }
    )

    def __init__(
        self,
        root: Path,
        ignore_dirs: frozenset[str] | None = None,
        ignore_extensions: frozenset[str] | None = None,
    ) -> None:
        self.root = root.resolve()
        self.ignore_dirs = ignore_dirs or self.DEFAULT_IGNORE_DIRS
        self.ignore_extensions = ignore_extensions or self.DEFAULT_IGNORE_EXTENSIONS

    def _top_level_dirs(self) -> list[str]:
        return sorted(d for d in os.listdir(self.root) if os.path.isdir(self.root / d) and d not in self.ignore_dirs)

    def generate(self, target_dir: str | None, output: Path) -> None:
        """Write tree to output file.

        Args:
            target_dir: Sub-directory name to scan, or None for the full project.
            output: Path to the output .txt file.
        """
        start = self.root / target_dir if target_dir else self.root
        title = f"Project Structure: {target_dir or 'Full Project'}"

        with open(output, "w", encoding="utf-8") as f:
            f.write(f"{title}\n{'=' * len(title)}\n\n")

            for current_root, dirs, files in os.walk(start, topdown=True):
                dirs[:] = sorted(d for d in dirs if d not in self.ignore_dirs)

                rel = os.path.relpath(current_root, start)
                if rel == ".":
                    level = 0
                    name = os.path.basename(start)
                else:
                    level = rel.count(os.sep) + 1
                    name = os.path.basename(current_root)

                indent = "    " * level
                f.write(f"{indent}📂 {name}/\n")

                sub = "    " * (level + 1)
                for file in sorted(files):
                    if not any(file.endswith(ext) for ext in self.ignore_extensions):
                        f.write(f"{sub}📄 {file}\n")

    def interactive(self, output: Path | None = None) -> None:
        """Show interactive folder selection menu and generate tree."""
        output = output or (self.root / "project_structure.txt")
        top_dirs = self._top_level_dirs()

        print(f"\n🔍 Project root: {self.root}")
        print("Select scope:\n")
        print("   0. 🌳 Full project")
        for idx, folder in enumerate(top_dirs, 1):
            print(f"   {idx}. 📁 {folder}/")

        while True:
            try:
                choice = input(f"\nEnter number (0-{len(top_dirs)}): ").strip()
                if not choice.isdigit():
                    raise ValueError
                idx = int(choice)
                if 0 <= idx <= len(top_dirs):
                    break
                print("Invalid number, try again.")
            except ValueError:
                print("Enter a number.")

        target = top_dirs[idx - 1] if idx > 0 else None
        label = target or "Full project"
        print(f"\n⚙️  Generating: {label}...")
        self.generate(target, output)
        print(f"✅ Saved to: {output}")

Functions

generate(target_dir, output)

Write tree to output file.

Parameters:

Name Type Description Default
target_dir str | None

Sub-directory name to scan, or None for the full project.

required
output Path

Path to the output .txt file.

required
Source code in src/codex_core/dev/project_tree.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def generate(self, target_dir: str | None, output: Path) -> None:
    """Write tree to output file.

    Args:
        target_dir: Sub-directory name to scan, or None for the full project.
        output: Path to the output .txt file.
    """
    start = self.root / target_dir if target_dir else self.root
    title = f"Project Structure: {target_dir or 'Full Project'}"

    with open(output, "w", encoding="utf-8") as f:
        f.write(f"{title}\n{'=' * len(title)}\n\n")

        for current_root, dirs, files in os.walk(start, topdown=True):
            dirs[:] = sorted(d for d in dirs if d not in self.ignore_dirs)

            rel = os.path.relpath(current_root, start)
            if rel == ".":
                level = 0
                name = os.path.basename(start)
            else:
                level = rel.count(os.sep) + 1
                name = os.path.basename(current_root)

            indent = "    " * level
            f.write(f"{indent}📂 {name}/\n")

            sub = "    " * (level + 1)
            for file in sorted(files):
                if not any(file.endswith(ext) for ext in self.ignore_extensions):
                    f.write(f"{sub}📄 {file}\n")

interactive(output=None)

Show interactive folder selection menu and generate tree.

Source code in src/codex_core/dev/project_tree.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def interactive(self, output: Path | None = None) -> None:
    """Show interactive folder selection menu and generate tree."""
    output = output or (self.root / "project_structure.txt")
    top_dirs = self._top_level_dirs()

    print(f"\n🔍 Project root: {self.root}")
    print("Select scope:\n")
    print("   0. 🌳 Full project")
    for idx, folder in enumerate(top_dirs, 1):
        print(f"   {idx}. 📁 {folder}/")

    while True:
        try:
            choice = input(f"\nEnter number (0-{len(top_dirs)}): ").strip()
            if not choice.isdigit():
                raise ValueError
            idx = int(choice)
            if 0 <= idx <= len(top_dirs):
                break
            print("Invalid number, try again.")
        except ValueError:
            print("Enter a number.")

    target = top_dirs[idx - 1] if idx > 0 else None
    label = target or "Full project"
    print(f"\n⚙️  Generating: {label}...")
    self.generate(target, output)
    print(f"✅ Saved to: {output}")