doc-code 0.1.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.
- doc_code/__init__.py +3 -0
- doc_code/ai.py +206 -0
- doc_code/cli.py +593 -0
- doc_code/config.py +362 -0
- doc_code/editor.py +496 -0
- doc_code/errors.py +21 -0
- doc_code/git.py +74 -0
- doc_code/py.typed +1 -0
- doc_code/scope.py +105 -0
- doc_code/symbols.py +594 -0
- doc_code-0.1.0.dist-info/METADATA +138 -0
- doc_code-0.1.0.dist-info/RECORD +16 -0
- doc_code-0.1.0.dist-info/WHEEL +5 -0
- doc_code-0.1.0.dist-info/entry_points.txt +2 -0
- doc_code-0.1.0.dist-info/licenses/LICENSE +21 -0
- doc_code-0.1.0.dist-info/top_level.txt +1 -0
doc_code/scope.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""File selection, including Git-aware filtering and repository scanning."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import Iterator
|
|
7
|
+
from fnmatch import fnmatch
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .config import Settings
|
|
11
|
+
from .errors import DocGubError, NoEligibleFilesError
|
|
12
|
+
from .git import GitRepo
|
|
13
|
+
|
|
14
|
+
SUPPORTED_SUFFIXES = {".py", ".js", ".jsx", ".ts", ".tsx"}
|
|
15
|
+
DEFAULT_EXCLUDED_PARTS = {".git", "node_modules", "dist", "build", ".venv", "venv", "__pycache__"}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _matches(relative: str, patterns: tuple[str, ...]) -> bool:
|
|
19
|
+
"""Match root and nested paths consistently for include and exclude patterns."""
|
|
20
|
+
normalized = relative.replace("\\", "/")
|
|
21
|
+
return any(
|
|
22
|
+
fnmatch(candidate, pattern)
|
|
23
|
+
for pattern in patterns
|
|
24
|
+
for candidate in (normalized, f"/{normalized}")
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _walk_candidates(root: Path, source: Path) -> Iterator[str]:
|
|
29
|
+
"""Yield files while pruning excluded and symbolic-link directories early."""
|
|
30
|
+
for directory, subdirectories, filenames in os.walk(source, followlinks=False):
|
|
31
|
+
current = Path(directory)
|
|
32
|
+
subdirectories[:] = sorted(
|
|
33
|
+
name
|
|
34
|
+
for name in subdirectories
|
|
35
|
+
if name not in DEFAULT_EXCLUDED_PARTS and not (current / name).is_symlink()
|
|
36
|
+
)
|
|
37
|
+
for filename in sorted(filenames):
|
|
38
|
+
yield (current / filename).relative_to(root).as_posix()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _is_oversized(repo: GitRepo, relative: str, settings: Settings) -> bool:
|
|
42
|
+
"""Return whether an existing supported file exceeds the byte limit."""
|
|
43
|
+
path = repo.root / relative
|
|
44
|
+
try:
|
|
45
|
+
return (
|
|
46
|
+
path.is_file()
|
|
47
|
+
and path.suffix.lower() in SUPPORTED_SUFFIXES
|
|
48
|
+
and path.stat().st_size > settings.max_file_bytes
|
|
49
|
+
)
|
|
50
|
+
except OSError:
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _eligible(repo: GitRepo, relative: str, settings: Settings) -> bool:
|
|
55
|
+
"""Return whether a worktree-relative path satisfies every scope rule."""
|
|
56
|
+
path = repo.root / relative
|
|
57
|
+
if path.is_symlink() or not path.is_file() or path.suffix.lower() not in SUPPORTED_SUFFIXES:
|
|
58
|
+
return False
|
|
59
|
+
try:
|
|
60
|
+
path.resolve(strict=True).relative_to(repo.root.resolve(strict=True))
|
|
61
|
+
except (OSError, ValueError):
|
|
62
|
+
return False
|
|
63
|
+
if _is_oversized(repo, relative, settings):
|
|
64
|
+
return False
|
|
65
|
+
if any(part in DEFAULT_EXCLUDED_PARTS for part in Path(relative).parts):
|
|
66
|
+
return False
|
|
67
|
+
if _matches(relative, settings.exclude):
|
|
68
|
+
return False
|
|
69
|
+
return not settings.include or _matches(relative, settings.include)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def resolve(repo: GitRepo, requested: list[Path] | None, settings: Settings) -> list[str]:
|
|
73
|
+
"""Resolve paths, Git changes, or a repository into a deduplicated file scope."""
|
|
74
|
+
if requested:
|
|
75
|
+
candidates: list[str] = []
|
|
76
|
+
for requested_path in requested:
|
|
77
|
+
relative = repo.relative_path(requested_path)
|
|
78
|
+
source = repo.root / relative
|
|
79
|
+
if source.is_file():
|
|
80
|
+
candidates.append(relative)
|
|
81
|
+
else:
|
|
82
|
+
candidates.extend(_walk_candidates(repo.root, source))
|
|
83
|
+
elif settings.selection == "changes":
|
|
84
|
+
candidates = repo.changed_files()
|
|
85
|
+
else:
|
|
86
|
+
candidates = list(_walk_candidates(repo.root, repo.root))
|
|
87
|
+
unique_candidates = set(candidates)
|
|
88
|
+
ignored_candidates = repo.ignored_paths(unique_candidates)
|
|
89
|
+
files = sorted(
|
|
90
|
+
item
|
|
91
|
+
for item in unique_candidates
|
|
92
|
+
if item not in ignored_candidates and _eligible(repo, item, settings)
|
|
93
|
+
)
|
|
94
|
+
if not files:
|
|
95
|
+
oversized = sorted(
|
|
96
|
+
item for item in unique_candidates if _is_oversized(repo, item, settings)
|
|
97
|
+
)
|
|
98
|
+
if oversized:
|
|
99
|
+
raise DocGubError(f"No eligible files: exceeds max_file_bytes: {', '.join(oversized)}.")
|
|
100
|
+
raise NoEligibleFilesError("No eligible Python, JavaScript, or TypeScript files found.")
|
|
101
|
+
if len(files) > settings.max_files_per_request:
|
|
102
|
+
raise DocGubError(
|
|
103
|
+
"The scope exceeds max_files_per_request; narrow the path or increase the limit."
|
|
104
|
+
)
|
|
105
|
+
return files
|