reposnap 0.3.1__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.
- reposnap-0.4.0/PKG-INFO +124 -0
- reposnap-0.4.0/README.md +111 -0
- {reposnap-0.3.1 → reposnap-0.4.0}/pyproject.toml +4 -2
- {reposnap-0.3.1 → reposnap-0.4.0}/requirements-dev.lock +6 -0
- {reposnap-0.3.1 → reposnap-0.4.0}/requirements.lock +6 -0
- reposnap-0.4.0/src/reposnap/controllers/project_controller.py +96 -0
- {reposnap-0.3.1 → reposnap-0.4.0}/src/reposnap/core/file_system.py +8 -6
- {reposnap-0.3.1 → reposnap-0.4.0}/src/reposnap/core/git_repo.py +11 -8
- {reposnap-0.3.1 → reposnap-0.4.0}/src/reposnap/core/markdown_generator.py +18 -15
- reposnap-0.4.0/src/reposnap/interfaces/cli.py +25 -0
- reposnap-0.4.0/src/reposnap/interfaces/gui.py +155 -0
- reposnap-0.4.0/src/reposnap/models/file_tree.py +78 -0
- reposnap-0.4.0/src/reposnap/utils/__init__.py +0 -0
- reposnap-0.4.0/src/reposnap/utils/path_utils.py +13 -0
- reposnap-0.4.0/tests/__init__.py +0 -0
- reposnap-0.4.0/tests/reposnap/__init__.py +0 -0
- {reposnap-0.3.1 → reposnap-0.4.0}/tests/reposnap/test_cli.py +9 -6
- {reposnap-0.3.1 → reposnap-0.4.0}/tests/reposnap/test_file_system.py +1 -2
- reposnap-0.4.0/tests/reposnap/test_file_tree.py +28 -0
- reposnap-0.4.0/tests/reposnap/test_gui.py +103 -0
- reposnap-0.4.0/tests/reposnap/test_project_controller.py +97 -0
- reposnap-0.3.1/PKG-INFO +0 -88
- reposnap-0.3.1/README.md +0 -76
- reposnap-0.3.1/src/reposnap/__init__.py +0 -4
- reposnap-0.3.1/src/reposnap/core/collector.py +0 -64
- reposnap-0.3.1/src/reposnap/interfaces/cli.py +0 -46
- reposnap-0.3.1/src/reposnap/utils/path_utils.py +0 -9
- reposnap-0.3.1/tests/reposnap/test_collector.py +0 -27
- {reposnap-0.3.1 → reposnap-0.4.0}/.gitignore +0 -0
- {reposnap-0.3.1 → reposnap-0.4.0}/.python-version +0 -0
- {reposnap-0.3.1 → reposnap-0.4.0}/LICENSE +0 -0
- {reposnap-0.3.1/src/reposnap/core → reposnap-0.4.0/src/reposnap}/__init__.py +0 -0
- {reposnap-0.3.1/src/reposnap/interfaces → reposnap-0.4.0/src/reposnap/controllers}/__init__.py +0 -0
- {reposnap-0.3.1/src/reposnap/utils → reposnap-0.4.0/src/reposnap/core}/__init__.py +0 -0
- {reposnap-0.3.1/tests → reposnap-0.4.0/src/reposnap/interfaces}/__init__.py +0 -0
- {reposnap-0.3.1/tests/reposnap → reposnap-0.4.0/src/reposnap/models}/__init__.py +0 -0
- {reposnap-0.3.1 → reposnap-0.4.0}/tests/reposnap/test_git_repo.py +0 -0
- {reposnap-0.3.1 → reposnap-0.4.0}/tests/reposnap/test_markdown_generator.py +0 -0
- {reposnap-0.3.1 → reposnap-0.4.0}/tests/reposnap/test_path_utils.py +0 -0
- {reposnap-0.3.1 → reposnap-0.4.0}/tests/resources/another_existing_file.py +0 -0
- {reposnap-0.3.1 → reposnap-0.4.0}/tests/resources/existing_file.py +0 -0
reposnap-0.4.0/PKG-INFO
ADDED
@@ -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.
|
reposnap-0.4.0/README.md
ADDED
@@ -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
|
+
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"
|
@@ -0,0 +1,96 @@
|
|
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
|
+
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
|
31
|
+
|
32
|
+
def run(self) -> None:
|
33
|
+
self.collect_file_tree()
|
34
|
+
self.apply_filters()
|
35
|
+
self.generate_output()
|
36
|
+
|
37
|
+
def collect_file_tree(self) -> None:
|
38
|
+
self.logger.info("Collecting git files.")
|
39
|
+
git_repo: GitRepo = GitRepo(self.root_dir)
|
40
|
+
git_files: List[Path] = git_repo.get_git_files()
|
41
|
+
self.logger.debug(f"Git files before filtering: {git_files}")
|
42
|
+
|
43
|
+
self.logger.info("Building tree structure.")
|
44
|
+
file_system: FileSystem = FileSystem(self.root_dir)
|
45
|
+
tree_structure: dict = file_system.build_tree_structure(git_files)
|
46
|
+
|
47
|
+
self.file_tree = FileTree(tree_structure)
|
48
|
+
self.logger.debug(f"Tree structure: {self.file_tree.structure}")
|
49
|
+
|
50
|
+
def apply_filters(self) -> None:
|
51
|
+
self.logger.info("Applying filters to the file tree.")
|
52
|
+
spec: pathspec.PathSpec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, self.gitignore_patterns)
|
53
|
+
self.logger.debug(f"Filter patterns: {self.gitignore_patterns}")
|
54
|
+
self.file_tree.filter_files(spec)
|
55
|
+
|
56
|
+
def generate_output(self) -> None:
|
57
|
+
self.logger.info("Starting markdown generation.")
|
58
|
+
markdown_generator: MarkdownGenerator = MarkdownGenerator(
|
59
|
+
root_dir=self.root_dir,
|
60
|
+
output_file=self.output_file,
|
61
|
+
structure_only=self.structure_only
|
62
|
+
)
|
63
|
+
markdown_generator.generate_markdown(self.file_tree.structure, self.file_tree.get_all_files())
|
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}.")
|
78
|
+
|
79
|
+
def _load_gitignore_patterns(self) -> List[str]:
|
80
|
+
gitignore_path: Path = self.root_dir / '.gitignore'
|
81
|
+
if not gitignore_path.exists():
|
82
|
+
for parent in self.root_dir.parents:
|
83
|
+
gitignore_path = parent / '.gitignore'
|
84
|
+
if gitignore_path.exists():
|
85
|
+
break
|
86
|
+
else:
|
87
|
+
gitignore_path = None
|
88
|
+
|
89
|
+
if gitignore_path and gitignore_path.exists():
|
90
|
+
with gitignore_path.open('r') as gitignore:
|
91
|
+
patterns: List[str] = gitignore.readlines()
|
92
|
+
self.logger.debug(f"Patterns from .gitignore in {gitignore_path.parent}: {patterns}")
|
93
|
+
return patterns
|
94
|
+
else:
|
95
|
+
self.logger.debug(f"No .gitignore found starting from {self.root_dir}. Proceeding without patterns.")
|
96
|
+
return []
|
@@ -2,29 +2,31 @@
|
|
2
2
|
|
3
3
|
import logging
|
4
4
|
from pathlib import Path
|
5
|
+
from typing import List, Dict, Any
|
6
|
+
|
5
7
|
|
6
8
|
class FileSystem:
|
7
9
|
def __init__(self, root_dir: Path):
|
8
|
-
self.root_dir = root_dir.resolve()
|
10
|
+
self.root_dir: Path = root_dir.resolve()
|
9
11
|
self.logger = logging.getLogger(__name__)
|
10
12
|
|
11
|
-
def build_tree_structure(self, files):
|
13
|
+
def build_tree_structure(self, files: List[Path]) -> Dict[str, Any]:
|
12
14
|
"""
|
13
15
|
Builds a hierarchical tree structure from the list of files.
|
14
16
|
|
15
17
|
Args:
|
16
|
-
files (
|
18
|
+
files (List[Path]): List of file paths relative to root_dir.
|
17
19
|
|
18
20
|
Returns:
|
19
|
-
|
21
|
+
Dict[str, Any]: Nested dictionary representing the directory structure.
|
20
22
|
"""
|
21
|
-
tree = {}
|
23
|
+
tree: Dict[str, Any] = {}
|
22
24
|
self.logger.debug("Building tree structure.")
|
23
25
|
for relative_path in files:
|
24
26
|
parts = relative_path.parts
|
25
27
|
current_level = tree
|
26
28
|
for part in parts[:-1]:
|
27
29
|
current_level = current_level.setdefault(part, {})
|
28
|
-
current_level[parts[-1]] =
|
30
|
+
current_level[parts[-1]] = None # Indicate a file node
|
29
31
|
self.logger.debug(f"Tree structure built: {tree}")
|
30
32
|
return tree
|
@@ -3,23 +3,25 @@
|
|
3
3
|
import logging
|
4
4
|
from pathlib import Path
|
5
5
|
from git import Repo, InvalidGitRepositoryError
|
6
|
+
from typing import List
|
7
|
+
|
6
8
|
|
7
9
|
class GitRepo:
|
8
10
|
def __init__(self, repo_path: Path):
|
9
|
-
self.repo_path = repo_path.resolve()
|
11
|
+
self.repo_path: Path = repo_path.resolve()
|
10
12
|
self.logger = logging.getLogger(__name__)
|
11
13
|
|
12
|
-
def get_git_files(self):
|
14
|
+
def get_git_files(self) -> List[Path]:
|
13
15
|
try:
|
14
|
-
repo = Repo(self.repo_path, search_parent_directories=True)
|
15
|
-
repo_root = Path(repo.working_tree_dir).resolve()
|
16
|
-
git_files = repo.git.ls_files().splitlines()
|
16
|
+
repo: Repo = Repo(self.repo_path, search_parent_directories=True)
|
17
|
+
repo_root: Path = Path(repo.working_tree_dir).resolve()
|
18
|
+
git_files: List[str] = repo.git.ls_files().splitlines()
|
17
19
|
self.logger.debug(f"Git files from {repo_root}: {git_files}")
|
18
|
-
git_files_relative = []
|
20
|
+
git_files_relative: List[Path] = []
|
19
21
|
for f in git_files:
|
20
|
-
absolute_path = (repo_root / f).resolve()
|
22
|
+
absolute_path: Path = (repo_root / f).resolve()
|
21
23
|
try:
|
22
|
-
relative_path = absolute_path.relative_to(self.repo_path)
|
24
|
+
relative_path: Path = absolute_path.relative_to(self.repo_path)
|
23
25
|
git_files_relative.append(relative_path)
|
24
26
|
except ValueError:
|
25
27
|
# Skip files not under root_dir
|
@@ -28,3 +30,4 @@ class GitRepo:
|
|
28
30
|
except InvalidGitRepositoryError:
|
29
31
|
self.logger.error(f"Invalid Git repository at: {self.repo_path}")
|
30
32
|
return []
|
33
|
+
|
@@ -2,28 +2,31 @@
|
|
2
2
|
|
3
3
|
import logging
|
4
4
|
from pathlib import Path
|
5
|
-
from
|
5
|
+
from reposnap.utils.path_utils import format_tree
|
6
|
+
from typing import List, Dict, Any
|
7
|
+
|
6
8
|
|
7
9
|
class MarkdownGenerator:
|
8
|
-
def __init__(self, root_dir: Path, output_file: Path, structure_only: bool = False):
|
9
|
-
self.root_dir = root_dir.resolve()
|
10
|
-
self.output_file = output_file.resolve()
|
11
|
-
self.structure_only = structure_only
|
10
|
+
def __init__(self, root_dir: Path, output_file: Path, structure_only: bool = False, hide_untoggled: bool = False):
|
11
|
+
self.root_dir: Path = root_dir.resolve()
|
12
|
+
self.output_file: Path = output_file.resolve()
|
13
|
+
self.structure_only: bool = structure_only
|
14
|
+
self.hide_untoggled: bool = hide_untoggled
|
12
15
|
self.logger = logging.getLogger(__name__)
|
13
16
|
|
14
|
-
def generate_markdown(self, tree_structure:
|
17
|
+
def generate_markdown(self, tree_structure: Dict[str, Any], files: List[Path]) -> None:
|
15
18
|
"""
|
16
19
|
Generates the Markdown file based on the provided tree structure and files.
|
17
20
|
|
18
21
|
Args:
|
19
|
-
tree_structure (
|
20
|
-
files (
|
22
|
+
tree_structure (Dict[str, Any]): The hierarchical structure of the project files.
|
23
|
+
files (List[Path]): List of file paths to include in the markdown.
|
21
24
|
"""
|
22
25
|
self._write_header(tree_structure)
|
23
26
|
if not self.structure_only:
|
24
27
|
self._write_file_contents(files)
|
25
28
|
|
26
|
-
def _write_header(self, tree_structure:
|
29
|
+
def _write_header(self, tree_structure: Dict[str, Any]) -> None:
|
27
30
|
"""
|
28
31
|
Writes the header and project structure to the Markdown file.
|
29
32
|
"""
|
@@ -32,7 +35,7 @@ class MarkdownGenerator:
|
|
32
35
|
with self.output_file.open('w', encoding='utf-8') as f:
|
33
36
|
f.write("# Project Structure\n\n")
|
34
37
|
f.write("```\n")
|
35
|
-
for line in format_tree(tree_structure):
|
38
|
+
for line in format_tree(tree_structure, hide_untoggled=self.hide_untoggled):
|
36
39
|
f.write(line)
|
37
40
|
f.write("```\n\n")
|
38
41
|
self.logger.debug("Header and project structure written successfully.")
|
@@ -40,16 +43,16 @@ class MarkdownGenerator:
|
|
40
43
|
self.logger.error(f"Failed to write header to {self.output_file}: {e}")
|
41
44
|
raise
|
42
45
|
|
43
|
-
def _write_file_contents(self, files:
|
46
|
+
def _write_file_contents(self, files: List[Path]) -> None:
|
44
47
|
"""
|
45
48
|
Writes the contents of each file to the Markdown file.
|
46
49
|
|
47
50
|
Args:
|
48
|
-
files (
|
51
|
+
files (List[Path]): List of file paths relative to root_dir.
|
49
52
|
"""
|
50
53
|
self.logger.debug("Writing file contents to Markdown.")
|
51
54
|
for relative_path in files:
|
52
|
-
file_path = self.root_dir / relative_path
|
55
|
+
file_path: Path = self.root_dir / relative_path
|
53
56
|
|
54
57
|
if not file_path.exists():
|
55
58
|
self.logger.debug(f"File not found: {file_path}. Skipping.")
|
@@ -57,13 +60,13 @@ class MarkdownGenerator:
|
|
57
60
|
|
58
61
|
self._write_file_content(file_path, relative_path.as_posix())
|
59
62
|
|
60
|
-
def _write_file_content(self, file_path: Path, relative_path: str):
|
63
|
+
def _write_file_content(self, file_path: Path, relative_path: str) -> None:
|
61
64
|
"""
|
62
65
|
Writes the content of a single file to the Markdown file with syntax highlighting.
|
63
66
|
"""
|
64
67
|
try:
|
65
68
|
with file_path.open('r', encoding='utf-8') as f:
|
66
|
-
content = f.read()
|
69
|
+
content: str = f.read()
|
67
70
|
with self.output_file.open('a', encoding='utf-8') as f:
|
68
71
|
f.write(f"## {relative_path}\n\n")
|
69
72
|
f.write("```python\n" if file_path.suffix == '.py' else "```\n")
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# src/reposnap/interfaces/cli.py
|
2
|
+
|
3
|
+
import argparse
|
4
|
+
import logging
|
5
|
+
from reposnap.controllers.project_controller import ProjectController
|
6
|
+
|
7
|
+
|
8
|
+
def main():
|
9
|
+
parser = argparse.ArgumentParser(description='Generate a Markdown representation of a Git repository.')
|
10
|
+
parser.add_argument('path', nargs='?', default='.', help='Path to the Git repository or subdirectory.')
|
11
|
+
parser.add_argument('-o', '--output', help='Output Markdown file', default='output.md')
|
12
|
+
parser.add_argument('--structure-only', action='store_true',
|
13
|
+
help='Only include the file structure without content.')
|
14
|
+
parser.add_argument('--debug', action='store_true', help='Enable debug-level logging.')
|
15
|
+
|
16
|
+
args = parser.parse_args()
|
17
|
+
|
18
|
+
log_level = logging.DEBUG if args.debug else logging.INFO
|
19
|
+
logging.basicConfig(level=log_level, format='%(asctime)s - %(levelname)s - %(message)s')
|
20
|
+
|
21
|
+
controller = ProjectController(args)
|
22
|
+
controller.run()
|
23
|
+
|
24
|
+
if __name__ == "__main__":
|
25
|
+
main()
|