Skip to content

Commit e20c8d9

Browse files
Roni VeghRoni Vegh
authored andcommitted
fix windows unicode separator crash in search output
1 parent 71736a3 commit e20c8d9

2 files changed

Lines changed: 47 additions & 3 deletions

File tree

mempalace/searcher.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"""
88

99
import logging
10+
import sys
1011
from pathlib import Path
1112

1213
import chromadb
@@ -18,6 +19,20 @@ class SearchError(Exception):
1819
"""Raised when search cannot proceed (e.g. no palace found)."""
1920

2021

22+
def _separator_line(width: int = 56) -> str:
23+
"""
24+
Return a separator line safe for the active stdout encoding.
25+
26+
Windows cp1252 terminals cannot encode the Unicode box-drawing char.
27+
"""
28+
encoding = getattr(sys.stdout, "encoding", None) or "utf-8"
29+
try:
30+
"─".encode(encoding)
31+
return "─" * width
32+
except Exception:
33+
return "-" * width
34+
35+
2136
def search(query: str, palace_path: str, wing: str = None, room: str = None, n_results: int = 5):
2237
"""
2338
Search the palace. Returns verbatim drawer content.
@@ -70,6 +85,7 @@ def search(query: str, palace_path: str, wing: str = None, room: str = None, n_r
7085
if room:
7186
print(f" Room: {room}")
7287
print(f"{'=' * 60}\n")
88+
separator = _separator_line()
7389

7490
for i, (doc, meta, dist) in enumerate(zip(docs, metas, dists), 1):
7591
similarity = round(1 - dist, 3)
@@ -85,7 +101,7 @@ def search(query: str, palace_path: str, wing: str = None, room: str = None, n_r
85101
for line in doc.strip().split("\n"):
86102
print(f" {line}")
87103
print()
88-
print(f" {'─' * 56}")
104+
print(f" {separator}")
89105

90106
print()
91107

tests/test_searcher.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
Tests the library-facing search interface (not the CLI print variant).
55
"""
66

7-
from mempalace.searcher import search_memories
8-
7+
import io
8+
from mempalace.searcher import search, search_memories
99

1010
class TestSearchMemories:
1111
def test_basic_search(self, palace_path, seeded_collection):
@@ -43,3 +43,31 @@ def test_result_fields(self, palace_path, seeded_collection):
4343
assert "source_file" in hit
4444
assert "similarity" in hit
4545
assert isinstance(hit["similarity"], float)
46+
47+
def test_cli_search_uses_ascii_separator_on_cp1252_stdout(self, monkeypatch):
48+
class _FakeCollection:
49+
def query(self, **_kwargs):
50+
return {
51+
"documents": [["JWT auth details"]],
52+
"metadatas": [[{"wing": "project", "room": "backend", "source_file": "auth.py"}]],
53+
"distances": [[0.1]],
54+
}
55+
56+
class _FakeClient:
57+
def __init__(self, path):
58+
self.path = path
59+
60+
def get_collection(self, _name):
61+
return _FakeCollection()
62+
63+
monkeypatch.setattr("mempalace.searcher.chromadb.PersistentClient", _FakeClient)
64+
65+
buf = io.BytesIO()
66+
fake_stdout = io.TextIOWrapper(buf, encoding="cp1252", errors="strict")
67+
monkeypatch.setattr("sys.stdout", fake_stdout)
68+
69+
search("anything", "/tmp/fake-palace")
70+
fake_stdout.flush()
71+
output = buf.getvalue().decode("cp1252")
72+
73+
assert " " + ("-" * 56) in output

0 commit comments

Comments
 (0)