stackvox 0.8.0__tar.gz → 0.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stackvox-0.8.0/stackvox.egg-info → stackvox-0.9.0}/PKG-INFO +1 -1
- {stackvox-0.8.0 → stackvox-0.9.0}/pyproject.toml +1 -1
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox/cli.py +9 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox/daemon.py +15 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox/text.py +17 -0
- {stackvox-0.8.0 → stackvox-0.9.0/stackvox.egg-info}/PKG-INFO +1 -1
- {stackvox-0.8.0 → stackvox-0.9.0}/tests/test_cli.py +20 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/tests/test_daemon_protocol.py +16 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/tests/test_text.py +26 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/LICENSE +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/NOTICE +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/README.md +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/setup.cfg +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox/__init__.py +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox/__main__.py +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox/config.py +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox/data/stackvox-say +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox/engine.py +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox/paths.py +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox/py.typed +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox/updates.py +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox.egg-info/SOURCES.txt +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox.egg-info/dependency_links.txt +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox.egg-info/entry_points.txt +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox.egg-info/requires.txt +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/stackvox.egg-info/top_level.txt +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/tests/test_config.py +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/tests/test_daemon.py +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/tests/test_engine.py +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/tests/test_paths.py +0 -0
- {stackvox-0.8.0 → stackvox-0.9.0}/tests/test_updates.py +0 -0
|
@@ -389,9 +389,18 @@ def _cmd_stop(_: argparse.Namespace) -> int:
|
|
|
389
389
|
|
|
390
390
|
|
|
391
391
|
def _cmd_status(_: argparse.Namespace) -> int:
|
|
392
|
+
installed = updates._current_version()
|
|
392
393
|
if daemon.is_running():
|
|
393
394
|
print(f"running (pid {daemon.PID_PATH.read_text().strip()}) on {daemon.SOCKET_PATH}")
|
|
394
395
|
rc = 0
|
|
396
|
+
# The running daemon can lag the installed package if it wasn't restarted
|
|
397
|
+
# after an upgrade — surface that skew instead of trusting it blindly.
|
|
398
|
+
got, running_version = daemon.version()
|
|
399
|
+
if got and running_version != installed:
|
|
400
|
+
print(
|
|
401
|
+
f"daemon running {running_version}, but {installed} is installed "
|
|
402
|
+
f"— restart the daemon (stackvox stop; stackvox serve) to pick it up"
|
|
403
|
+
)
|
|
395
404
|
else:
|
|
396
405
|
print("stopped")
|
|
397
406
|
rc = 1
|
|
@@ -222,6 +222,10 @@ class _Handler(socketserver.StreamRequestHandler):
|
|
|
222
222
|
self.wfile.write(b"ok\n")
|
|
223
223
|
return
|
|
224
224
|
|
|
225
|
+
if command == "version":
|
|
226
|
+
self.wfile.write(f"{updates._current_version()}\n".encode())
|
|
227
|
+
return
|
|
228
|
+
|
|
225
229
|
if command == "stop":
|
|
226
230
|
self.wfile.write(b"ok\n")
|
|
227
231
|
threading.Thread(target=self.server.shutdown, daemon=True).start()
|
|
@@ -343,3 +347,14 @@ def stop() -> tuple[bool, str]:
|
|
|
343
347
|
|
|
344
348
|
def ping() -> tuple[bool, str]:
|
|
345
349
|
return send({"command": "ping"}, timeout=PING_TIMEOUT_SECONDS)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def version(timeout: float = PING_TIMEOUT_SECONDS) -> tuple[bool, str]:
|
|
353
|
+
"""(got_version, version_or_error) reported by the running daemon.
|
|
354
|
+
|
|
355
|
+
The running daemon can differ from the installed package if it wasn't
|
|
356
|
+
restarted after an upgrade, so this reports what is actually serving — not
|
|
357
|
+
what's on disk. A version reply starts with a digit; error strings don't.
|
|
358
|
+
"""
|
|
359
|
+
_, resp = send({"command": "version"}, timeout=timeout)
|
|
360
|
+
return resp[:1].isdigit(), resp
|
|
@@ -18,6 +18,7 @@ __all__ = [
|
|
|
18
18
|
"markdown_to_paragraphs",
|
|
19
19
|
"strip_emoji",
|
|
20
20
|
"strip_thousands_separators",
|
|
21
|
+
"versions_to_words",
|
|
21
22
|
"decimals_to_words",
|
|
22
23
|
"expand_units",
|
|
23
24
|
"apply_pronunciations",
|
|
@@ -84,6 +85,21 @@ def strip_thousands_separators(text: str) -> str:
|
|
|
84
85
|
return re.sub(r"(?<=\d),(?=\d)", "", text)
|
|
85
86
|
|
|
86
87
|
|
|
88
|
+
_VERSION = re.compile(r"(?<!\d)\d+(?:\.\d+){2,}(?!\d)")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def versions_to_words(text: str) -> str:
|
|
92
|
+
"""1.2.3 -> "1 point 2 point 3"; 0.7.0 -> "0 point 7 point 0".
|
|
93
|
+
|
|
94
|
+
A dotted version string has more than one decimal point, which
|
|
95
|
+
`decimals_to_words` can't handle — it splits only the first, leaving a stray
|
|
96
|
+
full stop mid-number ("0.7.0" -> "0 point 7.0"). Run this before it. Two-part
|
|
97
|
+
numbers are left untouched so ordinary decimals still read digit-by-digit,
|
|
98
|
+
and a trailing sentence stop (``upgrade to 1.2.3.``) is preserved.
|
|
99
|
+
"""
|
|
100
|
+
return _VERSION.sub(lambda m: m.group(0).replace(".", " point "), text)
|
|
101
|
+
|
|
102
|
+
|
|
87
103
|
def decimals_to_words(text: str) -> str:
|
|
88
104
|
"""1198.9 -> "1198 point 9"; 770.72 -> "770 point 7 2". Removes the bare
|
|
89
105
|
"." between digits, which TTS can otherwise read as a full stop."""
|
|
@@ -287,6 +303,7 @@ def _shape_paragraph(
|
|
|
287
303
|
if expand_units_flag: # units BEFORE decimals (see note above)
|
|
288
304
|
text = expand_units(text, locale)
|
|
289
305
|
if expand_numbers_flag:
|
|
306
|
+
text = versions_to_words(text) # multi-dot versions BEFORE the decimal split
|
|
290
307
|
text = decimals_to_words(text)
|
|
291
308
|
return re.sub(r"[ \t]{2,}", " ", text).strip()
|
|
292
309
|
|
|
@@ -261,6 +261,9 @@ class TestCmdStatus:
|
|
|
261
261
|
pid_path.read_text.return_value = "12345\n"
|
|
262
262
|
mocker.patch.object(cli.daemon, "PID_PATH", pid_path)
|
|
263
263
|
mocker.patch.object(cli.daemon, "SOCKET_PATH", "/tmp/x.sock")
|
|
264
|
+
# status now queries the running daemon's version; stub it so the test
|
|
265
|
+
# doesn't reach a real socket.
|
|
266
|
+
mocker.patch.object(cli.daemon, "version", return_value=(False, "n/a"))
|
|
264
267
|
|
|
265
268
|
rc = cli._cmd_status(_ns())
|
|
266
269
|
|
|
@@ -269,6 +272,23 @@ class TestCmdStatus:
|
|
|
269
272
|
assert "running" in out
|
|
270
273
|
assert "12345" in out
|
|
271
274
|
|
|
275
|
+
def test_running_flags_daemon_version_skew(self, mocker, capsys):
|
|
276
|
+
mocker.patch.object(cli.daemon, "is_running", return_value=True)
|
|
277
|
+
pid_path = mocker.MagicMock()
|
|
278
|
+
pid_path.read_text.return_value = "1\n"
|
|
279
|
+
mocker.patch.object(cli.daemon, "PID_PATH", pid_path)
|
|
280
|
+
mocker.patch.object(cli.daemon, "SOCKET_PATH", "/tmp/x.sock")
|
|
281
|
+
mocker.patch.object(cli.daemon, "version", return_value=(True, "0.5.0"))
|
|
282
|
+
mocker.patch.object(cli.updates, "_current_version", return_value="0.8.0")
|
|
283
|
+
mocker.patch.object(cli.updates, "check_for_update", return_value=None)
|
|
284
|
+
|
|
285
|
+
rc = cli._cmd_status(_ns())
|
|
286
|
+
|
|
287
|
+
assert rc == 0
|
|
288
|
+
out = capsys.readouterr().out
|
|
289
|
+
assert "0.5.0" in out and "0.8.0" in out # the skew is surfaced
|
|
290
|
+
assert "restart" in out.lower()
|
|
291
|
+
|
|
272
292
|
def test_stopped_returns_one(self, mocker, capsys):
|
|
273
293
|
mocker.patch.object(cli.daemon, "is_running", return_value=False)
|
|
274
294
|
rc = cli._cmd_status(_ns())
|
|
@@ -76,6 +76,22 @@ def test_ping_returns_ok(server: ServerHarness):
|
|
|
76
76
|
assert _roundtrip(server.sock, json.dumps({"command": "ping"}) + "\n") == "ok"
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
def test_version_returns_installed_version(server: ServerHarness):
|
|
80
|
+
from stackvox import updates
|
|
81
|
+
|
|
82
|
+
reply = _roundtrip(server.sock, json.dumps({"command": "version"}) + "\n")
|
|
83
|
+
assert reply == updates._current_version()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_version_client_helper_reports_running_version(server: ServerHarness, monkeypatch):
|
|
87
|
+
from stackvox import daemon, updates
|
|
88
|
+
|
|
89
|
+
monkeypatch.setattr(daemon, "SOCKET_PATH", server.sock)
|
|
90
|
+
got, resp = daemon.version()
|
|
91
|
+
assert got is True
|
|
92
|
+
assert resp == updates._current_version()
|
|
93
|
+
|
|
94
|
+
|
|
79
95
|
def test_plain_text_is_treated_as_text_field(server: ServerHarness):
|
|
80
96
|
"""Non-JSON payloads are accepted and wrapped as `{"text": line}`."""
|
|
81
97
|
import time
|
|
@@ -10,6 +10,7 @@ from stackvox.text import (
|
|
|
10
10
|
shape_pauses,
|
|
11
11
|
strip_emoji,
|
|
12
12
|
strip_thousands_separators,
|
|
13
|
+
versions_to_words,
|
|
13
14
|
)
|
|
14
15
|
|
|
15
16
|
# --- numbers ---------------------------------------------------------------
|
|
@@ -28,6 +29,31 @@ def test_year_is_not_a_decimal():
|
|
|
28
29
|
assert decimals_to_words("built in 2023") == "built in 2023"
|
|
29
30
|
|
|
30
31
|
|
|
32
|
+
def test_semver_all_parts_spoken():
|
|
33
|
+
assert versions_to_words("0.7.0") == "0 point 7 point 0"
|
|
34
|
+
assert versions_to_words("1.2.3") == "1 point 2 point 3"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_semver_multi_digit_component():
|
|
38
|
+
assert versions_to_words("1.20.3") == "1 point 20 point 3"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_version_preserves_trailing_sentence_stop():
|
|
42
|
+
assert versions_to_words("upgrade to 1.2.3.") == "upgrade to 1 point 2 point 3."
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_two_part_number_is_left_for_the_decimal_pass():
|
|
46
|
+
# Only two components — an ordinary decimal, not a version.
|
|
47
|
+
assert versions_to_words("3.14") == "3.14"
|
|
48
|
+
assert decimals_to_words(versions_to_words("3.14")) == "3 point 1 4"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_semver_normalizes_end_to_end():
|
|
52
|
+
out = normalize_for_speech("Upgraded to 0.7.0 today.", markdown=False)
|
|
53
|
+
assert "0 point 7 point 0" in out
|
|
54
|
+
assert "0 point 7.0" not in out
|
|
55
|
+
|
|
56
|
+
|
|
31
57
|
# --- units -----------------------------------------------------------------
|
|
32
58
|
|
|
33
59
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|