Skip to content

Commit f90c541

Browse files
committed
Add case_sensitive=False as an option to Choice
Adds a test that validates both the new and the old behavior WRT case sensitivity. Applies lowercasing *after* token normalization, in case someone has a `token_normalize_func` which is, itself, case-sensitive. Closes #569
1 parent b471d34 commit f90c541

2 files changed

Lines changed: 53 additions & 6 deletions

File tree

click/types.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,16 @@ class Choice(ParamType):
129129
"""The choice type allows a value to be checked against a fixed set of
130130
supported values. All of these values have to be strings.
131131
132+
:param case_sensitive: Set to false to make choices case insensitive.
133+
Defaults to true.
134+
132135
See :ref:`choice-opts` for an example.
133136
"""
134137
name = 'choice'
135138

136-
def __init__(self, choices):
139+
def __init__(self, choices, case_sensitive=True):
137140
self.choices = choices
141+
self.case_sensitive = case_sensitive
138142

139143
def get_metavar(self, param):
140144
return '[%s]' % '|'.join(self.choices)
@@ -147,13 +151,25 @@ def convert(self, value, param, ctx):
147151
if value in self.choices:
148152
return value
149153

150-
# Match through normalization
154+
# Match through normalization and case sensitivity
155+
# first do token_normalize_func, then lowercase
156+
# preserve original `value` to produce an accurate message in
157+
# `self.fail`
158+
normed_value = value
159+
normed_choices = self.choices
160+
151161
if ctx is not None and \
152162
ctx.token_normalize_func is not None:
153-
value = ctx.token_normalize_func(value)
154-
for choice in self.choices:
155-
if ctx.token_normalize_func(choice) == value:
156-
return choice
163+
normed_value = ctx.token_normalize_func(value)
164+
normed_choices = [ctx.token_normalize_func(choice) for choice in
165+
self.choices]
166+
167+
if not self.case_sensitive:
168+
normed_value = normed_value.lower()
169+
normed_choices = [choice.lower() for choice in normed_choices]
170+
171+
if normed_value in normed_choices:
172+
return normed_value
157173

158174
self.fail('invalid choice: %s. (choose from %s)' %
159175
(value, ', '.join(self.choices)), param, ctx)

tests/test_options.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,37 @@ def cmd(foo):
259259
in result.output
260260

261261

262+
def test_case_insensitive_choice(runner):
263+
@click.command()
264+
@click.option('--foo', type=click.Choice(
265+
['Orange', 'Apple'], case_sensitive=False))
266+
def cmd(foo):
267+
click.echo(foo)
268+
269+
result = runner.invoke(cmd, ['--foo', 'apple'])
270+
assert result.exit_code == 0
271+
272+
result = runner.invoke(cmd, ['--foo', 'oRANGe'])
273+
assert result.exit_code == 0
274+
275+
result = runner.invoke(cmd, ['--foo', 'Apple'])
276+
assert result.exit_code == 0
277+
278+
@click.command()
279+
@click.option('--foo', type=click.Choice(['Orange', 'Apple']))
280+
def cmd2(foo):
281+
click.echo(foo)
282+
283+
result = runner.invoke(cmd2, ['--foo', 'apple'])
284+
assert result.exit_code == 2
285+
286+
result = runner.invoke(cmd2, ['--foo', 'oRANGe'])
287+
assert result.exit_code == 2
288+
289+
result = runner.invoke(cmd2, ['--foo', 'Apple'])
290+
assert result.exit_code == 0
291+
292+
262293
def test_multiline_help(runner):
263294
@click.command()
264295
@click.option('--foo', help="""

0 commit comments

Comments
 (0)