From 6229641a376df804061bbf3b4258edf4d55a85e1 Mon Sep 17 00:00:00 2001 From: ChengYe <666@orgleaf.com> Date: Wed, 29 Apr 2026 22:15:50 +0800 Subject: [PATCH] fix(calibre): make books list columns dynamic from returned fields - Render the books list table dynamically based on actual fields returned by list_books() so the displayed columns match --fields without showing always-empty Formats column by default. - Add E2E test verifying list_books honors a custom fields list. Addresses reviewer feedback on PR #223 (Formats column always empty). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../cli_anything/calibre/calibre_cli.py | 19 ++++++++++++------- .../calibre/tests/test_full_e2e.py | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/calibre/agent-harness/cli_anything/calibre/calibre_cli.py b/calibre/agent-harness/cli_anything/calibre/calibre_cli.py index 67fda52ff..e5a938951 100644 --- a/calibre/agent-harness/cli_anything/calibre/calibre_cli.py +++ b/calibre/agent-harness/cli_anything/calibre/calibre_cli.py @@ -264,15 +264,20 @@ def books_list(ctx, search, sort, limit, fields): if not book_list: click.echo(" No books found.") else: - click.echo(f" {'ID':<5} {'Title':<40} {'Authors':<30} {'Formats'}") - click.echo(f" {'─'*5} {'─'*40} {'─'*30} {'─'*20}") + # Determine columns from actual returned fields (id is always first) + actual_fields = list(book_list[0].keys()) + non_id_fields = [f for f in actual_fields if f != "id"] + + # Build header dynamically + header = f" {'ID':<5} " + " ".join(f"{f.capitalize():<30}" for f in non_id_fields) + sep = f" {'─'*5} " + " ".join(f"{'─'*30}" for _ in non_id_fields) + click.echo(header) + click.echo(sep) for b in book_list: - click.echo( - f" {b.get('id', ''):<5} " - f"{str(b.get('title', ''))[:40]:<40} " - f"{str(b.get('authors', ''))[:30]:<30} " - f"{b.get('formats', '')}" + row = f" {b.get('id', ''):<5} " + " ".join( + f"{str(b.get(f, ''))[:30]:<30}" for f in non_id_fields ) + click.echo(row) @books.command("search") diff --git a/calibre/agent-harness/cli_anything/calibre/tests/test_full_e2e.py b/calibre/agent-harness/cli_anything/calibre/tests/test_full_e2e.py index af57e64c3..9afde7f04 100644 --- a/calibre/agent-harness/cli_anything/calibre/tests/test_full_e2e.py +++ b/calibre/agent-harness/cli_anything/calibre/tests/test_full_e2e.py @@ -199,6 +199,21 @@ class TestLibraryOperations(CalibreTestMixin, unittest.TestCase): self.assertIn("title", book) print(f"\n list_books[0]: {book}") + def test_list_books_custom_fields(self): + """list_books with explicit fields returns exactly those fields per book.""" + from cli_anything.calibre.core.library import list_books + requested = ["id", "title", "tags"] + books = list_books(self.lib_dir, fields=requested) + self.assertGreater(len(books), 0) + book = books[0] + # Requested fields must be present + for field in requested: + self.assertIn(field, book, f"Missing field: {field}") + # Non-requested fields must be absent + for field in ("authors", "series", "rating"): + self.assertNotIn(field, book, f"Unexpected field: {field}") + print(f"\n list_books(fields={requested})[0]: {book}") + def test_search_books(self): from cli_anything.calibre.core.library import search_books ids = search_books(self.lib_dir, "title:Foundation")