reposnap 0.2.4__py3-none-any.whl → 0.3.0__py3-none-any.whl
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/core/collector.py +44 -10
- reposnap/core/file_system.py +17 -10
- reposnap/core/git_repo.py +19 -10
- reposnap/core/markdown_generator.py +11 -61
- reposnap/interfaces/cli.py +23 -7
- {reposnap-0.2.4.dist-info → reposnap-0.3.0.dist-info}/METADATA +1 -1
- reposnap-0.3.0.dist-info/RECORD +15 -0
- reposnap-0.2.4.dist-info/RECORD +0 -15
- {reposnap-0.2.4.dist-info → reposnap-0.3.0.dist-info}/WHEEL +0 -0
- {reposnap-0.2.4.dist-info → reposnap-0.3.0.dist-info}/entry_points.txt +0 -0
- {reposnap-0.2.4.dist-info → reposnap-0.3.0.dist-info}/licenses/LICENSE +0 -0
reposnap/core/collector.py
CHANGED
@@ -1,30 +1,64 @@
|
|
1
1
|
# src/reposnap/core/collector.py
|
2
2
|
|
3
|
-
from .file_system import FileSystem
|
4
|
-
from .git_repo import GitRepo
|
5
|
-
from .markdown_generator import MarkdownGenerator
|
6
|
-
import pathspec
|
7
3
|
import logging
|
8
4
|
from pathlib import Path
|
5
|
+
import pathspec
|
6
|
+
from .git_repo import GitRepo
|
7
|
+
from .file_system import FileSystem
|
8
|
+
from .markdown_generator import MarkdownGenerator
|
9
9
|
|
10
10
|
|
11
11
|
class ProjectContentCollector:
|
12
12
|
def __init__(self, root_dir: str, output_file: str, structure_only: bool, gitignore_patterns: list):
|
13
|
+
self.logger = logging.getLogger(__name__)
|
13
14
|
self.root_dir = Path(root_dir).resolve()
|
14
15
|
self.output_file = Path(output_file).resolve()
|
15
16
|
self.structure_only = structure_only
|
17
|
+
self.gitignore_patterns = gitignore_patterns
|
16
18
|
self.spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, gitignore_patterns)
|
17
|
-
|
19
|
+
|
20
|
+
# Initialize components
|
18
21
|
self.git_repo = GitRepo(self.root_dir)
|
22
|
+
self.file_system = FileSystem(self.root_dir)
|
19
23
|
self.markdown_generator = MarkdownGenerator(
|
20
24
|
root_dir=self.root_dir,
|
21
25
|
output_file=self.output_file,
|
22
26
|
structure_only=self.structure_only
|
23
27
|
)
|
24
|
-
self.logger = logging.getLogger(__name__)
|
25
28
|
|
26
|
-
|
27
|
-
self.
|
29
|
+
# Collect files and build tree during initialization
|
30
|
+
self.files = self.collect_files()
|
31
|
+
self.tree_structure = self.build_tree_structure()
|
32
|
+
|
33
|
+
def collect_files(self):
|
34
|
+
"""
|
35
|
+
Collects and filters files to be included in the documentation.
|
36
|
+
"""
|
37
|
+
self.logger.info("Collecting git files.")
|
28
38
|
git_files = self.git_repo.get_git_files()
|
29
|
-
|
30
|
-
|
39
|
+
self.logger.debug(f"Git files before filtering: {git_files}")
|
40
|
+
|
41
|
+
# Filter files based on .gitignore patterns
|
42
|
+
filtered_files = [
|
43
|
+
f for f in git_files if not self.spec.match_file(str(f))
|
44
|
+
]
|
45
|
+
self.logger.debug(f"Git files after filtering: {filtered_files}")
|
46
|
+
|
47
|
+
return filtered_files # Paths relative to root_dir
|
48
|
+
|
49
|
+
def build_tree_structure(self):
|
50
|
+
"""
|
51
|
+
Builds the tree structure from the collected files.
|
52
|
+
"""
|
53
|
+
self.logger.info("Building tree structure.")
|
54
|
+
tree = self.file_system.build_tree_structure(self.files)
|
55
|
+
self.logger.debug(f"Tree structure: {tree}")
|
56
|
+
return tree
|
57
|
+
|
58
|
+
def collect_and_generate(self):
|
59
|
+
"""
|
60
|
+
Initiates the markdown generation process.
|
61
|
+
"""
|
62
|
+
self.logger.info("Starting markdown generation.")
|
63
|
+
self.markdown_generator.generate_markdown(self.tree_structure, self.files)
|
64
|
+
self.logger.info("Markdown generation completed.")
|
reposnap/core/file_system.py
CHANGED
@@ -4,20 +4,27 @@ import logging
|
|
4
4
|
from pathlib import Path
|
5
5
|
|
6
6
|
class FileSystem:
|
7
|
-
def __init__(self, root_dir:
|
8
|
-
self.root_dir =
|
7
|
+
def __init__(self, root_dir: Path):
|
8
|
+
self.root_dir = root_dir.resolve()
|
9
|
+
self.logger = logging.getLogger(__name__)
|
9
10
|
|
10
11
|
def build_tree_structure(self, files):
|
12
|
+
"""
|
13
|
+
Builds a hierarchical tree structure from the list of files.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
files (list of Path): List of file paths relative to root_dir.
|
17
|
+
|
18
|
+
Returns:
|
19
|
+
dict: Nested dictionary representing the directory structure.
|
20
|
+
"""
|
11
21
|
tree = {}
|
12
|
-
|
13
|
-
for
|
14
|
-
|
15
|
-
logging.debug(f"Processing file:\n File Path: {file_path}\n Root Dir: {self.root_dir}")
|
16
|
-
relative_path = file_path.relative_to(self.root_dir).as_posix()
|
17
|
-
parts = relative_path.split('/')
|
22
|
+
self.logger.debug("Building tree structure.")
|
23
|
+
for relative_path in files:
|
24
|
+
parts = relative_path.parts
|
18
25
|
current_level = tree
|
19
26
|
for part in parts[:-1]:
|
20
27
|
current_level = current_level.setdefault(part, {})
|
21
|
-
current_level[parts[-1]] = relative_path
|
22
|
-
|
28
|
+
current_level[parts[-1]] = relative_path.as_posix()
|
29
|
+
self.logger.debug(f"Tree structure built: {tree}")
|
23
30
|
return tree
|
reposnap/core/git_repo.py
CHANGED
@@ -1,21 +1,30 @@
|
|
1
1
|
# src/reposnap/core/git_repo.py
|
2
2
|
|
3
3
|
import logging
|
4
|
+
from pathlib import Path
|
4
5
|
from git import Repo, InvalidGitRepositoryError
|
5
6
|
|
6
7
|
class GitRepo:
|
7
|
-
def __init__(self, repo_path:
|
8
|
-
self.repo_path = repo_path
|
8
|
+
def __init__(self, repo_path: Path):
|
9
|
+
self.repo_path = repo_path.resolve()
|
10
|
+
self.logger = logging.getLogger(__name__)
|
9
11
|
|
10
12
|
def get_git_files(self):
|
11
13
|
try:
|
12
|
-
repo = Repo(self.repo_path)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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()
|
17
|
+
self.logger.debug(f"Git files from {repo_root}: {git_files}")
|
18
|
+
git_files_relative = []
|
19
|
+
for f in git_files:
|
20
|
+
absolute_path = (repo_root / f).resolve()
|
21
|
+
try:
|
22
|
+
relative_path = absolute_path.relative_to(self.repo_path)
|
23
|
+
git_files_relative.append(relative_path)
|
24
|
+
except ValueError:
|
25
|
+
# Skip files not under root_dir
|
26
|
+
continue
|
27
|
+
return git_files_relative
|
19
28
|
except InvalidGitRepositoryError:
|
20
|
-
|
29
|
+
self.logger.error(f"Invalid Git repository at: {self.repo_path}")
|
21
30
|
return []
|
@@ -1,50 +1,31 @@
|
|
1
1
|
# src/reposnap/core/markdown_generator.py
|
2
2
|
|
3
|
+
import logging
|
3
4
|
from pathlib import Path
|
4
5
|
from ..utils.path_utils import format_tree
|
5
|
-
import logging
|
6
|
-
|
7
6
|
|
8
7
|
class MarkdownGenerator:
|
9
8
|
def __init__(self, root_dir: Path, output_file: Path, structure_only: bool = False):
|
10
|
-
"""
|
11
|
-
Initializes the MarkdownGenerator.
|
12
|
-
|
13
|
-
Args:
|
14
|
-
root_dir (Path): The root directory of the project.
|
15
|
-
output_file (Path): The path to the output Markdown file.
|
16
|
-
structure_only (bool): If True, only the directory structure is included without file contents.
|
17
|
-
"""
|
18
9
|
self.root_dir = root_dir.resolve()
|
19
10
|
self.output_file = output_file.resolve()
|
20
11
|
self.structure_only = structure_only
|
21
12
|
self.logger = logging.getLogger(__name__)
|
22
|
-
self.logger.debug(f"Initialized MarkdownGenerator with root_dir={self.root_dir}, "
|
23
|
-
f"output_file={self.output_file}, structure_only={self.structure_only}")
|
24
13
|
|
25
|
-
def generate_markdown(self, tree_structure: dict,
|
14
|
+
def generate_markdown(self, tree_structure: dict, files: list):
|
26
15
|
"""
|
27
|
-
Generates the Markdown file based on the provided tree structure and
|
16
|
+
Generates the Markdown file based on the provided tree structure and files.
|
28
17
|
|
29
18
|
Args:
|
30
19
|
tree_structure (dict): The hierarchical structure of the project files.
|
31
|
-
|
32
|
-
spec (pathspec.PathSpec, optional): PathSpec object for file exclusion based on patterns.
|
20
|
+
files (list of Path): List of file paths to include in the markdown.
|
33
21
|
"""
|
34
|
-
self.logger.info("Starting Markdown generation.")
|
35
22
|
self._write_header(tree_structure)
|
36
|
-
|
37
23
|
if not self.structure_only:
|
38
|
-
self._write_file_contents(
|
39
|
-
|
40
|
-
self.logger.info(f"Markdown file generated successfully at: {self.output_file}")
|
24
|
+
self._write_file_contents(files)
|
41
25
|
|
42
26
|
def _write_header(self, tree_structure: dict):
|
43
27
|
"""
|
44
28
|
Writes the header and project structure to the Markdown file.
|
45
|
-
|
46
|
-
Args:
|
47
|
-
tree_structure (dict): The hierarchical structure of the project files.
|
48
29
|
"""
|
49
30
|
self.logger.debug("Writing Markdown header and project structure.")
|
50
31
|
try:
|
@@ -59,64 +40,33 @@ class MarkdownGenerator:
|
|
59
40
|
self.logger.error(f"Failed to write header to {self.output_file}: {e}")
|
60
41
|
raise
|
61
42
|
|
62
|
-
def _write_file_contents(self,
|
43
|
+
def _write_file_contents(self, files: list):
|
63
44
|
"""
|
64
|
-
Writes the contents of each file to the Markdown file
|
45
|
+
Writes the contents of each file to the Markdown file.
|
65
46
|
|
66
47
|
Args:
|
67
|
-
|
68
|
-
spec (pathspec.PathSpec, optional): PathSpec object for file exclusion based on patterns.
|
48
|
+
files (list of Path): List of file paths relative to root_dir.
|
69
49
|
"""
|
70
50
|
self.logger.debug("Writing file contents to Markdown.")
|
71
|
-
for
|
72
|
-
file_path = self.
|
51
|
+
for relative_path in files:
|
52
|
+
file_path = self.root_dir / relative_path
|
73
53
|
|
74
54
|
if not file_path.exists():
|
75
55
|
self.logger.debug(f"File not found: {file_path}. Skipping.")
|
76
56
|
continue
|
77
57
|
|
78
|
-
|
79
|
-
if spec and spec.match_file(relative_path):
|
80
|
-
self.logger.debug(f"File excluded by spec: {relative_path}. Skipping.")
|
81
|
-
continue
|
82
|
-
|
83
|
-
self.logger.debug(f"Processing file: {file_path}")
|
84
|
-
self._write_file_content(file_path, relative_path)
|
85
|
-
|
86
|
-
def _resolve_file_path(self, file: str) -> Path:
|
87
|
-
"""
|
88
|
-
Resolves the absolute path of a file relative to the root directory.
|
89
|
-
|
90
|
-
Args:
|
91
|
-
file (str): The file path relative to the root directory.
|
92
|
-
|
93
|
-
Returns:
|
94
|
-
Path: The absolute path to the file.
|
95
|
-
"""
|
96
|
-
resolved_path = self.root_dir / file
|
97
|
-
self.logger.debug(f"Resolved file path: {file} to {resolved_path}")
|
98
|
-
return resolved_path
|
58
|
+
self._write_file_content(file_path, relative_path.as_posix())
|
99
59
|
|
100
60
|
def _write_file_content(self, file_path: Path, relative_path: str):
|
101
61
|
"""
|
102
62
|
Writes the content of a single file to the Markdown file with syntax highlighting.
|
103
|
-
|
104
|
-
Args:
|
105
|
-
file_path (Path): The absolute path to the file.
|
106
|
-
relative_path (str): The file path relative to the root directory.
|
107
63
|
"""
|
108
64
|
try:
|
109
|
-
print(f"Attempting to read file: {file_path}")
|
110
65
|
with file_path.open('r', encoding='utf-8') as f:
|
111
66
|
content = f.read()
|
112
|
-
self.logger.debug(f"Read content from {file_path}")
|
113
|
-
|
114
67
|
with self.output_file.open('a', encoding='utf-8') as f:
|
115
68
|
f.write(f"## {relative_path}\n\n")
|
116
69
|
f.write("```python\n" if file_path.suffix == '.py' else "```\n")
|
117
70
|
f.write(f"{content}\n```\n\n")
|
118
|
-
self.logger.debug(f"Wrote content of {relative_path} to Markdown.")
|
119
71
|
except IOError as e:
|
120
72
|
self.logger.error(f"Error reading or writing file {file_path}: {e}")
|
121
|
-
except Exception as e:
|
122
|
-
self.logger.error(f"Unexpected error processing file {file_path}: {e}")
|
reposnap/interfaces/cli.py
CHANGED
@@ -2,12 +2,14 @@
|
|
2
2
|
|
3
3
|
import argparse
|
4
4
|
import logging
|
5
|
+
import os
|
5
6
|
from reposnap.core.collector import ProjectContentCollector
|
7
|
+
from pathlib import Path
|
6
8
|
|
7
9
|
|
8
10
|
def main():
|
9
11
|
parser = argparse.ArgumentParser(description='Generate a Markdown representation of a Git repository.')
|
10
|
-
parser.add_argument('path', help='Path to the Git repository.')
|
12
|
+
parser.add_argument('path', help='Path to the Git repository or subdirectory.')
|
11
13
|
parser.add_argument('-o', '--output', help='Output Markdown file', default='output.md')
|
12
14
|
parser.add_argument('--structure-only', action='store_true',
|
13
15
|
help='Only include the file structure without content.')
|
@@ -18,13 +20,27 @@ def main():
|
|
18
20
|
log_level = logging.DEBUG if args.debug else logging.INFO
|
19
21
|
logging.basicConfig(level=log_level, format='%(asctime)s - %(levelname)s - %(message)s')
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
path = Path(args.path).resolve()
|
24
|
+
gitignore_path = path / '.gitignore'
|
25
|
+
if not gitignore_path.exists():
|
26
|
+
# Search for .gitignore in parent directories
|
27
|
+
for parent in path.parents:
|
28
|
+
gitignore_path = parent / '.gitignore'
|
29
|
+
if gitignore_path.exists():
|
30
|
+
break
|
31
|
+
else:
|
32
|
+
gitignore_path = None
|
33
|
+
|
34
|
+
if gitignore_path and gitignore_path.exists():
|
35
|
+
with gitignore_path.open('r') as gitignore:
|
36
|
+
patterns = gitignore.readlines()
|
37
|
+
logging.debug(f"Patterns from .gitignore in {gitignore_path.parent}: {patterns}")
|
38
|
+
else:
|
39
|
+
patterns = []
|
40
|
+
logging.debug(f"No .gitignore found starting from {args.path}. Proceeding without patterns.")
|
41
|
+
|
42
|
+
collector = ProjectContentCollector(str(path), args.output, args.structure_only, patterns)
|
26
43
|
collector.collect_and_generate()
|
27
44
|
|
28
|
-
|
29
45
|
if __name__ == "__main__":
|
30
46
|
main()
|
@@ -0,0 +1,15 @@
|
|
1
|
+
reposnap/__init__.py,sha256=FGadYKcWDEh_AL8PbUuUrcCZDlF510JZfdGhHdT6XQk,80
|
2
|
+
reposnap/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
reposnap/core/collector.py,sha256=UQNAaIqgALZmOvtSb7NDYaXmVp4-X4jSWikMMh_B0YY,2401
|
4
|
+
reposnap/core/file_system.py,sha256=yFIaHtJYqw5t8Lx9Nu7yb3G7XcCP1v1aKP_hGfS_KfQ,974
|
5
|
+
reposnap/core/git_repo.py,sha256=A8mdgmT9JGHllkFMx5Z_1gGp3Yw1dr-NFOw2-lH_DfA,1163
|
6
|
+
reposnap/core/markdown_generator.py,sha256=vJl_ac3vDs4CEanjwQazQM9fepGeFtGxo3ozajdkhqA,2889
|
7
|
+
reposnap/interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
+
reposnap/interfaces/cli.py,sha256=bU7QAg2wDzQwyAsRyq4zHLnO8nNiJGrWD24n7yZFEZk,1795
|
9
|
+
reposnap/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
reposnap/utils/path_utils.py,sha256=lMuZMMIX2lEE1o6V3QqifmyO5dO5fuA5OUXMxSvYcHI,290
|
11
|
+
reposnap-0.3.0.dist-info/METADATA,sha256=hcDKEinduZiYYDMrl5NsYkv0qz0H3EG-Ls_LfU01WGE,2329
|
12
|
+
reposnap-0.3.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
13
|
+
reposnap-0.3.0.dist-info/entry_points.txt,sha256=aaCuB0RZOLW2--ymW8VCEOm0Qz9KvTZCUjwQFZsGaDU,43
|
14
|
+
reposnap-0.3.0.dist-info/licenses/LICENSE,sha256=Aj7WCYBXi98pvi723HPn4GDRyjxToNWb3PC6j1_lnPk,1069
|
15
|
+
reposnap-0.3.0.dist-info/RECORD,,
|
reposnap-0.2.4.dist-info/RECORD
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
reposnap/__init__.py,sha256=FGadYKcWDEh_AL8PbUuUrcCZDlF510JZfdGhHdT6XQk,80
|
2
|
-
reposnap/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
reposnap/core/collector.py,sha256=TY42k2pN8B9vpuop_g6Dj5PACnr6ncpCYs3b08P1Rog,1253
|
4
|
-
reposnap/core/file_system.py,sha256=X3JdnClpNUqbz_Taj6G3GIozk63wpQ_nG3Jd0yNpxsY,886
|
5
|
-
reposnap/core/git_repo.py,sha256=Zx6DU87KuKAD-0JcKKqcb0OK1glzKMdS_przDlP4ZV0,707
|
6
|
-
reposnap/core/markdown_generator.py,sha256=dcQYDMyhZXjz1DY9e1zMsVS_dDGRVPm9f31Ug0QHANc,5126
|
7
|
-
reposnap/interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
-
reposnap/interfaces/cli.py,sha256=3bfyVpVNN5LiuxJ2UxUIpIdNugy75mLpuE-cgjI2J3I,1186
|
9
|
-
reposnap/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
reposnap/utils/path_utils.py,sha256=lMuZMMIX2lEE1o6V3QqifmyO5dO5fuA5OUXMxSvYcHI,290
|
11
|
-
reposnap-0.2.4.dist-info/METADATA,sha256=_HWwwMx9j57TYX_Aj_jahtu3LuCcrlBTZ1cupJ04JX4,2329
|
12
|
-
reposnap-0.2.4.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
13
|
-
reposnap-0.2.4.dist-info/entry_points.txt,sha256=aaCuB0RZOLW2--ymW8VCEOm0Qz9KvTZCUjwQFZsGaDU,43
|
14
|
-
reposnap-0.2.4.dist-info/licenses/LICENSE,sha256=Aj7WCYBXi98pvi723HPn4GDRyjxToNWb3PC6j1_lnPk,1069
|
15
|
-
reposnap-0.2.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|