Skip to content

Commit a468470

Browse files
Adding @optgoup.help_option decorator (#50)
1 parent b6c1620 commit a468470

File tree

4 files changed

+62
-14
lines changed

4 files changed

+62
-14
lines changed

click_option_group/_core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import collections
44
import inspect
55
import weakref
6-
from collections.abc import Callable
76
from typing import (
87
Any,
8+
Callable,
99
Dict,
1010
List,
1111
Mapping,

click_option_group/_decorators.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# -*- coding: utf-8 -*-
22

3-
from typing import Optional, NamedTuple, List, Tuple, Dict, Any, Type
3+
from typing import (Callable, Optional, NamedTuple, List,
4+
Tuple, Dict, Any, Type, TypeVar)
45

5-
import collections.abc as abc
66
import collections
77
import warnings
88
import inspect
@@ -15,6 +15,11 @@
1515
raise_mixing_decorators_error,
1616
)
1717

18+
T = TypeVar('T')
19+
F = TypeVar('F', bound=Callable)
20+
21+
Decorator = Callable[[F], F]
22+
1823

1924
class OptionStackItem(NamedTuple):
2025
param_decls: Tuple[str, ...]
@@ -62,8 +67,8 @@ class _OptGroup:
6267
"""
6368

6469
def __init__(self) -> None:
65-
self._decorating_state: Dict[abc.Callable, List[OptionStackItem]] = collections.defaultdict(list)
66-
self._not_attached_options: Dict[abc.Callable, List[click.Option]] = collections.defaultdict(list)
70+
self._decorating_state: Dict[Callable, List[OptionStackItem]] = collections.defaultdict(list)
71+
self._not_attached_options: Dict[Callable, List[click.Option]] = collections.defaultdict(list)
6772
self._outer_frame_index = 1
6873

6974
def __call__(self,
@@ -140,7 +145,7 @@ def decorator(func):
140145

141146
return decorator
142147

143-
def option(self, *param_decls, **attrs):
148+
def option(self, *param_decls, **attrs) -> Decorator:
144149
"""The decorator adds a new option to the group
145150
146151
The decorator is lazy. It adds option decls and attrs.
@@ -164,7 +169,30 @@ def decorator(func):
164169

165170
return decorator
166171

167-
def _add_not_attached_option(self, func, param_decls):
172+
def help_option(self, *param_decls, **attrs) -> Decorator:
173+
"""This decorator adds a help option to the group, which prints
174+
the command's help text and exits.
175+
"""
176+
if not param_decls:
177+
param_decls = ('--help',)
178+
179+
attrs.setdefault('is_flag', True)
180+
attrs.setdefault('is_eager', True)
181+
attrs.setdefault('expose_value', False)
182+
attrs.setdefault('help', 'Show this message and exit.')
183+
184+
if 'callback' not in attrs:
185+
def callback(ctx, _, value):
186+
if not value or ctx.resilient_parsing:
187+
return
188+
click.echo(ctx.get_help(), color=ctx.color)
189+
ctx.exit()
190+
191+
attrs['callback'] = callback
192+
193+
return self.option(*param_decls, **attrs)
194+
195+
def _add_not_attached_option(self, func, param_decls) -> None:
168196
click.option(
169197
*param_decls,
170198
all_not_attached_options=self._not_attached_options,
@@ -175,7 +203,7 @@ def _add_not_attached_option(self, func, param_decls):
175203
self._not_attached_options[callback].append(params[-1])
176204

177205
@staticmethod
178-
def _filter_not_attached(options):
206+
def _filter_not_attached(options: List[T]) -> List[T]:
179207
return [opt for opt in options if not isinstance(opt, _NotAttachedOption)]
180208

181209
@staticmethod

click_option_group/_helpers.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
# -*- coding: utf-8 -*-
22

3-
from typing import List, Tuple
3+
from typing import Callable, Tuple, List, TypeVar, NoReturn
44

5-
import collections.abc as abc
65
import random
76
import string
87

98
import click
109

1110

11+
F = TypeVar('F', bound=Callable)
12+
1213
FAKE_OPT_NAME_LEN = 30
1314

1415

15-
def get_callback_and_params(func) -> Tuple[abc.Callable, List[click.Option]]:
16+
def get_callback_and_params(func) -> Tuple[Callable, List[click.Option]]:
1617
"""Returns callback function and its parameters list
1718
1819
:param func: decorated function or click Command
@@ -28,11 +29,11 @@ def get_callback_and_params(func) -> Tuple[abc.Callable, List[click.Option]]:
2829
return func, params
2930

3031

31-
def get_fake_option_name(name_len: int = FAKE_OPT_NAME_LEN, prefix: str = 'fake'):
32+
def get_fake_option_name(name_len: int = FAKE_OPT_NAME_LEN, prefix: str = 'fake') -> str:
3233
return f'--{prefix}-' + ''.join(random.choices(string.ascii_lowercase, k=name_len))
3334

3435

35-
def raise_mixing_decorators_error(wrong_option: click.Option, callback: abc.Callable):
36+
def raise_mixing_decorators_error(wrong_option: click.Option, callback: Callable) -> NoReturn:
3637
error_hint = wrong_option.opts or [wrong_option.name]
3738

3839
raise TypeError((
@@ -41,6 +42,6 @@ def raise_mixing_decorators_error(wrong_option: click.Option, callback: abc.Call
4142
))
4243

4344

44-
def resolve_wrappers(f):
45+
def resolve_wrappers(f: F) -> F:
4546
"""Get the underlying function behind any level of function wrappers."""
4647
return resolve_wrappers(f.__wrapped__) if hasattr(f, "__wrapped__") else f

tests/test_click_option_group.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,25 @@ def cli(foo, bar):
733733
assert "bar" not in result.output
734734

735735

736+
def test_help_option(runner):
737+
@click.command()
738+
@optgroup('Help Options')
739+
@optgroup.help_option('-h', '--help')
740+
def cli() -> None:
741+
click.echo('Running command.')
742+
743+
result = runner.invoke(cli)
744+
assert not result.exception
745+
assert 'Running command.' in result.output
746+
assert 'Usage:' not in result.output
747+
748+
result = runner.invoke(cli, ['-h'])
749+
assert not result.exception
750+
assert 'Running command.' not in result.output
751+
assert 'Usage:' in result.output
752+
assert '-h, --help' in result.output
753+
754+
736755
def test_wrapped_functions(runner):
737756
def make_z():
738757
"""A unified option interface for making a `z`."""

0 commit comments

Comments
 (0)