Skip to content

Commit e0595d3

Browse files
committed
chore: update CHANGELOG for 0.3.2; fix lint, mypy, and stale test
- Promote [Unreleased] items into a new [0.3.2] section dated 2026-03-13 - Exclude tests/animation fixture dir from ruff - Drop unused 'misc' from type: ignore in watch.py - Decode bytes paths in watch event logger (mypy str-bytes-safe) - Rename 'found' → 'svg_meta' in read-meta to fix mypy no-redef error - Update test_cli_not_a_directory → test_cli_single_file to match the single-file input support added in ac1e51c
1 parent 7a656ed commit e0595d3

5 files changed

Lines changed: 41 additions & 18 deletions

File tree

CHANGELOG.md

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.3.2] - 2026-03-13
11+
1012
### Added
1113

12-
- **Multiple local roots**: `dirplot map bar baz` accepts two or more local directory
13-
paths. dirplot finds their common parent, scans each path independently, and assembles
14-
a synthetic wrapper tree that contains only the requested subtrees — siblings are
15-
excluded entirely.
16-
- **`--subtree` / `-s`** option (repeatable) — an allowlist complement to `--exclude`:
17-
after scanning the root, keep only the named direct children. Supports nested paths
18-
such as `--subtree src/dirplot/fonts`, which produces a synthetic `src → dirplot`
19-
chain containing only the `fonts` subtree. Useful when it is easier to name what you
20-
want than to enumerate what you don't.
14+
- **`dirplot watch`** subcommand — watches a directory and regenerates the treemap
15+
on every file-system change using watchdog (FSEvents on macOS, inotify on Linux,
16+
kqueue on BSD). Requires `watchdog`, now a core dependency.
17+
```bash
18+
dirplot watch . --output treemap.png
19+
dirplot watch . --output treemap.png --animate # APNG, one frame per change
20+
```
21+
- **Vertical file labels**: file tiles that are at least twice as tall as wide now
22+
display their label rotated 90° CCW, letting the text span the full tile height
23+
instead of being squeezed into the narrow width.
24+
- **Scan and render timing** shown in header output:
25+
`Found 1,414 files … [2.3s]` and `Saved dirplot to out.png [0.4s]`.
26+
- **Multiple local roots**: `dirplot map src tests` accepts two or more local
27+
directory paths, finds their common parent, and shows only those subtrees.
28+
- **`--subtree` / `-s`** option (repeatable) — allowlist complement to `--exclude`:
29+
keep only the named direct children of the root after scanning. Supports nested
30+
paths such as `--subtree src/dirplot/fonts`.
31+
32+
### Fixed
33+
34+
- `--exclude` on pod and Docker backends now prunes entire subtrees — previously only
35+
the exact path was matched, so all children leaked through.
36+
- Clearer error for distroless pods: exit code 126 from `kubectl exec` now surfaces as
37+
an actionable message explaining that the container has no shell or `find` utility.
38+
- Adaptive file-label font size is now computed with a single `textbbox` measurement
39+
(one call per tile) instead of stepping down one pixel at a time — eliminates an
40+
O(font_size × n_tiles) bottleneck that caused near-blocking on large trees such as
41+
`.venv` directories.
2142

2243
### Changed
2344

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ packages = ["src/dirplot"]
7878
line-length = 100
7979
target-version = "py310"
8080
src = ["src", "tests"]
81+
exclude = ["tests/animation"]
8182

8283
[tool.ruff.lint]
8384
select = ["E", "F", "W", "I", "UP", "B", "C4", "SIM"]

src/dirplot/main.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,17 +204,17 @@ def read_meta(
204204
except ET.ParseError as exc:
205205
typer.echo(f"Error parsing SVG: {exc}", err=True)
206206
raise typer.Exit(1) from exc
207-
found: dict[str, str] = {}
207+
svg_meta: dict[str, str] = {}
208208
for desc in root.iter("{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description"):
209209
for child in desc:
210210
local = child.tag.split("}")[-1] if "}" in child.tag else child.tag
211211
ns_uri = child.tag.split("}")[0].lstrip("{") if "}" in child.tag else ""
212212
if ns_uri == "https://github.com/deeplook/dirplot#" and child.text:
213-
found[local] = child.text
214-
if not found:
213+
svg_meta[local] = child.text
214+
if not svg_meta:
215215
typer.echo("No dirplot metadata found in SVG.", err=True)
216216
raise typer.Exit(1)
217-
for k, v in found.items():
217+
for k, v in svg_meta.items():
218218
typer.echo(f"{k}: {v}")
219219

220220
else:

src/dirplot/watch.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from watchdog.events import FileSystemEvent, FileSystemEventHandler
1010
from watchdog.observers import Observer
1111
except ImportError:
12-
Observer = None # type: ignore[assignment,misc]
12+
Observer = None # type: ignore[assignment]
1313

1414
from dirplot.render import create_treemap
1515
from dirplot.scanner import apply_log_sizes, build_tree
@@ -142,7 +142,9 @@ def _regenerate(self) -> None:
142142
def _log_event(self, verb: str, event: FileSystemEvent) -> None:
143143
src = event.src_path
144144
dest = getattr(event, "dest_path", None)
145-
msg = f"{verb}: {src}" if not dest else f"{verb}: {src}{dest}"
145+
src_s = src.decode() if isinstance(src, bytes) else src
146+
dest_s = dest.decode() if isinstance(dest, bytes) else dest
147+
msg = f"{verb}: {src_s}" if not dest_s else f"{verb}: {src_s}{dest_s}"
146148
print(msg, file=sys.stderr)
147149

148150
def on_created(self, event: FileSystemEvent) -> None:

tests/test_cli.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,11 @@ def test_cli_invalid_path() -> None:
2424
assert "does not exist" in result.output
2525

2626

27-
def test_cli_not_a_directory(tmp_path: Path) -> None:
27+
def test_cli_single_file(tmp_path: Path) -> None:
2828
f = tmp_path / "file.txt"
2929
f.write_text("hello")
3030
result = runner.invoke(app, ["map", str(f), "--no-show"])
31-
assert result.exit_code == 1
32-
assert "Not a directory" in result.output
31+
assert result.exit_code == 0
3332

3433

3534
def test_cli_bad_colormap(sample_tree: Path) -> None:

0 commit comments

Comments
 (0)