Files
Medios-Macina/docs/result_table.md
T
2026-05-26 15:32:01 -07:00

185 lines
5.6 KiB
Markdown

# ResultTable system: overview and usage
This document explains the `ResultTable` system used across the CLI: how tables
are built, how plugins integrate with them, and how `@N` selection, row replay,
and plugin selectors work.
## TL;DR
- `ResultTable` is the unified object used to render tabular results and drive `@N` behavior.
- Plugins return `SearchResult` objects or dicts and can either supply row selection args or implement a `selector()` method.
- Rows can also carry an explicit `selection_action`, which the CLI replays verbatim.
- Table metadata helps plugins attach context. Some metadata keys still use older `provider` names internally.
---
## 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()`
### ResultRow
A row holds columns, payload data, selection args, and optionally a full
`selection_action` token list. When `selection_action` is present, `@N` uses it
instead of reconstructing a command from the source command and row args.
### Plugin selector
If a plugin implements `selector(selected_items, ctx=..., stage_is_last=True)`,
that selector runs first when `@N` is used. If the selector returns `True`, it
has handled the selection, for example by publishing a nested table.
### Table metadata
`ResultTable.set_table_metadata(dict)` attaches plugin-specific context for
selectors and follow-up stages. You will still see legacy keys such as
`provider` in some metadata because parts of the runtime still consume them.
---
## Building a table
Typical plugin flow:
```py
from SYS.result_table import ResultTable
table = ResultTable("Plugin: X results").set_preserve_order(True)
table.set_table("plugin_name")
table.set_table_metadata({"plugin": "plugin_name", "view": "folders"})
table.set_source_command("search-file", ["-plugin", "plugin_name", "query"])
for result in results:
table.add_result(result)
ctx.set_last_result_table(table, payloads)
ctx.set_current_stage_table(table)
```
Notes:
- To drive a direct replay, call `table.set_row_selection_args(row_index, ["-open", "<id>"])`.
- For drill-in or interactive behavior, implement `plugin.selector()` and return `True` when handled.
- For exact row replay, call `table.set_row_selection_action(row_index, tokens)`.
---
## Selection flow
When a user enters `@N`:
1. The CLI chooses the active table.
2. It gathers the selected payloads.
3. `PipelineExecutor._maybe_run_class_selector()` runs plugin `selector()` hooks for the plugin inferred from the table or payloads.
4. If no selector handles the row, the CLI checks `row.selection_action` first.
5. If no explicit row action exists, the CLI expands `source_command + source_args + row_selection_args`.
6. Multi-selection results are piped downstream instead of being collapsed to one row replay.
---
## Columns and display
- Plugins can pass a `columns` list in the result dict or `SearchResult` to control column order and display.
- Otherwise, `ResultTable` uses sensible defaults.
- Rendering helpers such as `to_rich`, `format_json`, and `format_compact` support different CLI display paths.
---
## Plugin-specific examples
### AllDebrid
AllDebrid exposes a list of magnets as folder rows and the files inside each
magnet as file rows. The plugin uses `selector()` to drill into a magnet and
publish a new `ResultTable` of files.
Example commands:
```powershell
search-file -plugin alldebrid "*"
@3
```
Illustrative magnet row:
```py
SearchResult(
table="alldebrid",
title="My Magnet Title",
path="alldebrid:magnet:123",
media_kind="folder",
full_metadata={
"magnet_id": 123,
"plugin": "alldebrid",
"plugin_view": "folders",
},
)
```
Illustrative file row:
```py
SearchResult(
table="alldebrid",
title="Episode 01.mkv",
path="https://.../unlocked_direct_url",
media_kind="file",
full_metadata={
"magnet_id": 123,
"plugin": "alldebrid",
"plugin_view": "files",
},
)
```
Selection and download behavior:
- `@3` on a magnet row runs the plugin selector and shows a file table.
- `@2 | download-file` downloads the selected file row.
- `@1 | add-file -plugin local -instance <dest>` triggers add-file's plugin-aware handoff flow.
Configure the AllDebrid plugin in `.config plugins` and add its API key before
testing these flows.
### Bandcamp
Bandcamp search supports `artist:` queries. The Bandcamp plugin implements a
`selector()` that detects artist rows and expands them into a discography table.
Example:
```powershell
search-file -plugin bandcamp "artist:radiohead"
@1
```
Plugin selectors are the right tool when you need to replace one table with
another, such as artist to discography drill-in.
---
## Plugin author checklist
- Implement `search(query, limit, filters)` and return `SearchResult` objects or dicts.
- Include useful `full_metadata` values for drill-in and selection replay.
- Implement `download(result, output_dir)` when the plugin can materialize file bytes.
- Implement `selector(...)` for drill-in or interactive transforms.
- Add tests that cover search, select, and download flows.
---
## Debugging tips
- Use `ctx.set_last_result_table(table, payloads)` to publish a table while developing a selector.
- Add `log(...)` messages in plugin code to capture fail points.
- Inspect `full_metadata` when a selector or replay path is missing required context.
---
## Quick reference
- ResultTable implementation: `SYS/result_table.py`
- Pipeline helpers: `SYS/pipeline.py`
- Row replay and selection flow: `SYS/pipeline.py`
- Plugin authoring guide: `docs/plugin_authoring.md`