reposnap 0.3.2__tar.gz → 0.4.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 (38) hide show
  1. reposnap-0.4.0/PKG-INFO +124 -0
  2. reposnap-0.4.0/README.md +111 -0
  3. {reposnap-0.3.2 → reposnap-0.4.0}/pyproject.toml +4 -2
  4. {reposnap-0.3.2 → reposnap-0.4.0}/requirements-dev.lock +6 -0
  5. {reposnap-0.3.2 → reposnap-0.4.0}/requirements.lock +6 -0
  6. {reposnap-0.3.2 → reposnap-0.4.0}/src/reposnap/controllers/project_controller.py +28 -6
  7. {reposnap-0.3.2 → reposnap-0.4.0}/src/reposnap/core/markdown_generator.py +3 -3
  8. {reposnap-0.3.2 → reposnap-0.4.0}/src/reposnap/interfaces/cli.py +1 -1
  9. reposnap-0.4.0/src/reposnap/interfaces/gui.py +155 -0
  10. {reposnap-0.3.2 → reposnap-0.4.0}/src/reposnap/models/file_tree.py +24 -0
  11. reposnap-0.4.0/src/reposnap/utils/path_utils.py +13 -0
  12. reposnap-0.4.0/tests/reposnap/__init__.py +0 -0
  13. reposnap-0.4.0/tests/reposnap/test_gui.py +103 -0
  14. reposnap-0.3.2/PKG-INFO +0 -88
  15. reposnap-0.3.2/README.md +0 -76
  16. reposnap-0.3.2/src/reposnap/__init__.py +0 -4
  17. reposnap-0.3.2/src/reposnap/utils/path_utils.py +0 -11
  18. {reposnap-0.3.2 → reposnap-0.4.0}/.gitignore +0 -0
  19. {reposnap-0.3.2 → reposnap-0.4.0}/.python-version +0 -0
  20. {reposnap-0.3.2 → reposnap-0.4.0}/LICENSE +0 -0
  21. {reposnap-0.3.2/src/reposnap/controllers → reposnap-0.4.0/src/reposnap}/__init__.py +0 -0
  22. {reposnap-0.3.2/src/reposnap/core → reposnap-0.4.0/src/reposnap/controllers}/__init__.py +0 -0
  23. {reposnap-0.3.2/src/reposnap/interfaces → reposnap-0.4.0/src/reposnap/core}/__init__.py +0 -0
  24. {reposnap-0.3.2 → reposnap-0.4.0}/src/reposnap/core/file_system.py +0 -0
  25. {reposnap-0.3.2 → reposnap-0.4.0}/src/reposnap/core/git_repo.py +0 -0
  26. {reposnap-0.3.2/src/reposnap/models → reposnap-0.4.0/src/reposnap/interfaces}/__init__.py +0 -0
  27. {reposnap-0.3.2/src/reposnap/utils → reposnap-0.4.0/src/reposnap/models}/__init__.py +0 -0
  28. {reposnap-0.3.2/tests → reposnap-0.4.0/src/reposnap/utils}/__init__.py +0 -0
  29. {reposnap-0.3.2/tests/reposnap → reposnap-0.4.0/tests}/__init__.py +0 -0
  30. {reposnap-0.3.2 → reposnap-0.4.0}/tests/reposnap/test_cli.py +0 -0
  31. {reposnap-0.3.2 → reposnap-0.4.0}/tests/reposnap/test_file_system.py +0 -0
  32. {reposnap-0.3.2 → reposnap-0.4.0}/tests/reposnap/test_file_tree.py +0 -0
  33. {reposnap-0.3.2 → reposnap-0.4.0}/tests/reposnap/test_git_repo.py +0 -0
  34. {reposnap-0.3.2 → reposnap-0.4.0}/tests/reposnap/test_markdown_generator.py +0 -0
  35. {reposnap-0.3.2 → reposnap-0.4.0}/tests/reposnap/test_path_utils.py +0 -0
  36. {reposnap-0.3.2 → reposnap-0.4.0}/tests/reposnap/test_project_controller.py +0 -0
  37. {reposnap-0.3.2 → reposnap-0.4.0}/tests/resources/another_existing_file.py +0 -0
  38. {reposnap-0.3.2 → reposnap-0.4.0}/tests/resources/existing_file.py +0 -0
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.3
2
+ Name: reposnap
3
+ Version: 0.4.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
+
28
+ ## Installation
29
+
30
+ You can install `reposnap` using pip:
31
+
32
+ ```bash
33
+ pip install reposnap
34
+ ```
35
+
36
+ Alternatively, you can clone the repository and install the required dependencies:
37
+
38
+ ```bash
39
+ git clone https://github.com/username/reposnap.git
40
+ cd reposnap
41
+ pip install -r requirements.txt
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ### Command-Line Interface
47
+
48
+ To use `reposnap` from the command line, run it with the following options:
49
+
50
+ ```bash
51
+ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] path
52
+ ```
53
+
54
+ - `path`: Path to the Git repository or subdirectory.
55
+ - `-h, --help`: Show help message and exit.
56
+ - `-o, --output`: The name of the output Markdown file. Defaults to `output.md`.
57
+ - `--structure-only`: Generate a Markdown file that includes only the project structure, without file contents.
58
+ - `--debug`: Enable debug-level logging.
59
+
60
+ #### Examples
61
+
62
+ 1. **Generate a full project structure with file contents**:
63
+
64
+ ```bash
65
+ reposnap .
66
+ ```
67
+
68
+ 2. **Generate a project structure only**:
69
+
70
+ ```bash
71
+ reposnap my_project/ --structure-only
72
+ ```
73
+
74
+ 3. **Generate a Markdown file excluding certain files and directories**:
75
+
76
+ ```bash
77
+ reposnap my_project/ -o output.md
78
+ ```
79
+
80
+ ### Graphical User Interface
81
+
82
+ `reposnap` also provides a GUI for users who prefer an interactive interface.
83
+
84
+ To launch the GUI, simply run:
85
+
86
+ ```bash
87
+ reposnap-gui
88
+ ```
89
+
90
+ #### Using the GUI
91
+
92
+ 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.
93
+
94
+ 2. **Scan the Project**: Click the "Scan" button to analyze the project. The GUI will display the file tree of your project.
95
+
96
+ 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.
97
+
98
+ 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.
99
+
100
+ 5. **Exit**: Click the "Exit" button to close the GUI.
101
+
102
+ ## Testing
103
+
104
+ To run the tests, use the following command:
105
+
106
+ ```bash
107
+ pytest tests/
108
+ ```
109
+
110
+ Ensure that you have the `pytest` library installed:
111
+
112
+ ```bash
113
+ pip install pytest
114
+ ```
115
+
116
+ ## License
117
+
118
+ This project is licensed under the MIT License.
119
+
120
+ ## Acknowledgments
121
+
122
+ - [GitPython](https://gitpython.readthedocs.io/) - Used for interacting with Git repositories.
123
+ - [pathspec](https://pathspec.readthedocs.io/) - Used for pattern matching file paths.
124
+ - [Urwid](https://urwid.org/) - Used for creating the GUI interface.
@@ -0,0 +1,111 @@
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
+
15
+ ## Installation
16
+
17
+ You can install `reposnap` using pip:
18
+
19
+ ```bash
20
+ pip install reposnap
21
+ ```
22
+
23
+ Alternatively, you can clone the repository and install the required dependencies:
24
+
25
+ ```bash
26
+ git clone https://github.com/username/reposnap.git
27
+ cd reposnap
28
+ pip install -r requirements.txt
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ### Command-Line Interface
34
+
35
+ To use `reposnap` from the command line, run it with the following options:
36
+
37
+ ```bash
38
+ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] path
39
+ ```
40
+
41
+ - `path`: Path to the Git repository or subdirectory.
42
+ - `-h, --help`: Show help message and exit.
43
+ - `-o, --output`: The name of the output Markdown file. Defaults to `output.md`.
44
+ - `--structure-only`: Generate a Markdown file that includes only the project structure, without file contents.
45
+ - `--debug`: Enable debug-level logging.
46
+
47
+ #### Examples
48
+
49
+ 1. **Generate a full project structure with file contents**:
50
+
51
+ ```bash
52
+ reposnap .
53
+ ```
54
+
55
+ 2. **Generate a project structure only**:
56
+
57
+ ```bash
58
+ reposnap my_project/ --structure-only
59
+ ```
60
+
61
+ 3. **Generate a Markdown file excluding certain files and directories**:
62
+
63
+ ```bash
64
+ reposnap my_project/ -o output.md
65
+ ```
66
+
67
+ ### Graphical User Interface
68
+
69
+ `reposnap` also provides a GUI for users who prefer an interactive interface.
70
+
71
+ To launch the GUI, simply run:
72
+
73
+ ```bash
74
+ reposnap-gui
75
+ ```
76
+
77
+ #### Using the GUI
78
+
79
+ 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.
80
+
81
+ 2. **Scan the Project**: Click the "Scan" button to analyze the project. The GUI will display the file tree of your project.
82
+
83
+ 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.
84
+
85
+ 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.
86
+
87
+ 5. **Exit**: Click the "Exit" button to close the GUI.
88
+
89
+ ## Testing
90
+
91
+ To run the tests, use the following command:
92
+
93
+ ```bash
94
+ pytest tests/
95
+ ```
96
+
97
+ Ensure that you have the `pytest` library installed:
98
+
99
+ ```bash
100
+ pip install pytest
101
+ ```
102
+
103
+ ## License
104
+
105
+ This project is licensed under the MIT License.
106
+
107
+ ## Acknowledgments
108
+
109
+ - [GitPython](https://gitpython.readthedocs.io/) - Used for interacting with Git repositories.
110
+ - [pathspec](https://pathspec.readthedocs.io/) - Used for pattern matching file paths.
111
+ - [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.4.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
@@ -11,14 +11,23 @@ from typing import List, Optional
11
11
 
12
12
 
13
13
  class ProjectController:
14
- def __init__(self, args: object):
14
+ def __init__(self, args: Optional[object] = None):
15
15
  self.logger = logging.getLogger(__name__)
16
- self.root_dir: Path = Path(args.path).resolve()
17
- self.output_file: Path = Path(args.output).resolve()
18
- self.structure_only: bool = args.structure_only
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
19
  self.args: object = args
20
20
  self.file_tree: Optional[FileTree] = None
21
- self.gitignore_patterns: List[str] = self._load_gitignore_patterns()
21
+ self.gitignore_patterns: List[str] = []
22
+ if self.root_dir:
23
+ self.gitignore_patterns = self._load_gitignore_patterns()
24
+
25
+ def set_root_dir(self, root_dir: Path) -> None:
26
+ self.root_dir = root_dir
27
+ self.gitignore_patterns = self._load_gitignore_patterns()
28
+
29
+ def get_file_tree(self) -> Optional[FileTree]:
30
+ return self.file_tree
22
31
 
23
32
  def run(self) -> None:
24
33
  self.collect_file_tree()
@@ -52,7 +61,20 @@ class ProjectController:
52
61
  structure_only=self.structure_only
53
62
  )
54
63
  markdown_generator.generate_markdown(self.file_tree.structure, self.file_tree.get_all_files())
55
- self.logger.info("Markdown generation completed.")
64
+ self.logger.info(f"Markdown generated at {self.output_file}.")
65
+
66
+ def generate_output_from_selected(self, selected_files: set) -> None:
67
+ self.logger.info("Generating markdown from selected files.")
68
+ # Build a pruned tree structure based on selected files
69
+ pruned_tree = self.file_tree.prune_tree(selected_files)
70
+ markdown_generator: MarkdownGenerator = MarkdownGenerator(
71
+ root_dir=self.root_dir,
72
+ output_file=self.output_file,
73
+ structure_only=False,
74
+ hide_untoggled=True
75
+ )
76
+ markdown_generator.generate_markdown(pruned_tree, [Path(f) for f in selected_files])
77
+ self.logger.info(f"Markdown generated at {self.output_file}.")
56
78
 
57
79
  def _load_gitignore_patterns(self) -> List[str]:
58
80
  gitignore_path: Path = self.root_dir / '.gitignore'
@@ -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,7 +7,7 @@ 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.')
@@ -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()
@@ -52,3 +52,27 @@ class FileTree:
52
52
  filtered_subtree[key] = value
53
53
  return filtered_subtree
54
54
 
55
+ def prune_tree(self, selected_files: set) -> Dict[str, Any]:
56
+ """
57
+ Prunes the tree to include only the selected files and their directories.
58
+
59
+ Args:
60
+ selected_files (set): Set of selected file paths.
61
+
62
+ Returns:
63
+ Dict[str, Any]: Pruned tree structure.
64
+ """
65
+ return self._prune_tree(self.structure, selected_files)
66
+
67
+ def _prune_tree(self, subtree: Dict[str, Any], selected_files: set, path_prefix: str = '') -> Dict[str, Any]:
68
+ pruned_subtree: Dict[str, Any] = {}
69
+ for key, value in subtree.items():
70
+ current_path: str = f"{path_prefix}/{key}".lstrip('/')
71
+ if isinstance(value, dict):
72
+ pruned_value: Dict[str, Any] = self._prune_tree(value, selected_files, current_path)
73
+ if pruned_value:
74
+ pruned_subtree[key] = pruned_value
75
+ else:
76
+ if current_path in selected_files:
77
+ pruned_subtree[key] = value
78
+ return pruned_subtree
@@ -0,0 +1,13 @@
1
+ # src/reposnap/utils/path_utils.py
2
+ from typing import Dict, Generator, Any
3
+
4
+
5
+ def format_tree(tree: Dict[str, Any], indent: str = '', hide_untoggled: bool = False) -> Generator[str, None, None]:
6
+ for key, value in tree.items():
7
+ if value == '<hidden>':
8
+ yield f"{indent}<...>\n"
9
+ elif isinstance(value, dict):
10
+ yield f"{indent}{key}/\n"
11
+ yield from format_tree(value, indent + ' ', hide_untoggled)
12
+ else:
13
+ yield f"{indent}{key}\n"
File without changes
@@ -0,0 +1,103 @@
1
+ # tests/reposnap/test_gui.py
2
+
3
+ import pytest
4
+ import urwid
5
+ from unittest.mock import patch, MagicMock
6
+ from urwid import ExitMainLoop
7
+ from reposnap.interfaces.gui import RepoSnapGUI
8
+ from pathlib import Path
9
+
10
+
11
+ @pytest.fixture
12
+ def gui_app():
13
+ app = RepoSnapGUI()
14
+ app.main_loop = MagicMock()
15
+ app.main_loop.widget = None # Initialize the widget attribute
16
+ return app
17
+
18
+
19
+ def test_initial_state(gui_app):
20
+ assert isinstance(gui_app.main_widget, gui_app.main_widget.__class__)
21
+ assert gui_app.root_dir == Path('.').resolve()
22
+
23
+
24
+ def test_scan_button(gui_app):
25
+ with patch.object(gui_app.controller, 'set_root_dir') as mock_set_root_dir, \
26
+ patch.object(gui_app.controller, 'collect_file_tree') as mock_collect_file_tree, \
27
+ patch.object(gui_app.controller, 'get_file_tree') as mock_get_file_tree:
28
+
29
+ mock_get_file_tree.return_value = MagicMock(structure={
30
+ 'dir1': {
31
+ 'file1.py': None,
32
+ 'dir2': {
33
+ 'file2.py': None
34
+ }
35
+ },
36
+ 'file3.py': None
37
+ })
38
+
39
+ # Simulate pressing the "Scan" button
40
+ gui_app.on_scan(None)
41
+
42
+ mock_set_root_dir.assert_called_once_with(gui_app.root_dir)
43
+ mock_collect_file_tree.assert_called_once()
44
+ mock_get_file_tree.assert_called_once()
45
+ assert gui_app.file_tree is not None
46
+
47
+ # Ensure that main_widget is updated without errors
48
+ assert isinstance(gui_app.main_widget, urwid.Frame)
49
+
50
+
51
+ def test_render_button(gui_app):
52
+ with patch.object(gui_app.controller, 'generate_output_from_selected') as mock_generate_output:
53
+ # Simulate setting up the file tree
54
+ gui_app.file_tree = MagicMock()
55
+ gui_app.file_tree.structure = {}
56
+ gui_app.selected_files = {'file1.py', 'dir/file2.py'}
57
+
58
+ # Build the file tree menu to initialize widgets
59
+ gui_app.build_file_tree_menu()
60
+
61
+ # Simulate pressing the "Render" button
62
+ gui_app.on_render(None)
63
+
64
+ mock_generate_output.assert_called_once_with(gui_app.selected_files)
65
+ # Check that the main_widget has been updated to the result menu
66
+ assert 'Markdown generated at' in gui_app.main_widget.body.base_widget.text
67
+
68
+
69
+ def test_exit_program(gui_app):
70
+ with pytest.raises(ExitMainLoop):
71
+ gui_app.exit_program(None)
72
+
73
+
74
+ def test_toggle_children(gui_app):
75
+ # Setup the file tree structure
76
+ gui_app.file_tree = MagicMock()
77
+ gui_app.file_tree.structure = {
78
+ 'dir1': {
79
+ 'file1.py': None,
80
+ 'dir2': {
81
+ 'file2.py': None
82
+ }
83
+ },
84
+ 'file3.py': None
85
+ }
86
+
87
+ # Build the file tree menu to initialize widgets
88
+ gui_app.build_file_tree_menu()
89
+
90
+ # Simulate toggling a parent checkbox
91
+ listbox = gui_app.main_widget.body.original_widget.body
92
+ parent_padding = listbox[0] # The first item is Padding wrapping the checkbox
93
+ parent_checkbox = parent_padding.original_widget
94
+ gui_app.on_checkbox_change(parent_checkbox, True)
95
+
96
+ # Check that child checkboxes are also toggled
97
+ child_padding = listbox[1]
98
+ child_checkbox = child_padding.original_widget
99
+ assert child_checkbox.get_state() is True
100
+
101
+ # Check that selected_files contains both parent and child paths
102
+ expected_selected_files = {'dir1', 'dir1/file1.py', 'dir1/dir2', 'dir1/dir2/file2.py'}
103
+ assert gui_app.selected_files == expected_selected_files
reposnap-0.3.2/PKG-INFO DELETED
@@ -1,88 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: reposnap
3
- Version: 0.3.2
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
- Description-Content-Type: text/markdown
12
-
13
- # RepoSnap
14
-
15
- ## Overview
16
-
17
- `reposnap` is a Python script designed to generate a Markdown file that documents the structure and contents of a git project. This tool is particularly useful for creating a quick overview of a project's file hierarchy, including optional syntax-highlighted code snippets.
18
-
19
- ## Features
20
-
21
- - **Syntax Highlighting**: Includes syntax highlighting for known file types in the generated Markdown file.
22
- - **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.
23
-
24
- ## Installation
25
- ```bash
26
- pip install reposnap
27
- ```
28
-
29
- Alternatively, you can clone the repository and install the required dependencies:
30
-
31
- ```bash
32
- git clone https://github.com/username/reposnap.git
33
- cd reposnap
34
- pip install -r requirements.lock
35
- ```
36
-
37
- ## Usage
38
-
39
- ### Command-Line Interface
40
-
41
- To use `reposnap`, run it with the following options:
42
-
43
- ```bash
44
- reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] path
45
- ```
46
-
47
- - `path`: Path to the Git repository or subdirectory
48
- - `-h, --help`: show help message and exit
49
- - `-o, --output`: The name of the output Markdown file. Defaults to `output.md`.
50
- - `--structure-only`: Generate a Markdown file that includes only the project structure, without file contents.
51
- - `--debug`: Enable debug-level logging.
52
-
53
- ### Examples
54
-
55
- 1. **Generate a full project structure with file contents**:
56
-
57
- ```bash
58
- reposnap .
59
- ```
60
-
61
- 2. **Generate a project structure only**:
62
-
63
- ```bash
64
- reposnap my_project/ --structure-only
65
- ```
66
-
67
- 3. **Generate a Markdown file excluding certain files and directories**:
68
-
69
- ```bash
70
- reposnap my_project/ -o output.md
71
- ```
72
-
73
- ## Testing
74
-
75
- To run the tests, use `rye`:
76
-
77
- ```bash
78
- rye test
79
- ```
80
-
81
- ## License
82
-
83
- This project is licensed under the MIT License.
84
-
85
- ## Acknowledgments
86
-
87
- - [GitPython](https://gitpython.readthedocs.io/) - Used for interacting with Git repositories.
88
- - [pathspec](https://pathspec.readthedocs.io/) - Used for pattern matching file paths.
reposnap-0.3.2/README.md DELETED
@@ -1,76 +0,0 @@
1
- # RepoSnap
2
-
3
- ## Overview
4
-
5
- `reposnap` is a Python script designed to generate a Markdown file that documents the structure and contents of a git project. 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
- - **Syntax Highlighting**: Includes syntax highlighting for known file types in the generated Markdown file.
10
- - **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.
11
-
12
- ## Installation
13
- ```bash
14
- pip install reposnap
15
- ```
16
-
17
- Alternatively, you can clone the repository and install the required dependencies:
18
-
19
- ```bash
20
- git clone https://github.com/username/reposnap.git
21
- cd reposnap
22
- pip install -r requirements.lock
23
- ```
24
-
25
- ## Usage
26
-
27
- ### Command-Line Interface
28
-
29
- To use `reposnap`, run it with the following options:
30
-
31
- ```bash
32
- reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] path
33
- ```
34
-
35
- - `path`: Path to the Git repository or subdirectory
36
- - `-h, --help`: show help message and exit
37
- - `-o, --output`: The name of the output Markdown file. Defaults to `output.md`.
38
- - `--structure-only`: Generate a Markdown file that includes only the project structure, without file contents.
39
- - `--debug`: Enable debug-level logging.
40
-
41
- ### Examples
42
-
43
- 1. **Generate a full project structure with file contents**:
44
-
45
- ```bash
46
- reposnap .
47
- ```
48
-
49
- 2. **Generate a project structure only**:
50
-
51
- ```bash
52
- reposnap my_project/ --structure-only
53
- ```
54
-
55
- 3. **Generate a Markdown file excluding certain files and directories**:
56
-
57
- ```bash
58
- reposnap my_project/ -o output.md
59
- ```
60
-
61
- ## Testing
62
-
63
- To run the tests, use `rye`:
64
-
65
- ```bash
66
- rye test
67
- ```
68
-
69
- ## License
70
-
71
- This project is licensed under the MIT License.
72
-
73
- ## Acknowledgments
74
-
75
- - [GitPython](https://gitpython.readthedocs.io/) - Used for interacting with Git repositories.
76
- - [pathspec](https://pathspec.readthedocs.io/) - Used for pattern matching file paths.
@@ -1,4 +0,0 @@
1
- from reposnap.interfaces.cli import main
2
-
3
- if __name__ == "__main__":
4
- main()
@@ -1,11 +0,0 @@
1
- # src/reposnap/utils/path_utils.py
2
- from typing import Dict, Generator, Any
3
-
4
-
5
- def format_tree(tree: Dict[str, Any], indent: str = '') -> Generator[str, None, None]:
6
- for key, value in tree.items():
7
- if isinstance(value, dict):
8
- yield f"{indent}{key}/\n"
9
- yield from format_tree(value, indent + ' ')
10
- else:
11
- yield f"{indent}{key}\n"
File without changes
File without changes
File without changes