re
Some checks failed
smoke-mm / Install & smoke test mm --help (push) Has been cancelled
Some checks failed
smoke-mm / Install & smoke test mm --help (push) Has been cancelled
This commit is contained in:
195
CLI.py
195
CLI.py
@@ -1710,6 +1710,51 @@ class PipelineExecutor:
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _maybe_open_url_selection(current_table: Any, selected_items: list, *, stage_is_last: bool) -> bool:
|
||||
if not stage_is_last:
|
||||
return False
|
||||
if not selected_items or len(selected_items) != 1:
|
||||
return False
|
||||
|
||||
table_type = ""
|
||||
source_cmd = ""
|
||||
try:
|
||||
table_type = str(getattr(current_table, "table", "") or "").strip().lower()
|
||||
except Exception:
|
||||
table_type = ""
|
||||
try:
|
||||
source_cmd = str(getattr(current_table, "source_command", "") or "").strip().replace("_", "-").lower()
|
||||
except Exception:
|
||||
source_cmd = ""
|
||||
|
||||
if table_type != "url" and source_cmd != "get-url":
|
||||
return False
|
||||
|
||||
item = selected_items[0]
|
||||
url = None
|
||||
try:
|
||||
from cmdlet._shared import get_field
|
||||
|
||||
url = get_field(item, "url")
|
||||
except Exception:
|
||||
try:
|
||||
url = item.get("url") if isinstance(item, dict) else getattr(item, "url", None)
|
||||
except Exception:
|
||||
url = None
|
||||
|
||||
url_text = str(url or "").strip()
|
||||
if not url_text:
|
||||
return False
|
||||
|
||||
try:
|
||||
import webbrowser
|
||||
|
||||
webbrowser.open(url_text, new=2)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _maybe_enable_background_notifier(self, worker_manager: Any, config: Any, pipeline_session: Any) -> None:
|
||||
if not (pipeline_session and worker_manager and isinstance(config, dict)):
|
||||
return
|
||||
@@ -1798,12 +1843,15 @@ class PipelineExecutor:
|
||||
else:
|
||||
selected_row_args: List[str] = []
|
||||
skip_pipe_expansion = source_cmd == ".pipe" and len(stages) > 0
|
||||
if source_cmd and not skip_pipe_expansion:
|
||||
for idx in selection_indices:
|
||||
row_args = ctx.get_current_stage_table_row_selection_args(idx)
|
||||
if row_args:
|
||||
selected_row_args.extend(row_args)
|
||||
break
|
||||
# Only perform @N command expansion for *single-item* selections.
|
||||
# For multi-item selections (e.g. @*, @1-5), expanding to a single
|
||||
# row would silently drop items. In those cases we pipe the selected
|
||||
# items downstream instead.
|
||||
if source_cmd and not skip_pipe_expansion and len(selection_indices) == 1:
|
||||
idx = selection_indices[0]
|
||||
row_args = ctx.get_current_stage_table_row_selection_args(idx)
|
||||
if row_args:
|
||||
selected_row_args.extend(row_args)
|
||||
|
||||
if selected_row_args:
|
||||
if isinstance(source_cmd, list):
|
||||
@@ -1834,30 +1882,42 @@ class PipelineExecutor:
|
||||
command_expanded = True
|
||||
|
||||
if (not command_expanded) and selection_indices:
|
||||
last_piped_items = None
|
||||
try:
|
||||
last_piped_items = ctx.get_last_result_items()
|
||||
except Exception:
|
||||
last_piped_items = None
|
||||
|
||||
stage_table = None
|
||||
try:
|
||||
stage_table = ctx.get_current_stage_table()
|
||||
except Exception:
|
||||
stage_table = None
|
||||
if not stage_table and hasattr(ctx, "get_display_table"):
|
||||
try:
|
||||
stage_table = ctx.get_display_table()
|
||||
except Exception:
|
||||
stage_table = None
|
||||
|
||||
display_table = None
|
||||
try:
|
||||
display_table = ctx.get_display_table() if hasattr(ctx, "get_display_table") else None
|
||||
except Exception:
|
||||
display_table = None
|
||||
|
||||
if not stage_table and display_table is not None:
|
||||
stage_table = display_table
|
||||
if not stage_table:
|
||||
try:
|
||||
stage_table = ctx.get_last_result_table()
|
||||
except Exception:
|
||||
stage_table = None
|
||||
|
||||
resolved_items = last_piped_items if last_piped_items else []
|
||||
if last_piped_items:
|
||||
# Prefer selecting from the last selectable *table* (search/playlist)
|
||||
# rather than from display-only emitted items, unless we're explicitly
|
||||
# selecting from an overlay table.
|
||||
try:
|
||||
if display_table is not None and stage_table is display_table:
|
||||
items_list = ctx.get_last_result_items() or []
|
||||
else:
|
||||
if hasattr(ctx, "get_last_selectable_result_items"):
|
||||
items_list = ctx.get_last_selectable_result_items() or []
|
||||
else:
|
||||
items_list = ctx.get_last_result_items() or []
|
||||
except Exception:
|
||||
items_list = []
|
||||
|
||||
resolved_items = items_list if items_list else []
|
||||
if items_list:
|
||||
filtered = [resolved_items[i] for i in selection_indices if 0 <= i < len(resolved_items)]
|
||||
if not filtered:
|
||||
print("No items matched selection in pipeline\n")
|
||||
@@ -2003,6 +2063,14 @@ class PipelineExecutor:
|
||||
|
||||
try:
|
||||
self._try_clear_pipeline_stop(ctx)
|
||||
|
||||
# Preflight (URL-duplicate prompts, etc.) should be cached within a single
|
||||
# pipeline run, not across independent pipelines.
|
||||
try:
|
||||
ctx.store_value("preflight", {})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
stages = self._split_stages(tokens)
|
||||
if not stages:
|
||||
print("Invalid pipeline syntax\n")
|
||||
@@ -2066,11 +2134,39 @@ class PipelineExecutor:
|
||||
stage_args = stage_tokens[1:]
|
||||
|
||||
if cmd_name == "@":
|
||||
# Prefer piping the last emitted/visible items (e.g. add-file results)
|
||||
# over the result-table subject. The subject can refer to older context
|
||||
# (e.g. a playlist row) and may not contain store+hash.
|
||||
last_items = None
|
||||
try:
|
||||
last_items = ctx.get_last_result_items()
|
||||
except Exception:
|
||||
last_items = None
|
||||
|
||||
if last_items:
|
||||
from cmdlet._shared import coerce_to_pipe_object
|
||||
|
||||
try:
|
||||
pipe_items = [coerce_to_pipe_object(x) for x in list(last_items)]
|
||||
except Exception:
|
||||
pipe_items = list(last_items)
|
||||
piped_result = pipe_items if len(pipe_items) > 1 else pipe_items[0]
|
||||
try:
|
||||
ctx.set_last_items(pipe_items)
|
||||
except Exception:
|
||||
pass
|
||||
if pipeline_session and worker_manager:
|
||||
try:
|
||||
worker_manager.log_step(pipeline_session.worker_id, "@ used last result items")
|
||||
except Exception:
|
||||
pass
|
||||
continue
|
||||
|
||||
subject = ctx.get_last_result_subject()
|
||||
if subject is None:
|
||||
print("No current result context available for '@'\n")
|
||||
pipeline_status = "failed"
|
||||
pipeline_error = "No result subject for @"
|
||||
pipeline_error = "No result items/subject for @"
|
||||
return
|
||||
piped_result = subject
|
||||
try:
|
||||
@@ -2095,18 +2191,34 @@ class PipelineExecutor:
|
||||
return
|
||||
|
||||
selected_indices = []
|
||||
# Prefer selecting from the last selectable *table* (search/playlist)
|
||||
# rather than from display-only emitted items, unless we're explicitly
|
||||
# selecting from an overlay table.
|
||||
display_table = None
|
||||
try:
|
||||
display_table = ctx.get_display_table() if hasattr(ctx, "get_display_table") else None
|
||||
except Exception:
|
||||
display_table = None
|
||||
|
||||
stage_table = ctx.get_current_stage_table()
|
||||
if not stage_table and display_table is not None:
|
||||
stage_table = display_table
|
||||
if not stage_table:
|
||||
stage_table = ctx.get_last_result_table()
|
||||
|
||||
if display_table is not None and stage_table is display_table:
|
||||
items_list = ctx.get_last_result_items() or []
|
||||
else:
|
||||
if hasattr(ctx, "get_last_selectable_result_items"):
|
||||
items_list = ctx.get_last_selectable_result_items() or []
|
||||
else:
|
||||
items_list = ctx.get_last_result_items() or []
|
||||
|
||||
if is_select_all:
|
||||
last_items = ctx.get_last_result_items() or []
|
||||
selected_indices = list(range(len(last_items)))
|
||||
selected_indices = list(range(len(items_list)))
|
||||
else:
|
||||
selected_indices = sorted([i - 1 for i in selection]) # type: ignore[arg-type]
|
||||
|
||||
stage_table = ctx.get_current_stage_table()
|
||||
if not stage_table and hasattr(ctx, "get_display_table"):
|
||||
stage_table = ctx.get_display_table()
|
||||
if not stage_table:
|
||||
stage_table = ctx.get_last_result_table()
|
||||
items_list = ctx.get_last_result_items() or []
|
||||
resolved_items = items_list if items_list else []
|
||||
filtered = [resolved_items[i] for i in selected_indices if 0 <= i < len(resolved_items)]
|
||||
if not filtered:
|
||||
@@ -2115,6 +2227,20 @@ class PipelineExecutor:
|
||||
pipeline_error = "Empty selection"
|
||||
return
|
||||
|
||||
# UX: selecting a single URL row from get-url tables should open it.
|
||||
# Only do this when the selection stage is terminal to avoid surprising
|
||||
# side-effects in pipelines like `@1 | download-file`.
|
||||
current_table = ctx.get_current_stage_table() or ctx.get_last_result_table()
|
||||
if (not is_select_all) and (len(filtered) == 1):
|
||||
try:
|
||||
PipelineExecutor._maybe_open_url_selection(
|
||||
current_table,
|
||||
filtered,
|
||||
stage_is_last=(stage_index + 1 >= len(stages)),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if PipelineExecutor._maybe_run_class_selector(ctx, config, filtered, stage_is_last=(stage_index + 1 >= len(stages))):
|
||||
return
|
||||
|
||||
@@ -2366,12 +2492,19 @@ class PipelineExecutor:
|
||||
# the table and pause the pipeline so the user can pick @N.
|
||||
stage_table = ctx.get_current_stage_table() if hasattr(ctx, "get_current_stage_table") else None
|
||||
stage_table_type = str(getattr(stage_table, "table", "") or "").strip().lower() if stage_table else ""
|
||||
try:
|
||||
stage_table_source = str(getattr(stage_table, "source_command", "") or "").strip().replace("_", "-").lower() if stage_table else ""
|
||||
except Exception:
|
||||
stage_table_source = ""
|
||||
if (
|
||||
(not stage_is_last)
|
||||
and (not emits)
|
||||
and cmd_name in {"download-media", "download_media"}
|
||||
and stage_table is not None
|
||||
and stage_table_type in {"ytdlp.formatlist", "download-media", "download_media"}
|
||||
and (
|
||||
stage_table_type in {"ytdlp.formatlist", "download-media", "download_media", "bandcamp", "youtube"}
|
||||
or stage_table_source in {"download-media", "download_media"}
|
||||
)
|
||||
):
|
||||
try:
|
||||
is_selectable = not bool(getattr(stage_table, "no_choice", False))
|
||||
@@ -2407,6 +2540,10 @@ class PipelineExecutor:
|
||||
stdout_console().print()
|
||||
stdout_console().print(stage_table)
|
||||
|
||||
# Always pause the pipeline when a selectable table was produced.
|
||||
# The user will continue by running @N/@* which will re-attach the
|
||||
# pending downstream stages.
|
||||
|
||||
try:
|
||||
remaining = stages[stage_index + 1 :]
|
||||
source_cmd = (
|
||||
|
||||
Reference in New Issue
Block a user