Files
2026-05-26 15:32:01 -07:00

149 lines
5.9 KiB
Python

from __future__ import annotations
from importlib import import_module
from typing import Any, Dict, List, Sequence
import sys
from SYS.logger import log
from . import _shared as sh
Cmdlet = sh.Cmdlet
CmdletArg = sh.CmdletArg
SharedArgs = sh.SharedArgs
class File(Cmdlet):
"""Unified file command: file -search|-add|-delete|-download|-merge|..."""
_ACTION_FLAGS = {
"search": {"-search", "--search"},
"add": {"-add", "--add"},
"delete": {"-delete", "--delete", "-del", "--del"},
"merge": {"-merge", "--merge"},
"download": {"-download", "--download", "-dl", "--dl"},
"convert": {"-convert", "--convert"},
"trim": {"-trim", "--trim"},
"archive": {"-archive", "--archive"},
"screenshot": {"-screenshot", "--screenshot", "-screen-shot", "--screen-shot", "-shot", "--shot"},
}
_ACTION_MODULE = {
"add": "cmdlet.file.add",
"delete": "cmdlet.file.delete",
"merge": "cmdlet.file.merge",
"download": "cmdlet.file.download",
"search": "cmdlet.file.search",
"convert": "cmdlet.file.convert",
"trim": "cmdlet.file.trim",
"archive": "cmdlet.file.archive",
"screenshot": "cmdlet.file.screenshot",
}
def __init__(self) -> None:
super().__init__(
name="file",
summary="Manage file operations with one command",
usage='file -query <query> [args] | file (-search|-add|-delete|-merge|-download|-convert|-trim|-archive|-screenshot) [args]',
arg=[
SharedArgs.QUERY,
SharedArgs.PLUGIN,
SharedArgs.INSTANCE,
CmdletArg("-search", type="flag", required=False, description="Run search-file"),
CmdletArg("-add", type="flag", required=False, description="Run add-file"),
CmdletArg("-delete", type="flag", required=False, description="Run delete-file", alias="del"),
CmdletArg("-merge", type="flag", required=False, description="Run merge-file"),
CmdletArg("-download", type="flag", required=False, description="Run download-file", alias="dl"),
CmdletArg("-convert", type="flag", required=False, description="Run convert-file"),
CmdletArg("-trim", type="flag", required=False, description="Run trim-file"),
CmdletArg("-archive", type="flag", required=False, description="Run archive-file"),
CmdletArg("-screenshot", type="flag", required=False, description="Run screen-shot", alias="shot"),
],
detail=[
"- Use -search for explicit search mode, then add -plugin/-instance and -query as needed.",
"- Plain -query still routes to search-file for direct search entry.",
"- Otherwise, exactly one non-search action flag is required.",
"- Remaining args are passed through to the selected file cmdlet.",
"- Examples: file -search -plugin hydrusnetwork -query ..., file -add ..., file -delete ...",
],
exec=self.run,
)
self.register()
@staticmethod
def _has_query_arg(args: Sequence[str]) -> bool:
query_flags = {"-query", "--query"}
for token in args or []:
text = str(token or "").strip().lower()
if text in query_flags:
return True
if any(text.startswith(f"{flag}=") for flag in query_flags):
return True
return False
@classmethod
def _extract_action(cls, args: Sequence[str]) -> tuple[str | None, List[str], List[str]]:
matched_actions: List[str] = []
passthrough: List[str] = []
for token in args or []:
text = str(token or "")
lower = text.strip().lower()
matched = None
for action_name, variants in cls._ACTION_FLAGS.items():
if lower in variants:
matched = action_name
break
if matched:
matched_actions.append(matched)
continue
passthrough.append(text)
unique_actions: List[str] = []
for action in matched_actions:
if action not in unique_actions:
unique_actions.append(action)
if not unique_actions and cls._has_query_arg(passthrough):
return "search", passthrough, unique_actions
if len(unique_actions) != 1:
return None, passthrough, unique_actions
return unique_actions[0], passthrough, unique_actions
@classmethod
def _dispatch(cls, action: str, result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
module_name = cls._ACTION_MODULE.get(action)
if not module_name:
log(f"file: unsupported action '{action}'", file=sys.stderr)
return 1
module = import_module(module_name)
cmdlet_obj = getattr(module, "CMDLET", None)
if cmdlet_obj is not None:
exec_fn = getattr(cmdlet_obj, "exec", None)
if callable(exec_fn):
return int(exec_fn(result, args, config))
log(f"file: cannot dispatch action '{action}' via module '{module_name}'", file=sys.stderr)
return 1
def run(self, result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
action, passthrough_args, seen = self._extract_action(args)
if action is None:
if not seen:
log(
"file: missing action; use -search/-query for search or choose exactly one of -search, -add, -delete, -merge, -download, -convert, -trim, -archive, -screenshot",
file=sys.stderr,
)
else:
rendered = ", ".join(f"-{name}" for name in seen)
log(f"file: conflicting actions ({rendered}); choose exactly one", file=sys.stderr)
return 1
return self._dispatch(action, result, passthrough_args, config)
CMDLET = File()