2026-05-04 18:41:01 -07:00
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 ):
2026-05-24 12:32:57 -07:00
"""Unified file command: file -search|-add|-delete|-download|-merge|..."""
2026-05-04 18:41:01 -07:00
_ACTION_FLAGS = {
2026-05-24 12:32:57 -07:00
"search" : { "-search" , "--search" },
2026-05-04 18:41:01 -07:00
"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" ,
2026-05-24 12:32:57 -07:00
usage = 'file -query <query> [args] | file (-search|-add|-delete|-merge|-download|-convert|-trim|-archive|-screenshot) [args]' ,
2026-05-04 18:41:01 -07:00
arg = [
SharedArgs . QUERY ,
2026-05-14 17:15:13 -07:00
SharedArgs . PLUGIN ,
2026-05-04 18:41:01 -07:00
SharedArgs . INSTANCE ,
2026-05-24 12:32:57 -07:00
CmdletArg ( "-search" , type = "flag" , required = False , description = "Run search-file" ),
2026-05-04 18:41:01 -07:00
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 = [
2026-05-24 12:32:57 -07:00
"- 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." ,
2026-05-14 17:15:13 -07:00
"- Otherwise, exactly one non-search action flag is required." ,
2026-05-04 18:41:01 -07:00
"- Remaining args are passed through to the selected file cmdlet." ,
2026-05-24 12:32:57 -07:00
"- Examples: file -search -plugin hydrusnetwork -query ..., file -add ..., file -delete ..." ,
2026-05-04 18:41:01 -07:00
],
exec = self . run ,
)
self . register ()
2026-05-14 17:15:13 -07:00
@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
2026-05-04 18:41:01 -07:00
@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 )
2026-05-14 17:15:13 -07:00
if not unique_actions and cls . _has_query_arg ( passthrough ):
return "search" , passthrough , unique_actions
2026-05-04 18:41:01 -07:00
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 (
2026-05-24 12:32:57 -07:00
"file: missing action; use -search/-query for search or choose exactly one of -search, -add, -delete, -merge, -download, -convert, -trim, -archive, -screenshot" ,
2026-05-04 18:41:01 -07:00
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 ()