Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions graphify/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,17 +272,41 @@ def _load_graphifyignore(root: Path) -> list[tuple[Path, str]]:
parent directory still work when graphify is run on a subfolder.

Walks upward from *root* towards the filesystem root, stopping at a
``.git`` boundary. Lines starting with # are comments; blank lines ignored.
``.git`` boundary.

Comment handling (gitignore extension):
* Lines starting with # are full-line comments — skipped.
* Inline comments (whitespace + hash to end of line) are stripped
from each pattern. This matches user intuition for documenting
patterns inline, e.g. `chitta/varta/ # daily briefings`.
* Use ``\\#`` to keep a literal hash in a pattern (rare).
* Blank lines after stripping are ignored.
"""
patterns: list[tuple[Path, str]] = []
current = root.resolve()
while True:
ignore_file = current / ".graphifyignore"
if ignore_file.exists():
for line in ignore_file.read_text(encoding="utf-8", errors="ignore").splitlines():
line = line.strip()
if line and not line.startswith("#"):
patterns.append((current, line))
for raw_line in ignore_file.read_text(encoding="utf-8", errors="ignore").splitlines():
line = raw_line.strip()
if not line or line.startswith("#"):
continue
# Strip inline comment (whitespace + '#' to end of line),
# preserve escaped backslash-# as literal hash in pattern.
pieces = []
i = 0
while i < len(line):
if line[i] == "\\" and i + 1 < len(line) and line[i + 1] == "#":
pieces.append("#")
i += 2
continue
if line[i].isspace() and i + 1 < len(line) and line[i + 1] == "#":
break
pieces.append(line[i])
i += 1
pattern = "".join(pieces).rstrip()
if pattern:
patterns.append((current, pattern))
# Stop climbing once we've processed the git repo root
if (current / ".git").exists():
break
Expand Down
51 changes: 51 additions & 0 deletions tests/test_detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,57 @@ def test_graphifyignore_comments_ignored(tmp_path):
assert any("other.py" in f for f in result["files"]["code"])


def test_graphifyignore_inline_comments_stripped(tmp_path):
"""Inline comments (whitespace + '#' to end of line) are stripped from
pattern lines, so `vendor/ # legacy code` ignores the `vendor/` directory
rather than treating the entire line including the comment as the pattern.
"""
(tmp_path / ".graphifyignore").write_text(
"vendor/ # legacy code\n"
"*.generated.py # auto-generated, skip\n"
"main.py\n"
)
(tmp_path / "vendor").mkdir()
(tmp_path / "vendor" / "lib.py").write_text("x = 1")
(tmp_path / "auto.generated.py").write_text("x = 2")
(tmp_path / "main.py").write_text("x = 3")
(tmp_path / "other.py").write_text("x = 4")
result = detect(tmp_path)
assert not any("lib.py" in f for f in result["files"]["code"]), (
"vendor/ should be ignored after stripping inline comment"
)
assert not any("auto.generated.py" in f for f in result["files"]["code"]), (
"*.generated.py should match after stripping inline comment"
)
assert not any("main.py" in f for f in result["files"]["code"])
assert any("other.py" in f for f in result["files"]["code"])


def test_graphifyignore_inline_comment_requires_whitespace_before_hash(tmp_path):
"""A '#' without preceding whitespace is part of the pattern, not a comment.
Standard gitignore semantics — `path#name` matches a file literally named
`path#name`, not a comment.
"""
(tmp_path / ".graphifyignore").write_text("file#with#hash.py\n")
(tmp_path / "file#with#hash.py").write_text("x = 1")
(tmp_path / "other.py").write_text("x = 2")
result = detect(tmp_path)
assert not any("file#with#hash.py" in f for f in result["files"]["code"])
assert any("other.py" in f for f in result["files"]["code"])


def test_graphifyignore_escaped_hash_is_literal(tmp_path):
r"""Backslash-hash (``\#``) keeps a literal '#' in the pattern even after
whitespace, escaping the inline-comment marker.
"""
(tmp_path / ".graphifyignore").write_text("file \\# name.py\n")
(tmp_path / "file # name.py").write_text("x = 1")
(tmp_path / "other.py").write_text("x = 2")
result = detect(tmp_path)
assert not any("file # name.py" in f for f in result["files"]["code"])
assert any("other.py" in f for f in result["files"]["code"])


def test_detect_follows_symlinked_directory(tmp_path):
real_dir = tmp_path / "real_lib"
real_dir.mkdir()
Expand Down