cmdlet refactor

This commit is contained in:
2026-05-04 18:41:01 -07:00
parent 3ce339b3c1
commit 24f983473f
44 changed files with 1320 additions and 309 deletions
+79 -36
View File
@@ -1282,15 +1282,30 @@ class PipelineExecutor:
def _norm(name: str) -> str:
return str(name or "").replace("_", "-").strip().lower()
def _file_action(stage_tokens: List[str]) -> str | None:
if not stage_tokens:
return None
head = _norm(stage_tokens[0])
if head in {"download-file", "add-file"}:
return head
if head != "file":
return None
args = {_norm(t) for t in stage_tokens[1:]}
if "-download" in args or "--download" in args or "-dl" in args or "--dl" in args:
return "download-file"
if "-add" in args or "--add" in args:
return "add-file"
return None
names: List[str] = []
for stage in stages or []:
if not stage:
continue
names.append(_norm(stage[0]))
dl_idxs = [i for i, n in enumerate(names) if n == "download-file"]
dl_idxs = [i for i, stage in enumerate(stages or []) if _file_action(stage or []) == "download-file"]
rel_idxs = [i for i, n in enumerate(names) if n == "add-relationship"]
add_file_idxs = [i for i, n in enumerate(names) if n == "add-file"]
add_file_idxs = [i for i, stage in enumerate(stages or []) if _file_action(stage or []) == "add-file"]
if not dl_idxs or not rel_idxs:
return True
@@ -1981,6 +1996,23 @@ class PipelineExecutor:
def _norm_cmd_name(value: Any) -> str:
return str(value or "").replace("_", "-").strip().lower()
def _stage_file_action(stage_tokens: Sequence[Any]) -> str | None:
if not stage_tokens:
return None
head = _norm_cmd_name(stage_tokens[0])
if head in {"add-file", "download-file", "delete-file"}:
return head
if head != "file":
return None
args = {_norm_cmd_name(t) for t in stage_tokens[1:]}
if "-add" in args or "--add" in args:
return "add-file"
if "-download" in args or "--download" in args or "-dl" in args or "--dl" in args:
return "download-file"
if "-delete" in args or "--delete" in args or "-del" in args or "--del" in args:
return "delete-file"
return None
# ============================================================================
# PHASE 2: Parse source command and table metadata
# ============================================================================
@@ -2333,8 +2365,8 @@ class PipelineExecutor:
# ====================================================================
if not stages:
if isinstance(table_type, str) and table_type.startswith("metadata."):
print("Auto-applying metadata selection via get-tag")
stages.append(["get-tag"])
print("Auto-applying metadata selection via metadata -get")
stages.append(["metadata", "-get"])
elif auto_stage:
try:
print(f"Auto-running selection via {auto_stage[0]}")
@@ -2383,12 +2415,12 @@ class PipelineExecutor:
first_cmd_norm = _norm_cmd_name(first_cmd)
inserted_provider_download = False
if first_cmd_norm == "add-file":
if _stage_file_action(stages[0]) == "add-file":
# If selected rows advertise an explicit download-file action,
# run download before add-file so add-file receives local files.
if len(selection_indices) == 1:
row_action = _get_row_action(selection_indices[0], items_list)
if row_action and _norm_cmd_name(row_action[0]) == "download-file":
if row_action and _stage_file_action(row_action) == "download-file":
stages.insert(0, [str(x) for x in row_action if x is not None])
inserted_provider_download = True
debug("Auto-inserting row download-file action before add-file")
@@ -2401,24 +2433,24 @@ class PipelineExecutor:
has_download_row_action = False
for idx in selection_indices:
row_action = _get_row_action(idx, items_list)
if row_action and _norm_cmd_name(row_action[0]) == "download-file":
if row_action and _stage_file_action(row_action) == "download-file":
has_download_row_action = True
break
if has_download_row_action:
stages.insert(0, ["download-file"])
stages.insert(0, ["file", "-download"])
inserted_provider_download = True
debug("Auto-inserting download-file before add-file for provider selection")
except Exception:
pass
if isinstance(table_type, str) and table_type.startswith("metadata.") and first_cmd not in (
"get-tag",
"get_tag",
"metadata",
"tag",
".pipe",
".mpv",
):
print("Auto-inserting get-tag after metadata selection")
stages.insert(0, ["get-tag"])
print("Auto-inserting metadata -get after metadata selection")
stages.insert(0, ["metadata", "-get"])
elif auto_stage:
first_cmd_norm = _norm_cmd_name(stages[0][0] if stages and stages[0] else None)
auto_cmd_norm = _norm_cmd_name(auto_stage[0])
@@ -2513,8 +2545,7 @@ class PipelineExecutor:
# add-file directory selector stage: avoid Live progress so the
# selection table renders cleanly.
if name in {"add-file",
"add_file"}:
if _stage_file_action(stage_tokens) == "add-file" or name in {"add_file"}:
try:
from pathlib import Path as _Path
@@ -2559,8 +2590,7 @@ class PipelineExecutor:
continue
# `delete-file` prints a Rich table directly; Live progress interferes and
# can truncate/overwrite the output.
if name in {"delete-file",
"del-file"}:
if _stage_file_action(stage_tokens) == "delete-file" or name in {"del-file"}:
continue
pipe_stage_indices.append(idx)
pipe_labels.append(name)
@@ -2707,7 +2737,7 @@ class PipelineExecutor:
stage_args = stage_tokens[1:]
if cmd_name == "@":
# Special-case get-tag tables: `@ | add-tag ...` should target the
# Special-case metadata -get tables: `@ | metadata -add ...` should target the
# underlying file subject once, not each emitted TagItem row.
try:
next_cmd = None
@@ -2721,28 +2751,36 @@ class PipelineExecutor:
current_table = None
source_cmd = str(getattr(current_table, "source_command", "") or "").replace("_", "-").strip().lower()
is_get_tag_table = source_cmd == "get-tag"
is_get_tag_table = source_cmd == "metadata"
if is_get_tag_table and next_cmd in {"add-tag"}:
if is_get_tag_table and next_cmd == "metadata":
subject = ctx.get_last_result_subject()
if subject is not None:
piped_result = subject
try:
subject_items = subject if isinstance(subject, list) else [subject]
ctx.set_last_items(subject_items)
except Exception:
logger.exception("Failed to set last_items from get-tag subject during @ handling")
if pipeline_session and worker_manager:
next_args = [
str(x).replace("_", "-").strip().lower()
for x in (stages[stage_index + 1][1:] if stage_index + 1 < len(stages) else [])
]
if "-add" not in next_args and "--add" not in next_args:
# Only apply this flattening for explicit tag-add pipelines.
pass
else:
piped_result = subject
try:
worker_manager.log_step(
pipeline_session.worker_id,
"@ used get-tag table subject for add-tag"
)
subject_items = subject if isinstance(subject, list) else [subject]
ctx.set_last_items(subject_items)
except Exception:
logger.exception("Failed to record pipeline log step for '@ used get-tag table subject for add-tag' (pipeline_session=%r)", getattr(pipeline_session, 'worker_id', None))
continue
logger.exception("Failed to set last_items from tag subject during @ handling")
if pipeline_session and worker_manager:
try:
worker_manager.log_step(
pipeline_session.worker_id,
"@ used metadata table subject for metadata -add"
)
except Exception:
logger.exception("Failed to record pipeline log step for '@ used metadata table subject for metadata -add' (pipeline_session=%r)", getattr(pipeline_session, 'worker_id', None))
continue
except Exception:
logger.exception("Failed to evaluate get-tag @ subject special-case")
logger.exception("Failed to evaluate tag @ subject special-case")
# Prefer piping the last emitted/visible items (e.g. add-file results)
# over the result-table subject. The subject can refer to older context
@@ -2962,17 +3000,23 @@ class PipelineExecutor:
stage_is_last=(stage_index + 1 >= len(stages))):
return
# Special case: selecting multiple tags from get-tag and piping into delete-tag
# Special case: selecting multiple metadata tag rows and piping into metadata -delete
# should batch into a single operation (one backend call).
next_cmd = None
next_args: List[str] = []
try:
if stage_index + 1 < len(stages) and stages[stage_index + 1]:
next_cmd = str(stages[stage_index + 1][0]
).replace("_",
"-").lower()
next_args = [
str(x).replace("_", "-").strip().lower()
for x in stages[stage_index + 1][1:]
]
except Exception:
logger.exception("Failed to determine next_cmd during selection expansion for stage_index %s", stage_index)
next_cmd = None
next_args = []
def _is_tag_row(obj: Any) -> bool:
try:
@@ -2991,8 +3035,7 @@ class PipelineExecutor:
logger.exception("Failed to inspect dict tag_name while checking _is_tag_row")
return False
if (next_cmd in {"delete-tag",
"delete_tag"} and len(filtered) > 1
if (next_cmd == "tag" and ("-delete" in next_args or "--delete" in next_args) and len(filtered) > 1
and all(_is_tag_row(x) for x in filtered)):
from SYS.field_access import get_field