Skip to content

Commit c9000bf

Browse files
committed
feat: add 'zday man' interactive documentation browser
- New zebra_day/cli/man.py with 12 topics, Rich markdown rendering, interactive menu, search, partial/numeric slug resolution, non-interactive fallback for piping - Register man_app in cli/__init__.py - Add docs/*.md to pyproject.toml package-data for pip distribution - 15 new tests (304 total passing)
1 parent 6c702a4 commit c9000bf

File tree

4 files changed

+606
-0
lines changed

4 files changed

+606
-0
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ include = ["zebra_day*"]
9494
[tool.setuptools.package-data]
9595
zebra_day = [
9696
"bin/*",
97+
"docs/*.md",
9798
"etc/*",
9899
"etc/label_styles/*",
99100
"etc/label_styles/tmps/*",

tests/test_cli.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,136 @@ def test_bootstrap_json_hides_scan_progress(self, mock_zpl_cls):
253253
assert result.exit_code == 0, result.output
254254
assert "Probing" not in result.output
255255
assert "printers_found" in result.output
256+
257+
258+
259+
# ---------------------------------------------------------------------------
260+
# zday man — interactive documentation browser
261+
# ---------------------------------------------------------------------------
262+
263+
264+
class TestCLIManHelp:
265+
"""Tests for zday man --help output."""
266+
267+
def test_man_help(self):
268+
"""Test man --help shows expected options."""
269+
result = runner.invoke(app, ["man", "--help"])
270+
assert result.exit_code == 0
271+
assert "Interactive documentation browser" in result.output
272+
assert "--search" in result.output
273+
assert "--list" in result.output
274+
275+
def test_man_in_main_help(self):
276+
"""man subcommand appears in top-level help."""
277+
result = runner.invoke(app, ["--help"])
278+
assert result.exit_code == 0
279+
assert "man" in result.output
280+
281+
282+
class TestCLIManList:
283+
"""Tests for zday man --list."""
284+
285+
def test_list_topics(self):
286+
"""--list shows the topic table."""
287+
result = runner.invoke(app, ["man", "--list"])
288+
assert result.exit_code == 0
289+
assert "Quickstart" in result.output
290+
assert "CLI Reference" in result.output
291+
assert "DynamoDB" in result.output
292+
assert "Troubleshooting" in result.output
293+
294+
295+
class TestCLIManTopics:
296+
"""Tests for direct topic display."""
297+
298+
def test_quickstart(self):
299+
"""zday man quickstart renders content."""
300+
result = runner.invoke(app, ["man", "quickstart"])
301+
assert result.exit_code == 0
302+
assert "Quickstart" in result.output
303+
304+
def test_cli_reference(self):
305+
"""zday man cli renders content."""
306+
result = runner.invoke(app, ["man", "cli"])
307+
assert result.exit_code == 0
308+
assert "CLI Reference" in result.output
309+
310+
def test_gui(self):
311+
"""zday man gui renders the UI guide."""
312+
result = runner.invoke(app, ["man", "gui"])
313+
assert result.exit_code == 0
314+
assert "GUI Usage" in result.output
315+
316+
def test_https(self):
317+
"""zday man https renders HTTPS docs."""
318+
result = runner.invoke(app, ["man", "https"])
319+
assert result.exit_code == 0
320+
assert "HTTPS" in result.output
321+
322+
def test_hardware(self):
323+
"""zday man hardware renders hardware guide."""
324+
result = runner.invoke(app, ["man", "hardware"])
325+
assert result.exit_code == 0
326+
assert "Hardware" in result.output
327+
328+
def test_unknown_topic_exits_1(self):
329+
"""Unknown topic prints error and exits 1."""
330+
result = runner.invoke(app, ["man", "nonexistent_xyz"])
331+
assert result.exit_code == 1
332+
assert "Unknown topic" in result.output
333+
334+
def test_partial_match(self):
335+
"""Partial topic slug resolves correctly."""
336+
result = runner.invoke(app, ["man", "quick"])
337+
assert result.exit_code == 0
338+
assert "Quickstart" in result.output
339+
340+
def test_numeric_topic(self):
341+
"""Numeric input resolves to topic by index."""
342+
result = runner.invoke(app, ["man", "1"])
343+
assert result.exit_code == 0
344+
assert "Overview" in result.output
345+
346+
347+
class TestCLIManSearch:
348+
"""Tests for zday man --search."""
349+
350+
def test_search_finds_results(self):
351+
"""--search returns matching lines."""
352+
result = runner.invoke(app, ["man", "--search", "printer"])
353+
assert result.exit_code == 0
354+
assert "matches" in result.output.lower() or "printer" in result.output.lower()
355+
356+
def test_search_no_results(self):
357+
"""--search with nonsense term shows no results."""
358+
result = runner.invoke(app, ["man", "--search", "xyzzy_nonexistent_qwerty"])
359+
assert result.exit_code == 0
360+
assert "No results" in result.output
361+
362+
363+
class TestCLIManGracefulDegradation:
364+
"""Tests for graceful handling of missing doc files."""
365+
366+
def test_missing_file_shows_warning(self):
367+
"""A topic pointing to a missing file shows a warning, not a crash."""
368+
from zebra_day.cli.man import TOPICS, Topic, TopicSource, _get_topic_content
369+
370+
fake_topic = Topic(
371+
name="Missing",
372+
description="test",
373+
sources=[TopicSource("does_not_exist.md")],
374+
)
375+
content = _get_topic_content(fake_topic)
376+
assert "not found" in content.lower()
377+
378+
def test_missing_section_shows_warning(self):
379+
"""A topic with a bad heading shows a warning, not a crash."""
380+
from zebra_day.cli.man import TOPICS, Topic, TopicSource, _get_topic_content
381+
382+
fake_topic = Topic(
383+
name="BadSection",
384+
description="test",
385+
sources=[TopicSource("README.md", "HEADING_THAT_DOES_NOT_EXIST_12345")],
386+
)
387+
content = _get_topic_content(fake_topic)
388+
assert "not found" in content.lower()

zebra_day/cli/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from zebra_day.cli.dynamo import dynamo_app
1818
from zebra_day.cli.env import env_app
1919
from zebra_day.cli.gui import gui_app
20+
from zebra_day.cli.man import man_app
2021
from zebra_day.cli.printer import printer_app
2122
from zebra_day.cli.template import template_app
2223

@@ -37,6 +38,7 @@
3738
app.add_typer(env_app, name="env", help="Development environment management")
3839
app.add_typer(cognito_app, name="cognito", help="Cognito authentication management")
3940
app.add_typer(dynamo_app, name="dynamo", help="DynamoDB shared configuration management")
41+
app.add_typer(man_app, name="man", help="Interactive documentation browser")
4042

4143

4244
def _get_version() -> str:

0 commit comments

Comments
 (0)