Files
Medios-Macina/docs/result_table.md

9.7 KiB

ResultTable system — Overview & usage

This document explains the ResultTable system used across the CLI and TUI: how tables are built, how providers integrate with them, and how @N selection/expansion and provider selectors work.

TL;DR

  • ResultTable is the unified object used to render tabular results and drive selection (@N) behavior.
  • Providers should return SearchResult objects (or dicts) and can either supply selection_args per row or implement a selector() method to handle @N selections.
  • Table metadata (set_table_metadata) helps providers attach context (e.g., provider_view, magnet_id) that selectors can use.

Key concepts

  • ResultTable (SYS/result_table.py)

    • Renders rows as a rich table and stores metadata used for selection expansion.
    • Important APIs: add_result(), set_table(), set_source_command(), set_row_selection_args(), set_row_selection_action(), set_table_metadata(), and select_interactive().
  • ResultRow

    • Holds columns plus selection_args (used for @N expansion) and payload (original object).
    • Optionally stores selection_action, a full list of CLI tokens to run when @N selects this row. When present the CLI honors the explicit action instead of reconstructing it from source_command and selection_args.
  • Provider selector

    • If a provider implements selector(selected_items, ctx=..., stage_is_last=True), it is run first when @N is used; if the selector returns True it has handled the selection (e.g., drilling into a folder and publishing a new ResultTable).
  • Pipeline / CLI expansion

    • When the user types @N, CLI tries provider selectors first. If none handle it, CLI re-runs source_command + source_args + row_selection_args (for single-selection) or pipes items downstream for multi-selection.
  • Table metadata

    • ResultTable.set_table_metadata(dict) allows attaching provider-specific context (for example: {"provider":"alldebrid","view":"files","magnet_id":123}) for selectors and other code to use.

How to build a table (provider pattern)

Typical provider flow (pseudocode):

from SYS.result_table import ResultTable

table = ResultTable("Provider: X result").set_preserve_order(True)
table.set_table("provider_name")
table.set_table_metadata({"provider":"provider_name","view":"folders"})
table.set_source_command("search-file", ["-provider","provider_name","query"])

for r in results:
    table.add_result(r)  # r can be a SearchResult, dict, or PipeObject

ctx.set_last_result_table(table, payloads)
ctx.set_current_stage_table(table)

Notes:

  • To drive a direct @N re-run, call table.set_row_selection_args(row_index, ["-open", "<id>"]).
  • For more advanced or interactive behavior (e.g., drill-into, fetch more rows), implement provider.selector() and return True when handled.

Selection (@N) flow (brief)

  1. User enters @N in the CLI.
  2. CLI chooses the appropriate table (overlay > last table > history) and gathers the selected payload(s).
  3. PipelineExecutor._maybe_run_class_selector() runs provider selector() hooks for the provider inferred from table or payloads. If any selector returns True, expansion stops.
  4. Otherwise, for single selections, CLI grabs row.selection_args and expands: source_command + source_args + row_selection_args and inserts it as the expanded stage. For multi-selections, items are piped downstream.

Columns & display

  • Providers can pass a columns list ([(name, value), ...]) in the result dict/SearchResult to control which columns are shown and their order.
  • Otherwise, ResultTable uses a priority list (title/store/size/ext) and sensible defaults.
  • The table rendering functions (to_rich, format_json, format_compact) are available for different UIs.

Provider-specific examples

AllDebrid (debrid file hosting)

AllDebrid exposes a list of magnets (folder rows) and the files inside each magnet. The provider returns folder SearchResults for magnets and file SearchResults for individual files. The provider includes a selector() that drills into a magnet by calling search(..., filters={"view":"files","magnet_id":...}) and builds a new ResultTable of files.

Example commands:

# List magnets in your account
search-file -provider alldebrid "*"

# Open magnet id 123 and list its files
search-file -provider alldebrid -open 123 "*"

# Or expand via @ selection (selector handles drilling):
search-file -provider alldebrid "*"
@3  # selector will open the magnet referenced by row #3 and show the file table

Illustrative folder (magnet) SearchResult:

SearchResult(
    table="alldebrid",
    title="My Magnet Title",
    path="alldebrid:magnet:123",
    detail="OK",
    annotations=["folder", "ready"],
    media_kind="folder",
    columns=[("Folder", "My Magnet Title"), ("ID", "123"), ("Status", "ready"), ("Ready", "yes")],
    full_metadata={
        "magnet": {...},
        "magnet_id": 123,
        "provider": "alldebrid",
        "provider_view": "folders",
        "magnet_name": "My Magnet Title",
    },
)
  1. Otherwise, for single selections, CLI checks for row.selection_action and runs that verbatim if present; otherwise it expands source_command + source_args + row_selection_args. For multi-selections, items are piped downstream.
SearchResult(
    table="alldebrid",
    title="Episode 01.mkv",
    path="https://.../unlocked_direct_url",
    detail="My Magnet Title",
    annotations=["file"],
    media_kind="file",
    size_bytes=123456789,
    columns=[("File", "Episode 01.mkv"), ("Folder", "My Magnet Title"), ("ID", "123")],
    full_metadata={
        "magnet": {...},
        "magnet_id": 123,
        "magnet_name": "My Magnet Title",
        "relpath": "Season 1/E01.mkv",
        "provider": "alldebrid",
        "provider_view": "files",
        "file": {...},
    },
)

Selection & download flows

  • Drill-in (selector): @3 on a magnet row runs the provider's selector() to build a new file table and show it. The selector uses search(..., filters={"view":"files","magnet_id":...}) to fetch file rows.

  • download-file integration: With a file row (http(s) path), @2 | download-file will download the file. The download-file cmdlet expands AllDebrid magnet folders and will call the provider layer to fetch file bytes as appropriate.

  • add-file convenience: Piping a file row into add-file -path <dest> will trigger add-file's provider-aware logic. If the piped item has table == 'alldebrid' and a http(s) path, add-file will call provider.download() into a temporary directory and then ingest the downloaded file, cleaning up the temp when done. Example:

# Expand magnet and add first file to local directory
search-file -provider alldebrid "*"
@3  # view files
@1 | add-file -path C:\mydir

Notes & troubleshooting

  • Configure an AllDebrid API key (see Provider/alldebrid._get_debrid_api_key()).
  • If a magnet isn't ready the selector or download-file will log the magnet status and avoid attempting file downloads.

Bandcamp (artist → discography drill-in)

Bandcamp search supports artist: queries. Bandcamp's provider implements a selector() that detects artist results and scrapes the artist's page using Playwright to build a discography ResultTable.

Example usage:

# Search for an artist
search-file -provider bandcamp "artist:radiohead"

# Select an artist row to expand into releases
@1

Bandcamp SearchResult (artist / album rows):

SearchResult(
    table="bandcamp",
    title="Album Title",
    path="https://bandcamp.com/album_url",
    detail="By: Artist",
    annotations=["album"],
    media_kind="audio",
    columns=[("Title","Album Title"), ("Location","Artist"), ("Type","album"), ("Url","https://...")],
    full_metadata={"artist":"Artist","type":"album","url":"https://..."}
)

Notes:

  • Playwright is required for Bandcamp scraping. The selector will log an informative message if Playwright is missing.
  • Provider selectors are ideal when you need to replace one table with another (artist → discography).

Provider author checklist (short)

  • Implement search(query, limit, filters) and return SearchResult objects or dicts; include useful full_metadata (IDs, view names) for selection/drilling.
  • If you support fetching downloadable file bytes, implement download(result, output_dir) -> Optional[Path].
  • For drill-in or interactive transforms, implement selector(selected_items, ctx=..., stage_is_last=True) and call ctx.set_last_result_table(...) / ctx.set_current_stage_table(...); return True when handled.
  • Add tests (unit/integration) that exercise search → select → download flows.

Debugging tips

  • Use ctx.set_last_result_table(table, payloads) to immediately show a table while developing a selector.
  • Add log(...) messages in provider code to capture fail points.
  • Check full_metadata attached to SearchResults to pass extra context (IDs, view names, provider names).

Quick reference

  • ResultTable location: SYS/result_table.py
  • Pipeline helpers: SYS/pipeline.py (set_last_result_table, set_current_stage_table, get_current_stage_table_row_selection_args)
  • CLI expansion: CLI.py (handles @N, provider selectors, and insertion of expanded stages)
  • Provider selector pattern: Implement .selector(selected_items, ctx=..., stage_is_last=True) in provider class.

For more detail on ResultTable provider authoring, see docs/provider_authoring.md.

If you'd like, I can also:

  • Add provider-specific examples (AllDebrid, Bandcamp) into this doc
  • Add a short checklist for PR reviewers when adding new providers

Created by GitHub Copilot (Raptor mini - Preview) — brief guide to the ResultTable system. Feedback welcome!