reposnap 0.4.0__tar.gz → 0.5.0__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.
Files changed (35) hide show
  1. {reposnap-0.4.0 → reposnap-0.5.0}/PKG-INFO +30 -4
  2. {reposnap-0.4.0 → reposnap-0.5.0}/README.md +29 -3
  3. {reposnap-0.4.0 → reposnap-0.5.0}/pyproject.toml +1 -1
  4. {reposnap-0.4.0 → reposnap-0.5.0}/src/reposnap/controllers/project_controller.py +26 -0
  5. {reposnap-0.4.0 → reposnap-0.5.0}/src/reposnap/interfaces/cli.py +7 -0
  6. reposnap-0.5.0/tests/reposnap/test_project_controller.py +306 -0
  7. reposnap-0.4.0/tests/reposnap/test_project_controller.py +0 -97
  8. {reposnap-0.4.0 → reposnap-0.5.0}/.gitignore +0 -0
  9. {reposnap-0.4.0 → reposnap-0.5.0}/.python-version +0 -0
  10. {reposnap-0.4.0 → reposnap-0.5.0}/LICENSE +0 -0
  11. {reposnap-0.4.0 → reposnap-0.5.0}/requirements-dev.lock +0 -0
  12. {reposnap-0.4.0 → reposnap-0.5.0}/requirements.lock +0 -0
  13. {reposnap-0.4.0 → reposnap-0.5.0}/src/reposnap/__init__.py +0 -0
  14. {reposnap-0.4.0 → reposnap-0.5.0}/src/reposnap/controllers/__init__.py +0 -0
  15. {reposnap-0.4.0 → reposnap-0.5.0}/src/reposnap/core/__init__.py +0 -0
  16. {reposnap-0.4.0 → reposnap-0.5.0}/src/reposnap/core/file_system.py +0 -0
  17. {reposnap-0.4.0 → reposnap-0.5.0}/src/reposnap/core/git_repo.py +0 -0
  18. {reposnap-0.4.0 → reposnap-0.5.0}/src/reposnap/core/markdown_generator.py +0 -0
  19. {reposnap-0.4.0 → reposnap-0.5.0}/src/reposnap/interfaces/__init__.py +0 -0
  20. {reposnap-0.4.0 → reposnap-0.5.0}/src/reposnap/interfaces/gui.py +0 -0
  21. {reposnap-0.4.0 → reposnap-0.5.0}/src/reposnap/models/__init__.py +0 -0
  22. {reposnap-0.4.0 → reposnap-0.5.0}/src/reposnap/models/file_tree.py +0 -0
  23. {reposnap-0.4.0 → reposnap-0.5.0}/src/reposnap/utils/__init__.py +0 -0
  24. {reposnap-0.4.0 → reposnap-0.5.0}/src/reposnap/utils/path_utils.py +0 -0
  25. {reposnap-0.4.0 → reposnap-0.5.0}/tests/__init__.py +0 -0
  26. {reposnap-0.4.0 → reposnap-0.5.0}/tests/reposnap/__init__.py +0 -0
  27. {reposnap-0.4.0 → reposnap-0.5.0}/tests/reposnap/test_cli.py +0 -0
  28. {reposnap-0.4.0 → reposnap-0.5.0}/tests/reposnap/test_file_system.py +0 -0
  29. {reposnap-0.4.0 → reposnap-0.5.0}/tests/reposnap/test_file_tree.py +0 -0
  30. {reposnap-0.4.0 → reposnap-0.5.0}/tests/reposnap/test_git_repo.py +0 -0
  31. {reposnap-0.4.0 → reposnap-0.5.0}/tests/reposnap/test_gui.py +0 -0
  32. {reposnap-0.4.0 → reposnap-0.5.0}/tests/reposnap/test_markdown_generator.py +0 -0
  33. {reposnap-0.4.0 → reposnap-0.5.0}/tests/reposnap/test_path_utils.py +0 -0
  34. {reposnap-0.4.0 → reposnap-0.5.0}/tests/resources/another_existing_file.py +0 -0
  35. {reposnap-0.4.0 → reposnap-0.5.0}/tests/resources/existing_file.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: reposnap
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Generate a Markdown file with all contents of your project
5
5
  Author: agoloborodko
6
6
  License-File: LICENSE
@@ -24,6 +24,7 @@ Description-Content-Type: text/markdown
24
24
  - **Syntax Highlighting**: Includes syntax highlighting for known file types in the generated Markdown file.
25
25
  - **Structure Only Option**: The `--structure-only` flag can be used to generate the Markdown file with just the directory structure, omitting the contents of the files.
26
26
  - **Gitignore Support**: Automatically respects `.gitignore` patterns to exclude files and directories.
27
+ - **Include and Exclude Patterns**: Use `--include` and `--exclude` to specify patterns for files and directories to include or exclude.
27
28
 
28
29
  ## Installation
29
30
 
@@ -48,7 +49,7 @@ pip install -r requirements.txt
48
49
  To use `reposnap` from the command line, run it with the following options:
49
50
 
50
51
  ```bash
51
- reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] path
52
+ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]] [-e EXCLUDE [EXCLUDE ...]] path
52
53
  ```
53
54
 
54
55
  - `path`: Path to the Git repository or subdirectory.
@@ -56,6 +57,19 @@ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] path
56
57
  - `-o, --output`: The name of the output Markdown file. Defaults to `output.md`.
57
58
  - `--structure-only`: Generate a Markdown file that includes only the project structure, without file contents.
58
59
  - `--debug`: Enable debug-level logging.
60
+ - `-i, --include`: File/folder patterns to include. For example, `-i "*.py"` includes only Python files.
61
+ - `-e, --exclude`: File/folder patterns to exclude. For example, `-e "*.md"` excludes all Markdown files.
62
+
63
+ #### Pattern Matching
64
+
65
+ - **Pattern Interpretation**: Patterns follow gitignore-style syntax but with a twist.
66
+ - **Patterns without Wildcards**: If a pattern does not contain any wildcard characters (`*`, `?`, or `[`), it is treated as `*pattern*`. This means it will match any file or directory containing `pattern` in its name.
67
+ - **Patterns with Wildcards**: If a pattern contains wildcard characters, it retains its original behavior.
68
+
69
+ - **Examples**:
70
+ - `-e "gui"`: Excludes any files or directories containing `"gui"` in their names.
71
+ - `-i "*.py"`: Includes only files ending with `.py`.
72
+ - `-e "*.test.*"`: Excludes files with `.test.` in their names.
59
73
 
60
74
  #### Examples
61
75
 
@@ -71,10 +85,22 @@ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] path
71
85
  reposnap my_project/ --structure-only
72
86
  ```
73
87
 
74
- 3. **Generate a Markdown file excluding certain files and directories**:
88
+ 3. **Generate a Markdown file including only Python files**:
89
+
90
+ ```bash
91
+ reposnap my_project/ -i "*.py"
92
+ ```
93
+
94
+ 4. **Generate a Markdown file excluding certain files and directories**:
95
+
96
+ ```bash
97
+ reposnap my_project/ -e "tests" -e "*.md"
98
+ ```
99
+
100
+ 5. **Exclude files and directories containing a substring**:
75
101
 
76
102
  ```bash
77
- reposnap my_project/ -o output.md
103
+ reposnap my_project/ -e "gui"
78
104
  ```
79
105
 
80
106
  ### Graphical User Interface
@@ -11,6 +11,7 @@
11
11
  - **Syntax Highlighting**: Includes syntax highlighting for known file types in the generated Markdown file.
12
12
  - **Structure Only Option**: The `--structure-only` flag can be used to generate the Markdown file with just the directory structure, omitting the contents of the files.
13
13
  - **Gitignore Support**: Automatically respects `.gitignore` patterns to exclude files and directories.
14
+ - **Include and Exclude Patterns**: Use `--include` and `--exclude` to specify patterns for files and directories to include or exclude.
14
15
 
15
16
  ## Installation
16
17
 
@@ -35,7 +36,7 @@ pip install -r requirements.txt
35
36
  To use `reposnap` from the command line, run it with the following options:
36
37
 
37
38
  ```bash
38
- reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] path
39
+ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]] [-e EXCLUDE [EXCLUDE ...]] path
39
40
  ```
40
41
 
41
42
  - `path`: Path to the Git repository or subdirectory.
@@ -43,6 +44,19 @@ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] path
43
44
  - `-o, --output`: The name of the output Markdown file. Defaults to `output.md`.
44
45
  - `--structure-only`: Generate a Markdown file that includes only the project structure, without file contents.
45
46
  - `--debug`: Enable debug-level logging.
47
+ - `-i, --include`: File/folder patterns to include. For example, `-i "*.py"` includes only Python files.
48
+ - `-e, --exclude`: File/folder patterns to exclude. For example, `-e "*.md"` excludes all Markdown files.
49
+
50
+ #### Pattern Matching
51
+
52
+ - **Pattern Interpretation**: Patterns follow gitignore-style syntax but with a twist.
53
+ - **Patterns without Wildcards**: If a pattern does not contain any wildcard characters (`*`, `?`, or `[`), it is treated as `*pattern*`. This means it will match any file or directory containing `pattern` in its name.
54
+ - **Patterns with Wildcards**: If a pattern contains wildcard characters, it retains its original behavior.
55
+
56
+ - **Examples**:
57
+ - `-e "gui"`: Excludes any files or directories containing `"gui"` in their names.
58
+ - `-i "*.py"`: Includes only files ending with `.py`.
59
+ - `-e "*.test.*"`: Excludes files with `.test.` in their names.
46
60
 
47
61
  #### Examples
48
62
 
@@ -58,10 +72,22 @@ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] path
58
72
  reposnap my_project/ --structure-only
59
73
  ```
60
74
 
61
- 3. **Generate a Markdown file excluding certain files and directories**:
75
+ 3. **Generate a Markdown file including only Python files**:
76
+
77
+ ```bash
78
+ reposnap my_project/ -i "*.py"
79
+ ```
80
+
81
+ 4. **Generate a Markdown file excluding certain files and directories**:
82
+
83
+ ```bash
84
+ reposnap my_project/ -e "tests" -e "*.md"
85
+ ```
86
+
87
+ 5. **Exclude files and directories containing a substring**:
62
88
 
63
89
  ```bash
64
- reposnap my_project/ -o output.md
90
+ reposnap my_project/ -e "gui"
65
91
  ```
66
92
 
67
93
  ### Graphical User Interface
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "reposnap"
3
- version = "0.4.0"
3
+ version = "0.5.0"
4
4
  description = "Generate a Markdown file with all contents of your project"
5
5
  authors = [
6
6
  { name = "agoloborodko" }
@@ -19,6 +19,8 @@ class ProjectController:
19
19
  self.args: object = args
20
20
  self.file_tree: Optional[FileTree] = None
21
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 []
22
24
  if self.root_dir:
23
25
  self.gitignore_patterns = self._load_gitignore_patterns()
24
26
 
@@ -40,6 +42,30 @@ class ProjectController:
40
42
  git_files: List[Path] = git_repo.get_git_files()
41
43
  self.logger.debug(f"Git files before filtering: {git_files}")
42
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
+
43
69
  self.logger.info("Building tree structure.")
44
70
  file_system: FileSystem = FileSystem(self.root_dir)
45
71
  tree_structure: dict = file_system.build_tree_structure(git_files)
@@ -13,6 +13,12 @@ def main():
13
13
  help='Only include the file structure without content.')
14
14
  parser.add_argument('--debug', action='store_true', help='Enable debug-level logging.')
15
15
 
16
+ # New arguments for include and exclude patterns
17
+ parser.add_argument('-i', '--include', nargs='*', default=[],
18
+ help='File/folder patterns to include.')
19
+ parser.add_argument('-e', '--exclude', nargs='*', default=[],
20
+ help='File/folder patterns to exclude.')
21
+
16
22
  args = parser.parse_args()
17
23
 
18
24
  log_level = logging.DEBUG if args.debug else logging.INFO
@@ -21,5 +27,6 @@ def main():
21
27
  controller = ProjectController(args)
22
28
  controller.run()
23
29
 
30
+
24
31
  if __name__ == "__main__":
25
32
  main()
@@ -0,0 +1,306 @@
1
+ # tests/reposnap/test_project_controller.py
2
+
3
+ import pytest
4
+ import tempfile
5
+ import os
6
+ from pathlib import Path
7
+ from unittest.mock import patch, MagicMock
8
+ from reposnap.controllers.project_controller import ProjectController
9
+
10
+
11
+ def create_file(file_path: str, content: str = ''):
12
+ with open(file_path, 'w') as f:
13
+ f.write(content)
14
+
15
+
16
+ def create_directory_structure(base_dir: str, structure: dict):
17
+ for name, content in structure.items():
18
+ path = os.path.join(base_dir, name)
19
+ if isinstance(content, dict):
20
+ os.makedirs(path, exist_ok=True)
21
+ create_directory_structure(path, content)
22
+ else:
23
+ create_file(path, content)
24
+
25
+
26
+ def test_project_controller_includes_py_files():
27
+ with tempfile.TemporaryDirectory() as temp_dir:
28
+ gitignore_content = """
29
+ *.py[oc]
30
+ """
31
+ structure = {
32
+ 'src': {
33
+ 'module': {
34
+ 'file1.py': 'print("File 1")',
35
+ 'file2.py': 'print("File 2")',
36
+ 'file3.pyc': 'Compiled code',
37
+ }
38
+ },
39
+ '.gitignore': gitignore_content,
40
+ }
41
+
42
+ create_directory_structure(temp_dir, structure)
43
+
44
+ args = type('Args', (object,), {
45
+ 'path': temp_dir,
46
+ 'output': os.path.join(temp_dir, 'output.md'),
47
+ 'structure_only': False,
48
+ 'debug': False
49
+ })
50
+
51
+ with patch('reposnap.controllers.project_controller.GitRepo') as MockGitRepo:
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
+
59
+ controller = ProjectController(args)
60
+ controller.run()
61
+
62
+ # Read the output file
63
+ with open(args.output, 'r') as f:
64
+ output_content = f.read()
65
+
66
+ # Check that contents of .py files are included
67
+ assert 'print("File 1")' in output_content
68
+ assert 'print("File 2")' in output_content
69
+ # .pyc files should be ignored
70
+ assert 'Compiled code' not in output_content
71
+
72
+
73
+ @patch('reposnap.controllers.project_controller.MarkdownGenerator')
74
+ @patch('reposnap.controllers.project_controller.FileSystem')
75
+ @patch('reposnap.controllers.project_controller.GitRepo')
76
+ def test_project_controller_run(mock_git_repo, mock_file_system, mock_markdown_generator):
77
+ # Setup mocks
78
+ mock_git_repo_instance = MagicMock()
79
+ mock_file_system_instance = MagicMock()
80
+ mock_markdown_generator_instance = MagicMock()
81
+
82
+ mock_git_repo.return_value = mock_git_repo_instance
83
+ mock_file_system.return_value = mock_file_system_instance
84
+ mock_markdown_generator.return_value = mock_markdown_generator_instance
85
+
86
+ # Use Path objects instead of strings
87
+ mock_git_repo_instance.get_git_files.return_value = [Path('file1.py'), Path('file2.py')]
88
+ mock_file_system_instance.build_tree_structure.return_value = {'dir': {'file1.py': 'file1.py'}}
89
+
90
+ args = MagicMock()
91
+ args.path = 'root_dir'
92
+ args.output = 'output.md'
93
+ args.structure_only = False
94
+ args.include = []
95
+ args.exclude = []
96
+ args.debug = False # Add if necessary
97
+
98
+ controller = ProjectController(args)
99
+ controller.run()
100
+
101
+ mock_markdown_generator_instance.generate_markdown.assert_called_once()
102
+
103
+
104
+ def test_include_pattern():
105
+ with tempfile.TemporaryDirectory() as temp_dir:
106
+ structure = {
107
+ 'src': {
108
+ 'module': {
109
+ 'file1.py': 'print("File 1")',
110
+ 'file2.txt': 'File 2 content',
111
+ 'submodule': {
112
+ 'file3.py': 'print("File 3")',
113
+ 'file4.md': '# File 4',
114
+ }
115
+ }
116
+ },
117
+ 'README.md': '# Project README',
118
+ 'setup.py': 'setup code',
119
+ 'notes.txt': 'Some notes',
120
+ }
121
+
122
+ create_directory_structure(temp_dir, structure)
123
+
124
+ args = type('Args', (object,), {
125
+ 'path': temp_dir,
126
+ 'output': os.path.join(temp_dir, 'output.md'),
127
+ 'structure_only': False,
128
+ 'debug': False,
129
+ 'include': ['*.py'],
130
+ 'exclude': []
131
+ })
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
+
147
+ controller = ProjectController(args)
148
+ controller.collect_file_tree()
149
+
150
+ # Get the list of files included in the tree
151
+ included_files = []
152
+
153
+ def traverse(tree, path=''):
154
+ for name, node in tree.items():
155
+ current_path = os.path.join(path, name)
156
+ if isinstance(node, dict):
157
+ traverse(node, current_path)
158
+ else:
159
+ included_files.append(current_path)
160
+
161
+ traverse(controller.file_tree.structure)
162
+
163
+ expected_files = [
164
+ os.path.join('src', 'module', 'file1.py'),
165
+ os.path.join('src', 'module', 'submodule', 'file3.py'),
166
+ 'setup.py',
167
+ ]
168
+
169
+ assert sorted(included_files) == sorted(expected_files)
170
+
171
+
172
+ def test_exclude_pattern():
173
+ with tempfile.TemporaryDirectory() as temp_dir:
174
+ structure = {
175
+ 'src': {
176
+ 'module': {
177
+ 'file1.py': 'print("File 1")',
178
+ 'file2.txt': 'File 2 content',
179
+ 'submodule': {
180
+ 'file3.py': 'print("File 3")',
181
+ 'file4.md': '# File 4',
182
+ }
183
+ }
184
+ },
185
+ 'README.md': '# Project README',
186
+ 'setup.py': 'setup code',
187
+ 'notes.txt': 'Some notes',
188
+ }
189
+
190
+ create_directory_structure(temp_dir, structure)
191
+
192
+ args = type('Args', (object,), {
193
+ 'path': temp_dir,
194
+ 'output': os.path.join(temp_dir, 'output.md'),
195
+ 'structure_only': False,
196
+ 'debug': False,
197
+ 'include': [],
198
+ 'exclude': ['*.md', '*.txt']
199
+ })
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
+
214
+ controller = ProjectController(args)
215
+ controller.collect_file_tree()
216
+
217
+ included_files = []
218
+
219
+ def traverse(tree, path=''):
220
+ for name, node in tree.items():
221
+ current_path = os.path.join(path, name)
222
+ if isinstance(node, dict):
223
+ traverse(node, current_path)
224
+ else:
225
+ included_files.append(current_path)
226
+
227
+ traverse(controller.file_tree.structure)
228
+
229
+ expected_files = [
230
+ os.path.join('src', 'module', 'file1.py'),
231
+ os.path.join('src', 'module', 'submodule', 'file3.py'),
232
+ 'setup.py',
233
+ ]
234
+
235
+ assert sorted(included_files) == sorted(expected_files)
236
+
237
+
238
+ def test_include_and_exclude_patterns():
239
+ with tempfile.TemporaryDirectory() as temp_dir:
240
+ structure = {
241
+ 'src': {
242
+ 'foo_module': {
243
+ 'foo_file1.py': 'print("Foo File 1")',
244
+ 'file2.py': 'print("File 2")',
245
+ 'submodule': {
246
+ 'foo_file3.py': 'print("Foo File 3")',
247
+ 'file4.py': 'print("File 4")',
248
+ }
249
+ },
250
+ 'bar_module': {
251
+ 'bar_file1.py': 'print("Bar File 1")',
252
+ }
253
+ },
254
+ 'README.md': '# Project README',
255
+ 'setup.py': 'setup code',
256
+ 'notes.txt': 'Some notes',
257
+ }
258
+
259
+ create_directory_structure(temp_dir, structure)
260
+
261
+ args = type('Args', (object,), {
262
+ 'path': temp_dir,
263
+ 'output': os.path.join(temp_dir, 'output.md'),
264
+ 'structure_only': False,
265
+ 'debug': False,
266
+ 'include': ['*foo*'],
267
+ 'exclude': ['*submodule*']
268
+ })
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
+
283
+ controller = ProjectController(args)
284
+ controller.collect_file_tree()
285
+
286
+ included_files = []
287
+
288
+ def traverse(tree, path=''):
289
+ for name, node in tree.items():
290
+ current_path = os.path.join(path, name)
291
+ if isinstance(node, dict):
292
+ included_files.append(current_path)
293
+ traverse(node, current_path)
294
+ else:
295
+ included_files.append(current_path)
296
+
297
+ traverse(controller.file_tree.structure)
298
+
299
+ expected_files = [
300
+ os.path.join('src'),
301
+ os.path.join('src', 'foo_module'),
302
+ os.path.join('src', 'foo_module', 'foo_file1.py'),
303
+ os.path.join('src', 'foo_module', 'file2.py'), # Include this file
304
+ ]
305
+
306
+ assert sorted(included_files) == sorted(expected_files)
@@ -1,97 +0,0 @@
1
- # tests/reposnap/test_project_controller.py
2
-
3
- import pytest
4
- import tempfile
5
- import os
6
- from pathlib import Path
7
- from unittest.mock import patch, MagicMock
8
- from reposnap.controllers.project_controller import ProjectController
9
-
10
-
11
- def create_file(file_path: str, content: str = ''):
12
- with open(file_path, 'w') as f:
13
- f.write(content)
14
-
15
-
16
- def create_directory_structure(base_dir: str, structure: dict):
17
- for name, content in structure.items():
18
- path = os.path.join(base_dir, name)
19
- if isinstance(content, dict):
20
- os.makedirs(path, exist_ok=True)
21
- create_directory_structure(path, content)
22
- else:
23
- create_file(path, content)
24
-
25
-
26
- def test_project_controller_includes_py_files():
27
- with tempfile.TemporaryDirectory() as temp_dir:
28
- gitignore_content = """
29
- *.py[oc]
30
- """
31
- structure = {
32
- 'src': {
33
- 'module': {
34
- 'file1.py': 'print("File 1")',
35
- 'file2.py': 'print("File 2")',
36
- 'file3.pyc': 'Compiled code',
37
- }
38
- },
39
- '.gitignore': gitignore_content,
40
- }
41
-
42
- create_directory_structure(temp_dir, structure)
43
-
44
- args = type('Args', (object,), {
45
- 'path': temp_dir,
46
- 'output': os.path.join(temp_dir, 'output.md'),
47
- 'structure_only': False,
48
- 'debug': False
49
- })
50
-
51
- with patch('reposnap.controllers.project_controller.GitRepo') as MockGitRepo:
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
-
59
- controller = ProjectController(args)
60
- controller.run()
61
-
62
- # Read the output file
63
- with open(args.output, 'r') as f:
64
- output_content = f.read()
65
-
66
- # Check that contents of .py files are included
67
- assert 'print("File 1")' in output_content
68
- assert 'print("File 2")' in output_content
69
- # .pyc files should be ignored
70
- assert 'Compiled code' not in output_content
71
-
72
-
73
- @patch('reposnap.controllers.project_controller.MarkdownGenerator')
74
- @patch('reposnap.controllers.project_controller.FileSystem')
75
- @patch('reposnap.controllers.project_controller.GitRepo')
76
- def test_project_controller_run(mock_git_repo, mock_file_system, mock_markdown_generator):
77
- # Setup mocks
78
- mock_git_repo_instance = MagicMock()
79
- mock_file_system_instance = MagicMock()
80
- mock_markdown_generator_instance = MagicMock()
81
-
82
- mock_git_repo.return_value = mock_git_repo_instance
83
- mock_file_system.return_value = mock_file_system_instance
84
- mock_markdown_generator.return_value = mock_markdown_generator_instance
85
-
86
- mock_git_repo_instance.get_git_files.return_value = ['file1.py', 'file2.py']
87
- mock_file_system_instance.build_tree_structure.return_value = {'dir': {'file1.py': 'file1.py'}}
88
-
89
- args = MagicMock()
90
- args.path = 'root_dir'
91
- args.output = 'output.md'
92
- args.structure_only = False
93
-
94
- controller = ProjectController(args)
95
- controller.run()
96
-
97
- mock_markdown_generator_instance.generate_markdown.assert_called_once()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes