From b7b58f0e42bc7954436378527b2329a3c53ae0d3 Mon Sep 17 00:00:00 2001 From: Nose Date: Mon, 12 Jan 2026 12:17:50 -0800 Subject: [PATCH] h --- Provider/zeroxzero.py | 3 + ProviderCore/base.py | 7 +- ProviderCore/registry.py | 6 +- cmdlet/provider_table.py | 4 +- cmdlet/select_item.py | 226 ---------------------------------- docs/result_table_selector.md | 8 +- readme.md | 21 +--- 7 files changed, 21 insertions(+), 254 deletions(-) delete mode 100644 cmdlet/select_item.py diff --git a/Provider/zeroxzero.py b/Provider/zeroxzero.py index 658f0e7..6ad07b1 100644 --- a/Provider/zeroxzero.py +++ b/Provider/zeroxzero.py @@ -11,6 +11,9 @@ from SYS.logger import log class ZeroXZero(Provider): """File provider for 0x0.st.""" + NAME = "0x0" + PROVIDER_ALIASES = ("zeroxzero",) + def upload(self, file_path: str, **kwargs: Any) -> str: from API.HTTP import HTTPClient from SYS.models import ProgressFileReader diff --git a/ProviderCore/base.py b/ProviderCore/base.py index 4a173dd..80fe301 100644 --- a/ProviderCore/base.py +++ b/ProviderCore/base.py @@ -142,7 +142,12 @@ class Provider(ABC): def __init__(self, config: Optional[Dict[str, Any]] = None): self.config = config or {} - self.name = self.__class__.__name__.lower() + # Prioritize explicit NAME property for the instance name + self.name = str( + getattr(self, "NAME", None) + or getattr(self, "PROVIDER_NAME", None) + or self.__class__.__name__ + ).lower() @classmethod def config(cls) -> List[Dict[str, Any]]: diff --git a/ProviderCore/registry.py b/ProviderCore/registry.py index c05839b..3a22008 100644 --- a/ProviderCore/registry.py +++ b/ProviderCore/registry.py @@ -69,10 +69,10 @@ class ProviderRegistry: if override_name: _add(override_name) else: - # Use class name as the primary canonical name - _add(getattr(provider_class, "__name__", None)) - _add(getattr(provider_class, "PROVIDER_NAME", None)) + # Use explicit NAME or PROVIDER_NAME if available, else class name _add(getattr(provider_class, "NAME", None)) + _add(getattr(provider_class, "PROVIDER_NAME", None)) + _add(getattr(provider_class, "__name__", None)) for alias in getattr(provider_class, "PROVIDER_ALIASES", ()) or (): _add(alias) diff --git a/cmdlet/provider_table.py b/cmdlet/provider_table.py index 69d9db3..68297b6 100644 --- a/cmdlet/provider_table.py +++ b/cmdlet/provider_table.py @@ -27,8 +27,8 @@ CMDLET = Cmdlet( ], detail=[ "Use a registered provider to build a table and optionally run another cmdlet with selection args.", - "Emits pipeline-friendly dicts enriched with `_selection_args` so you can pipe into `select` and other cmdlets.", - "Example: provider-table -provider example -sample | select -select 1 | add-file -store my_store", + "Emits pipeline-friendly dicts enriched with `_selection_args` so you can use @N syntax to select and chain.", + "Example: provider-table -provider example -sample | @1 | add-file -store my_store", ], ) diff --git a/cmdlet/select_item.py b/cmdlet/select_item.py deleted file mode 100644 index 6618432..0000000 --- a/cmdlet/select_item.py +++ /dev/null @@ -1,226 +0,0 @@ -from __future__ import annotations - -import sys -from typing import Any, Dict, List, Sequence -from . import _shared as sh -from SYS.logger import log, debug -from SYS import pipeline as ctx - -from SYS.result_table_api import ResultModel -from SYS.result_table_adapters import get_provider -from SYS.result_table_renderers import RichRenderer - -Cmdlet = sh.Cmdlet -CmdletArg = sh.CmdletArg -parse_cmdlet_args = sh.parse_cmdlet_args -normalize_result_input = sh.normalize_result_input - - -CMDLET = Cmdlet( - name="select", - summary="Select items from a piped result set (interactive or via -select) and emit the selected item(s).", - usage="select -select [-multi] [-run-cmd ]", - arg=[ - CmdletArg("select", type="string", description="Selection string (e.g., 1, 2-4)", alias="s"), - CmdletArg("multi", type="flag", description="Allow multiple selections."), - CmdletArg("interactive", type="flag", description="Prompt interactively for selection."), - CmdletArg("run-cmd", type="string", description="Cmdlet to invoke with selected items (each)"), - ], - detail=[ - "Accepts piped input from provider-table or other sources and emits the selected item(s) as dicts.", - "If -run-cmd is provided, invokes the named cmdlet for each selected item with selector args and the item as piped input.", - ], -) - - -def _parse_selection(selection: str, max_len: int) -> List[int]: - """Parse a selection string like '1', '1-3', '1,3,5-7' into 0-based indices.""" - if not selection: - return [] - parts = [p.strip() for p in str(selection).split(",") if p.strip()] - indices = set() - for part in parts: - if "-" in part: - try: - a, b = part.split("-", 1) - start = int(a.strip()) - end = int(b.strip()) - if start > end: - start, end = end, start - for i in range(start, end + 1): - if 1 <= i <= max_len: - indices.add(i - 1) - except Exception: - raise ValueError(f"Invalid range: {part}") - else: - try: - v = int(part) - if 1 <= v <= max_len: - indices.add(v - 1) - except Exception: - raise ValueError(f"Invalid selection: {part}") - return sorted(indices) - - -def _dict_to_result_model(d: Dict[str, Any]) -> ResultModel: - if isinstance(d, ResultModel): - return d - # Allow dicts or objects with attributes - title = d.get("title") or d.get("name") or (d.get("path") and str(d.get("path")).split("/")[-1]) - return ResultModel( - title=str(title) if title is not None else "", - path=d.get("path") if d.get("path") is not None else None, - ext=d.get("ext") if d.get("ext") is not None else None, - size_bytes=d.get("size_bytes") if d.get("size_bytes") is not None else None, - metadata=d.get("metadata") or {}, - source=d.get("source") or None, - ) - - -def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int: - parsed = parse_cmdlet_args(args, CMDLET) - - select_raw = parsed.get("select") - allow_multi = bool(parsed.get("multi", False)) - interactive = bool(parsed.get("interactive", False)) - run_cmd = parsed.get("run-cmd") - - inputs = normalize_result_input(result) - if not inputs: - log("No input provided to select; pipe provider-table output or use a cmdlet that emits items.", file=sys.stderr) - return 1 - - first_src = inputs[0].get("source") if isinstance(inputs[0], dict) else None - if not first_src: - log("Input items must include 'source' to resolve provider for selection.", file=sys.stderr) - return 1 - - try: - provider = get_provider(first_src) - except Exception: - log(f"Unknown provider: {first_src}", file=sys.stderr) - return 1 - - # Model-ize items - rows = [_dict_to_result_model(item if isinstance(item, dict) else item) for item in inputs] - - # Columns: provider must supply them (no legacy defaults) - cols = provider.get_columns(rows) - - # Render table to console - try: - table = RichRenderer().render(rows, cols, None) - try: - from rich.console import Console - - Console().print(table) - except Exception: - for r in rows: - print(" ".join(str((c.extractor(r) or "")) for c in cols)) - except Exception as exc: - log(f"Rendering failed: {exc}", file=sys.stderr) - return 1 - - # Determine selection indices - indices: List[int] = [] - if select_raw: - try: - indices = _parse_selection(str(select_raw), len(rows)) - except ValueError as exc: - log(str(exc), file=sys.stderr) - return 1 - elif interactive: - # Prompt user (single index only unless multi) - try: - from rich.prompt import Prompt - - prompt_text = "Select item(s) (e.g., 1 or 1,3-5)" - if not allow_multi: - prompt_text += " (single)" - choice = Prompt.ask(prompt_text).strip() - indices = _parse_selection(choice, len(rows)) - except Exception as exc: - log(f"Interactive selection failed: {exc}", file=sys.stderr) - return 1 - else: - log("No selection requested. Use -select or -interactive.", file=sys.stderr) - return 1 - - if not indices: - log("No valid selection indices provided", file=sys.stderr) - return 1 - - # Build selected items and emit - selected_items: List[Dict[str, Any]] = [] - for idx in indices: - try: - raw = inputs[idx] if idx < len(inputs) else None - if isinstance(raw, dict): - selected = dict(raw) - elif isinstance(raw, ResultModel): - selected = { - "title": raw.title, - "path": raw.path, - "ext": raw.ext, - "size_bytes": raw.size_bytes, - "metadata": raw.metadata or {}, - "source": raw.source, - } - else: - try: - selected = raw.to_dict() - except Exception: - selected = {"title": getattr(raw, "title", str(raw))} - - # Ensure selection args exist using provider's selector only - if not selected.get("_selection_args"): - try: - sel_args = provider.selection_args(rows[idx]) - selected["_selection_args"] = sel_args - except Exception: - log("Selection args missing and provider selector failed.", file=sys.stderr) - return 1 - - selected_items.append(selected) - except Exception: - continue - - # Emit selected items so downstream cmdlets can consume them - try: - for itm in selected_items: - ctx.emit(itm) - except Exception: - pass - - # Optionally run follow-up cmdlet for each selected item - if run_cmd: - try: - from cmdlet import ensure_cmdlet_modules_loaded, get as get_cmdlet - - ensure_cmdlet_modules_loaded() - cmd_fn = get_cmdlet(run_cmd) - if not cmd_fn: - log(f"Follow-up cmdlet not found: {run_cmd}", file=sys.stderr) - return 1 - - exit_code = 0 - for itm in selected_items: - sel_args = itm.get("_selection_args") or [] - # Invoke follow-up cmdlet with the selected item as piped input - try: - ret = cmd_fn(itm, sel_args, config or {}) - except Exception as exc: - log(f"Follow-up cmdlet raised: {exc}", file=sys.stderr) - ret = 1 - if ret != 0: - exit_code = ret - return exit_code - except Exception as exc: - log(f"Failed to invoke follow-up cmdlet: {exc}", file=sys.stderr) - return 1 - - return 0 - - -CMDLET.exec = _run -CMDLET.register() diff --git a/docs/result_table_selector.md b/docs/result_table_selector.md index f532412..87180e1 100644 --- a/docs/result_table_selector.md +++ b/docs/result_table_selector.md @@ -5,11 +5,11 @@ and cmdlets to interact via a simple, pipable API. Key ideas - `provider-table` renders a provider result set and *emits* pipeline-friendly dicts for each row. Each emitted item includes `_selection_args`, a list of args the provider suggests for selecting that row (e.g., `['-path', '/tmp/file']`). -- `select` accepts piped items, displays a table (Rich-based), and supports selecting rows either via `-select` or `-interactive` prompt. Selected items are emitted for downstream cmdlets or you can use `-run-cmd` to invoke another cmdlet for each selected item. +- Use the `@N` syntax to select an item from a table and chain it to the next cmdlet. -Example (non-interactive): +Example: - provider-table -provider example -sample | select -select 1 | add-file -store default + provider-table -provider example -sample | @1 | add-file -store default What providers must implement - An adapter that yields `ResultModel` objects (breaking API). @@ -17,6 +17,6 @@ What providers must implement Implementation notes - `provider-table` emits dicts like `{ 'title': ..., 'path': ..., 'metadata': ..., '_selection_args': [...] }`. -- `select` will prefer `_selection_args` if present; otherwise it will fall back to provider selection logic or sensible defaults (`-path` or `-title`). +- Selection syntax (`@1`) will prefer `_selection_args` if present; otherwise it will fall back to provider selection logic or sensible defaults (`-path` or `-title`). This design keeps the selector-focused UX small and predictable while enabling full cmdlet interoperability via piping and `-run-cmd`. diff --git a/readme.md b/readme.md index 205e973..b8ebeb5 100644 --- a/readme.md +++ b/readme.md @@ -41,30 +41,15 @@ Medios-Macina is a CLI file media manager and toolkit focused on downloading, ta
-console command +installation console command
git clone https://code.glowers.club/goyimnose/Medios-Macina.git
 python Medios-Macina/scripts/bootstrap.py
 
- - +
+Start the CLI by simply running "mm" - - When run interactively (a normal terminal), `bootstrap.py` will show a short menu to Install or Uninstall the project. -1. rename config.conf.remove to config.conf, [config tutorial](https://code.glowers.club/goyimnose/Medios-Macina/wiki/Config.conf) -### MINIMAL EXAMPLE CONFIG - CHANGE VALUES -```Minimal config -temp="C:\\Users\\Admin\\Downloads" - -[store=folder] -name="default" -path="C:\Users\Public\Documents\library" - -``` - -3. Start the CLI by simply running "mm" in shell or run python cli.py - -# [CLICK FOR GUIDED TUTORIAL](/docs/tutorial.md)