Skip to content

Commit 7260e57

Browse files
committed
Refactor ignore logic into utility function and add RepoStructure class for directory tree generation
1 parent 3efd649 commit 7260e57

File tree

3 files changed

+165
-53
lines changed

3 files changed

+165
-53
lines changed

repo_context/repo_converter.py

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import logging
22
import tempfile
3-
from fnmatch import fnmatch
43
from multiprocessing import Pool, cpu_count
54
from pathlib import Path
65

@@ -9,6 +8,7 @@
98
from tqdm.contrib.logging import logging_redirect_tqdm
109

1110
from repo_context.ignore import EXTENSIONS, FILES, PATTERNS
11+
from repo_context.utils import should_ignore
1212

1313
logger = logging.getLogger("repo_context.repo_converter")
1414

@@ -68,57 +68,6 @@ def progress_callback(op_code, cur_count, max_count=None, message=""):
6868
logger.error(f"Failed to clone repository: {e}")
6969
raise
7070

71-
def should_ignore(self, path: Path) -> bool:
72-
"""Check if path matches ignore patterns.
73-
74-
Args:
75-
path: Path to check against ignore patterns
76-
77-
Returns:
78-
True if path should be ignored
79-
"""
80-
fname = path.name
81-
path_str = str(path)
82-
relative_path = self._get_relative_path(path)
83-
84-
for pattern in self.ignore_patterns:
85-
if pattern in FILES and fname == pattern:
86-
return True
87-
88-
if pattern in EXTENSIONS and fnmatch(fname, pattern):
89-
return True
90-
91-
if pattern in PATTERNS:
92-
if pattern in path_str:
93-
return True
94-
95-
normalized_path = relative_path.replace("\\", "/")
96-
normalized_pattern = pattern.replace("\\", "/")
97-
if fnmatch(normalized_path, normalized_pattern):
98-
return True
99-
100-
if fnmatch(path_str, pattern):
101-
return True
102-
103-
return False
104-
105-
@staticmethod
106-
def _get_relative_path(path: Path) -> str:
107-
"""
108-
Get the relative path of the given Path object with respect to the current working directory.
109-
110-
Args:
111-
path (Path): The Path object to be converted to a relative path.
112-
113-
Returns:
114-
str: The relative path as a string if the given path is within the current working directory,
115-
otherwise the absolute path as a string.
116-
"""
117-
try:
118-
return str(path.resolve().relative_to(Path.cwd()))
119-
except ValueError:
120-
return str(path)
121-
12271
def _process_file_wrapper(self, args: tuple[str, str]) -> str | None:
12372
"""
12473
Wrapper method to process a file with given file path and repository path.
@@ -182,7 +131,7 @@ def _is_valid_file(self, path: Path) -> bool:
182131
"""Check if file should be processed."""
183132
return (
184133
path.is_file()
185-
and not self.should_ignore(path)
134+
and not should_ignore(path, self.ignore_patterns)
186135
and path.stat().st_size <= self.max_file_size
187136
)
188137

repo_context/structure.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import logging
2+
from pathlib import Path
3+
4+
from repo_context.ignore import EXTENSIONS, FILES, PATTERNS
5+
from repo_context.utils import should_ignore
6+
7+
logger = logging.getLogger("repo_context.structure")
8+
9+
10+
class RepoStructure:
11+
def __init__(self, ignore_patterns: list[str] | None = None) -> None:
12+
self.ignore_patterns = ignore_patterns or []
13+
self.ignore_patterns += FILES + EXTENSIONS + PATTERNS
14+
15+
def generate_tree(
16+
self,
17+
directory: Path,
18+
prefix: str = "",
19+
is_last: bool = True,
20+
ignore_patterns: list[str] | None = None,
21+
) -> list[str]:
22+
"""
23+
Recursively generate a tree structure of the directory.
24+
25+
Args:
26+
directory: Path object pointing to the directory
27+
prefix: Prefix for the current line (used for recursion)
28+
is_last: Boolean indicating if this is the last item in current directory
29+
ignore_patterns: List of patterns to ignore
30+
31+
Returns:
32+
List[str]: Lines of the tree structure
33+
"""
34+
if ignore_patterns is None:
35+
ignore_patterns = []
36+
37+
if not directory.is_dir():
38+
logger.error(f"'{directory}' is not a valid directory")
39+
return []
40+
41+
tree_lines = []
42+
items = [
43+
item
44+
for item in sorted(directory.iterdir())
45+
if not should_ignore(item.name, ignore_patterns)
46+
]
47+
48+
for i, item in enumerate(items):
49+
is_last_item = i == len(items) - 1
50+
connector = "??? " if is_last_item else "??? "
51+
52+
tree_lines.append(f"{prefix}{connector}{item.name}")
53+
54+
if item.is_dir():
55+
extension = " " if is_last_item else "? "
56+
tree_lines.extend(
57+
self.generate_tree(
58+
item,
59+
prefix + extension,
60+
is_last_item,
61+
ignore_patterns,
62+
)
63+
)
64+
65+
return tree_lines
66+
67+
def create_tree_structure(
68+
self,
69+
path: str,
70+
output_file: str | None = None,
71+
ignore_patterns: list[str] | None = None,
72+
) -> None:
73+
"""
74+
Create and display/save a tree structure of the specified directory.
75+
76+
Args:
77+
path: Path to the directory
78+
output_file: Optional file path to save the tree structure
79+
ignore_patterns: List of patterns to ignore
80+
"""
81+
directory = Path(path)
82+
if not directory.exists():
83+
logger.error(f"Directory '{path}' does not exist")
84+
return
85+
86+
logger.info(f"Generating tree structure for: {directory.absolute()}")
87+
88+
tree_lines = ["Directory Structure:", directory.name]
89+
tree_lines.extend(
90+
self.generate_tree(directory, ignore_patterns=ignore_patterns or [])
91+
)
92+
93+
# Join lines with newlines
94+
tree_structure = "\n".join(tree_lines)
95+
96+
# Print to console
97+
logger.info(tree_structure)
98+
99+
# Save to file if specified
100+
if output_file:
101+
output_path = Path(output_file)
102+
try:
103+
output_path.write_text(tree_structure)
104+
logger.info(f"Tree structure saved to: {output_path.absolute()}")
105+
except Exception as e:
106+
logger.error(f"Failed to save tree structure: {e}")

repo_context/utils.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from fnmatch import fnmatch
2+
from pathlib import Path
3+
4+
from repo_context.ignore import EXTENSIONS, FILES, PATTERNS
5+
6+
7+
def get_relative_path(path: Path) -> str:
8+
"""
9+
Get the relative path of the given Path object with respect to the current working directory.
10+
11+
Args:
12+
path (Path): The Path object to be converted to a relative path.
13+
14+
Returns:
15+
str: The relative path as a string if the given path is within the current working directory,
16+
otherwise the absolute path as a string.
17+
"""
18+
try:
19+
return str(path.resolve().relative_to(Path.cwd()))
20+
except ValueError:
21+
return str(path)
22+
23+
24+
def should_ignore(path: Path, ignore_patterns: list[str]) -> bool:
25+
"""Check if path matches ignore patterns.
26+
27+
Args:
28+
path (Path): Path to check against ignore patterns
29+
ignore_patterns (list[str]): List of ignore patterns
30+
31+
Returns:
32+
True if path should be ignored
33+
"""
34+
fname = path.name
35+
path_str = str(path)
36+
relative_path = get_relative_path(path)
37+
38+
for pattern in ignore_patterns:
39+
if pattern in FILES and fname == pattern:
40+
return True
41+
42+
if pattern in EXTENSIONS and fnmatch(fname, pattern):
43+
return True
44+
45+
if pattern in PATTERNS:
46+
if pattern in path_str:
47+
return True
48+
49+
normalized_path = relative_path.replace("\\", "/")
50+
normalized_pattern = pattern.replace("\\", "/")
51+
if fnmatch(normalized_path, normalized_pattern):
52+
return True
53+
54+
if fnmatch(path_str, pattern):
55+
return True
56+
57+
return False

0 commit comments

Comments
 (0)