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 ( )