reposnap 0.5.1__tar.gz → 0.6.3__tar.gz
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.
- {reposnap-0.5.1 → reposnap-0.6.3}/.gitignore +4 -0
- reposnap-0.6.3/.python-version +1 -0
- reposnap-0.6.3/.vscode/launch.json +13 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/PKG-INFO +5 -5
- {reposnap-0.5.1 → reposnap-0.6.3}/README.md +3 -3
- {reposnap-0.5.1 → reposnap-0.6.3}/pyproject.toml +1 -1
- {reposnap-0.5.1 → reposnap-0.6.3}/requirements-dev.lock +11 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/requirements.lock +8 -0
- reposnap-0.6.3/src/reposnap/controllers/project_controller.py +199 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/src/reposnap/interfaces/cli.py +9 -4
- {reposnap-0.5.1 → reposnap-0.6.3}/src/reposnap/models/file_tree.py +2 -1
- reposnap-0.6.3/tests/reposnap/test_collected_tree.py +133 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/tests/reposnap/test_project_controller.py +112 -109
- reposnap-0.5.1/.python-version +0 -1
- reposnap-0.5.1/src/reposnap/controllers/project_controller.py +0 -122
- {reposnap-0.5.1 → reposnap-0.6.3}/LICENSE +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/src/reposnap/__init__.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/src/reposnap/controllers/__init__.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/src/reposnap/core/__init__.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/src/reposnap/core/file_system.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/src/reposnap/core/git_repo.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/src/reposnap/core/markdown_generator.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/src/reposnap/interfaces/__init__.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/src/reposnap/interfaces/gui.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/src/reposnap/models/__init__.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/src/reposnap/utils/__init__.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/src/reposnap/utils/path_utils.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/tests/__init__.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/tests/reposnap/__init__.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/tests/reposnap/test_cli.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/tests/reposnap/test_file_system.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/tests/reposnap/test_file_tree.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/tests/reposnap/test_git_repo.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/tests/reposnap/test_gui.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/tests/reposnap/test_markdown_generator.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/tests/reposnap/test_path_utils.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/tests/resources/another_existing_file.py +0 -0
- {reposnap-0.5.1 → reposnap-0.6.3}/tests/resources/existing_file.py +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
3.12.6
|
@@ -0,0 +1,13 @@
|
|
1
|
+
{
|
2
|
+
"version": "0.2.0",
|
3
|
+
"configurations": [
|
4
|
+
{
|
5
|
+
"name": "Python Debugger: Run reposnap",
|
6
|
+
"type": "debugpy",
|
7
|
+
"request": "launch",
|
8
|
+
"module": "reposnap.interfaces.cli",
|
9
|
+
"console": "integratedTerminal",
|
10
|
+
"args": ["src/reposnap/controllers", "src/reposnap/interfaces", "README.md", "tests/reposnap"]
|
11
|
+
}
|
12
|
+
]
|
13
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: reposnap
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.3
|
4
4
|
Summary: Generate a Markdown file with all contents of your project
|
5
5
|
Author: agoloborodko
|
6
6
|
License-File: LICENSE
|
@@ -49,10 +49,10 @@ pip install -r requirements.lock
|
|
49
49
|
To use `reposnap` from the command line, run it with the following options:
|
50
50
|
|
51
51
|
```bash
|
52
|
-
reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]] [-e EXCLUDE [EXCLUDE ...]]
|
52
|
+
reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]] [-e EXCLUDE [EXCLUDE ...]] paths [paths ...]
|
53
53
|
```
|
54
54
|
|
55
|
-
- `
|
55
|
+
- `paths`: One or more paths (files or directories) within the repository whose content and structure should be rendered.
|
56
56
|
- `-h, --help`: Show help message and exit.
|
57
57
|
- `-o, --output`: The name of the output Markdown file. Defaults to `output.md`.
|
58
58
|
- `--structure-only`: Generate a Markdown file that includes only the project structure, without file contents.
|
@@ -94,7 +94,7 @@ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]
|
|
94
94
|
4. **Generate a Markdown file excluding certain files and directories**:
|
95
95
|
|
96
96
|
```bash
|
97
|
-
reposnap
|
97
|
+
reposnap my_project_folder my_project_folder_2 -e "tests" -e "*.md"
|
98
98
|
```
|
99
99
|
|
100
100
|
5. **Exclude files and directories containing a substring**:
|
@@ -36,10 +36,10 @@ pip install -r requirements.lock
|
|
36
36
|
To use `reposnap` from the command line, run it with the following options:
|
37
37
|
|
38
38
|
```bash
|
39
|
-
reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]] [-e EXCLUDE [EXCLUDE ...]]
|
39
|
+
reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]] [-e EXCLUDE [EXCLUDE ...]] paths [paths ...]
|
40
40
|
```
|
41
41
|
|
42
|
-
- `
|
42
|
+
- `paths`: One or more paths (files or directories) within the repository whose content and structure should be rendered.
|
43
43
|
- `-h, --help`: Show help message and exit.
|
44
44
|
- `-o, --output`: The name of the output Markdown file. Defaults to `output.md`.
|
45
45
|
- `--structure-only`: Generate a Markdown file that includes only the project structure, without file contents.
|
@@ -81,7 +81,7 @@ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]
|
|
81
81
|
4. **Generate a Markdown file excluding certain files and directories**:
|
82
82
|
|
83
83
|
```bash
|
84
|
-
reposnap
|
84
|
+
reposnap my_project_folder my_project_folder_2 -e "tests" -e "*.md"
|
85
85
|
```
|
86
86
|
|
87
87
|
5. **Exclude files and directories containing a substring**:
|
@@ -11,14 +11,25 @@
|
|
11
11
|
|
12
12
|
-e file:.
|
13
13
|
gitdb==4.0.11
|
14
|
+
# via gitpython
|
14
15
|
gitpython==3.1.43
|
16
|
+
# via reposnap
|
15
17
|
iniconfig==2.0.0
|
18
|
+
# via pytest
|
16
19
|
packaging==24.1
|
20
|
+
# via pytest
|
17
21
|
pathlib==1.0.1
|
22
|
+
# via reposnap
|
18
23
|
pathspec==0.12.1
|
24
|
+
# via reposnap
|
19
25
|
pluggy==1.5.0
|
26
|
+
# via pytest
|
20
27
|
pytest==8.3.2
|
21
28
|
smmap==5.0.1
|
29
|
+
# via gitdb
|
22
30
|
typing-extensions==4.12.2
|
31
|
+
# via urwid
|
23
32
|
urwid==2.6.15
|
33
|
+
# via reposnap
|
24
34
|
wcwidth==0.2.13
|
35
|
+
# via urwid
|
@@ -11,10 +11,18 @@
|
|
11
11
|
|
12
12
|
-e file:.
|
13
13
|
gitdb==4.0.11
|
14
|
+
# via gitpython
|
14
15
|
gitpython==3.1.43
|
16
|
+
# via reposnap
|
15
17
|
pathlib==1.0.1
|
18
|
+
# via reposnap
|
16
19
|
pathspec==0.12.1
|
20
|
+
# via reposnap
|
17
21
|
smmap==5.0.1
|
22
|
+
# via gitdb
|
18
23
|
typing-extensions==4.12.2
|
24
|
+
# via urwid
|
19
25
|
urwid==2.6.15
|
26
|
+
# via reposnap
|
20
27
|
wcwidth==0.2.13
|
28
|
+
# via urwid
|
@@ -0,0 +1,199 @@
|
|
1
|
+
import logging
|
2
|
+
from pathlib import Path
|
3
|
+
from reposnap.core.file_system import FileSystem
|
4
|
+
from reposnap.models.file_tree import FileTree
|
5
|
+
import pathspec
|
6
|
+
from typing import List, Optional
|
7
|
+
|
8
|
+
class ProjectController:
|
9
|
+
def __init__(self, args: Optional[object] = None):
|
10
|
+
self.logger = logging.getLogger(__name__)
|
11
|
+
# Always determine repository root using Git (or cwd)
|
12
|
+
self.root_dir = self._get_repo_root().resolve()
|
13
|
+
if args:
|
14
|
+
self.args = args
|
15
|
+
# Treat positional arguments as literal file/directory names.
|
16
|
+
input_paths = [Path(p) for p in (args.paths if hasattr(args, 'paths') else [args.path])]
|
17
|
+
self.input_paths = []
|
18
|
+
for p in input_paths:
|
19
|
+
candidate = (self.root_dir / p).resolve()
|
20
|
+
if candidate.exists():
|
21
|
+
try:
|
22
|
+
rel = candidate.relative_to(self.root_dir)
|
23
|
+
if rel != Path('.'):
|
24
|
+
self.input_paths.append(rel)
|
25
|
+
except ValueError:
|
26
|
+
self.logger.warning(f"Path {p} is not under repository root {self.root_dir}. Ignoring.")
|
27
|
+
else:
|
28
|
+
self.logger.warning(f"Path {p} does not exist relative to repository root {self.root_dir}.")
|
29
|
+
self.output_file: Path = Path(args.output).resolve() if args.output else self.root_dir / 'output.md'
|
30
|
+
self.structure_only: bool = args.structure_only if hasattr(args, 'structure_only') else False
|
31
|
+
self.include_patterns: List[str] = args.include if hasattr(args, 'include') else []
|
32
|
+
self.exclude_patterns: List[str] = args.exclude if hasattr(args, 'exclude') else []
|
33
|
+
else:
|
34
|
+
self.args = None
|
35
|
+
self.input_paths = []
|
36
|
+
self.output_file = self.root_dir / 'output.md'
|
37
|
+
self.structure_only = False
|
38
|
+
self.include_patterns = []
|
39
|
+
self.exclude_patterns = []
|
40
|
+
self.file_tree: Optional[FileTree] = None
|
41
|
+
self.gitignore_patterns: List[str] = []
|
42
|
+
if self.root_dir:
|
43
|
+
self.gitignore_patterns = self._load_gitignore_patterns()
|
44
|
+
|
45
|
+
def _get_repo_root(self) -> Path:
|
46
|
+
"""
|
47
|
+
Determine the repository root using Git if available,
|
48
|
+
otherwise use the current directory.
|
49
|
+
"""
|
50
|
+
from git import Repo, InvalidGitRepositoryError
|
51
|
+
try:
|
52
|
+
repo = Repo(Path.cwd(), search_parent_directories=True)
|
53
|
+
return Path(repo.working_tree_dir).resolve()
|
54
|
+
except InvalidGitRepositoryError:
|
55
|
+
self.logger.warning("Not a git repository. Using current directory as root.")
|
56
|
+
return Path.cwd().resolve()
|
57
|
+
|
58
|
+
def set_root_dir(self, root_dir: Path) -> None:
|
59
|
+
self.root_dir = root_dir.resolve()
|
60
|
+
self.gitignore_patterns = self._load_gitignore_patterns()
|
61
|
+
|
62
|
+
def get_file_tree(self) -> Optional[FileTree]:
|
63
|
+
return self.file_tree
|
64
|
+
|
65
|
+
def _apply_include_exclude(self, files: List[Path]) -> List[Path]:
|
66
|
+
"""Filter a list of file paths using include and exclude patterns."""
|
67
|
+
def adjust_patterns(patterns):
|
68
|
+
adjusted = []
|
69
|
+
for p in patterns:
|
70
|
+
if any(ch in p for ch in ['*', '?', '[']):
|
71
|
+
adjusted.append(p)
|
72
|
+
else:
|
73
|
+
adjusted.append(f'*{p}*')
|
74
|
+
return adjusted
|
75
|
+
if self.include_patterns:
|
76
|
+
inc = adjust_patterns(self.include_patterns)
|
77
|
+
spec_inc = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, inc)
|
78
|
+
files = [f for f in files if spec_inc.match_file(f.as_posix())]
|
79
|
+
if self.exclude_patterns:
|
80
|
+
exc = adjust_patterns(self.exclude_patterns)
|
81
|
+
spec_exc = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, exc)
|
82
|
+
files = [f for f in files if not spec_exc.match_file(f.as_posix())]
|
83
|
+
return files
|
84
|
+
|
85
|
+
def collect_file_tree(self) -> None:
|
86
|
+
self.logger.info("Collecting files from Git tracked files if available.")
|
87
|
+
try:
|
88
|
+
from reposnap.core.git_repo import GitRepo
|
89
|
+
git_repo = GitRepo(self.root_dir)
|
90
|
+
all_files = git_repo.get_git_files()
|
91
|
+
self.logger.debug(f"Git tracked files: {all_files}")
|
92
|
+
except Exception as e:
|
93
|
+
self.logger.warning(f"Error obtaining Git tracked files: {e}.")
|
94
|
+
all_files = []
|
95
|
+
# If Git returns an empty list but files exist on disk, fall back to filesystem scan.
|
96
|
+
if not all_files:
|
97
|
+
file_list = [p for p in self.root_dir.rglob("*") if p.is_file()]
|
98
|
+
if file_list:
|
99
|
+
self.logger.info("Git tracked files empty, using filesystem scan fallback.")
|
100
|
+
all_files = []
|
101
|
+
for path in file_list:
|
102
|
+
try:
|
103
|
+
rel = path.relative_to(self.root_dir)
|
104
|
+
all_files.append(rel)
|
105
|
+
except ValueError:
|
106
|
+
continue
|
107
|
+
all_files = self._apply_include_exclude(all_files)
|
108
|
+
self.logger.debug(f"All files after applying include/exclude: {all_files}")
|
109
|
+
if self.input_paths:
|
110
|
+
trees = []
|
111
|
+
for input_path in self.input_paths:
|
112
|
+
subset = [f for f in all_files if f == input_path or list(f.parts[:len(input_path.parts)]) == list(input_path.parts)]
|
113
|
+
self.logger.debug(f"Files for input path '{input_path}': {subset}")
|
114
|
+
if subset:
|
115
|
+
tree = FileSystem(self.root_dir).build_tree_structure(subset)
|
116
|
+
trees.append(tree)
|
117
|
+
if trees:
|
118
|
+
merged_tree = self.merge_trees(trees)
|
119
|
+
else:
|
120
|
+
merged_tree = {}
|
121
|
+
else:
|
122
|
+
merged_tree = FileSystem(self.root_dir).build_tree_structure(all_files)
|
123
|
+
self.logger.info("Merged tree built from input paths.")
|
124
|
+
self.file_tree = FileTree(merged_tree)
|
125
|
+
self.logger.debug(f"Merged tree structure: {self.file_tree.structure}")
|
126
|
+
|
127
|
+
def merge_trees(self, trees: List[dict]) -> dict:
|
128
|
+
"""Recursively merge a list of tree dictionaries."""
|
129
|
+
merged = {}
|
130
|
+
for tree in trees:
|
131
|
+
merged = self._merge_two_trees(merged, tree)
|
132
|
+
return merged
|
133
|
+
|
134
|
+
def _merge_two_trees(self, tree1: dict, tree2: dict) -> dict:
|
135
|
+
merged = dict(tree1)
|
136
|
+
for key, value in tree2.items():
|
137
|
+
if key in merged:
|
138
|
+
if isinstance(merged[key], dict) and isinstance(value, dict):
|
139
|
+
merged[key] = self._merge_two_trees(merged[key], value)
|
140
|
+
else:
|
141
|
+
merged[key] = value
|
142
|
+
else:
|
143
|
+
merged[key] = value
|
144
|
+
return merged
|
145
|
+
|
146
|
+
def apply_filters(self) -> None:
|
147
|
+
self.logger.info("Applying .gitignore filters to the merged tree.")
|
148
|
+
spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, self.gitignore_patterns)
|
149
|
+
self.logger.debug(f".gitignore patterns: {self.gitignore_patterns}")
|
150
|
+
self.file_tree.filter_files(spec)
|
151
|
+
|
152
|
+
def generate_output(self) -> None:
|
153
|
+
self.logger.info("Starting Markdown generation.")
|
154
|
+
from reposnap.core.markdown_generator import MarkdownGenerator
|
155
|
+
markdown_generator = MarkdownGenerator(
|
156
|
+
root_dir=self.root_dir,
|
157
|
+
output_file=self.output_file,
|
158
|
+
structure_only=self.structure_only
|
159
|
+
)
|
160
|
+
markdown_generator.generate_markdown(self.file_tree.structure, self.file_tree.get_all_files())
|
161
|
+
self.logger.info(f"Markdown generated at {self.output_file}.")
|
162
|
+
|
163
|
+
def generate_output_from_selected(self, selected_files: set) -> None:
|
164
|
+
self.logger.info("Generating Markdown from selected files.")
|
165
|
+
pruned_tree = self.file_tree.prune_tree(selected_files)
|
166
|
+
from reposnap.core.markdown_generator import MarkdownGenerator
|
167
|
+
markdown_generator = MarkdownGenerator(
|
168
|
+
root_dir=self.root_dir,
|
169
|
+
output_file=self.output_file,
|
170
|
+
structure_only=False,
|
171
|
+
hide_untoggled=True
|
172
|
+
)
|
173
|
+
markdown_generator.generate_markdown(pruned_tree, [Path(f) for f in selected_files])
|
174
|
+
self.logger.info(f"Markdown generated at {self.output_file}.")
|
175
|
+
|
176
|
+
def run(self) -> None:
|
177
|
+
"""Run the entire process: collect files, apply filters, and generate Markdown."""
|
178
|
+
self.collect_file_tree()
|
179
|
+
self.apply_filters()
|
180
|
+
self.generate_output()
|
181
|
+
|
182
|
+
def _load_gitignore_patterns(self) -> List[str]:
|
183
|
+
gitignore_path = self.root_dir / '.gitignore'
|
184
|
+
if not gitignore_path.exists():
|
185
|
+
for parent in self.root_dir.parents:
|
186
|
+
candidate = parent / '.gitignore'
|
187
|
+
if candidate.exists():
|
188
|
+
gitignore_path = candidate
|
189
|
+
break
|
190
|
+
else:
|
191
|
+
gitignore_path = None
|
192
|
+
if gitignore_path and gitignore_path.exists():
|
193
|
+
with gitignore_path.open('r') as gitignore:
|
194
|
+
patterns = [line.strip() for line in gitignore if line.strip() and not line.strip().startswith('#')]
|
195
|
+
self.logger.debug(f"Loaded .gitignore patterns from {gitignore_path.parent}: {patterns}")
|
196
|
+
return patterns
|
197
|
+
else:
|
198
|
+
self.logger.debug(f"No .gitignore found starting from {self.root_dir}.")
|
199
|
+
return []
|
@@ -6,14 +6,19 @@ from reposnap.controllers.project_controller import ProjectController
|
|
6
6
|
|
7
7
|
|
8
8
|
def main():
|
9
|
-
parser = argparse.ArgumentParser(
|
10
|
-
|
9
|
+
parser = argparse.ArgumentParser(
|
10
|
+
description='Generate a Markdown representation of a Git repository.'
|
11
|
+
)
|
12
|
+
# Changed positional argument to allow one or more paths.
|
13
|
+
parser.add_argument(
|
14
|
+
'paths',
|
15
|
+
nargs='+',
|
16
|
+
help='One or more paths (files or directories) to include in the Markdown output.'
|
17
|
+
)
|
11
18
|
parser.add_argument('-o', '--output', help='Output Markdown file', default='output.md')
|
12
19
|
parser.add_argument('--structure-only', action='store_true',
|
13
20
|
help='Only include the file structure without content.')
|
14
21
|
parser.add_argument('--debug', action='store_true', help='Enable debug-level logging.')
|
15
|
-
|
16
|
-
# New arguments for include and exclude patterns
|
17
22
|
parser.add_argument('-i', '--include', nargs='*', default=[],
|
18
23
|
help='File/folder patterns to include.')
|
19
24
|
parser.add_argument('-e', '--exclude', nargs='*', default=[],
|
@@ -48,7 +48,8 @@ class FileTree:
|
|
48
48
|
if filtered_value:
|
49
49
|
filtered_subtree[key] = filtered_value
|
50
50
|
else:
|
51
|
-
if
|
51
|
+
# Exclude the file if either the full path OR its basename matches a .gitignore pattern.
|
52
|
+
if not spec.match_file(current_path) and not spec.match_file(Path(current_path).name):
|
52
53
|
filtered_subtree[key] = value
|
53
54
|
return filtered_subtree
|
54
55
|
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# tests/reposnap/test_collected_tree.py
|
2
|
+
|
3
|
+
import os
|
4
|
+
import tempfile
|
5
|
+
from pathlib import Path
|
6
|
+
import pytest
|
7
|
+
from reposnap.controllers.project_controller import ProjectController
|
8
|
+
from unittest.mock import patch
|
9
|
+
|
10
|
+
def create_directory_structure(base_dir: str, structure: dict):
|
11
|
+
"""
|
12
|
+
Recursively creates directories and files based on the provided structure.
|
13
|
+
"""
|
14
|
+
for name, content in structure.items():
|
15
|
+
path = os.path.join(base_dir, name)
|
16
|
+
if isinstance(content, dict):
|
17
|
+
os.makedirs(path, exist_ok=True)
|
18
|
+
create_directory_structure(path, content)
|
19
|
+
else:
|
20
|
+
with open(path, 'w') as f:
|
21
|
+
f.write(content)
|
22
|
+
|
23
|
+
def traverse_tree(tree: dict, path=''):
|
24
|
+
files = []
|
25
|
+
for name, node in tree.items():
|
26
|
+
current_path = os.path.join(path, name)
|
27
|
+
if isinstance(node, dict):
|
28
|
+
files.extend(traverse_tree(node, current_path))
|
29
|
+
else:
|
30
|
+
files.append(current_path)
|
31
|
+
return files
|
32
|
+
|
33
|
+
def test_collect_tree_all_files():
|
34
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
35
|
+
structure = {
|
36
|
+
'src': {
|
37
|
+
'module': {
|
38
|
+
'a.py': 'print("a")',
|
39
|
+
'b.txt': 'text',
|
40
|
+
}
|
41
|
+
},
|
42
|
+
'tests': {
|
43
|
+
'test_a.py': 'print("test")'
|
44
|
+
},
|
45
|
+
'README.md': '# Readme'
|
46
|
+
}
|
47
|
+
create_directory_structure(temp_dir, structure)
|
48
|
+
args = type('Args', (object,), {
|
49
|
+
'path': temp_dir,
|
50
|
+
'output': os.path.join(temp_dir, 'output.md'),
|
51
|
+
'structure_only': True,
|
52
|
+
'debug': False,
|
53
|
+
'include': [],
|
54
|
+
'exclude': []
|
55
|
+
})
|
56
|
+
with patch('reposnap.controllers.project_controller.ProjectController._get_repo_root', return_value=Path(temp_dir)):
|
57
|
+
controller = ProjectController(args)
|
58
|
+
controller.collect_file_tree()
|
59
|
+
collected = traverse_tree(controller.file_tree.structure)
|
60
|
+
expected = [
|
61
|
+
'README.md',
|
62
|
+
os.path.join('src', 'module', 'a.py'),
|
63
|
+
os.path.join('src', 'module', 'b.txt'),
|
64
|
+
os.path.join('tests', 'test_a.py')
|
65
|
+
]
|
66
|
+
assert sorted(collected) == sorted(expected)
|
67
|
+
|
68
|
+
def test_collect_tree_literal_path():
|
69
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
70
|
+
structure = {
|
71
|
+
'src': {
|
72
|
+
'module': {
|
73
|
+
'a.py': 'print("a")',
|
74
|
+
'b.txt': 'text',
|
75
|
+
}
|
76
|
+
},
|
77
|
+
'tests': {
|
78
|
+
'test_a.py': 'print("test")'
|
79
|
+
},
|
80
|
+
'README.md': '# Readme'
|
81
|
+
}
|
82
|
+
create_directory_structure(temp_dir, structure)
|
83
|
+
# Request only the 'src' directory.
|
84
|
+
args = type('Args', (object,), {
|
85
|
+
'paths': ['src'],
|
86
|
+
'output': os.path.join(temp_dir, 'output.md'),
|
87
|
+
'structure_only': True,
|
88
|
+
'debug': False,
|
89
|
+
'include': [],
|
90
|
+
'exclude': []
|
91
|
+
})
|
92
|
+
with patch('reposnap.controllers.project_controller.ProjectController._get_repo_root', return_value=Path(temp_dir)):
|
93
|
+
controller = ProjectController(args)
|
94
|
+
controller.collect_file_tree()
|
95
|
+
collected = traverse_tree(controller.file_tree.structure)
|
96
|
+
expected = [
|
97
|
+
os.path.join('src', 'module', 'a.py'),
|
98
|
+
os.path.join('src', 'module', 'b.txt')
|
99
|
+
]
|
100
|
+
assert sorted(collected) == sorted(expected)
|
101
|
+
|
102
|
+
def test_collect_tree_multiple_paths():
|
103
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
104
|
+
structure = {
|
105
|
+
'src': {
|
106
|
+
'module': {
|
107
|
+
'a.py': 'print("a")',
|
108
|
+
}
|
109
|
+
},
|
110
|
+
'tests': {
|
111
|
+
'test_a.py': 'print("test")'
|
112
|
+
},
|
113
|
+
'README.md': '# Readme'
|
114
|
+
}
|
115
|
+
create_directory_structure(temp_dir, structure)
|
116
|
+
# Request multiple literal paths.
|
117
|
+
args = type('Args', (object,), {
|
118
|
+
'paths': ['README.md', 'tests'],
|
119
|
+
'output': os.path.join(temp_dir, 'output.md'),
|
120
|
+
'structure_only': True,
|
121
|
+
'debug': False,
|
122
|
+
'include': [],
|
123
|
+
'exclude': []
|
124
|
+
})
|
125
|
+
with patch('reposnap.controllers.project_controller.ProjectController._get_repo_root', return_value=Path(temp_dir)):
|
126
|
+
controller = ProjectController(args)
|
127
|
+
controller.collect_file_tree()
|
128
|
+
collected = traverse_tree(controller.file_tree.structure)
|
129
|
+
expected = [
|
130
|
+
'README.md',
|
131
|
+
os.path.join('tests', 'test_a.py')
|
132
|
+
]
|
133
|
+
assert sorted(collected) == sorted(expected)
|
@@ -14,6 +14,9 @@ def create_file(file_path: str, content: str = ''):
|
|
14
14
|
|
15
15
|
|
16
16
|
def create_directory_structure(base_dir: str, structure: dict):
|
17
|
+
"""
|
18
|
+
Recursively creates directories and files based on the provided structure.
|
19
|
+
"""
|
17
20
|
for name, content in structure.items():
|
18
21
|
path = os.path.join(base_dir, name)
|
19
22
|
if isinstance(content, dict):
|
@@ -26,8 +29,8 @@ def create_directory_structure(base_dir: str, structure: dict):
|
|
26
29
|
def test_project_controller_includes_py_files():
|
27
30
|
with tempfile.TemporaryDirectory() as temp_dir:
|
28
31
|
gitignore_content = """
|
29
|
-
|
30
|
-
|
32
|
+
*.py[oc]
|
33
|
+
"""
|
31
34
|
structure = {
|
32
35
|
'src': {
|
33
36
|
'module': {
|
@@ -38,7 +41,6 @@ def test_project_controller_includes_py_files():
|
|
38
41
|
},
|
39
42
|
'.gitignore': gitignore_content,
|
40
43
|
}
|
41
|
-
|
42
44
|
create_directory_structure(temp_dir, structure)
|
43
45
|
|
44
46
|
args = type('Args', (object,), {
|
@@ -47,58 +49,47 @@ def test_project_controller_includes_py_files():
|
|
47
49
|
'structure_only': False,
|
48
50
|
'debug': False
|
49
51
|
})
|
50
|
-
|
51
|
-
with patch('reposnap.controllers.project_controller.
|
52
|
-
mock_git_repo_instance = MockGitRepo.return_value
|
53
|
-
mock_git_repo_instance.get_git_files.return_value = [
|
54
|
-
Path('src/module/file1.py'),
|
55
|
-
Path('src/module/file2.py'),
|
56
|
-
Path('.gitignore')
|
57
|
-
]
|
58
|
-
|
52
|
+
# Force repository root to be our temporary directory.
|
53
|
+
with patch('reposnap.controllers.project_controller.ProjectController._get_repo_root', return_value=Path(temp_dir)):
|
59
54
|
controller = ProjectController(args)
|
60
55
|
controller.run()
|
61
56
|
|
62
|
-
# Read the output file
|
63
57
|
with open(args.output, 'r') as f:
|
64
58
|
output_content = f.read()
|
65
59
|
|
66
|
-
# Check that contents of
|
60
|
+
# Check that the contents of the Python files are included
|
67
61
|
assert 'print("File 1")' in output_content
|
68
62
|
assert 'print("File 2")' in output_content
|
69
|
-
# .pyc
|
63
|
+
# The .pyc file should be filtered out by .gitignore
|
70
64
|
assert 'Compiled code' not in output_content
|
71
65
|
|
72
66
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
args.include = []
|
95
|
-
args.exclude = []
|
96
|
-
args.debug = False # Add if necessary
|
67
|
+
def test_project_controller_run():
|
68
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
69
|
+
structure = {
|
70
|
+
'file1.txt': 'content',
|
71
|
+
'file2.py': 'print("hello")',
|
72
|
+
}
|
73
|
+
create_directory_structure(temp_dir, structure)
|
74
|
+
args = type('Args', (object,), {
|
75
|
+
'path': temp_dir,
|
76
|
+
'output': os.path.join(temp_dir, 'output.md'),
|
77
|
+
'structure_only': False,
|
78
|
+
'debug': False,
|
79
|
+
'include': [],
|
80
|
+
'exclude': []
|
81
|
+
})
|
82
|
+
# Patch _get_repo_root to force temp_dir as repo root.
|
83
|
+
with patch('reposnap.controllers.project_controller.ProjectController._get_repo_root', return_value=Path(temp_dir)):
|
84
|
+
# Patch the MarkdownGenerator in its actual module.
|
85
|
+
with patch('reposnap.core.markdown_generator.MarkdownGenerator') as MockMarkdownGenerator:
|
86
|
+
mock_instance = MagicMock()
|
87
|
+
MockMarkdownGenerator.return_value = mock_instance
|
97
88
|
|
98
|
-
|
99
|
-
|
89
|
+
controller = ProjectController(args)
|
90
|
+
controller.run()
|
100
91
|
|
101
|
-
|
92
|
+
mock_instance.generate_markdown.assert_called_once()
|
102
93
|
|
103
94
|
|
104
95
|
def test_include_pattern():
|
@@ -118,9 +109,7 @@ def test_include_pattern():
|
|
118
109
|
'setup.py': 'setup code',
|
119
110
|
'notes.txt': 'Some notes',
|
120
111
|
}
|
121
|
-
|
122
112
|
create_directory_structure(temp_dir, structure)
|
123
|
-
|
124
113
|
args = type('Args', (object,), {
|
125
114
|
'path': temp_dir,
|
126
115
|
'output': os.path.join(temp_dir, 'output.md'),
|
@@ -129,25 +118,11 @@ def test_include_pattern():
|
|
129
118
|
'include': ['*.py'],
|
130
119
|
'exclude': []
|
131
120
|
})
|
132
|
-
|
133
|
-
# Mock the GitRepo class
|
134
|
-
with patch('reposnap.controllers.project_controller.GitRepo') as MockGitRepo:
|
135
|
-
mock_git_repo_instance = MockGitRepo.return_value
|
136
|
-
|
137
|
-
# Collect all files under temp_dir
|
138
|
-
all_files = []
|
139
|
-
for root, dirs, files in os.walk(temp_dir):
|
140
|
-
for name in files:
|
141
|
-
file_path = Path(root) / name
|
142
|
-
rel_path = file_path.relative_to(temp_dir)
|
143
|
-
all_files.append(rel_path)
|
144
|
-
|
145
|
-
mock_git_repo_instance.get_git_files.return_value = all_files
|
146
|
-
|
121
|
+
with patch('reposnap.controllers.project_controller.ProjectController._get_repo_root', return_value=Path(temp_dir)):
|
147
122
|
controller = ProjectController(args)
|
148
123
|
controller.collect_file_tree()
|
149
124
|
|
150
|
-
#
|
125
|
+
# Traverse the merged tree and collect file paths.
|
151
126
|
included_files = []
|
152
127
|
|
153
128
|
def traverse(tree, path=''):
|
@@ -165,7 +140,6 @@ def test_include_pattern():
|
|
165
140
|
os.path.join('src', 'module', 'submodule', 'file3.py'),
|
166
141
|
'setup.py',
|
167
142
|
]
|
168
|
-
|
169
143
|
assert sorted(included_files) == sorted(expected_files)
|
170
144
|
|
171
145
|
|
@@ -186,9 +160,7 @@ def test_exclude_pattern():
|
|
186
160
|
'setup.py': 'setup code',
|
187
161
|
'notes.txt': 'Some notes',
|
188
162
|
}
|
189
|
-
|
190
163
|
create_directory_structure(temp_dir, structure)
|
191
|
-
|
192
164
|
args = type('Args', (object,), {
|
193
165
|
'path': temp_dir,
|
194
166
|
'output': os.path.join(temp_dir, 'output.md'),
|
@@ -197,25 +169,10 @@ def test_exclude_pattern():
|
|
197
169
|
'include': [],
|
198
170
|
'exclude': ['*.md', '*.txt']
|
199
171
|
})
|
200
|
-
|
201
|
-
with patch('reposnap.controllers.project_controller.GitRepo') as MockGitRepo:
|
202
|
-
mock_git_repo_instance = MockGitRepo.return_value
|
203
|
-
|
204
|
-
# Collect all files under temp_dir
|
205
|
-
all_files = []
|
206
|
-
for root, dirs, files in os.walk(temp_dir):
|
207
|
-
for name in files:
|
208
|
-
file_path = Path(root) / name
|
209
|
-
rel_path = file_path.relative_to(temp_dir)
|
210
|
-
all_files.append(rel_path)
|
211
|
-
|
212
|
-
mock_git_repo_instance.get_git_files.return_value = all_files
|
213
|
-
|
172
|
+
with patch('reposnap.controllers.project_controller.ProjectController._get_repo_root', return_value=Path(temp_dir)):
|
214
173
|
controller = ProjectController(args)
|
215
174
|
controller.collect_file_tree()
|
216
|
-
|
217
175
|
included_files = []
|
218
|
-
|
219
176
|
def traverse(tree, path=''):
|
220
177
|
for name, node in tree.items():
|
221
178
|
current_path = os.path.join(path, name)
|
@@ -223,18 +180,14 @@ def test_exclude_pattern():
|
|
223
180
|
traverse(node, current_path)
|
224
181
|
else:
|
225
182
|
included_files.append(current_path)
|
226
|
-
|
227
183
|
traverse(controller.file_tree.structure)
|
228
|
-
|
229
184
|
expected_files = [
|
230
185
|
os.path.join('src', 'module', 'file1.py'),
|
231
186
|
os.path.join('src', 'module', 'submodule', 'file3.py'),
|
232
187
|
'setup.py',
|
233
188
|
]
|
234
|
-
|
235
189
|
assert sorted(included_files) == sorted(expected_files)
|
236
190
|
|
237
|
-
|
238
191
|
def test_include_and_exclude_patterns():
|
239
192
|
with tempfile.TemporaryDirectory() as temp_dir:
|
240
193
|
structure = {
|
@@ -255,9 +208,7 @@ def test_include_and_exclude_patterns():
|
|
255
208
|
'setup.py': 'setup code',
|
256
209
|
'notes.txt': 'Some notes',
|
257
210
|
}
|
258
|
-
|
259
211
|
create_directory_structure(temp_dir, structure)
|
260
|
-
|
261
212
|
args = type('Args', (object,), {
|
262
213
|
'path': temp_dir,
|
263
214
|
'output': os.path.join(temp_dir, 'output.md'),
|
@@ -266,41 +217,93 @@ def test_include_and_exclude_patterns():
|
|
266
217
|
'include': ['*foo*'],
|
267
218
|
'exclude': ['*submodule*']
|
268
219
|
})
|
269
|
-
|
270
|
-
with patch('reposnap.controllers.project_controller.GitRepo') as MockGitRepo:
|
271
|
-
mock_git_repo_instance = MockGitRepo.return_value
|
272
|
-
|
273
|
-
# Collect all files under temp_dir
|
274
|
-
all_files = []
|
275
|
-
for root, dirs, files in os.walk(temp_dir):
|
276
|
-
for name in files:
|
277
|
-
file_path = Path(root) / name
|
278
|
-
rel_path = file_path.relative_to(temp_dir)
|
279
|
-
all_files.append(rel_path)
|
280
|
-
|
281
|
-
mock_git_repo_instance.get_git_files.return_value = all_files
|
282
|
-
|
220
|
+
with patch('reposnap.controllers.project_controller.ProjectController._get_repo_root', return_value=Path(temp_dir)):
|
283
221
|
controller = ProjectController(args)
|
284
222
|
controller.collect_file_tree()
|
285
|
-
|
286
|
-
included_files = []
|
287
|
-
|
223
|
+
collected = []
|
288
224
|
def traverse(tree, path=''):
|
289
225
|
for name, node in tree.items():
|
290
226
|
current_path = os.path.join(path, name)
|
291
227
|
if isinstance(node, dict):
|
292
|
-
|
228
|
+
collected.append(current_path)
|
293
229
|
traverse(node, current_path)
|
294
230
|
else:
|
295
|
-
|
296
|
-
|
231
|
+
collected.append(current_path)
|
297
232
|
traverse(controller.file_tree.structure)
|
298
|
-
|
299
|
-
expected_files = [
|
233
|
+
expected = [
|
300
234
|
os.path.join('src'),
|
301
235
|
os.path.join('src', 'foo_module'),
|
302
236
|
os.path.join('src', 'foo_module', 'foo_file1.py'),
|
303
|
-
os.path.join('src', 'foo_module', 'file2.py'),
|
237
|
+
os.path.join('src', 'foo_module', 'file2.py'),
|
304
238
|
]
|
239
|
+
assert sorted(collected) == sorted(expected)
|
305
240
|
|
306
|
-
|
241
|
+
|
242
|
+
def test_project_controller_multiple_paths():
|
243
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
244
|
+
# Define a structure similar to the provided example.
|
245
|
+
structure = {
|
246
|
+
'README.md': 'Project README content',
|
247
|
+
'pyproject.toml': 'project configuration',
|
248
|
+
'LICENSE': 'MIT License',
|
249
|
+
'src': {
|
250
|
+
'reposnap': {
|
251
|
+
'__init__.py': '',
|
252
|
+
'controllers': {
|
253
|
+
'__init__.py': '',
|
254
|
+
'project_controller.py': 'print("controller")',
|
255
|
+
},
|
256
|
+
'core': {
|
257
|
+
'__init__.py': '',
|
258
|
+
'file_system.py': 'print("filesystem")',
|
259
|
+
'git_repo.py': 'print("git")',
|
260
|
+
'markdown_generator.py': 'print("markdown")',
|
261
|
+
},
|
262
|
+
'interfaces': {
|
263
|
+
'__init__.py': '',
|
264
|
+
'cli.py': 'print("cli")',
|
265
|
+
'gui.py': 'print("gui")',
|
266
|
+
},
|
267
|
+
'models': {
|
268
|
+
'__init__.py': '',
|
269
|
+
'file_tree.py': 'print("file tree")',
|
270
|
+
},
|
271
|
+
'utils': {
|
272
|
+
'__init__.py': '',
|
273
|
+
'path_utils.py': 'print("path utils")',
|
274
|
+
},
|
275
|
+
},
|
276
|
+
},
|
277
|
+
'tests': {
|
278
|
+
'__init__.py': '',
|
279
|
+
'some_test.py': 'print("test")'
|
280
|
+
},
|
281
|
+
'extras': {
|
282
|
+
'notes.txt': 'Some notes'
|
283
|
+
}
|
284
|
+
}
|
285
|
+
create_directory_structure(temp_dir, structure)
|
286
|
+
|
287
|
+
# Create args with multiple paths.
|
288
|
+
args = type('Args', (object,), {
|
289
|
+
'paths': ['README.md', 'src', 'pyproject.toml'],
|
290
|
+
'output': os.path.join(temp_dir, 'output.md'),
|
291
|
+
'structure_only': True,
|
292
|
+
'debug': False,
|
293
|
+
'include': [],
|
294
|
+
'exclude': []
|
295
|
+
})
|
296
|
+
# Patch _get_repo_root to return our temp_dir.
|
297
|
+
with patch('reposnap.controllers.project_controller.ProjectController._get_repo_root', return_value=Path(temp_dir)):
|
298
|
+
controller = ProjectController(args)
|
299
|
+
controller.collect_file_tree()
|
300
|
+
|
301
|
+
tree = controller.file_tree.structure
|
302
|
+
# The merged tree should only include keys for the provided paths.
|
303
|
+
assert 'README.md' in tree
|
304
|
+
assert 'pyproject.toml' in tree
|
305
|
+
assert 'src' in tree
|
306
|
+
# Keys that are not part of the requested paths (like LICENSE, tests, extras) should be absent.
|
307
|
+
assert 'LICENSE' not in tree
|
308
|
+
assert 'tests' not in tree
|
309
|
+
assert 'extras' not in tree
|
reposnap-0.5.1/.python-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
3.12.4
|
@@ -1,122 +0,0 @@
|
|
1
|
-
# src/reposnap/controllers/project_controller.py
|
2
|
-
|
3
|
-
import logging
|
4
|
-
from pathlib import Path
|
5
|
-
from reposnap.core.git_repo import GitRepo
|
6
|
-
from reposnap.core.file_system import FileSystem
|
7
|
-
from reposnap.core.markdown_generator import MarkdownGenerator
|
8
|
-
from reposnap.models.file_tree import FileTree
|
9
|
-
import pathspec
|
10
|
-
from typing import List, Optional
|
11
|
-
|
12
|
-
|
13
|
-
class ProjectController:
|
14
|
-
def __init__(self, args: Optional[object] = None):
|
15
|
-
self.logger = logging.getLogger(__name__)
|
16
|
-
self.root_dir: Path = Path(args.path).resolve() if args else Path('.').resolve()
|
17
|
-
self.output_file: Path = Path(args.output).resolve() if args else Path('output.md').resolve()
|
18
|
-
self.structure_only: bool = args.structure_only if args else False
|
19
|
-
self.args: object = args
|
20
|
-
self.file_tree: Optional[FileTree] = None
|
21
|
-
self.gitignore_patterns: List[str] = []
|
22
|
-
self.include_patterns: List[str] = args.include if args and hasattr(args, 'include') else []
|
23
|
-
self.exclude_patterns: List[str] = args.exclude if args and hasattr(args, 'exclude') else []
|
24
|
-
if self.root_dir:
|
25
|
-
self.gitignore_patterns = self._load_gitignore_patterns()
|
26
|
-
|
27
|
-
def set_root_dir(self, root_dir: Path) -> None:
|
28
|
-
self.root_dir = root_dir
|
29
|
-
self.gitignore_patterns = self._load_gitignore_patterns()
|
30
|
-
|
31
|
-
def get_file_tree(self) -> Optional[FileTree]:
|
32
|
-
return self.file_tree
|
33
|
-
|
34
|
-
def run(self) -> None:
|
35
|
-
self.collect_file_tree()
|
36
|
-
self.apply_filters()
|
37
|
-
self.generate_output()
|
38
|
-
|
39
|
-
def collect_file_tree(self) -> None:
|
40
|
-
self.logger.info("Collecting git files.")
|
41
|
-
git_repo: GitRepo = GitRepo(self.root_dir)
|
42
|
-
git_files: List[Path] = git_repo.get_git_files()
|
43
|
-
self.logger.debug(f"Git files before filtering: {git_files}")
|
44
|
-
|
45
|
-
# Adjust patterns
|
46
|
-
def adjust_patterns(patterns):
|
47
|
-
adjusted = []
|
48
|
-
for pattern in patterns:
|
49
|
-
if '*' in pattern or '?' in pattern or '[' in pattern:
|
50
|
-
adjusted.append(pattern)
|
51
|
-
else:
|
52
|
-
adjusted.append(f'*{pattern}*')
|
53
|
-
return adjusted
|
54
|
-
|
55
|
-
# Apply include patterns
|
56
|
-
if self.include_patterns:
|
57
|
-
adjusted_include_patterns = adjust_patterns(self.include_patterns)
|
58
|
-
include_spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, adjusted_include_patterns)
|
59
|
-
git_files = [f for f in git_files if include_spec.match_file(f.as_posix())]
|
60
|
-
self.logger.debug(f"Git files after include patterns: {git_files}")
|
61
|
-
|
62
|
-
# Apply exclude patterns
|
63
|
-
if self.exclude_patterns:
|
64
|
-
adjusted_exclude_patterns = adjust_patterns(self.exclude_patterns)
|
65
|
-
exclude_spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, adjusted_exclude_patterns)
|
66
|
-
git_files = [f for f in git_files if not exclude_spec.match_file(f.as_posix())]
|
67
|
-
self.logger.debug(f"Git files after exclude patterns: {git_files}")
|
68
|
-
|
69
|
-
self.logger.info("Building tree structure.")
|
70
|
-
file_system: FileSystem = FileSystem(self.root_dir)
|
71
|
-
tree_structure: dict = file_system.build_tree_structure(git_files)
|
72
|
-
|
73
|
-
self.file_tree = FileTree(tree_structure)
|
74
|
-
self.logger.debug(f"Tree structure: {self.file_tree.structure}")
|
75
|
-
|
76
|
-
def apply_filters(self) -> None:
|
77
|
-
self.logger.info("Applying filters to the file tree.")
|
78
|
-
spec: pathspec.PathSpec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, self.gitignore_patterns)
|
79
|
-
self.logger.debug(f"Filter patterns: {self.gitignore_patterns}")
|
80
|
-
self.file_tree.filter_files(spec)
|
81
|
-
|
82
|
-
def generate_output(self) -> None:
|
83
|
-
self.logger.info("Starting markdown generation.")
|
84
|
-
markdown_generator: MarkdownGenerator = MarkdownGenerator(
|
85
|
-
root_dir=self.root_dir,
|
86
|
-
output_file=self.output_file,
|
87
|
-
structure_only=self.structure_only
|
88
|
-
)
|
89
|
-
markdown_generator.generate_markdown(self.file_tree.structure, self.file_tree.get_all_files())
|
90
|
-
self.logger.info(f"Markdown generated at {self.output_file}.")
|
91
|
-
|
92
|
-
def generate_output_from_selected(self, selected_files: set) -> None:
|
93
|
-
self.logger.info("Generating markdown from selected files.")
|
94
|
-
# Build a pruned tree structure based on selected files
|
95
|
-
pruned_tree = self.file_tree.prune_tree(selected_files)
|
96
|
-
markdown_generator: MarkdownGenerator = MarkdownGenerator(
|
97
|
-
root_dir=self.root_dir,
|
98
|
-
output_file=self.output_file,
|
99
|
-
structure_only=False,
|
100
|
-
hide_untoggled=True
|
101
|
-
)
|
102
|
-
markdown_generator.generate_markdown(pruned_tree, [Path(f) for f in selected_files])
|
103
|
-
self.logger.info(f"Markdown generated at {self.output_file}.")
|
104
|
-
|
105
|
-
def _load_gitignore_patterns(self) -> List[str]:
|
106
|
-
gitignore_path: Path = self.root_dir / '.gitignore'
|
107
|
-
if not gitignore_path.exists():
|
108
|
-
for parent in self.root_dir.parents:
|
109
|
-
gitignore_path = parent / '.gitignore'
|
110
|
-
if gitignore_path.exists():
|
111
|
-
break
|
112
|
-
else:
|
113
|
-
gitignore_path = None
|
114
|
-
|
115
|
-
if gitignore_path and gitignore_path.exists():
|
116
|
-
with gitignore_path.open('r') as gitignore:
|
117
|
-
patterns: List[str] = gitignore.readlines()
|
118
|
-
self.logger.debug(f"Patterns from .gitignore in {gitignore_path.parent}: {patterns}")
|
119
|
-
return patterns
|
120
|
-
else:
|
121
|
-
self.logger.debug(f"No .gitignore found starting from {self.root_dir}. Proceeding without patterns.")
|
122
|
-
return []
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|