Files
Medios-Macina/SYS/pipeline_state.py
T

1209 lines
37 KiB
Python

"""
Pipeline state management for cmdlet.
"""
from __future__ import annotations
import sys
import time
import re
from contextlib import contextmanager
from dataclasses import dataclass, field
from contextvars import ContextVar
from typing import Any, Dict, List, Optional, Sequence, Callable
from SYS.models import PipelineStageContext
from SYS.logger import log, debug, debug_panel, is_debug_enabled
import logging
logger = logging.getLogger(__name__)
# SYS.worker deferred: ffmpeg+attr+rich (~260ms) loaded lazily on first pipeline run.
_worker_mod: Any = None
# SYS.cli_parsing deferred: prompt_toolkit (~300ms) loaded lazily on first selection.
_cli_parsing_mod: Any = None
# SYS.result_table deferred: textual (~140ms) loaded lazily on first Table use.
_result_table_mod: Any = None
def _worker() -> Any:
global _worker_mod
if _worker_mod is None:
import SYS.worker as _m
_worker_mod = _m
return _worker_mod
def _cli_parsing() -> Any:
global _cli_parsing_mod
if _cli_parsing_mod is None:
import SYS.cli_parsing as _m
_cli_parsing_mod = _m
return _cli_parsing_mod
def _result_table() -> Any:
global _result_table_mod
if _result_table_mod is None:
from SYS.result_table import Table as _T
_result_table_mod = _T
return _result_table_mod
HELP_EXAMPLE_SOURCE_COMMANDS = {
".help-example",
"help-example",
}
# ---------------------------------------------------------------------------
# Helper functions
# ---------------------------------------------------------------------------
def _emit_selection_debug_panel(
*,
selection_token: Any,
selection_indices: Sequence[int],
item_count: int,
filtered_count: int,
stage_table_present: bool,
display_table_present: bool,
stage_is_last: bool,
row_action: Optional[Sequence[Any]] = None,
downstream_stages: Optional[Sequence[Sequence[Any]]] = None,
mode: Optional[str] = None,
) -> None:
if not is_debug_enabled():
return
try:
rows: List[tuple[str, Any]] = [
("selection", str(selection_token or "")),
("indices", [int(idx) + 1 for idx in (selection_indices or [])]),
("items", int(item_count)),
("filtered", int(filtered_count)),
("stage_table", bool(stage_table_present)),
("display_table", bool(display_table_present)),
("stage_is_last", bool(stage_is_last)),
("downstream_stages", len(list(downstream_stages or []))),
]
if mode:
rows.insert(1, ("mode", str(mode)))
if row_action:
rows.append(
("row_action", " ".join(str(part) for part in row_action if part is not None))
)
debug_panel(
f"Selection replay {selection_token}",
rows,
border_style="magenta",
)
except Exception:
pass
def set_live_progress(progress_ui: Any) -> None:
state = _get_pipeline_state()
state.live_progress = progress_ui
def get_live_progress() -> Any:
state = _get_pipeline_state()
return state.live_progress
def set_progress_event_callback(callback: Any) -> None:
state = _get_pipeline_state()
state.progress_event_callback = callback
def get_progress_event_callback() -> Any:
state = _get_pipeline_state()
return state.progress_event_callback
@contextmanager
def suspend_live_progress() -> Any:
ui = get_live_progress()
paused = False
try:
if ui is not None and hasattr(ui, "pause"):
try:
ui.pause()
paused = True
except Exception as exc:
logger.exception("Failed to pause live progress UI: %s", exc)
paused = False
yield
finally:
if get_pipeline_stop() is None:
if paused and ui is not None and hasattr(ui, "resume"):
try:
ui.resume()
except Exception:
logger.exception("Failed to resume live progress UI after suspend")
def _is_selectable_table(table: Any) -> bool:
return table is not None and not getattr(table, "no_choice", False)
# ---------------------------------------------------------------------------
# PipelineState
# ---------------------------------------------------------------------------
@dataclass
class PipelineState:
current_context: Optional[PipelineStageContext] = None
last_search_query: Optional[str] = None
pipeline_refreshed: bool = False
last_items: List[Any] = field(default_factory=list)
last_result_table: Optional[Any] = None
last_result_items: List[Any] = field(default_factory=list)
last_result_subject: Optional[Any] = None
result_table_history: List[tuple[Optional[Any], List[Any], Optional[Any]]] = field(
default_factory=list
)
result_table_forward: List[tuple[Optional[Any], List[Any], Optional[Any]]] = field(
default_factory=list
)
current_stage_table: Optional[Any] = None
display_items: List[Any] = field(default_factory=list)
display_table: Optional[Any] = None
display_subject: Optional[Any] = None
last_selection: List[int] = field(default_factory=list)
pipeline_command_text: str = ""
current_cmdlet_name: str = ""
current_stage_text: str = ""
pipeline_values: Dict[str, Any] = field(default_factory=dict)
pending_pipeline_tail: List[List[str]] = field(default_factory=list)
pending_pipeline_source: Optional[str] = None
ui_library_refresh_callback: Optional[Any] = None
pipeline_stop: Optional[Dict[str, Any]] = None
live_progress: Any = None
last_execution_result: Dict[str, Any] = field(default_factory=dict)
progress_event_callback: Any = None
def reset(self) -> None:
self.current_context = None
self.last_search_query = None
self.pipeline_refreshed = False
self.last_items = []
self.last_result_table = None
self.last_result_items = []
self.last_result_subject = None
self.result_table_history = []
self.result_table_forward = []
self.current_stage_table = None
self.display_items = []
self.display_table = None
self.display_subject = None
self.last_selection = []
self.pipeline_command_text = ""
self.current_cmdlet_name = ""
self.current_stage_text = ""
self.pipeline_values = {}
self.pending_pipeline_tail = []
self.pending_pipeline_source = None
self.ui_library_refresh_callback = None
self.pipeline_stop = None
self.live_progress = None
self.last_execution_result = {}
self.progress_event_callback = None
# ---------------------------------------------------------------------------
# ContextVar and global fallback
# ---------------------------------------------------------------------------
_CTX_STATE: ContextVar[Optional[PipelineState]] = ContextVar("_pipeline_state", default=None)
_GLOBAL_STATE: PipelineState = PipelineState()
def _get_pipeline_state() -> PipelineState:
state = _CTX_STATE.get()
return state if state is not None else _GLOBAL_STATE
@contextmanager
def new_pipeline_state() -> Any:
token = _CTX_STATE.set(PipelineState())
try:
yield _CTX_STATE.get()
finally:
_CTX_STATE.reset(token)
def get_pipeline_state() -> PipelineState:
return _get_pipeline_state()
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
MAX_RESULT_TABLE_HISTORY = 20
PIPELINE_MISSING = object()
# ---------------------------------------------------------------------------
# Pipeline stop
# ---------------------------------------------------------------------------
def request_pipeline_stop(*, reason: str = "", exit_code: int = 0) -> None:
state = _get_pipeline_state()
state.pipeline_stop = {
"reason": str(reason or "").strip(),
"exit_code": int(exit_code),
}
def get_pipeline_stop() -> Optional[Dict[str, Any]]:
state = _get_pipeline_state()
return state.pipeline_stop
def clear_pipeline_stop() -> None:
state = _get_pipeline_state()
state.pipeline_stop = None
# ---------------------------------------------------------------------------
# Stage context
# ---------------------------------------------------------------------------
def set_stage_context(context: Optional[PipelineStageContext]) -> None:
state = _get_pipeline_state()
state.current_context = context
def get_stage_context() -> Optional[PipelineStageContext]:
state = _get_pipeline_state()
return state.current_context
# ---------------------------------------------------------------------------
# Emit helpers
# ---------------------------------------------------------------------------
def emit(obj: Any) -> None:
ctx = _get_pipeline_state().current_context
if ctx is not None:
ctx.emit(obj)
def emit_list(objects: List[Any]) -> None:
ctx = _get_pipeline_state().current_context
if ctx is not None:
ctx.emit(objects)
def print_if_visible(*args: Any, file: Any = None, **kwargs: Any) -> None:
try:
ctx = _get_pipeline_state().current_context
should_print = (ctx is None) or (ctx and ctx.is_last_stage)
if file is not None:
should_print = True
if should_print:
log(*args, **kwargs) if file is None else log(*args, file=file, **kwargs)
except Exception:
logger.exception("Error in print_if_visible")
# ---------------------------------------------------------------------------
# Pipeline values
# ---------------------------------------------------------------------------
def store_value(key: str, value: Any) -> None:
if not isinstance(key, str):
return
text = key.strip().lower()
if not text:
return
try:
state = _get_pipeline_state()
state.pipeline_values[text] = value
except Exception:
logger.exception("Failed to store pipeline value '%s'", key)
def load_value(key: str, default: Any = None) -> Any:
if not isinstance(key, str):
return default
text = key.strip()
if not text:
return default
parts = [segment.strip() for segment in text.split(".") if segment.strip()]
if not parts:
return default
root_key = parts[0].lower()
state = _get_pipeline_state()
container = state.pipeline_values.get(root_key, PIPELINE_MISSING)
if container is PIPELINE_MISSING:
return default
if len(parts) == 1:
return container
current: Any = container
for fragment in parts[1:]:
if isinstance(current, dict):
fragment_lower = fragment.lower()
if fragment in current:
current = current[fragment]
continue
match = PIPELINE_MISSING
for key_name, value in current.items():
if isinstance(key_name, str) and key_name.lower() == fragment_lower:
match = value
break
if match is PIPELINE_MISSING:
return default
current = match
continue
if isinstance(current, (list, tuple)):
if fragment.isdigit():
try:
idx = int(fragment)
except ValueError:
return default
if 0 <= idx < len(current):
current = current[idx]
continue
return default
if hasattr(current, fragment):
try:
current = getattr(current, fragment)
continue
except Exception:
return default
return default
return current
# ---------------------------------------------------------------------------
# Execution result
# ---------------------------------------------------------------------------
def set_last_execution_result(
*,
status: str,
error: str = "",
command_text: str = "",
) -> None:
state = _get_pipeline_state()
text_status = str(status or "").strip().lower() or "unknown"
state.last_execution_result = {
"status": text_status,
"success": text_status == "completed",
"error": str(error or "").strip(),
"command_text": str(command_text or "").strip(),
"finished_at": time.time(),
}
def get_last_execution_result() -> Dict[str, Any]:
state = _get_pipeline_state()
payload = state.last_execution_result
return dict(payload) if isinstance(payload, dict) else {}
# ---------------------------------------------------------------------------
# Pending pipeline tail
# ---------------------------------------------------------------------------
def set_pending_pipeline_tail(
stages: Optional[Sequence[Sequence[str]]],
source_command: Optional[str] = None,
) -> None:
state = _get_pipeline_state()
try:
pending: List[List[str]] = []
for stage in stages or []:
if isinstance(stage, (list, tuple)):
pending.append([str(token) for token in stage])
state.pending_pipeline_tail = pending
clean_source = (source_command or "").strip()
state.pending_pipeline_source = clean_source if clean_source else None
except Exception:
logger.exception("Failed to set pending pipeline tail; keeping existing pending tail")
def get_pending_pipeline_tail() -> List[List[str]]:
state = _get_pipeline_state()
return [list(stage) for stage in state.pending_pipeline_tail]
def get_pending_pipeline_source() -> Optional[str]:
state = _get_pipeline_state()
return state.pending_pipeline_source
def clear_pending_pipeline_tail() -> None:
state = _get_pipeline_state()
state.pending_pipeline_tail = []
state.pending_pipeline_source = None
# ---------------------------------------------------------------------------
# Reset
# ---------------------------------------------------------------------------
def reset() -> None:
state = _get_pipeline_state()
state.reset()
# ---------------------------------------------------------------------------
# Emitted items
# ---------------------------------------------------------------------------
def get_emitted_items() -> List[Any]:
state = _get_pipeline_state()
ctx = state.current_context
if ctx is not None:
return list(ctx.emits)
return []
def clear_emits() -> None:
state = _get_pipeline_state()
ctx = state.current_context
if ctx is not None:
ctx.emits.clear()
# ---------------------------------------------------------------------------
# Selection
# ---------------------------------------------------------------------------
def set_last_selection(indices: Sequence[int]) -> None:
state = _get_pipeline_state()
state.last_selection = list(indices or [])
def get_last_selection() -> List[int]:
state = _get_pipeline_state()
return list(state.last_selection)
def clear_last_selection() -> None:
state = _get_pipeline_state()
state.last_selection = []
# ---------------------------------------------------------------------------
# Command text
# ---------------------------------------------------------------------------
def set_current_command_text(command_text: Optional[str]) -> None:
state = _get_pipeline_state()
state.pipeline_command_text = (command_text or "").strip()
def get_current_command_text(default: str = "") -> str:
state = _get_pipeline_state()
text = state.pipeline_command_text.strip()
return text if text else default
def clear_current_command_text() -> None:
state = _get_pipeline_state()
state.pipeline_command_text = ""
# ---------------------------------------------------------------------------
# Pipeline text splitting
# ---------------------------------------------------------------------------
def split_pipeline_text(pipeline_text: str) -> List[str]:
text = str(pipeline_text or "")
if not text:
return []
stages: List[str] = []
buf: List[str] = []
quote: Optional[str] = None
escape = False
for ch in text:
if escape:
buf.append(ch)
escape = False
continue
if ch == "\\" and quote is not None:
buf.append(ch)
escape = True
continue
if ch in ('"', "'"):
if quote is None:
quote = ch
elif quote == ch:
quote = None
buf.append(ch)
continue
if ch == "|" and quote is None:
stages.append("".join(buf).strip())
buf = []
continue
buf.append(ch)
tail = "".join(buf).strip()
if tail:
stages.append(tail)
return [s for s in stages if s]
def get_current_command_stages() -> List[str]:
return split_pipeline_text(get_current_command_text(""))
# ---------------------------------------------------------------------------
# Stage text
# ---------------------------------------------------------------------------
def set_current_stage_text(stage_text: Optional[str]) -> None:
state = _get_pipeline_state()
state.current_stage_text = str(stage_text or "").strip()
def get_current_stage_text(default: str = "") -> str:
state = _get_pipeline_state()
text = state.current_stage_text.strip()
return text if text else default
def clear_current_stage_text() -> None:
state = _get_pipeline_state()
state.current_stage_text = ""
# ---------------------------------------------------------------------------
# Cmdlet name
# ---------------------------------------------------------------------------
def set_current_cmdlet_name(cmdlet_name: Optional[str]) -> None:
state = _get_pipeline_state()
state.current_cmdlet_name = str(cmdlet_name or "").strip()
def get_current_cmdlet_name(default: str = "") -> str:
state = _get_pipeline_state()
text = state.current_cmdlet_name.strip()
return text if text else default
def clear_current_cmdlet_name() -> None:
state = _get_pipeline_state()
state.current_cmdlet_name = ""
# ---------------------------------------------------------------------------
# Search query
# ---------------------------------------------------------------------------
def set_search_query(query: Optional[str]) -> None:
state = _get_pipeline_state()
state.last_search_query = query
def get_search_query() -> Optional[str]:
state = _get_pipeline_state()
return state.last_search_query
# ---------------------------------------------------------------------------
# Refresh tracking
# ---------------------------------------------------------------------------
def set_pipeline_refreshed(refreshed: bool) -> None:
state = _get_pipeline_state()
state.pipeline_refreshed = refreshed
def was_pipeline_refreshed() -> bool:
state = _get_pipeline_state()
return state.pipeline_refreshed
# ---------------------------------------------------------------------------
# Last items
# ---------------------------------------------------------------------------
def set_last_items(items: list) -> None:
state = _get_pipeline_state()
state.last_items = list(items) if items else []
def get_last_items() -> List[Any]:
state = _get_pipeline_state()
return list(state.last_items)
# ---------------------------------------------------------------------------
# UI library refresh callback
# ---------------------------------------------------------------------------
def set_ui_library_refresh_callback(callback: Any) -> None:
state = _get_pipeline_state()
state.ui_library_refresh_callback = callback
def get_ui_library_refresh_callback() -> Optional[Any]:
state = _get_pipeline_state()
return state.ui_library_refresh_callback
def trigger_ui_library_refresh(library_filter: str = "local") -> None:
callback = get_ui_library_refresh_callback()
if callback:
try:
callback(library_filter)
except Exception as e:
print(
f"[trigger_ui_library_refresh] Error calling refresh callback: {e}",
file=sys.stderr,
)
# ---------------------------------------------------------------------------
# Result table: set (push to history)
# ---------------------------------------------------------------------------
def set_last_result_table(
result_table: Optional[Any],
items: Optional[List[Any]] = None,
subject: Optional[Any] = None,
) -> None:
state = _get_pipeline_state()
if state.last_result_table is not None:
state.result_table_history.append(
(
state.last_result_table,
state.last_result_items,
state.last_result_subject,
)
)
if len(state.result_table_history) > MAX_RESULT_TABLE_HISTORY:
state.result_table_history.pop(0)
state.display_items = []
state.display_table = None
state.display_subject = None
state.last_result_table = result_table
state.last_result_items = items or []
state.last_result_subject = subject
if (
result_table is not None
and hasattr(result_table, "sort_by_title")
and not getattr(result_table, "preserve_order", False)
):
try:
result_table.sort_by_title()
if state.last_result_items and hasattr(result_table, "rows"):
sorted_items: List[Any] = []
for row in result_table.rows:
src_idx = getattr(row, "source_index", None)
if isinstance(src_idx, int) and 0 <= src_idx < len(state.last_result_items):
sorted_items.append(state.last_result_items[src_idx])
if result_table.rows and len(sorted_items) == len(result_table.rows):
state.last_result_items = sorted_items
except Exception:
logger.exception("Failed to sort result_table and reorder items")
# ---------------------------------------------------------------------------
# Result table: overlay (no history push)
# ---------------------------------------------------------------------------
def set_last_result_table_overlay(
result_table: Optional[Any],
items: Optional[List[Any]] = None,
subject: Optional[Any] = None,
) -> None:
state = _get_pipeline_state()
state.display_table = result_table
state.display_items = items or []
state.display_subject = subject
# ---------------------------------------------------------------------------
# Result table: items only (no history, no table)
# ---------------------------------------------------------------------------
def set_last_result_items_only(items: Optional[List[Any]]) -> None:
state = _get_pipeline_state()
state.display_items = items or []
state.display_table = None
state.display_subject = None
# ---------------------------------------------------------------------------
# Result table history navigation (consolidated)
# ---------------------------------------------------------------------------
def _restore_result_table(forward: bool) -> bool:
"""Consolidated implementation for restore_previous/restore_next_result_table."""
state = _get_pipeline_state()
pop_stack = state.result_table_forward if forward else state.result_table_history
push_stack = state.result_table_history if forward else state.result_table_forward
label = "restore_next_result_table" if forward else "restore_previous_result_table"
if state.display_items or state.display_table or state.display_subject is not None:
state.display_items = []
state.display_table = None
state.display_subject = None
if state.last_result_table is not None:
state.current_stage_table = state.last_result_table
return True
if not pop_stack:
state.current_stage_table = state.last_result_table
return True
if not pop_stack:
return False
push_stack.append(
(state.last_result_table, state.last_result_items, state.last_result_subject)
)
prev = pop_stack.pop()
if isinstance(prev, tuple) and len(prev) >= 3:
state.last_result_table, state.last_result_items, state.last_result_subject = (
prev[0],
prev[1],
prev[2],
)
elif isinstance(prev, tuple) and len(prev) == 2:
state.last_result_table, state.last_result_items = prev
state.last_result_subject = None
else:
state.last_result_table, state.last_result_items, state.last_result_subject = None, [], None
state.display_items = []
state.display_table = None
state.display_subject = None
state.current_stage_table = state.last_result_table
try:
debug_table_state(label)
except Exception:
logger.exception("Failed to debug_table_state during %s", label)
return True
def restore_previous_result_table() -> bool:
return _restore_result_table(forward=False)
def restore_next_result_table() -> bool:
return _restore_result_table(forward=True)
# ---------------------------------------------------------------------------
# Display table accessors
# ---------------------------------------------------------------------------
def get_display_table() -> Optional[Any]:
state = _get_pipeline_state()
return state.display_table
def get_last_result_subject() -> Optional[Any]:
state = _get_pipeline_state()
if state.display_subject is not None:
return state.display_subject
return state.last_result_subject
def get_last_result_table() -> Optional[Any]:
state = _get_pipeline_state()
return state.last_result_table
def get_last_result_items() -> List[Any]:
state = _get_pipeline_state()
if state.display_items:
if state.display_table is not None and not _is_selectable_table(state.display_table):
return []
return state.display_items
if state.last_result_table is None:
return state.last_result_items
if _is_selectable_table(state.last_result_table):
return state.last_result_items
return []
# ---------------------------------------------------------------------------
# Debug table state
# ---------------------------------------------------------------------------
def debug_table_state(label: str = "") -> None:
if not is_debug_enabled():
return
state = _get_pipeline_state()
def _tbl(name: str, t: Any) -> None:
if t is None:
debug(f"[table] {name}: None")
return
try:
table_type = getattr(t, "table", None)
except Exception:
table_type = None
try:
title = getattr(t, "title", None)
except Exception:
title = None
try:
src_cmd = getattr(t, "source_command", None)
except Exception:
src_cmd = None
try:
src_args = getattr(t, "source_args", None)
except Exception:
src_args = None
try:
no_choice = bool(getattr(t, "no_choice", False))
except Exception:
no_choice = False
try:
preserve_order = bool(getattr(t, "preserve_order", False))
except Exception:
preserve_order = False
try:
row_count = len(getattr(t, "rows", []) or [])
except Exception:
row_count = 0
try:
meta = (
t.get_table_metadata()
if hasattr(t, "get_table_metadata")
else getattr(t, "table_metadata", None)
)
except Exception:
meta = None
meta_keys = list(meta.keys()) if isinstance(meta, dict) else []
debug(
f"[table] {name}: id={id(t)} class={type(t).__name__} title={repr(title)} table={repr(table_type)} rows={row_count} "
f"source={repr(src_cmd)} source_args={repr(src_args)} no_choice={no_choice} preserve_order={preserve_order} meta_keys={meta_keys}"
)
if label:
debug(f"[table] state: {label}")
_tbl("display_table", getattr(state, "display_table", None))
_tbl("current_stage_table", getattr(state, "current_stage_table", None))
_tbl("last_result_table", getattr(state, "last_result_table", None))
try:
debug(
f"[table] buffers: display_items={len(state.display_items or [])} last_result_items={len(state.last_result_items or [])} "
f"history={len(state.result_table_history or [])} forward={len(state.result_table_forward or [])} last_selection={list(state.last_selection or [])}"
)
except Exception:
logger.exception("Failed to debug_table_state buffers summary")
# ---------------------------------------------------------------------------
# Selectable result items
# ---------------------------------------------------------------------------
def get_last_selectable_result_items() -> List[Any]:
state = _get_pipeline_state()
if state.last_result_table is None:
return list(state.last_result_items)
if _is_selectable_table(state.last_result_table):
return list(state.last_result_items)
return []
# ---------------------------------------------------------------------------
# Table source command/args accessors (consolidated helpers)
# ---------------------------------------------------------------------------
def _get_table_source_command(table: Any) -> Optional[str]:
if table is not None and _is_selectable_table(table) and hasattr(table, "source_command"):
return getattr(table, "source_command")
return None
def _get_table_source_args(table: Any) -> List[str]:
if table is not None and _is_selectable_table(table) and hasattr(table, "source_args"):
return getattr(table, "source_args") or []
return []
def _get_table_row_selection_args(table: Any, row_index: int) -> Optional[List[str]]:
if table is not None and _is_selectable_table(table) and hasattr(table, "rows"):
rows = table.rows
if 0 <= row_index < len(rows):
row = rows[row_index]
if hasattr(row, "selection_args"):
return getattr(row, "selection_args")
return None
def _get_table_row_selection_action(table: Any, row_index: int) -> Optional[List[str]]:
if table is not None and _is_selectable_table(table) and hasattr(table, "rows"):
rows = table.rows
if 0 <= row_index < len(rows):
row = rows[row_index]
if hasattr(row, "selection_action"):
return getattr(row, "selection_action")
return None
# ---------------------------------------------------------------------------
# Public table accessor functions (delegate to helpers)
# ---------------------------------------------------------------------------
def get_last_result_table_source_command() -> Optional[str]:
state = _get_pipeline_state()
return _get_table_source_command(state.last_result_table)
def get_last_result_table_source_args() -> List[str]:
state = _get_pipeline_state()
return _get_table_source_args(state.last_result_table)
def get_last_result_table_row_selection_args(row_index: int) -> Optional[List[str]]:
state = _get_pipeline_state()
return _get_table_row_selection_args(state.last_result_table, row_index)
def get_last_result_table_row_selection_action(row_index: int) -> Optional[List[str]]:
state = _get_pipeline_state()
return _get_table_row_selection_action(state.last_result_table, row_index)
def get_current_stage_table() -> Optional[Any]:
state = _get_pipeline_state()
return state.current_stage_table
def set_current_stage_table(result_table: Optional[Any]) -> None:
state = _get_pipeline_state()
state.current_stage_table = result_table
def get_current_stage_table_source_command() -> Optional[str]:
state = _get_pipeline_state()
return _get_table_source_command(state.current_stage_table)
def get_current_stage_table_source_args() -> List[str]:
state = _get_pipeline_state()
return _get_table_source_args(state.current_stage_table)
def get_current_stage_table_row_selection_args(row_index: int) -> Optional[List[str]]:
state = _get_pipeline_state()
return _get_table_row_selection_args(state.current_stage_table, row_index)
def get_current_stage_table_row_selection_action(row_index: int) -> Optional[List[str]]:
state = _get_pipeline_state()
return _get_table_row_selection_action(state.current_stage_table, row_index)
def get_current_stage_table_row_source_index(row_index: int) -> Optional[int]:
state = _get_pipeline_state()
table = state.current_stage_table
if table is not None and _is_selectable_table(table) and hasattr(table, "rows"):
rows = table.rows
if 0 <= row_index < len(rows):
row = rows[row_index]
return getattr(row, "source_index", None)
return None
def clear_last_result() -> None:
state = _get_pipeline_state()
state.last_result_table = None
state.last_result_items = []
state.last_result_subject = None
# ---------------------------------------------------------------------------
# Pipeline token splitting (used by executor)
# ---------------------------------------------------------------------------
def _split_pipeline_tokens(tokens: Sequence[str]) -> List[List[str]]:
stages: List[List[str]] = []
current: List[str] = []
for token in tokens:
if token == "|":
if current:
stages.append(current)
current = []
continue
current.append(str(token))
if current:
stages.append(current)
return [stage for stage in stages if stage]
# ---------------------------------------------------------------------------
# Module __all__
# ---------------------------------------------------------------------------
__all__ = [
# State class
"PipelineState",
# ContextVar and factories
"_CTX_STATE",
"_GLOBAL_STATE",
"_get_pipeline_state",
"get_pipeline_state",
"new_pipeline_state",
# Constants
"MAX_RESULT_TABLE_HISTORY",
"PIPELINE_MISSING",
"HELP_EXAMPLE_SOURCE_COMMANDS",
# Deferred module loaders
"_worker",
"_cli_parsing",
"_result_table",
# Live progress
"set_live_progress",
"get_live_progress",
"set_progress_event_callback",
"get_progress_event_callback",
"suspend_live_progress",
# Pipeline stop
"request_pipeline_stop",
"get_pipeline_stop",
"clear_pipeline_stop",
# Stage context
"set_stage_context",
"get_stage_context",
# Emit helpers
"emit",
"emit_list",
"print_if_visible",
# Pipeline values
"store_value",
"load_value",
# Execution result
"set_last_execution_result",
"get_last_execution_result",
# Pending pipeline tail
"set_pending_pipeline_tail",
"get_pending_pipeline_tail",
"get_pending_pipeline_source",
"clear_pending_pipeline_tail",
# Reset
"reset",
# Emitted items
"get_emitted_items",
"clear_emits",
# Selection
"set_last_selection",
"get_last_selection",
"clear_last_selection",
# Command text
"set_current_command_text",
"get_current_command_text",
"clear_current_command_text",
# Pipeline splitting
"split_pipeline_text",
"get_current_command_stages",
"_split_pipeline_tokens",
# Stage text
"set_current_stage_text",
"get_current_stage_text",
"clear_current_stage_text",
# Cmdlet name
"set_current_cmdlet_name",
"get_current_cmdlet_name",
"clear_current_cmdlet_name",
# Search query
"set_search_query",
"get_search_query",
# Refresh
"set_pipeline_refreshed",
"was_pipeline_refreshed",
# Last items
"set_last_items",
"get_last_items",
# UI library
"set_ui_library_refresh_callback",
"get_ui_library_refresh_callback",
"trigger_ui_library_refresh",
# Result table setters
"set_last_result_table",
"set_last_result_table_overlay",
"set_last_result_items_only",
# History navigation
"restore_previous_result_table",
"restore_next_result_table",
# Display getters
"get_display_table",
"get_last_result_subject",
"get_last_result_table",
"get_last_result_items",
"get_last_selectable_result_items",
# Table accessors
"get_last_result_table_source_command",
"get_last_result_table_source_args",
"get_last_result_table_row_selection_args",
"get_last_result_table_row_selection_action",
"get_current_stage_table",
"set_current_stage_table",
"get_current_stage_table_source_command",
"get_current_stage_table_source_args",
"get_current_stage_table_row_selection_args",
"get_current_stage_table_row_selection_action",
"get_current_stage_table_row_source_index",
# Clear
"clear_last_result",
# Debug
"debug_table_state",
# Helpers (used internally by executor)
"_emit_selection_debug_panel",
"_is_selectable_table",
"_get_table_source_command",
"_get_table_source_args",
"_get_table_row_selection_args",
"_get_table_row_selection_action",
]