2525from __future__ import annotations
2626
2727import asyncio
28+ import inspect
2829from collections .abc import Callable , Coroutine
29- from typing import TYPE_CHECKING , Any , Concatenate , Generic , ParamSpec , TypeAlias , TypeVar , Unpack
30+ from types import UnionType
31+ from typing import TYPE_CHECKING , Any , Concatenate , Generic , ParamSpec , TypeAlias , TypeVar , Union , Unpack
32+
33+ from twitchio .utils import MISSING
3034
3135from .exceptions import *
3236from .types_ import CommandOptions , Component_T
@@ -112,10 +116,134 @@ def extras(self) -> dict[Any, Any]:
112116 def has_error (self ) -> bool :
113117 return self ._error is not None
114118
119+ async def _do_conversion (self , context : Context , param : inspect .Parameter , * , annotation : Any , raw : str | None ) -> Any :
120+ name : str = param .name
121+
122+ if isinstance (annotation , UnionType ) or getattr (annotation , "__origin__" , None ) is Union :
123+ converters = list (annotation .__args__ )
124+ converters .remove (type (None ))
125+
126+ result : Any = MISSING
127+
128+ for c in converters :
129+ try :
130+ result = await self ._do_conversion (context , param = param , annotation = c , raw = raw )
131+ except Exception :
132+ continue
133+
134+ if result is MISSING :
135+ raise BadArgument (
136+ f'Failed to convert argument "{ name } " with any converter from Union: { converters } . No default value was provided.' ,
137+ name = name ,
138+ value = raw ,
139+ )
140+
141+ return result
142+
143+ base = context .bot ._base_converter ._DEFAULTS .get (annotation , None if annotation != param .empty else str )
144+ if base :
145+ try :
146+ result = base (raw )
147+ except Exception as e :
148+ raise BadArgument (f'Failed to convert "{ name } " to { base } ' , name = name , value = raw ) from e
149+
150+ return result
151+
152+ converter = context .bot ._base_converter ._MAPPING .get (annotation , annotation )
153+
154+ try :
155+ result = converter (context , raw )
156+ except Exception as e :
157+ raise BadArgument (f'Failed to convert "{ name } " to { type (converter )} ' , name = name , value = raw ) from e
158+
159+ if not asyncio .iscoroutine (result ):
160+ return result
161+
162+ try :
163+ result = await result
164+ except Exception as e :
165+ raise BadArgument (f'Failed to convert "{ name } " to { type (converter )} ' , name = name , value = raw ) from e
166+
167+ return result
168+
169+ async def _parse_arguments (self , context : Context ) -> ...:
170+ context ._view .skip_ws ()
171+ signature : inspect .Signature = inspect .signature (self ._callback )
172+
173+ # We expect context always and self with commands in components...
174+ skip : int = 1 if not self ._injected else 2
175+ params : list [inspect .Parameter ] = list (signature .parameters .values ())[skip :]
176+
177+ args : list [Any ] = []
178+ kwargs = {}
179+
180+ for param in params :
181+ if param .kind == param .KEYWORD_ONLY :
182+ raw = context ._view .read_rest ()
183+
184+ if not raw :
185+ if param .default == param .empty :
186+ raise MissingRequiredArgument (param = param )
187+
188+ kwargs [param .name ] = param .default
189+ continue
190+
191+ result = await self ._do_conversion (context , param = param , raw = raw , annotation = param .annotation )
192+ kwargs [param .name ] = result
193+ break
194+
195+ elif param .kind == param .VAR_POSITIONAL :
196+ packed : list [Any ] = []
197+
198+ while True :
199+ context ._view .skip_ws ()
200+ raw = context ._view .get_quoted_word ()
201+ if not raw :
202+ break
203+
204+ result = await self ._do_conversion (context , param = param , raw = raw , annotation = param .annotation )
205+ packed .append (result )
206+
207+ args .extend (packed )
208+ break
209+
210+ elif param .kind == param .POSITIONAL_OR_KEYWORD :
211+ raw = context ._view .get_quoted_word ()
212+ context ._view .skip_ws ()
213+
214+ if not raw :
215+ if param .default == param .empty :
216+ raise MissingRequiredArgument (param = param )
217+
218+ args .append (param .default )
219+ continue
220+
221+ result = await self ._do_conversion (context , param = param , raw = raw , annotation = param .annotation )
222+ args .append (result )
223+
224+ return args , kwargs
225+
226+ async def _do_checks (self , context : Context ) -> ...:
227+ # Bot
228+ # Component
229+ # Command
230+ ...
231+
115232 async def _invoke (self , context : Context ) -> None :
116- # TODO: Argument parsing...
117- # TODO: Checks... Including cooldowns...
118- callback = self ._callback (self ._injected , context ) if self ._injected else self ._callback (context ) # type: ignore
233+ try :
234+ args , kwargs = await self ._parse_arguments (context )
235+ except (ConversionError , MissingRequiredArgument ):
236+ raise
237+ except Exception as e :
238+ raise ConversionError ("An unknown error occurred converting arguments." ) from e
239+
240+ context ._args = args
241+ context ._kwargs = kwargs
242+
243+ args : list [Any ] = [context , * args ]
244+ args .insert (0 , self ._injected ) if self ._injected else None
245+
246+ callback = self ._callback (* args , ** kwargs ) # type: ignore
119247
120248 try :
121249 await callback
@@ -127,6 +255,9 @@ async def invoke(self, context: Context) -> None:
127255 await self ._invoke (context )
128256 except CommandError as e :
129257 await self ._dispatch_error (context , e )
258+ except Exception as e :
259+ error = CommandInvokeError (str (e ), original = e )
260+ await self ._dispatch_error (context , error )
130261
131262 async def _dispatch_error (self , context : Context , exception : CommandError ) -> None :
132263 payload = CommandErrorPayload (context = context , exception = exception )
0 commit comments