Skip to content

Commit c0e4b77

Browse files
jottakkacursoragent
andcommitted
fix: wrap long CLI paths in table output
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 6aaef3c commit c0e4b77

2 files changed

Lines changed: 160 additions & 6 deletions

File tree

librarian/cli.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ def list_sources(
446446

447447
table = Table(title="Document Sources", show_header=True, header_style="bold cyan")
448448
table.add_column("Name", style="green")
449-
table.add_column("Path", style="blue")
449+
table.add_column("Path", style="blue", overflow="fold")
450450
table.add_column("Type", style="magenta")
451451
table.add_column("Status", style="yellow")
452452

@@ -1029,7 +1029,7 @@ def docs_overview(ctx: typer.Context) -> None:
10291029

10301030
table = Table(title="Sources", show_header=True, header_style="bold cyan")
10311031
table.add_column("Name", style="green")
1032-
table.add_column("Path", style="blue")
1032+
table.add_column("Path", style="blue", overflow="fold")
10331033
table.add_column("Docs", justify="right", style="yellow")
10341034
table.add_column("Status")
10351035

@@ -1102,7 +1102,7 @@ def docs_list(
11021102
)
11031103
table.add_column("ID", style="dim", width=4)
11041104
table.add_column("Title", style="green", max_width=38)
1105-
table.add_column("Path", style="blue", max_width=46)
1105+
table.add_column("Path", style="blue", max_width=46, overflow="fold")
11061106
table.add_column("Type", style="magenta", width=6)
11071107

11081108
home = str(Path.home())
@@ -1161,7 +1161,7 @@ def docs_search(
11611161
)
11621162
table.add_column("ID", style="dim", width=4)
11631163
table.add_column("Title", style="green")
1164-
table.add_column("Path", style="blue", max_width=50)
1164+
table.add_column("Path", style="blue", max_width=50, overflow="fold")
11651165

11661166
home = str(Path.home())
11671167
for doc in matches[:limit]:
@@ -1331,7 +1331,7 @@ def search_cmd(
13311331
table = Table(show_header=True, header_style="bold cyan", box=None)
13321332
table.add_column("#", style="dim", width=3)
13331333
table.add_column("Score", justify="right", width=7)
1334-
table.add_column("Path", style="blue")
1334+
table.add_column("Path", style="blue", overflow="fold")
13351335

13361336
for i, result in enumerate(results, 1):
13371337
score = result.get("score", 0)

tests/test_cli.py

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,41 @@
11
"""Tests for CLI behavior."""
22

33
from pathlib import Path
4+
from types import SimpleNamespace
45
from typing import Any
56

67
import pytest
8+
from rich.console import Console
79
from typer.testing import CliRunner
810

911
from librarian import cli
1012

13+
LONG_WINDOWS_PATH = (
14+
r"C:\Users\example\Documents\Codex\2026-05-16"
15+
r"\concurso-browser-deckbuilder\docs\planning"
16+
r"\vgf-56-revolt-fx-distribution-and-ui-editor-plan.md"
17+
)
18+
LONG_WINDOWS_FILENAME = "vgf-56-revolt-fx-distribution-and-ui-editor-plan.md"
19+
20+
21+
def assert_table_preserves_long_path_reference(result: Any) -> None:
22+
assert result.exit_code == 0
23+
assert "\ufffd" not in result.output
24+
assert "…" not in result.output
25+
normalized_output = "".join(
26+
char for char in result.output if char.isalnum() or char in "\\/:._-"
27+
)
28+
assert LONG_WINDOWS_FILENAME in normalized_output
29+
30+
31+
def fake_document() -> SimpleNamespace:
32+
return SimpleNamespace(
33+
id=1,
34+
title="VGF-56 plan",
35+
path=LONG_WINDOWS_PATH,
36+
asset_type=SimpleNamespace(value="text"),
37+
)
38+
1139

1240
def test_add_directory_exits_nonzero_when_indexing_errors(
1341
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
@@ -23,6 +51,7 @@ def test_add_directory_exits_nonzero_when_indexing_errors(
2351
monkeypatch.setattr(cli, "SOURCES_FILE", config_dir / "sources.json")
2452
monkeypatch.setattr(cli, "SETTINGS_FILE", config_dir / "settings.json")
2553
monkeypatch.setattr(cli, "_get_config", lambda: {"ensure_directories": lambda: None})
54+
monkeypatch.setattr(cli, "console", Console(width=500, color_system=None))
2655

2756
async def fake_server_ingest(context: Any, directory: str) -> dict[str, Any]:
2857
return {
@@ -31,7 +60,7 @@ async def fake_server_ingest(context: Any, directory: str) -> dict[str, Any]:
3160
"indexed": 0,
3261
"updated": 0,
3362
"skipped": 0,
34-
"errors": [{"path": str(fixture), "error": "parser exploded"}],
63+
"errors": [{"path": fixture.name, "error": "parser exploded"}],
3564
"files": [],
3665
}
3766

@@ -44,3 +73,128 @@ async def fake_server_ingest(context: Any, directory: str) -> dict[str, Any]:
4473
assert result.exit_code == 1
4574
assert "Errors:" in result.output
4675
assert "parser exploded" in result.output
76+
77+
78+
def test_search_table_wraps_long_windows_paths(
79+
monkeypatch: pytest.MonkeyPatch,
80+
) -> None:
81+
"""Narrow table output should wrap paths instead of replacing them with ellipses."""
82+
monkeypatch.setattr(cli, "_get_config", lambda: {"ensure_directories": lambda: None})
83+
monkeypatch.setattr(cli, "console", Console(width=80, color_system=None))
84+
85+
async def fake_search_library(**kwargs: Any) -> list[dict[str, Any]]:
86+
return [
87+
{
88+
"score": 1.0,
89+
"document_path": LONG_WINDOWS_PATH,
90+
"content": "matched content",
91+
"heading_path": None,
92+
}
93+
]
94+
95+
monkeypatch.setattr("librarian.server.search_library", fake_search_library)
96+
97+
result = CliRunner().invoke(
98+
cli.app,
99+
[
100+
"search",
101+
"VGF-56 UI editor Fabric Tweakpane Pixi Layout Pixi UI",
102+
"--format",
103+
"table",
104+
],
105+
)
106+
107+
assert_table_preserves_long_path_reference(result)
108+
109+
110+
def test_list_table_wraps_long_windows_paths(monkeypatch: pytest.MonkeyPatch) -> None:
111+
"""Source listing should not truncate long paths with an ellipsis."""
112+
monkeypatch.setattr(cli, "console", Console(width=80, color_system=None))
113+
monkeypatch.setattr(
114+
cli,
115+
"_load_sources",
116+
lambda: [{"name": "docs", "path": LONG_WINDOWS_PATH, "is_file": False}],
117+
)
118+
119+
result = CliRunner().invoke(cli.app, ["list"])
120+
121+
assert_table_preserves_long_path_reference(result)
122+
123+
124+
def test_docs_overview_wraps_long_windows_paths(monkeypatch: pytest.MonkeyPatch) -> None:
125+
"""Document source overview should wrap long paths."""
126+
monkeypatch.setattr(cli, "_get_config", lambda: {"ensure_directories": lambda: None})
127+
monkeypatch.setattr(cli, "console", Console(width=80, color_system=None))
128+
monkeypatch.setattr(
129+
cli,
130+
"_load_sources",
131+
lambda: [{"name": "docs", "path": LONG_WINDOWS_PATH, "is_file": False}],
132+
)
133+
monkeypatch.setattr(
134+
"librarian.storage.database.get_database",
135+
lambda: SimpleNamespace(list_documents=lambda: [fake_document()]),
136+
)
137+
138+
result = CliRunner().invoke(cli.app, ["docs"])
139+
140+
assert_table_preserves_long_path_reference(result)
141+
142+
143+
def test_docs_list_table_wraps_long_windows_paths(monkeypatch: pytest.MonkeyPatch) -> None:
144+
"""Document listing should wrap long paths in table output."""
145+
monkeypatch.setattr(cli, "_get_config", lambda: {"ensure_directories": lambda: None})
146+
monkeypatch.setattr(cli, "console", Console(width=80, color_system=None))
147+
monkeypatch.setattr(
148+
"librarian.storage.database.get_database",
149+
lambda: SimpleNamespace(list_documents=lambda: [fake_document()]),
150+
)
151+
152+
result = CliRunner().invoke(cli.app, ["docs", "list", "--format", "table"])
153+
154+
assert_table_preserves_long_path_reference(result)
155+
156+
157+
def test_docs_search_table_wraps_long_windows_paths(monkeypatch: pytest.MonkeyPatch) -> None:
158+
"""Document title search should wrap long paths in table output."""
159+
monkeypatch.setattr(cli, "_get_config", lambda: {"ensure_directories": lambda: None})
160+
monkeypatch.setattr(cli, "console", Console(width=80, color_system=None))
161+
monkeypatch.setattr(
162+
"librarian.storage.database.get_database",
163+
lambda: SimpleNamespace(list_documents=lambda: [fake_document()]),
164+
)
165+
166+
result = CliRunner().invoke(cli.app, ["docs", "search", "VGF", "--format", "table"])
167+
168+
assert_table_preserves_long_path_reference(result)
169+
170+
171+
def test_search_paths_outputs_complete_long_windows_paths(
172+
monkeypatch: pytest.MonkeyPatch,
173+
) -> None:
174+
"""Paths output remains the copyable full-path mode for search results."""
175+
monkeypatch.setattr(cli, "_get_config", lambda: {"ensure_directories": lambda: None})
176+
177+
async def fake_search_library(**kwargs: Any) -> list[dict[str, Any]]:
178+
return [
179+
{
180+
"score": 1.0,
181+
"document_path": LONG_WINDOWS_PATH,
182+
"content": "matched content",
183+
"heading_path": None,
184+
}
185+
]
186+
187+
monkeypatch.setattr("librarian.server.search_library", fake_search_library)
188+
189+
result = CliRunner().invoke(
190+
cli.app,
191+
[
192+
"search",
193+
"VGF-56 UI editor Fabric Tweakpane Pixi Layout Pixi UI",
194+
"--format",
195+
"paths",
196+
],
197+
)
198+
199+
assert result.exit_code == 0
200+
assert result.output == f"{LONG_WINDOWS_PATH}\n"

0 commit comments

Comments
 (0)