Skip to content
Merged
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
47 changes: 43 additions & 4 deletions ontrack.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,37 @@ def _is_on_track(
return True


def _has_visible_files(directory: str, patterns: list[str]) -> bool:
"""Return True iff *directory* directly contains at least one visible file.

A "visible file" is a regular file whose name is not ``ontrack.yml`` and
does not match any pattern in *patterns*. Entries that cannot be stat'd
are silently skipped.

Args:
directory: Path to the directory to inspect.
patterns: Shell-style glob patterns (see :func:`_is_ignored`). Files
whose names match any pattern are not considered visible.
"""
try:
for entry in os.scandir(directory):
try:
if (
entry.is_file(follow_symlinks=False)
and entry.name != _ONTRACK_YML
and not _is_ignored(entry.name, patterns)
):
return True
except OSError:
pass
except OSError:
pass
return False


def _find_reporting_directories(
directory: str, ignore_patterns: list[str] | None = None
directory: str,
ignore_patterns: list[str] | None = None,
) -> list[str]:
"""Return reporting directories within *directory*.

Expand All @@ -198,9 +227,11 @@ def _find_reporting_directories(
If *directory* contains only ignored files and subdirectories, or contains
no files at all, the search recurses into each non-ignored subdirectory. An
empty directory (no files, no subdirectories) is itself treated as a
reporting directory. Entries that cannot be stat'd are silently skipped.
Subdirectories whose names match *ignore_patterns* are not descended into
and are not considered reporting directories.
reporting directory, unless the result set also contains directories that do
have visible files — in that case the empty directories are dropped so that
they are not reported alongside real content. Entries that cannot be stat'd
are silently skipped. Subdirectories whose names match *ignore_patterns*
are not descended into and are not considered reporting directories.

**ontrack.yml special handling:**
When an ``ontrack.yml`` file is found in *directory*, descent stops
Expand Down Expand Up @@ -252,6 +283,14 @@ def _find_reporting_directories(
result: list[str] = []
for subdir in subdirs:
result.extend(_find_reporting_directories(subdir, patterns))

# If some of the collected reporting directories have visible files and
# others are empty leaves, discard the empty ones. An empty directory
# should not be reported alongside real content at the same level.
non_empty = [p for p in result if _has_visible_files(p, patterns)]
if non_empty:
result = non_empty

# Fall back to the current directory if all recursive calls returned nothing
# (e.g. every subdirectory raised OSError and could not be scanned).
return result if result else [directory]
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "ontrack"
version = "0.2.0"
version = "0.2.1"
description = "Scan directory trees and report file statistics from YAML configuration."
readme = "README.md"
requires-python = ">=3.10"
Expand Down
28 changes: 28 additions & 0 deletions tests/test_ontrack.py
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,34 @@ def test_find_reporting_directories_files_and_subdir(tmp_path):
assert str(dir012) not in result


def test_find_reporting_directories_empty_sibling_not_reported(tmp_path):
"""An empty subdirectory is not reported when a sibling directory contains files.

Structure:
parent/
empty_subdir/ <- empty (no files, no subdirs)
project_dir/
data.txt <- has content

Expected: project_dir is reported; empty_subdir is NOT reported because it
has a sibling directory with actual content. The parent directory itself
should not appear either.
"""
parent = tmp_path / "parent"
parent.mkdir()
empty_subdir = parent / "empty_subdir"
empty_subdir.mkdir()
project_dir = parent / "project_dir"
project_dir.mkdir()
(project_dir / "data.txt").write_text("content")

result = _find_reporting_directories(str(parent))

assert str(project_dir) in result
assert str(empty_subdir) not in result
assert str(parent) not in result


# ---------------------------------------------------------------------------
# main – group from config file
# ---------------------------------------------------------------------------
Expand Down
Loading