Skip to content

Commit 9be044e

Browse files
committed
Update docs and type hints.
1 parent 0cbe18c commit 9be044e

File tree

5 files changed

+118
-61
lines changed

5 files changed

+118
-61
lines changed

consolekit/__init__.py

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@
3333

3434
# 3rd party
3535
import click
36-
from click.parser import split_opt
37-
from click.utils import make_str
3836

3937
# this package
4038
from consolekit import input, terminal_colours, utils
39+
from consolekit.commands import SuggestionGroup
40+
from consolekit.options import _Option
4141

4242
__author__: str = "Dominic Davis-Foster"
4343
__copyright__: str = "2020 Dominic Davis-Foster"
@@ -46,7 +46,6 @@
4646
__email__: str = "[email protected]"
4747

4848
if not bool(getattr(sys, "ps1", sys.flags.interactive)): # pragma: no cover
49-
5049
try:
5150
# stdlib
5251
import readline
@@ -66,36 +65,18 @@
6665
]
6766

6867
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"], max_content_width=120)
69-
click_command = partial(click.command, context_settings=CONTEXT_SETTINGS)
70-
click_group = partial(click.group, context_settings=CONTEXT_SETTINGS, cls=SuggestionGroup)
71-
72-
73-
class _Option(click.Option):
74-
75-
def prompt_for_value(self, ctx):
76-
"""
77-
This is an alternative flow that can be activated in the full value processing if a value does not exist.
78-
79-
It will prompt the user until a valid value exists and then returns the processed value as result.
80-
"""
81-
82-
# Calculate the default before prompting anything to be stable.
83-
default = self.get_default(ctx)
84-
85-
# If this is a prompt for a flag we need to handle this
86-
# differently.
87-
if self.is_bool_flag:
88-
return input.confirm(self.prompt, default)
8968

90-
return input.prompt(
91-
self.prompt,
92-
default=default,
93-
type=self.type,
94-
hide_input=self.hide_input,
95-
show_choices=self.show_choices,
96-
confirmation_prompt=self.confirmation_prompt,
97-
value_proc=lambda x: self.process_value(ctx, x),
98-
)
69+
click_command = partial(click.command, context_settings=CONTEXT_SETTINGS)
70+
"""
71+
Shortcut to :func:`click.command`, with the ``-h``/``--help`` option enabled and a max width of ``120``.
72+
"""
9973

74+
click_group = partial(click.group, context_settings=CONTEXT_SETTINGS, cls=SuggestionGroup)
75+
"""
76+
Shortcut to :func:`click.group`, with the ``-h``/``--help`` option enabled and a max width of ``120``.
77+
"""
10078

10179
option = partial(click.option, cls=_Option)
80+
"""
81+
Shortcut to :func:`click.option`, but using :func:`consolekit.input.confirm` when prompting for a boolean flag.
82+
"""

consolekit/input.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -125,27 +125,25 @@ def prompt(
125125
"""
126126
Prompts a user for input.
127127
128-
This is a convenience function that can be used to prompt a user for input later.
129-
130128
If the user aborts the input by sending an interrupt signal, this
131129
function will catch it and raise a :exc:`click.exceptions.Abort` exception.
132130
133131
:param text: The text to show for the prompt.
134132
:param default: The default value to use if no input happens.
135-
If this is not given it will prompt until it's aborted.
133+
If this is not given it will prompt until it is aborted.
136134
:param hide_input: If :py:obj:`True` then the input value will be hidden.
137135
:param confirmation_prompt: Asks for confirmation for the value.
138-
:param type: The type to use to check the value against.
139-
:param value_proc: If this parameter is provided it's a function that
136+
:param type: The type to check the value against.
137+
:param value_proc: If this parameter is provided it must be a function that
140138
is invoked instead of the type conversion to convert a value.
141139
:param prompt_suffix: A suffix that should be added to the prompt.
142140
:param show_default: Shows or hides the default value in the prompt.
143141
:param err: If :py:obj:`True` the file defaults to ``stderr`` instead of
144-
``stdout``, the same as with echo.
145-
:param show_choices: Show or hide choices if the passed type is a Choice.
146-
For example if type is a Choice of either day or week,
147-
``show_choices`` is :py:obj:`True` and text is ``'Group by'`` then the
148-
prompt will be ``'Group by (day, week): '``.
142+
``stdout``, the same as with :func:`click.echo`.
143+
:param show_choices: Show or hide choices if the passed type is a :class:`click.Choice`.
144+
For example, if the choice is either ``day`` or ``week``,
145+
``show_choices`` is :py:obj:`True` and ``text`` is ``'Group by'`` then the
146+
prompt will be ``'Group by (day, week): '``.
149147
"""
150148

151149
result = None # noqa
@@ -169,6 +167,7 @@ def prompt_func(text):
169167
while True:
170168
while True:
171169
value = prompt_func(prompt)
170+
172171
if value:
173172
break
174173
elif default is not None:
@@ -177,19 +176,24 @@ def prompt_func(text):
177176
value = default
178177
break
179178
return default
179+
180180
try:
181181
result = value_proc(value)
182182
except UsageError as e:
183183
click.echo(f"Error: {e.message}", err=err) # noqa: B306
184184
continue
185+
185186
if not confirmation_prompt:
186187
return result
188+
187189
while True:
188190
value2 = prompt_func("Repeat for confirmation: ")
189191
if value2:
190192
break
193+
191194
if value == value2:
192195
return result
196+
193197
click.echo("Error: the two entered values do not match", err=err)
194198

195199

@@ -245,9 +249,11 @@ def stderr_input(prompt: str = '', file: IO = sys.stdout) -> str: # pragma: no
245249
Read a string from standard input, but prompt to standard error.
246250
247251
The trailing newline is stripped.
248-
If the user hits EOF (Unix: Ctl-D, Windows: Ctl-Z+Return), raise EOFError.
249-
On Unix, GNU readline is used if enabled. The prompt string, if given,
250-
is printed to stderr without a trailing newline before reading.
252+
If the user hits EOF (Unix: :kbd:`Ctrl-D`, Windows: :kbd:`Ctrl-Z+Return`), raise :exc:`EOFError`.
253+
254+
On Unix, GNU readline is used if enabled.
255+
256+
The ``prompt`` string, if given, is printed to stderr without a trailing newline before reading.
251257
"""
252258

253259
if file is sys.stdout:
@@ -341,7 +347,7 @@ def choice(
341347
:param options:
342348
:param text: The text to show for the prompt.
343349
:param default: The index of the default value to use if no input happens.
344-
If this is not given it will prompt until it's aborted.
350+
If this is not given it will prompt until it is aborted.
345351
:param prompt_suffix: A suffix that should be added to the prompt.
346352
:param show_default: Shows or hides the default value in the prompt.
347353
:param err: If :py:obj:`True` the file defaults to ``stderr`` instead of

consolekit/options.py

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,15 @@
6060

6161
# stdlib
6262
import inspect
63-
from typing import Any, Callable, List, Optional, cast
63+
from typing import Any, Callable, List, Optional, TypeVar, cast
6464

6565
# 3rd party
6666
import click
67-
from click import Context, Option, OptionParser
67+
from click import Argument, Context, Option, OptionParser
6868
from click.decorators import _param_memo # type: ignore
6969

7070
# this package
71+
import consolekit.input
7172
from consolekit._types import Callback, _ConvertibleType
7273

7374
__all__ = [
@@ -81,8 +82,11 @@
8182
"auto_default_option",
8283
]
8384

85+
_A = TypeVar("_A", bound=click.Argument)
86+
_C = TypeVar("_C", bound=click.Command)
8487

85-
def verbose_option(help_text: str = "Show verbose output.") -> Callable:
88+
89+
def verbose_option(help_text: str = "Show verbose output.") -> Callable[..., click.Command]:
8690
"""
8791
Adds an option (via the parameter ``verbose``: :class:`int`) to enable verbose output.
8892
@@ -101,15 +105,32 @@ def verbose_option(help_text: str = "Show verbose output.") -> Callable:
101105
)
102106

103107

104-
def version_option(callback: Callable[[Context, Option, int], Any]) -> Callable:
108+
def version_option(callback: Callable[[Context, Option, int], Any]) -> Callable[..., click.Command]:
105109
"""
106110
Adds an option to show the version and exit.
107111
108112
The option can be provided multiple times by the user.
113+
The count is stored as an integer and passed as the third parameter to the callback function.
109114
110115
.. versionadded:: 0.4.0
111116
112117
:param callback: The callback to invoke when the option is provided.
118+
119+
The callback function might look like:
120+
121+
.. code-block:: python
122+
123+
def version_callback(ctx: click.Context, param: click.Option, value: int):
124+
if not value or ctx.resilient_parsing:
125+
return
126+
127+
if value > 1:
128+
click.echo(f"consolekit version {__version__}, Python {sys.version}")
129+
else:
130+
click.echo(f"consolekit version {__version__}")
131+
132+
ctx.exit()
133+
113134
"""
114135

115136
return click.option(
@@ -122,7 +143,7 @@ def version_option(callback: Callable[[Context, Option, int], Any]) -> Callable:
122143
)
123144

124145

125-
def colour_option(help_text="Whether to use coloured output.") -> Callable:
146+
def colour_option(help_text="Whether to use coloured output.") -> Callable[..., click.Command]:
126147
"""
127148
Adds an option (via the parameter ``colour``: :class:`bool`) to enable verbose output.
128149
@@ -138,7 +159,7 @@ def colour_option(help_text="Whether to use coloured output.") -> Callable:
138159
)
139160

140161

141-
def force_option(help_text: str) -> Callable:
162+
def force_option(help_text: str) -> Callable[..., click.Command]:
142163
"""
143164
Decorator to add the ``-f / --force`` option to a click command.
144165
@@ -150,10 +171,12 @@ def force_option(help_text: str) -> Callable:
150171
return flag_option("-f", "--force", help=help_text)
151172

152173

153-
def no_pager_option(help_text="Disable the output pager.") -> Callable:
174+
def no_pager_option(help_text="Disable the output pager.") -> Callable[..., click.Command]:
154175
"""
155176
Decorator to add the ``--no-pager`` option to a click command.
156177
178+
The value is exposed via the parameter ``no_pager``: :class:`bool`.
179+
157180
.. versionadded:: 0.5.0
158181
159182
:param help_text: The help text for the option.
@@ -162,7 +185,7 @@ def no_pager_option(help_text="Disable the output pager.") -> Callable:
162185
return flag_option("--no-pager", help=help_text)
163186

164187

165-
def flag_option(*args, default: Optional[bool] = False, **kwargs) -> Callable:
188+
def flag_option(*args, default: Optional[bool] = False, **kwargs) -> Callable[..., click.Command]:
166189
r"""
167190
Decorator to a flag option to a click command.
168191
@@ -181,7 +204,7 @@ def flag_option(*args, default: Optional[bool] = False, **kwargs) -> Callable:
181204
)
182205

183206

184-
def auto_default_option(*param_decls, **attrs) -> Callable:
207+
def auto_default_option(*param_decls, **attrs) -> Callable[..., click.Command]:
185208
"""
186209
Attaches an option to the command, with a default value determined from the decorated function's signature.
187210
@@ -195,7 +218,7 @@ def auto_default_option(*param_decls, **attrs) -> Callable:
195218
:param cls: the option class to instantiate. This defaults to :class:`click.Option`.
196219
"""
197220

198-
def decorator(f):
221+
def decorator(f: _C) -> _C:
199222
option_attrs = attrs.copy()
200223

201224
if "help" in option_attrs:
@@ -234,15 +257,30 @@ class MultiValueOption(click.Option):
234257
The later is converted into the former automatically if supported.
235258
:param required: Controls whether this is optional.
236259
:param default: The default value if omitted.
237-
This can also be a callable, in which case it's invoked when the default is needed without any arguments.
260+
This can also be a callable, in which case it is invoked when the default is needed without any arguments.
238261
:param callback: A callback that should be executed after the parameter was matched.
239262
This is called as ``fn(ctx, param, value)`` and needs to return the value.
240263
:param metavar: How the value is represented in the help page.
241264
:param expose_value: If :py:obj:`True` then the value is passed onwards to the command callback
242-
and stored on the context, otherwise it's skipped.
265+
and stored on the context, otherwise it is skipped.
243266
:param is_eager: Eager values are processed before non eager ones.
244267
245268
.. versionadded:: 0.6.0
269+
270+
Example usage:
271+
272+
.. code-block:: python
273+
274+
@click.option(
275+
"--select",
276+
type=click.STRING,
277+
help="The checks to enable",
278+
cls=MultiValueOption,
279+
)
280+
@click_command()
281+
def main(select: Iterable[str]):
282+
select = list(select)
283+
246284
"""
247285

248286
def __init__(
@@ -312,3 +350,30 @@ def parser_process(value, state):
312350
break
313351

314352
return retval
353+
354+
355+
class _Option(click.Option):
356+
357+
def prompt_for_value(self, ctx):
358+
"""
359+
This is an alternative flow that can be activated in the full value processing if a value does not exist.
360+
361+
It will prompt the user until a valid value exists and then returns the processed value as result.
362+
"""
363+
364+
# Calculate the default before prompting anything to be stable.
365+
default = self.get_default(ctx)
366+
367+
# If this is a prompt for a flag we need to handle this differently.
368+
if self.is_bool_flag:
369+
return consolekit.input.confirm(self.prompt, default)
370+
371+
return consolekit.input.prompt(
372+
self.prompt,
373+
default=default,
374+
type=self.type,
375+
hide_input=self.hide_input,
376+
show_choices=self.show_choices,
377+
confirmation_prompt=self.confirmation_prompt,
378+
value_proc=lambda x: self.process_value(ctx, x),
379+
)

consolekit/terminal_colours.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,16 @@
123123
style_stack: List[str] = []
124124

125125

126-
def resolve_color_default(color: Optional[bool] = None) -> Optional[bool]:
126+
def resolve_color_default(color: ColourTrilean = None) -> ColourTrilean:
127127
"""
128128
Internal helper to get the default value of the color flag. If a
129129
value is passed it's returned unchanged, otherwise it's looked up from
130130
the current context.
131131
132-
If the environment variable ``PYCHARM_HOSTED`` is 1
132+
If a value is passed it is returned unchanged,
133+
otherwise it's looked up from the current context.
134+
135+
If the environment variable ``PYCHARM_HOSTED`` is ``1``
133136
(which is the case if running in PyCharm)
134137
the output will be coloured by default.
135138

consolekit/utils.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
# stdlib
3030
import difflib
3131
import os
32+
import sys
33+
from functools import lru_cache
3234
from itertools import cycle
3335
from types import ModuleType
3436
from typing import IO, List, Sequence
@@ -39,7 +41,7 @@
3941
from domdf_python_tools.stringlist import StringList
4042

4143
# this package
42-
from consolekit.terminal_colours import Colour, Cursor, Fore
44+
from consolekit.terminal_colours import Colour, Cursor, Fore, Style, code_to_chars
4345

4446
__all__ = [
4547
"get_env_vars",

0 commit comments

Comments
 (0)