9.2 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 ✅
ResultTableis the unified object used to render tabular results and drive selection (@N) behavior.- Providers should return
SearchResultobjects (or dicts) and can either supplyselection_argsper row or implement aselector()method to handle@Nselections. - 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_table_metadata(), andselect_interactive().
-
ResultRow
- Holds columns plus
selection_args(used for@Nexpansion) andpayload(original object).
- Holds columns plus
-
Provider selector
- If a provider implements
selector(selected_items, ctx=..., stage_is_last=True), it is run first when@Nis used; if the selector returnsTrueit has handled the selection (e.g., drilling into a folder and publishing a new ResultTable).
- If a provider implements
-
Pipeline / CLI expansion
- When the user types
@N, CLI tries provider selectors first. If none handle it, CLI re-runssource_command + source_args + row_selection_args(for single-selection) or pipes items downstream for multi-selection.
- When the user types
-
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
@Nre-run, calltable.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 returnTruewhen handled.
Selection (@N) flow (brief)
- User enters
@Nin the CLI. - CLI chooses the appropriate table (overlay > last table > history) and gathers the selected payload(s).
PipelineExecutor._maybe_run_class_selector()runs providerselector()hooks for the provider inferred from table or payloads. If any selector returnsTrue, expansion stops.- Otherwise, for single selections, CLI grabs
row.selection_argsand expands:source_command + source_args + row_selection_argsand inserts it as the expanded stage. For multi-selections, items are piped downstream.
Columns & display
- Providers can pass a
columnslist ([(name, value), ...]) in the result dict/SearchResult to control which columns are shown and their order. - Otherwise,
ResultTableuses 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",
},
)
Illustrative file SearchResult (after drilling):
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):
@3on a magnet row runs the provider'sselector()to build a new file table and show it. The selector usessearch(..., filters={"view":"files","magnet_id":...})to fetch file rows. -
download-fileintegration: With a file row (http(s) path),@2 | download-filewill download the file. Thedownload-filecmdlet expands AllDebrid magnet folders and will call the provider layer to fetch file bytes as appropriate. -
add-fileconvenience: Piping a file row intoadd-file -path <dest>will trigger add-file's provider-aware logic. If the piped item hastable == 'alldebrid'and a http(s)path,add-filewill callprovider.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-filewill 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 returnSearchResultobjects or dicts; include usefulfull_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 callctx.set_last_result_table(...)/ctx.set_current_stage_table(...); returnTruewhen 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_metadataattached 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.
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!