Skip to content

Commit c709d91

Browse files
committed
Add command.signature property
1 parent 399ee7c commit c709d91

File tree

1 file changed

+71
-1
lines changed

1 file changed

+71
-1
lines changed

twitchio/ext/commands/core.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,10 @@ def __init__(
204204
name: str,
205205
**kwargs: Unpack[CommandOptions],
206206
) -> None:
207+
self._signature: str | None
208+
207209
self._name: str = name
210+
self._parent: Group[Component_T, P] | None = kwargs.get("parent")
208211
self.callback = callback
209212
self._aliases: list[str] = kwargs.get("aliases", [])
210213
self._guards: list[Callable[..., bool] | Callable[..., CoroC]] = getattr(callback, "__command_guards__", [])
@@ -215,7 +218,6 @@ def __init__(
215218
self._injected: Component_T | None = None
216219
self._error: Callable[[Component_T, CommandErrorPayload], Coro] | Callable[[CommandErrorPayload], Coro] | None = None
217220
self._extras: dict[Any, Any] = kwargs.get("extras", {})
218-
self._parent: Group[Component_T, P] | None = kwargs.get("parent")
219221
self._bypass_global_guards: bool = kwargs.get("bypass_global_guards", False)
220222

221223
self._before_hook: Callable[[Component_T, Context[Any]], Coro] | Callable[[Context[Any]], Coro] | None = None
@@ -234,6 +236,73 @@ async def __call__(self, context: Context[BotT]) -> Any:
234236
callback = self._callback(self._injected, context) if self._injected else self._callback(context) # type: ignore
235237
return await callback # type: ignore will fix later
236238

239+
def _get_signature(self) -> None:
240+
params: dict[str, inspect.Parameter] | None = getattr(self, "_params", None)
241+
if not params:
242+
self._signature = None
243+
return
244+
245+
help_sig = ""
246+
for name, param in params.items():
247+
s = "<{}>"
248+
249+
if param.default is not param.empty:
250+
s = "[{}]"
251+
252+
if param.kind == param.KEYWORD_ONLY:
253+
s = s.format(f"...{name}")
254+
255+
elif param.kind == param.VAR_POSITIONAL:
256+
s = s.format(f"{name}...")
257+
258+
elif param.kind == param.POSITIONAL_OR_KEYWORD:
259+
s = s.format(f"{name}")
260+
261+
help_sig += f" {s}"
262+
263+
self._signature = help_sig
264+
265+
@property
266+
def signature(self) -> str | None:
267+
"""Property returning an easily readable POSIX-like argument signature for the command.
268+
269+
Useful when generating or displaying help.
270+
271+
The format is described below:
272+
273+
- ``<arg>`` is a required argument.
274+
- ``[arg]`` is an optional argument.
275+
- ``...arg`` is a consume-rest argument.
276+
- ``arg...`` is an argument list.
277+
278+
Example
279+
-------
280+
281+
.. code:: python3
282+
283+
@commands.command()
284+
async def test(ctx: commands.Context, hello: str, world: int) -> None: ...
285+
...
286+
287+
print(test.signature) # <hello> <world>
288+
289+
290+
@commands.command()
291+
async def test(ctx: commands.Context, arg: str, *, other: str | None = None) -> None: ...
292+
...
293+
294+
print(test.signature) # <arg> [...other]
295+
296+
297+
@commands.command()
298+
async def test(ctx: commands.Context, arg: str, other: str | None = None, *args: str | None = None) -> None: ...
299+
...
300+
301+
print(test.signature) # <arg> [other] [args...]
302+
303+
"""
304+
return self._signature
305+
237306
@property
238307
def help(self) -> str:
239308
"""Property returning a :class:`str` which is the docstring associated with the command callback.
@@ -351,6 +420,7 @@ def callback(
351420
globalns = {}
352421

353422
self._params: dict[str, inspect.Parameter] = get_signature_parameters(func, globalns)
423+
self._get_signature()
354424

355425
def _convert_literal_type(
356426
self, context: Context[BotT], param: inspect.Parameter, args: tuple[Any, ...], *, raw: str | None

0 commit comments

Comments
 (0)