reposnap 0.3.2__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 (40) hide show
  1. reposnap-0.5.0/PKG-INFO +150 -0
  2. reposnap-0.5.0/README.md +137 -0
  3. {reposnap-0.3.2 → reposnap-0.5.0}/pyproject.toml +4 -2
  4. {reposnap-0.3.2 → reposnap-0.5.0}/requirements-dev.lock +6 -0
  5. {reposnap-0.3.2 → reposnap-0.5.0}/requirements.lock +6 -0
  6. reposnap-0.5.0/src/reposnap/controllers/project_controller.py +122 -0
  7. {reposnap-0.3.2 → reposnap-0.5.0}/src/reposnap/core/markdown_generator.py +3 -3
  8. {reposnap-0.3.2 → reposnap-0.5.0}/src/reposnap/interfaces/cli.py +8 -1
  9. reposnap-0.5.0/src/reposnap/interfaces/gui.py +155 -0
  10. {reposnap-0.3.2 → reposnap-0.5.0}/src/reposnap/models/file_tree.py +24 -0
  11. reposnap-0.5.0/src/reposnap/utils/path_utils.py +13 -0
  12. reposnap-0.5.0/tests/reposnap/__init__.py +0 -0
  13. reposnap-0.5.0/tests/reposnap/test_gui.py +103 -0
  14. reposnap-0.5.0/tests/reposnap/test_project_controller.py +306 -0
  15. reposnap-0.3.2/PKG-INFO +0 -88
  16. reposnap-0.3.2/README.md +0 -76
  17. reposnap-0.3.2/src/reposnap/__init__.py +0 -4
  18. reposnap-0.3.2/src/reposnap/controllers/project_controller.py +0 -74
  19. reposnap-0.3.2/src/reposnap/utils/path_utils.py +0 -11
  20. reposnap-0.3.2/tests/reposnap/test_project_controller.py +0 -97
  21. {reposnap-0.3.2 → reposnap-0.5.0}/.gitignore +0 -0
  22. {reposnap-0.3.2 → reposnap-0.5.0}/.python-version +0 -0
  23. {reposnap-0.3.2 → reposnap-0.5.0}/LICENSE +0 -0
  24. {reposnap-0.3.2/src/reposnap/controllers → reposnap-0.5.0/src/reposnap}/__init__.py +0 -0
  25. {reposnap-0.3.2/src/reposnap/core → reposnap-0.5.0/src/reposnap/controllers}/__init__.py +0 -0
  26. {reposnap-0.3.2/src/reposnap/interfaces → reposnap-0.5.0/src/reposnap/core}/__init__.py +0 -0
  27. {reposnap-0.3.2 → reposnap-0.5.0}/src/reposnap/core/file_system.py +0 -0
  28. {reposnap-0.3.2 → reposnap-0.5.0}/src/reposnap/core/git_repo.py +0 -0
  29. {reposnap-0.3.2/src/reposnap/models → reposnap-0.5.0/src/reposnap/interfaces}/__init__.py +0 -0
  30. {reposnap-0.3.2/src/reposnap/utils → reposnap-0.5.0/src/reposnap/models}/__init__.py +0 -0
  31. {reposnap-0.3.2/tests → reposnap-0.5.0/src/reposnap/utils}/__init__.py +0 -0
  32. {reposnap-0.3.2/tests/reposnap → reposnap-0.5.0/tests}/__init__.py +0 -0
  33. {reposnap-0.3.2 → reposnap-0.5.0}/tests/reposnap/test_cli.py +0 -0
  34. {reposnap-0.3.2 → reposnap-0.5.0}/tests/reposnap/test_file_system.py +0 -0
  35. {reposnap-0.3.2 → reposnap-0.5.0}/tests/reposnap/test_file_tree.py +0 -0
  36. {reposnap-0.3.2 → reposnap-0.5.0}/tests/reposnap/test_git_repo.py +0 -0
  37. {reposnap-0.3.2 → reposnap-0.5.0}/tests/reposnap/test_markdown_generator.py +0 -0
  38. {reposnap-0.3.2 → reposnap-0.5.0}/tests/reposnap/test_path_utils.py +0 -0
  39. {reposnap-0.3.2 → reposnap-0.5.0}/tests/resources/another_existing_file.py +0 -0
  40. {reposnap-0.3.2 → reposnap-0.5.0}/tests/resources/existing_file.py +0 -0
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.3
2
+ Name: reposnap
3
+ Version: 0.5.0
4
+ Summary: Generate a Markdown file with all contents of your project
5
+ Author: agoloborodko
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.8
8
+ Requires-Dist: gitpython>=3.1.43
9
+ Requires-Dist: pathlib>=1.0.1
10
+ Requires-Dist: pathspec>=0.12.1
11
+ Requires-Dist: urwid>=2.6.15
12
+ Description-Content-Type: text/markdown
13
+
14
+ # RepoSnap
15
+
16
+ ## Overview
17
+
18
+ `reposnap` is a Python tool designed to generate a Markdown file that documents the structure and contents of a Git project. It provides both a command-line interface (CLI) and a graphical user interface (GUI) for ease of use. This tool is particularly useful for creating a quick overview of a project's file hierarchy, including optional syntax-highlighted code snippets.
19
+
20
+ ## Features
21
+
22
+ - **Command-Line Interface (CLI)**: Quickly generate documentation from the terminal.
23
+ - **Graphical User Interface (GUI)**: A user-friendly GUI if you want to select files and directories interactively.
24
+ - **Syntax Highlighting**: Includes syntax highlighting for known file types in the generated Markdown file.
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
+ - **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.
28
+
29
+ ## Installation
30
+
31
+ You can install `reposnap` using pip:
32
+
33
+ ```bash
34
+ pip install reposnap
35
+ ```
36
+
37
+ Alternatively, you can clone the repository and install the required dependencies:
38
+
39
+ ```bash
40
+ git clone https://github.com/username/reposnap.git
41
+ cd reposnap
42
+ pip install -r requirements.txt
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ ### Command-Line Interface
48
+
49
+ To use `reposnap` from the command line, run it with the following options:
50
+
51
+ ```bash
52
+ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]] [-e EXCLUDE [EXCLUDE ...]] path
53
+ ```
54
+
55
+ - `path`: Path to the Git repository or subdirectory.
56
+ - `-h, --help`: Show help message and exit.
57
+ - `-o, --output`: The name of the output Markdown file. Defaults to `output.md`.
58
+ - `--structure-only`: Generate a Markdown file that includes only the project structure, without file contents.
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.
73
+
74
+ #### Examples
75
+
76
+ 1. **Generate a full project structure with file contents**:
77
+
78
+ ```bash
79
+ reposnap .
80
+ ```
81
+
82
+ 2. **Generate a project structure only**:
83
+
84
+ ```bash
85
+ reposnap my_project/ --structure-only
86
+ ```
87
+
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**:
101
+
102
+ ```bash
103
+ reposnap my_project/ -e "gui"
104
+ ```
105
+
106
+ ### Graphical User Interface
107
+
108
+ `reposnap` also provides a GUI for users who prefer an interactive interface.
109
+
110
+ To launch the GUI, simply run:
111
+
112
+ ```bash
113
+ reposnap-gui
114
+ ```
115
+
116
+ #### Using the GUI
117
+
118
+ 1. **Select Root Directory**: When the GUI opens, you can specify the root directory of your Git project. By default, it uses the current directory.
119
+
120
+ 2. **Scan the Project**: Click the "Scan" button to analyze the project. The GUI will display the file tree of your project.
121
+
122
+ 3. **Select Files and Directories**: Use the checkboxes to select which files and directories you want to include in the Markdown documentation. Toggling a directory checkbox will toggle all its child files and directories.
123
+
124
+ 4. **Generate Markdown**: After selecting the desired files, click the "Render" button. The Markdown file will be generated and saved as `output.md` in the current directory.
125
+
126
+ 5. **Exit**: Click the "Exit" button to close the GUI.
127
+
128
+ ## Testing
129
+
130
+ To run the tests, use the following command:
131
+
132
+ ```bash
133
+ pytest tests/
134
+ ```
135
+
136
+ Ensure that you have the `pytest` library installed:
137
+
138
+ ```bash
139
+ pip install pytest
140
+ ```
141
+
142
+ ## License
143
+
144
+ This project is licensed under the MIT License.
145
+
146
+ ## Acknowledgments
147
+
148
+ - [GitPython](https://gitpython.readthedocs.io/) - Used for interacting with Git repositories.
149
+ - [pathspec](https://pathspec.readthedocs.io/) - Used for pattern matching file paths.
150
+ - [Urwid](https://urwid.org/) - Used for creating the GUI interface.
@@ -0,0 +1,137 @@
1
+ # RepoSnap
2
+
3
+ ## Overview
4
+
5
+ `reposnap` is a Python tool designed to generate a Markdown file that documents the structure and contents of a Git project. It provides both a command-line interface (CLI) and a graphical user interface (GUI) for ease of use. This tool is particularly useful for creating a quick overview of a project's file hierarchy, including optional syntax-highlighted code snippets.
6
+
7
+ ## Features
8
+
9
+ - **Command-Line Interface (CLI)**: Quickly generate documentation from the terminal.
10
+ - **Graphical User Interface (GUI)**: A user-friendly GUI if you want to select files and directories interactively.
11
+ - **Syntax Highlighting**: Includes syntax highlighting for known file types in the generated Markdown file.
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
+ - **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.
15
+
16
+ ## Installation
17
+
18
+ You can install `reposnap` using pip:
19
+
20
+ ```bash
21
+ pip install reposnap
22
+ ```
23
+
24
+ Alternatively, you can clone the repository and install the required dependencies:
25
+
26
+ ```bash
27
+ git clone https://github.com/username/reposnap.git
28
+ cd reposnap
29
+ pip install -r requirements.txt
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ### Command-Line Interface
35
+
36
+ To use `reposnap` from the command line, run it with the following options:
37
+
38
+ ```bash
39
+ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]] [-e EXCLUDE [EXCLUDE ...]] path
40
+ ```
41
+
42
+ - `path`: Path to the Git repository or subdirectory.
43
+ - `-h, --help`: Show help message and exit.
44
+ - `-o, --output`: The name of the output Markdown file. Defaults to `output.md`.
45
+ - `--structure-only`: Generate a Markdown file that includes only the project structure, without file contents.
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.
60
+
61
+ #### Examples
62
+
63
+ 1. **Generate a full project structure with file contents**:
64
+
65
+ ```bash
66
+ reposnap .
67
+ ```
68
+
69
+ 2. **Generate a project structure only**:
70
+
71
+ ```bash
72
+ reposnap my_project/ --structure-only
73
+ ```
74
+
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**:
88
+
89
+ ```bash
90
+ reposnap my_project/ -e "gui"
91
+ ```
92
+
93
+ ### Graphical User Interface
94
+
95
+ `reposnap` also provides a GUI for users who prefer an interactive interface.
96
+
97
+ To launch the GUI, simply run:
98
+
99
+ ```bash
100
+ reposnap-gui
101
+ ```
102
+
103
+ #### Using the GUI
104
+
105
+ 1. **Select Root Directory**: When the GUI opens, you can specify the root directory of your Git project. By default, it uses the current directory.
106
+
107
+ 2. **Scan the Project**: Click the "Scan" button to analyze the project. The GUI will display the file tree of your project.
108
+
109
+ 3. **Select Files and Directories**: Use the checkboxes to select which files and directories you want to include in the Markdown documentation. Toggling a directory checkbox will toggle all its child files and directories.
110
+
111
+ 4. **Generate Markdown**: After selecting the desired files, click the "Render" button. The Markdown file will be generated and saved as `output.md` in the current directory.
112
+
113
+ 5. **Exit**: Click the "Exit" button to close the GUI.
114
+
115
+ ## Testing
116
+
117
+ To run the tests, use the following command:
118
+
119
+ ```bash
120
+ pytest tests/
121
+ ```
122
+
123
+ Ensure that you have the `pytest` library installed:
124
+
125
+ ```bash
126
+ pip install pytest
127
+ ```
128
+
129
+ ## License
130
+
131
+ This project is licensed under the MIT License.
132
+
133
+ ## Acknowledgments
134
+
135
+ - [GitPython](https://gitpython.readthedocs.io/) - Used for interacting with Git repositories.
136
+ - [pathspec](https://pathspec.readthedocs.io/) - Used for pattern matching file paths.
137
+ - [Urwid](https://urwid.org/) - Used for creating the GUI interface.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "reposnap"
3
- version = "0.3.2"
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" }
@@ -9,6 +9,7 @@ dependencies = [
9
9
  "pathlib>=1.0.1",
10
10
  "pathspec>=0.12.1",
11
11
  "gitpython>=3.1.43",
12
+ "urwid>=2.6.15",
12
13
  ]
13
14
  readme = "README.md"
14
15
  requires-python = ">= 3.8"
@@ -30,4 +31,5 @@ allow-direct-references = true
30
31
  packages = ["src/reposnap"]
31
32
 
32
33
  [project.scripts]
33
- reposnap = "reposnap:main"
34
+ reposnap = "reposnap.interfaces.cli:main"
35
+ reposnap-gui = "reposnap.interfaces.gui:main"
@@ -27,3 +27,9 @@ pluggy==1.5.0
27
27
  pytest==8.3.2
28
28
  smmap==5.0.1
29
29
  # via gitdb
30
+ typing-extensions==4.12.2
31
+ # via urwid
32
+ urwid==2.6.15
33
+ # via reposnap
34
+ wcwidth==0.2.13
35
+ # via urwid
@@ -20,3 +20,9 @@ pathspec==0.12.1
20
20
  # via reposnap
21
21
  smmap==5.0.1
22
22
  # via gitdb
23
+ typing-extensions==4.12.2
24
+ # via urwid
25
+ urwid==2.6.15
26
+ # via reposnap
27
+ wcwidth==0.2.13
28
+ # via urwid
@@ -0,0 +1,122 @@
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 []
@@ -7,10 +7,11 @@ from typing import List, Dict, Any
7
7
 
8
8
 
9
9
  class MarkdownGenerator:
10
- def __init__(self, root_dir: Path, output_file: Path, structure_only: bool = False):
10
+ def __init__(self, root_dir: Path, output_file: Path, structure_only: bool = False, hide_untoggled: bool = False):
11
11
  self.root_dir: Path = root_dir.resolve()
12
12
  self.output_file: Path = output_file.resolve()
13
13
  self.structure_only: bool = structure_only
14
+ self.hide_untoggled: bool = hide_untoggled
14
15
  self.logger = logging.getLogger(__name__)
15
16
 
16
17
  def generate_markdown(self, tree_structure: Dict[str, Any], files: List[Path]) -> None:
@@ -34,7 +35,7 @@ class MarkdownGenerator:
34
35
  with self.output_file.open('w', encoding='utf-8') as f:
35
36
  f.write("# Project Structure\n\n")
36
37
  f.write("```\n")
37
- for line in format_tree(tree_structure):
38
+ for line in format_tree(tree_structure, hide_untoggled=self.hide_untoggled):
38
39
  f.write(line)
39
40
  f.write("```\n\n")
40
41
  self.logger.debug("Header and project structure written successfully.")
@@ -72,4 +73,3 @@ class MarkdownGenerator:
72
73
  f.write(f"{content}\n```\n\n")
73
74
  except IOError as e:
74
75
  self.logger.error(f"Error reading or writing file {file_path}: {e}")
75
-
@@ -7,12 +7,18 @@ from reposnap.controllers.project_controller import ProjectController
7
7
 
8
8
  def main():
9
9
  parser = argparse.ArgumentParser(description='Generate a Markdown representation of a Git repository.')
10
- parser.add_argument('path', help='Path to the Git repository or subdirectory.')
10
+ parser.add_argument('path', nargs='?', default='.', help='Path to the Git repository or subdirectory.')
11
11
  parser.add_argument('-o', '--output', help='Output Markdown file', default='output.md')
12
12
  parser.add_argument('--structure-only', action='store_true',
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,155 @@
1
+ # src/reposnap/interfaces/gui.py
2
+
3
+ import urwid
4
+ from pathlib import Path
5
+ from reposnap.controllers.project_controller import ProjectController
6
+
7
+
8
+ class MyCheckBox(urwid.CheckBox):
9
+ def __init__(self, label, user_data=None, **kwargs):
10
+ super().__init__(label, **kwargs)
11
+ self.user_data = user_data
12
+
13
+
14
+ class RepoSnapGUI:
15
+ def __init__(self):
16
+ self.controller = ProjectController()
17
+ self.root_dir = Path('.').resolve()
18
+ self.file_tree = None
19
+ self.selected_files = set()
20
+
21
+ self.main_loop = None
22
+ self.build_main_menu()
23
+
24
+ def build_main_menu(self):
25
+ self.root_dir_edit = urwid.Edit(('bold', "Root Directory: "), str(self.root_dir))
26
+ scan_button = urwid.Button("Scan", on_press=self.on_scan)
27
+
28
+ main_menu = urwid.Frame(
29
+ header=urwid.Text(('bold', "RepoSnap - Main Menu")),
30
+ body=urwid.Padding(
31
+ urwid.LineBox(
32
+ urwid.ListBox(
33
+ urwid.SimpleFocusListWalker([
34
+ self.root_dir_edit
35
+ ])
36
+ ),
37
+ title="Enter Root Directory"
38
+ ),
39
+ left=2, right=2
40
+ ),
41
+ footer=urwid.Padding(scan_button, align='center')
42
+ )
43
+
44
+ self.main_widget = main_menu
45
+
46
+ def on_scan(self, button):
47
+ self.root_dir = Path(self.root_dir_edit.edit_text).resolve()
48
+ self.controller.set_root_dir(self.root_dir)
49
+ self.controller.collect_file_tree()
50
+ self.file_tree = self.controller.get_file_tree()
51
+ self.build_file_tree_menu()
52
+
53
+ def build_file_tree_menu(self):
54
+ tree_widgets = self.build_tree_widget(self.file_tree.structure)
55
+ tree_listbox = urwid.ListBox(urwid.SimpleFocusListWalker(tree_widgets))
56
+ render_button = urwid.Button("Render", on_press=self.on_render)
57
+
58
+ tree_menu = urwid.Frame(
59
+ header=urwid.Text(('bold', f"File Tree of {self.root_dir}")),
60
+ body=urwid.LineBox(tree_listbox),
61
+ footer=urwid.Padding(render_button, align='center')
62
+ )
63
+
64
+ self.main_widget = tree_menu
65
+ self.refresh()
66
+
67
+ def build_tree_widget(self, tree_structure, parent_path="", level=0):
68
+ widgets = []
69
+ for key, value in sorted(tree_structure.items()):
70
+ node_path = f"{parent_path}/{key}".lstrip('/')
71
+ checkbox = MyCheckBox(
72
+ key,
73
+ user_data={'path': node_path, 'level': level},
74
+ state=False,
75
+ on_state_change=self.on_checkbox_change
76
+ )
77
+ indented_checkbox = urwid.Padding(checkbox, left=4*level)
78
+ widgets.append(indented_checkbox)
79
+ if isinstance(value, dict):
80
+ widgets.extend(self.build_tree_widget(value, node_path, level=level+1))
81
+ return widgets
82
+
83
+ def on_checkbox_change(self, checkbox, state):
84
+ user_data = checkbox.user_data
85
+ node_path = user_data['path']
86
+ level = user_data['level']
87
+ if state:
88
+ self.selected_files.add(node_path)
89
+ else:
90
+ self.selected_files.discard(node_path)
91
+ # Handle toggling all children
92
+ self.toggle_children(checkbox, state, level)
93
+
94
+ def toggle_children(self, checkbox, state, level):
95
+ listbox = self.main_widget.body.original_widget.body
96
+ walker = listbox
97
+ # Find the index of the Padding widget that contains the checkbox
98
+ idx = None
99
+ for i, widget in enumerate(walker):
100
+ if isinstance(widget, urwid.Padding) and widget.original_widget == checkbox:
101
+ idx = i
102
+ break
103
+ if idx is None:
104
+ return
105
+ idx += 1
106
+ while idx < len(walker):
107
+ widget = walker[idx]
108
+ if isinstance(widget, urwid.Padding):
109
+ checkbox_widget = widget.original_widget
110
+ widget_user_data = checkbox_widget.user_data
111
+ widget_level = widget_user_data['level']
112
+ if widget_level > level:
113
+ checkbox_widget.set_state(state, do_callback=False)
114
+ node_path = widget_user_data['path']
115
+ if state:
116
+ self.selected_files.add(node_path)
117
+ else:
118
+ self.selected_files.discard(node_path)
119
+ idx += 1
120
+ else:
121
+ break
122
+ else:
123
+ idx += 1
124
+
125
+ def on_render(self, button):
126
+ self.controller.generate_output_from_selected(self.selected_files)
127
+ message = urwid.Text(('bold', f"Markdown generated at {self.controller.output_file}"))
128
+ exit_button = urwid.Button("Exit", on_press=self.exit_program)
129
+ result_menu = urwid.Frame(
130
+ header=urwid.Text(('bold', "Success")),
131
+ body=urwid.Filler(message, valign='middle'),
132
+ footer=urwid.Padding(exit_button, align='center')
133
+ )
134
+ self.main_widget = result_menu
135
+ self.refresh()
136
+
137
+ def refresh(self):
138
+ if self.main_loop:
139
+ self.main_loop.widget = self.main_widget
140
+
141
+ def exit_program(self, button):
142
+ raise urwid.ExitMainLoop()
143
+
144
+ def run(self):
145
+ self.main_loop = urwid.MainLoop(self.main_widget)
146
+ self.main_loop.run()
147
+
148
+
149
+ def main():
150
+ app = RepoSnapGUI()
151
+ app.run()
152
+
153
+
154
+ if __name__ == "__main__":
155
+ main()