folderTree-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- folderTree/__init__.py +0 -0
- folderTree/__main__.py +5 -0
- folderTree/analyzers/__init__.py +0 -0
- folderTree/analyzers/languages.py +65 -0
- folderTree/analyzers/statistics.py +70 -0
- folderTree/cli/__init__.py +0 -0
- folderTree/cli/main.py +66 -0
- folderTree/cli/parser.py +35 -0
- folderTree/config.py +17 -0
- folderTree/exporters/__init__.py +0 -0
- folderTree/exporters/markdown.py +27 -0
- folderTree/exporters/txt.py +28 -0
- folderTree/generators/__init__.py +0 -0
- folderTree/generators/documentation.py +116 -0
- folderTree/generators/tree.py +76 -0
- folderTree/models/__init__.py +11 -0
- folderTree/models/file.py +56 -0
- folderTree/models/folder.py +47 -0
- folderTree/models/metadata.py +13 -0
- folderTree/models/project.py +18 -0
- folderTree/scanner/__init__.py +0 -0
- folderTree/scanner/ignore.py +37 -0
- folderTree/scanner/metadata_collector.py +20 -0
- folderTree/scanner/scanner.py +111 -0
- folderTree/utils/__init__.py +0 -0
- folderTree/utils/constants.py +68 -0
- folderTree/utils/formatter.py +53 -0
- folderTree/utils/helpers.py +41 -0
- foldertree_cli-0.1.0.dist-info/METADATA +203 -0
- foldertree_cli-0.1.0.dist-info/RECORD +34 -0
- foldertree_cli-0.1.0.dist-info/WHEEL +5 -0
- foldertree_cli-0.1.0.dist-info/entry_points.txt +2 -0
- foldertree_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- foldertree_cli-0.1.0.dist-info/top_level.txt +1 -0
folderTree/__init__.py
ADDED
|
File without changes
|
folderTree/__main__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
from folderTree.models import Folder, Project
|
|
6
|
+
from folderTree.utils.constants import EXTENSION_TO_LANGUAGE
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(slots=True)
|
|
10
|
+
class LanguageStatistics:
|
|
11
|
+
"""
|
|
12
|
+
Stores language statistics for a project.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
language_counts: dict[str, int] = field(default_factory=dict)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LanguageAnalyzer:
|
|
19
|
+
"""
|
|
20
|
+
Analyzes programming languages used in a project.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def analyze(self, project: Project) -> LanguageStatistics:
|
|
24
|
+
"""
|
|
25
|
+
Analyze the project and return language statistics.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
statistics = LanguageStatistics()
|
|
29
|
+
|
|
30
|
+
self._walk_folder(
|
|
31
|
+
project.root_folder,
|
|
32
|
+
statistics,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return statistics
|
|
36
|
+
|
|
37
|
+
def _walk_folder(
|
|
38
|
+
self,
|
|
39
|
+
folder: Folder,
|
|
40
|
+
statistics: LanguageStatistics,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Recursively walk through folders.
|
|
44
|
+
"""
|
|
45
|
+
# Process files in the current folder
|
|
46
|
+
for file in folder.files:
|
|
47
|
+
|
|
48
|
+
language = EXTENSION_TO_LANGUAGE.get(
|
|
49
|
+
file.extension.lower(),
|
|
50
|
+
"Unknown",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
file.language = language
|
|
54
|
+
|
|
55
|
+
statistics.language_counts[language] = (
|
|
56
|
+
statistics.language_counts.get(language, 0) + 1
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Recursively process subfolders
|
|
60
|
+
for subfolder in folder.folders:
|
|
61
|
+
|
|
62
|
+
self._walk_folder(
|
|
63
|
+
subfolder,
|
|
64
|
+
statistics,
|
|
65
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
from folderTree.models import Folder, Project
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(slots=True)
|
|
9
|
+
class Statistics:
|
|
10
|
+
"""
|
|
11
|
+
Stores statistics about a scanned project.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
total_files: int = 0
|
|
15
|
+
total_folders: int = 0
|
|
16
|
+
total_size: int = 0
|
|
17
|
+
|
|
18
|
+
extension_counts: dict[str, int] = field(default_factory=dict)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class StatisticsAnalyzer:
|
|
22
|
+
"""
|
|
23
|
+
Analyzes a Project and generates statistics.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def analyze(self, project: Project) -> Statistics:
|
|
27
|
+
"""
|
|
28
|
+
Analyze a project and return statistics.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
statistics = Statistics()
|
|
32
|
+
|
|
33
|
+
self._walk_folder(
|
|
34
|
+
project.root_folder,
|
|
35
|
+
statistics,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return statistics
|
|
39
|
+
|
|
40
|
+
def _walk_folder(
|
|
41
|
+
self,
|
|
42
|
+
folder: Folder,
|
|
43
|
+
statistics: Statistics,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Recursively walk through folders and collect statistics.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# Count the current folder
|
|
50
|
+
statistics.total_folders += 1
|
|
51
|
+
|
|
52
|
+
# Process files in the current folder
|
|
53
|
+
for file in folder.files:
|
|
54
|
+
|
|
55
|
+
statistics.total_files += 1
|
|
56
|
+
|
|
57
|
+
statistics.total_size += file.size
|
|
58
|
+
|
|
59
|
+
extension = file.extension.lower()
|
|
60
|
+
|
|
61
|
+
statistics.extension_counts[extension] = (
|
|
62
|
+
statistics.extension_counts.get(extension, 0) + 1
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Recursively process subfolders
|
|
66
|
+
for subfolder in folder.folders:
|
|
67
|
+
self._walk_folder(
|
|
68
|
+
subfolder,
|
|
69
|
+
statistics,
|
|
70
|
+
)
|
|
File without changes
|
folderTree/cli/main.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from folderTree.exporters.markdown import MarkdownExporter
|
|
4
|
+
from folderTree.exporters.txt import TXTExporter
|
|
5
|
+
from folderTree.generators.documentation import DocumentationGenerator
|
|
6
|
+
from folderTree.scanner.scanner import ProjectScanner
|
|
7
|
+
from folderTree.cli.parser import create_parser
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main() -> None:
|
|
11
|
+
"""
|
|
12
|
+
Entry point for the FolderTree CLI.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
parser = create_parser()
|
|
16
|
+
|
|
17
|
+
args = parser.parse_args()
|
|
18
|
+
|
|
19
|
+
if args.command == "scan":
|
|
20
|
+
scan(Path(args.path), args.output)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def scan(
|
|
24
|
+
project_path: Path,
|
|
25
|
+
output: str | None,
|
|
26
|
+
) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Scan a project and generate documentation.
|
|
29
|
+
"""
|
|
30
|
+
scanner = ProjectScanner()
|
|
31
|
+
|
|
32
|
+
project = scanner.scan(project_path)
|
|
33
|
+
|
|
34
|
+
generator = DocumentationGenerator()
|
|
35
|
+
|
|
36
|
+
documentation = generator.generate(project)
|
|
37
|
+
|
|
38
|
+
if output is None:
|
|
39
|
+
print(documentation)
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
output_path = Path(output)
|
|
43
|
+
|
|
44
|
+
suffix = output_path.suffix.lower()
|
|
45
|
+
|
|
46
|
+
if suffix == ".md":
|
|
47
|
+
exporter = MarkdownExporter()
|
|
48
|
+
|
|
49
|
+
elif suffix == ".txt":
|
|
50
|
+
exporter = TXTExporter()
|
|
51
|
+
|
|
52
|
+
else:
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"Unsupported output format: {suffix}"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
exporter.export(
|
|
58
|
+
documentation,
|
|
59
|
+
output_path,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
print("✓ Documentation generated successfully.")
|
|
63
|
+
print(f"Output: {output_path}")
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
main()
|
folderTree/cli/parser.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from argparse import ArgumentParser
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def create_parser() -> ArgumentParser:
|
|
5
|
+
"""
|
|
6
|
+
Create and configure the FolderTree CLI parser.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
parser = ArgumentParser(
|
|
10
|
+
prog="folderTree",
|
|
11
|
+
description="Generate project documentation.",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
subparsers = parser.add_subparsers(
|
|
15
|
+
dest="command",
|
|
16
|
+
required=True,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
scan_parser = subparsers.add_parser(
|
|
20
|
+
"scan",
|
|
21
|
+
help="Scan a project directory.",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
scan_parser.add_argument(
|
|
25
|
+
"path",
|
|
26
|
+
help="Path to the project directory.",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
scan_parser.add_argument(
|
|
30
|
+
"-o",
|
|
31
|
+
"--output",
|
|
32
|
+
help="Output file (.md or .txt).",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return parser
|
folderTree/config.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
from folderTree.utils.constants import DEFAULT_IGNORES
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(slots=True)
|
|
7
|
+
class Config:
|
|
8
|
+
"""
|
|
9
|
+
Runtime configuration for FolderTree.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
max_depth: int | None = None
|
|
13
|
+
show_hidden: bool = False
|
|
14
|
+
follow_symlinks: bool = False
|
|
15
|
+
ignore_patterns: set[str] = field(
|
|
16
|
+
default_factory=lambda: set(DEFAULT_IGNORES)
|
|
17
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MarkdownExporter:
|
|
5
|
+
"""
|
|
6
|
+
Exports documentation as a Markdown file.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def export(
|
|
10
|
+
self,
|
|
11
|
+
content: str,
|
|
12
|
+
output_path: Path,
|
|
13
|
+
) -> Path:
|
|
14
|
+
"""
|
|
15
|
+
Write Markdown content to a file.
|
|
16
|
+
"""
|
|
17
|
+
output_path.parent.mkdir(
|
|
18
|
+
parents=True,
|
|
19
|
+
exist_ok=True,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
output_path.write_text(
|
|
23
|
+
content,
|
|
24
|
+
encoding="utf-8",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
return output_path
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class TXTExporter:
|
|
5
|
+
"""
|
|
6
|
+
Exports text content to a .txt file.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def export(
|
|
10
|
+
self,
|
|
11
|
+
content: str,
|
|
12
|
+
output_path: Path,
|
|
13
|
+
) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Write text content to the given file.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# Create parent directories if they don't exist
|
|
19
|
+
output_path.parent.mkdir(
|
|
20
|
+
parents=True,
|
|
21
|
+
exist_ok=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Write the content to the file
|
|
25
|
+
output_path.write_text(
|
|
26
|
+
content,
|
|
27
|
+
encoding="utf-8",
|
|
28
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from folderTree.utils.formatter import Formatter
|
|
4
|
+
from folderTree.analyzers.languages import LanguageAnalyzer
|
|
5
|
+
from folderTree.analyzers.statistics import StatisticsAnalyzer
|
|
6
|
+
from folderTree.generators.tree import TreeGenerator
|
|
7
|
+
from folderTree.models import Project
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DocumentationGenerator:
|
|
11
|
+
"""
|
|
12
|
+
Generates Markdown documentation for a project.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.tree_generator = TreeGenerator()
|
|
17
|
+
self.statistics_analyzer = StatisticsAnalyzer()
|
|
18
|
+
self.language_analyzer = LanguageAnalyzer()
|
|
19
|
+
|
|
20
|
+
def generate(
|
|
21
|
+
self,
|
|
22
|
+
project: Project,
|
|
23
|
+
) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Generate complete project documentation.
|
|
26
|
+
"""
|
|
27
|
+
return "\n\n".join(
|
|
28
|
+
[
|
|
29
|
+
"# Project Documentation",
|
|
30
|
+
"",
|
|
31
|
+
self._project_information(project),
|
|
32
|
+
self._statistics_section(project),
|
|
33
|
+
self._languages_section(project),
|
|
34
|
+
self._tree_section(project),
|
|
35
|
+
]
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def _project_information(
|
|
39
|
+
self,
|
|
40
|
+
project: Project,
|
|
41
|
+
) -> str:
|
|
42
|
+
"""
|
|
43
|
+
Generate the project information section.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
metadata = project.root_folder.metadata
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
"## Project Information\n\n"
|
|
50
|
+
f"- **Project Name:** {project.name}\n"
|
|
51
|
+
f"- **Project Location:** {project.root_path.resolve()}\n"
|
|
52
|
+
f"- **Project Created:** "
|
|
53
|
+
f"{Formatter.format_datetime(metadata.created_at)}\n"
|
|
54
|
+
f"- **Last Modified:** "
|
|
55
|
+
f"{Formatter.format_datetime(metadata.modified_at)}\n"
|
|
56
|
+
f"- **Documentation Generated:** "
|
|
57
|
+
f"{Formatter.format_datetime(datetime.now())}\n"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def _statistics_section(
|
|
61
|
+
self,
|
|
62
|
+
project: Project,
|
|
63
|
+
) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Generate the statistics section.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
statistics = self.statistics_analyzer.analyze(project)
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
"## Statistics\n\n"
|
|
72
|
+
f"- **Total Files:** {statistics.total_files}\n"
|
|
73
|
+
f"- **Total Folders:** {statistics.total_folders}\n"
|
|
74
|
+
f"- **Total Size:** {Formatter.format_bytes(statistics.total_size)}\n"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def _languages_section(
|
|
78
|
+
self,
|
|
79
|
+
project: Project,
|
|
80
|
+
) -> str:
|
|
81
|
+
"""
|
|
82
|
+
Generate the languages section.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
statistics = self.language_analyzer.analyze(project)
|
|
86
|
+
|
|
87
|
+
lines = [
|
|
88
|
+
"## Languages",
|
|
89
|
+
"",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
for language, count in sorted(
|
|
93
|
+
statistics.language_counts.items()
|
|
94
|
+
):
|
|
95
|
+
lines.append(
|
|
96
|
+
f"- **{language}:** {count}"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return "\n".join(lines)
|
|
100
|
+
|
|
101
|
+
def _tree_section(
|
|
102
|
+
self,
|
|
103
|
+
project: Project,
|
|
104
|
+
) -> str:
|
|
105
|
+
"""
|
|
106
|
+
Generate the folder tree section.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
tree = self.tree_generator.generate(project)
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
"## Folder Structure\n\n"
|
|
113
|
+
"```text\n"
|
|
114
|
+
f"{tree}\n"
|
|
115
|
+
"```\n"
|
|
116
|
+
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from folderTree.models import Project, Folder
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TreeGenerator:
|
|
7
|
+
"""
|
|
8
|
+
Generates a tree representation from a Project model.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def generate(self, project: Project) -> str:
|
|
12
|
+
"""
|
|
13
|
+
Generate a tree string from a project.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
lines: list[str] = []
|
|
17
|
+
|
|
18
|
+
lines.append(f"{project.name}/")
|
|
19
|
+
|
|
20
|
+
self._build_tree(
|
|
21
|
+
folder=project.root_folder,
|
|
22
|
+
prefix="",
|
|
23
|
+
lines=lines,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
return "\n".join(lines)
|
|
27
|
+
|
|
28
|
+
def _build_tree(
|
|
29
|
+
self,
|
|
30
|
+
folder: Folder,
|
|
31
|
+
prefix: str,
|
|
32
|
+
lines: list[str],
|
|
33
|
+
) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Recursively build tree lines from a folder.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
entries = sorted(
|
|
39
|
+
[
|
|
40
|
+
*folder.folders,
|
|
41
|
+
*folder.files
|
|
42
|
+
],
|
|
43
|
+
key = lambda entry: (
|
|
44
|
+
not isinstance(entry, Folder),
|
|
45
|
+
entry.name.lower(),
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
for index, entry in enumerate(entries):
|
|
50
|
+
|
|
51
|
+
is_last = index == len(entries) - 1
|
|
52
|
+
|
|
53
|
+
if is_last:
|
|
54
|
+
connector = "└── "
|
|
55
|
+
next_prefix = prefix + " "
|
|
56
|
+
else:
|
|
57
|
+
connector = "├── "
|
|
58
|
+
next_prefix = prefix + "│ "
|
|
59
|
+
|
|
60
|
+
if isinstance(entry, Folder):
|
|
61
|
+
|
|
62
|
+
lines.append(
|
|
63
|
+
f"{prefix}{connector}{entry.name}/"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
self._build_tree(
|
|
67
|
+
folder=entry,
|
|
68
|
+
prefix=next_prefix,
|
|
69
|
+
lines=lines,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
else:
|
|
73
|
+
|
|
74
|
+
lines.append(
|
|
75
|
+
f"{prefix}{connector}{entry.name}"
|
|
76
|
+
)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .metadata import Metadata
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(slots=True)
|
|
11
|
+
class File:
|
|
12
|
+
"""
|
|
13
|
+
Represents a file in the scanned project.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
name: str
|
|
17
|
+
path: Path
|
|
18
|
+
extension: str
|
|
19
|
+
metadata: Metadata
|
|
20
|
+
|
|
21
|
+
language: str | None = None
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def size(self) -> int:
|
|
25
|
+
"""
|
|
26
|
+
Returns the file size in bytes.
|
|
27
|
+
"""
|
|
28
|
+
return self.metadata.size
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def created_at(self) -> datetime:
|
|
32
|
+
"""
|
|
33
|
+
Returns the file creation timestamp.
|
|
34
|
+
"""
|
|
35
|
+
return self.metadata.created_at
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def modified_at(self) -> datetime:
|
|
39
|
+
"""
|
|
40
|
+
Returns the file's last modification timestamp.
|
|
41
|
+
"""
|
|
42
|
+
return self.metadata.modified_at
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def size_kb(self) -> float:
|
|
46
|
+
"""
|
|
47
|
+
Returns the file size in kilobytes.
|
|
48
|
+
"""
|
|
49
|
+
return self.size / 1024
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def size_mb(self) -> float:
|
|
53
|
+
"""
|
|
54
|
+
Returns the file size in megabytes.
|
|
55
|
+
"""
|
|
56
|
+
return self.size / (1024 * 1024)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .file import File
|
|
7
|
+
from .metadata import Metadata
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(slots=True)
|
|
11
|
+
class Folder:
|
|
12
|
+
"""
|
|
13
|
+
Represents a folder in the scanned project.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
name: str
|
|
17
|
+
path: Path
|
|
18
|
+
metadata: Metadata
|
|
19
|
+
|
|
20
|
+
files: list[File] = field(default_factory=list)
|
|
21
|
+
folders: list["Folder"] = field(default_factory=list)
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def file_count(self) -> int:
|
|
25
|
+
"""
|
|
26
|
+
Returns the number of files directly inside this folder.
|
|
27
|
+
"""
|
|
28
|
+
return len(self.files)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def folder_count(self) -> int:
|
|
32
|
+
"""
|
|
33
|
+
Returns the number of immediate subfolders.
|
|
34
|
+
"""
|
|
35
|
+
return len(self.folders)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def total_size(self) -> int:
|
|
39
|
+
"""
|
|
40
|
+
Returns the total size of this folder recursively.
|
|
41
|
+
"""
|
|
42
|
+
size = sum(file.size for file in self.files)
|
|
43
|
+
|
|
44
|
+
for folder in self.folders:
|
|
45
|
+
size += folder.total_size
|
|
46
|
+
|
|
47
|
+
return size
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from .folder import Folder
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(slots=True)
|
|
9
|
+
class Project:
|
|
10
|
+
"""
|
|
11
|
+
Represents the scanned project.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
name: str
|
|
15
|
+
root_path: Path
|
|
16
|
+
root_folder: Folder
|
|
17
|
+
|
|
18
|
+
scanned_at: datetime = field(default_factory=datetime.now)
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from fnmatch import fnmatch
|
|
3
|
+
|
|
4
|
+
from folderTree.config import Config
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class IgnoreEngine:
|
|
8
|
+
"""
|
|
9
|
+
Handles all ignore logic for files and directories.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, config: Config):
|
|
13
|
+
self.config = config
|
|
14
|
+
|
|
15
|
+
def should_ignore(self, path: Path) -> bool:
|
|
16
|
+
if not self.config.show_hidden and self.is_hidden(path):
|
|
17
|
+
return True
|
|
18
|
+
|
|
19
|
+
if self.matches_ignore_pattern(path):
|
|
20
|
+
return True
|
|
21
|
+
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def matches_ignore_pattern(self, path: Path) -> bool:
|
|
26
|
+
for pattern in self.config.ignore_patterns:
|
|
27
|
+
if fnmatch(path.name, pattern):
|
|
28
|
+
return True
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def is_hidden(path: Path) -> bool:
|
|
34
|
+
"""
|
|
35
|
+
Return True if the path is hidden.
|
|
36
|
+
"""
|
|
37
|
+
return path.name.startswith(".")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from folderTree.models import Metadata
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MetadataCollector:
|
|
8
|
+
"""
|
|
9
|
+
Collects metadata from filesystem paths.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def collect(path: Path) -> Metadata:
|
|
14
|
+
stats = path.stat()
|
|
15
|
+
|
|
16
|
+
return Metadata(
|
|
17
|
+
size=stats.st_size,
|
|
18
|
+
created_at=datetime.fromtimestamp(stats.st_ctime),
|
|
19
|
+
modified_at=datetime.fromtimestamp(stats.st_mtime),
|
|
20
|
+
)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from folderTree.config import Config
|
|
4
|
+
from folderTree.models import File, Folder, Project
|
|
5
|
+
from folderTree.scanner.ignore import IgnoreEngine
|
|
6
|
+
from folderTree.scanner.metadata_collector import MetadataCollector
|
|
7
|
+
from folderTree.utils.helpers import validate_path, get_extension
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ProjectScanner:
|
|
11
|
+
"""
|
|
12
|
+
Scans a directory and builds a Project model.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, config: Config | None = None):
|
|
16
|
+
self.config = config or Config()
|
|
17
|
+
self.ignore_engine = IgnoreEngine(self.config)
|
|
18
|
+
self.metadata_collector = MetadataCollector()
|
|
19
|
+
|
|
20
|
+
def scan(self, path: str | Path) -> Project:
|
|
21
|
+
"""
|
|
22
|
+
Scan the given project directory.
|
|
23
|
+
"""
|
|
24
|
+
root_path = validate_path(path)
|
|
25
|
+
|
|
26
|
+
if not root_path.is_dir():
|
|
27
|
+
raise NotADirectoryError(f"{root_path} is not a directory.")
|
|
28
|
+
|
|
29
|
+
root_folder = self._scan_folder(root_path)
|
|
30
|
+
|
|
31
|
+
return Project(
|
|
32
|
+
name=root_path.name,
|
|
33
|
+
root_path=root_path,
|
|
34
|
+
root_folder=root_folder,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def _scan_folder(self, path: Path, depth: int = 0) -> Folder:
|
|
38
|
+
"""
|
|
39
|
+
Recursively scan a folder and return a Folder model.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
# Stop recursion if max depth is reached
|
|
43
|
+
if (
|
|
44
|
+
self.config.max_depth is not None
|
|
45
|
+
and depth > self.config.max_depth
|
|
46
|
+
):
|
|
47
|
+
metadata = self.metadata_collector.collect(path)
|
|
48
|
+
|
|
49
|
+
return Folder(
|
|
50
|
+
name=path.name,
|
|
51
|
+
path=path,
|
|
52
|
+
metadata=metadata,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
metadata = self.metadata_collector.collect(path)
|
|
56
|
+
|
|
57
|
+
folder = Folder(
|
|
58
|
+
name=path.name,
|
|
59
|
+
path=path,
|
|
60
|
+
metadata=metadata,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
entries = sorted(
|
|
65
|
+
path.iterdir(),
|
|
66
|
+
key=lambda entry: (entry.is_file(), entry.name.lower())
|
|
67
|
+
)
|
|
68
|
+
except (PermissionError, FileNotFoundError, OSError):
|
|
69
|
+
return folder
|
|
70
|
+
|
|
71
|
+
for entry in entries:
|
|
72
|
+
|
|
73
|
+
if self.ignore_engine.should_ignore(entry):
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
if entry.is_dir():
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
subfolder = self._scan_folder(
|
|
80
|
+
entry,
|
|
81
|
+
depth + 1,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
folder.folders.append(subfolder)
|
|
85
|
+
except (PermissionError, FileNotFoundError, OSError):
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
elif entry.is_file():
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
file = self._create_file(entry)
|
|
92
|
+
|
|
93
|
+
folder.files.append(file)
|
|
94
|
+
except (PermissionError, FileNotFoundError, OSError):
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
return folder
|
|
98
|
+
|
|
99
|
+
def _create_file(self, path: Path) -> File:
|
|
100
|
+
"""
|
|
101
|
+
Create a File model from a filesystem path.
|
|
102
|
+
"""
|
|
103
|
+
metadata = self.metadata_collector.collect(path)
|
|
104
|
+
|
|
105
|
+
return File(
|
|
106
|
+
name = path.name,
|
|
107
|
+
path = path,
|
|
108
|
+
extension = get_extension(path),
|
|
109
|
+
metadata = metadata,
|
|
110
|
+
language = None
|
|
111
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Application constants.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
APP_NAME = "FolderTree"
|
|
6
|
+
VERSION = "0.1.0"
|
|
7
|
+
|
|
8
|
+
TREE_BRANCH = "├── "
|
|
9
|
+
TREE_LAST_BRANCH = "└── "
|
|
10
|
+
TREE_PIPE = "│ "
|
|
11
|
+
TREE_SPACE = " "
|
|
12
|
+
|
|
13
|
+
DEFAULT_IGNORES = {
|
|
14
|
+
".git",
|
|
15
|
+
".github",
|
|
16
|
+
".venv",
|
|
17
|
+
"venv",
|
|
18
|
+
"env",
|
|
19
|
+
"test_env",
|
|
20
|
+
"test_venv",
|
|
21
|
+
"build",
|
|
22
|
+
"dist",
|
|
23
|
+
"*.egg-info",
|
|
24
|
+
"__pycache__",
|
|
25
|
+
".pytest_cache",
|
|
26
|
+
".mypy_cache",
|
|
27
|
+
".idea",
|
|
28
|
+
".vscode",
|
|
29
|
+
"node_modules",
|
|
30
|
+
".DS_Store",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
EXTENSION_TO_LANGUAGE = {
|
|
34
|
+
".py": "Python",
|
|
35
|
+
".js": "JavaScript",
|
|
36
|
+
".ts": "TypeScript",
|
|
37
|
+
".java": "Java",
|
|
38
|
+
".c": "C",
|
|
39
|
+
".cpp": "C++",
|
|
40
|
+
".cs": "C#",
|
|
41
|
+
".go": "Go",
|
|
42
|
+
".rs": "Rust",
|
|
43
|
+
".php": "PHP",
|
|
44
|
+
".rb": "Ruby",
|
|
45
|
+
|
|
46
|
+
".html": "HTML",
|
|
47
|
+
".css": "CSS",
|
|
48
|
+
".scss": "SCSS",
|
|
49
|
+
|
|
50
|
+
".json": "JSON",
|
|
51
|
+
".xml": "XML",
|
|
52
|
+
".yaml": "YAML",
|
|
53
|
+
".yml": "YAML",
|
|
54
|
+
".toml": "TOML",
|
|
55
|
+
".ini": "INI",
|
|
56
|
+
|
|
57
|
+
".md": "Markdown",
|
|
58
|
+
".txt": "Text",
|
|
59
|
+
|
|
60
|
+
".sh": "Shell",
|
|
61
|
+
".bat": "Batch",
|
|
62
|
+
".ps1": "PowerShell",
|
|
63
|
+
|
|
64
|
+
".sql": "SQL",
|
|
65
|
+
|
|
66
|
+
".dockerfile": "Docker",
|
|
67
|
+
".env": "Environment",
|
|
68
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Formatter:
|
|
7
|
+
"""
|
|
8
|
+
Utility class for formatting values for output.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def format_bytes(size: int) -> str:
|
|
13
|
+
"""
|
|
14
|
+
Format bytes into a human-readable string.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
units = ["bytes", "KB", "MB", "GB", "TB"]
|
|
18
|
+
|
|
19
|
+
value = float(size)
|
|
20
|
+
|
|
21
|
+
for unit in units:
|
|
22
|
+
|
|
23
|
+
if value < 1024 or unit == units[-1]:
|
|
24
|
+
if unit == "bytes":
|
|
25
|
+
return f"{int(value)} {unit}"
|
|
26
|
+
|
|
27
|
+
return f"{value:.2f} {unit}"
|
|
28
|
+
|
|
29
|
+
value /= 1024
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def format_datetime(value: datetime) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Format a datetime object.
|
|
35
|
+
"""
|
|
36
|
+
return value.strftime("%Y-%m-%d %H:%M:%S")
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def heading(
|
|
40
|
+
text: str,
|
|
41
|
+
level: int = 1,
|
|
42
|
+
) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Return a Markdown heading.
|
|
45
|
+
"""
|
|
46
|
+
return f'{"#" * level} {text}'
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def bullet(text: str) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Return a Markdown bullet.
|
|
52
|
+
"""
|
|
53
|
+
return f"- {text}"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def validate_path(path: str | Path) -> Path:
|
|
5
|
+
"""
|
|
6
|
+
Validate that a path exists and return it as a resolved Path object.
|
|
7
|
+
"""
|
|
8
|
+
path = Path(path).expanduser().resolve()
|
|
9
|
+
|
|
10
|
+
if not path.exists():
|
|
11
|
+
raise FileNotFoundError(f"Path does not exist: {path}")
|
|
12
|
+
|
|
13
|
+
return path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def is_hidden(path: Path) -> bool:
|
|
17
|
+
"""
|
|
18
|
+
Return True if the file or folder is hidden.
|
|
19
|
+
"""
|
|
20
|
+
return path.name.startswith(".")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_extension(path: Path) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Return the file extension in lowercase.
|
|
26
|
+
"""
|
|
27
|
+
return path.suffix.lower()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def format_size(size: int) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Convert bytes into a human-readable string.
|
|
33
|
+
"""
|
|
34
|
+
units = ["B", "KB", "MB", "GB", "TB"]
|
|
35
|
+
|
|
36
|
+
value = float(size)
|
|
37
|
+
|
|
38
|
+
for unit in units:
|
|
39
|
+
if value < 1024 or unit == units[-1]:
|
|
40
|
+
return f"{value:.2f} {unit}"
|
|
41
|
+
value /= 1024
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: folderTree-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate beautiful folder tree structures and project documentation.
|
|
5
|
+
Author: Kaushal Kumar Jha
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Nikk-hub-code/folderTree
|
|
8
|
+
Project-URL: Repository, https://github.com/Nikk-hub-code/folderTree
|
|
9
|
+
Project-URL: Issues, https://github.com/Nikk-hub-code/folderTree/issues
|
|
10
|
+
Keywords: folder-tree,tree,documentation,cli,filesystem,project,directory,python
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# FolderTree
|
|
27
|
+
|
|
28
|
+
A lightweight command-line tool that automatically analyzes a project directory and generates clean, well-structured project documentation.
|
|
29
|
+
|
|
30
|
+
FolderTree scans your project, collects metadata, analyzes project statistics, detects programming languages, generates a folder tree, and exports documentation in Markdown or TXT format.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- 📁 Scan any project directory
|
|
37
|
+
- 🌳 Generate a beautiful folder tree
|
|
38
|
+
- 📊 Analyze project statistics
|
|
39
|
+
- 💻 Detect programming languages
|
|
40
|
+
- 📝 Generate project documentation
|
|
41
|
+
- 📄 Export documentation as Markdown or TXT
|
|
42
|
+
- ⚡ Fast and simple CLI interface
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Requirements
|
|
47
|
+
|
|
48
|
+
- Python **3.11** or later
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install folderTree
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
After installation, the `folderTree` command will be available globally.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
### Show available commands
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
folderTree --help
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### Generate documentation in the terminal
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
folderTree scan .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
or
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
folderTree scan path/to/project
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Example
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
folderTree scan sample_projects/python_project
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### Export as Markdown
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
folderTree scan . -o PROJECT_DOCUMENTATION.md
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### Export as TXT
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
folderTree scan . -o PROJECT_DOCUMENTATION.txt
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Example Output
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
# Project Documentation
|
|
112
|
+
|
|
113
|
+
## Project Information
|
|
114
|
+
|
|
115
|
+
- Project Name: folderTree
|
|
116
|
+
- Project Location: D:\Projects\folderTree
|
|
117
|
+
- Project Created: 2026-07-10
|
|
118
|
+
- Documentation Generated: 2026-07-19
|
|
119
|
+
|
|
120
|
+
## Statistics
|
|
121
|
+
|
|
122
|
+
- Total Files: 59
|
|
123
|
+
- Total Folders: 22
|
|
124
|
+
- Total Size: 41.94 KB
|
|
125
|
+
|
|
126
|
+
## Languages
|
|
127
|
+
|
|
128
|
+
- Python: 47
|
|
129
|
+
- Markdown: 4
|
|
130
|
+
- TOML: 1
|
|
131
|
+
- HTML: 1
|
|
132
|
+
- CSS: 1
|
|
133
|
+
- JavaScript: 1
|
|
134
|
+
|
|
135
|
+
## Folder Structure
|
|
136
|
+
|
|
137
|
+
folderTree/
|
|
138
|
+
├── src/
|
|
139
|
+
│ └── folderTree/
|
|
140
|
+
├── tests/
|
|
141
|
+
├── README.md
|
|
142
|
+
├── LICENSE
|
|
143
|
+
└── pyproject.toml
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Project Structure
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
folderTree/
|
|
152
|
+
│
|
|
153
|
+
├── src/
|
|
154
|
+
│ └── folderTree/
|
|
155
|
+
│ ├── analyzers/
|
|
156
|
+
│ ├── cli/
|
|
157
|
+
│ ├── exporters/
|
|
158
|
+
│ ├── generators/
|
|
159
|
+
│ ├── models/
|
|
160
|
+
│ ├── scanner/
|
|
161
|
+
│ └── utils/
|
|
162
|
+
│
|
|
163
|
+
├── tests/
|
|
164
|
+
├── docs/
|
|
165
|
+
├── README.md
|
|
166
|
+
├── LICENSE
|
|
167
|
+
├── CHANGELOG.md
|
|
168
|
+
└── pyproject.toml
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
Clone the repository:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
git clone https://github.com/Nikk-hub-code/folderTree.git
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Move into the project directory:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
cd folderTree
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Install in editable mode:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
pip install -e .
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Run the test suite:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
pytest
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
folderTree/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
folderTree/__main__.py,sha256=ikrOjMyTd655yw1MLY0-tV-iu0UCYEZd_pOEov1-7kY,70
|
|
3
|
+
folderTree/config.py,sha256=X5h0kBsje5-inszSfPhPChtdSfxi3lRMecBWfIVoqUg,405
|
|
4
|
+
folderTree/analyzers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
folderTree/analyzers/languages.py,sha256=sZChTsWu4m50InhzcOxG0v52XDjGktz8jrYkxlTqEZw,1585
|
|
6
|
+
folderTree/analyzers/statistics.py,sha256=RULiHRDGbf-xu6pCk7YCIaudjAk_DjMPkizGguVHbHQ,1638
|
|
7
|
+
folderTree/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
folderTree/cli/main.py,sha256=i5AJ6oAijhAoeTVwA04RjoIFnwFuVOoJTXq9ZhlFzQQ,1450
|
|
9
|
+
folderTree/cli/parser.py,sha256=ZMOBmLGMgIA0SxSvWoOG_XHfWSc0TxUUF7j603wl8kY,729
|
|
10
|
+
folderTree/exporters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
folderTree/exporters/markdown.py,sha256=Zh57kfsYryxQuB9laIrR16ucb6T-7bWkaiF_exImOQA,518
|
|
12
|
+
folderTree/exporters/txt.py,sha256=f31wDP-_KY31IRme_9t2tFwuSUfqiu_f9-SZ4_QPWfc,582
|
|
13
|
+
folderTree/generators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
folderTree/generators/documentation.py,sha256=fgFvyf44dCpZCimupNV0Wp3LQjMl7h7V9-Zgu0VfyrI,3189
|
|
15
|
+
folderTree/generators/tree.py,sha256=hbrU-pPepuen-1yF6clKqfUK2mqNVg7FVdWT08Npdt8,1811
|
|
16
|
+
folderTree/models/__init__.py,sha256=9929wrH7bIXGkuzTf7F2HqEAS_6Cn9lnCFidrbm6Sgk,191
|
|
17
|
+
folderTree/models/file.py,sha256=a2NMhDEhcNoOTX0pkVsltByXLGp3JN_n4ryoI7LGPUw,1195
|
|
18
|
+
folderTree/models/folder.py,sha256=4JT--NG3a5DeSl6XFg01IyOLX3IgXREMQ1sf3tbTf9g,1076
|
|
19
|
+
folderTree/models/metadata.py,sha256=DS7V_C2CYV5Fl6_eI8_P3mh50yxiLNEUjpSAcgqatO0,256
|
|
20
|
+
folderTree/models/project.py,sha256=MfzWgS_AyDyblHIbD24DsRYU2iQhMUgtMnwhPJFrT88,355
|
|
21
|
+
folderTree/scanner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
folderTree/scanner/ignore.py,sha256=qVNlyXZmynRjs6GzKAi7KAAfs0W-ZhIzftN8UNsLBLg,894
|
|
23
|
+
folderTree/scanner/metadata_collector.py,sha256=ZBh9BWlNwgflBYFjz-Zvfvjxeu3BaAeUE4ayC5AjAfE,484
|
|
24
|
+
folderTree/scanner/scanner.py,sha256=TXG9tfalbcS7RyTPHMkOABItzZlofiu6GV_ZLKY1qCs,3228
|
|
25
|
+
folderTree/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
folderTree/utils/constants.py,sha256=EWAvqw7c2F929bBJZG5jZlOj7Z-g8Dhan2mW-PrDseI,1146
|
|
27
|
+
folderTree/utils/formatter.py,sha256=6t_YVrLSWyv6m5dkiEDZgAo1kxyNzVG7bdYhPOLLLlk,1162
|
|
28
|
+
folderTree/utils/helpers.py,sha256=z1oBQqV4pfjjxdYctyXdqj80zcCl_sHR1oixpKSYjZI,924
|
|
29
|
+
foldertree_cli-0.1.0.dist-info/licenses/LICENSE,sha256=gr-BnW0GXsyXE2Q_nY9FgKNGVb0FKDeUhoKWzs-tPBQ,1093
|
|
30
|
+
foldertree_cli-0.1.0.dist-info/METADATA,sha256=mwnw0RWV6zJb2v_Z3yk1WMuMnyvl5I6PIQFgGpR91Ws,3795
|
|
31
|
+
foldertree_cli-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
32
|
+
foldertree_cli-0.1.0.dist-info/entry_points.txt,sha256=bCL98tMdRfekt2s0Bao5RNRMMDHn3EZt0S6yFbf0urU,56
|
|
33
|
+
foldertree_cli-0.1.0.dist-info/top_level.txt,sha256=smlF8L6ttPFbjZLugMNV5vHTfJhPRVJrSyk5XqxytAo,11
|
|
34
|
+
foldertree_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kaushal Kumar Jha
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
folderTree
|