Only ask questions if we are an interactive process (#30)

* Only ask questions if we are an interactive process

* add tests for the question script

* tweak color

* add a breaking changes entry

* typo

* make ci happy
This commit is contained in:
Zach White 2021-05-17 16:11:41 -07:00 committed by GitHub
parent 2984f547c5
commit ca854efbc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 90 additions and 8 deletions

2
.yapfignore Normal file
View file

@ -0,0 +1,2 @@
.git
docs

8
color
View file

@ -5,21 +5,23 @@ PYTHON_ARGCOMPLETE_OK
"""
from milc import cli
colors = ('black', 'blue', 'cyan', 'green', 'magenta', 'red', 'white', 'yellow')
@cli.entrypoint('Show all the colors available to us.')
def main(cli):
cli.echo('|Normal | FG | ExtFG | BG | ExtBG |')
for color in ('black', 'blue', 'cyan', 'green', 'magenta', 'red', 'white', 'yellow'):
for color in colors:
cli.echo(f'|{color:8}|{{fg_{color}}}xxxxxxxx{{fg_reset}}|{{fg_light{color}_ex}}xxxxxxxx{{fg_reset}}|{{bg_{color}}}xxxxxxxx{{bg_reset}}|{{bg_light{color}_ex}}xxxxxxxx{{bg_reset}}|')
print()
cli.echo('|Bright | FG | ExtFG | BG | ExtBG |')
for color in ('black', 'blue', 'cyan', 'green', 'magenta', 'red', 'white', 'yellow'):
for color in colors:
cli.echo(f'|{color:8}|{{style_bright}}{{fg_{color}}}xxxxxxxx{{style_reset_all}}|{{style_bright}}{{fg_light{color}_ex}}xxxxxxxx{{style_reset_all}}|{{style_bright}}{{bg_{color}}}xxxxxxxx{{style_reset_all}}|{{style_bright}}{{bg_light{color}_ex}}xxxxxxxx{{style_reset_all}}|')
print()
cli.echo('|Dim | FG | ExtFG | BG | ExtBG |')
for color in ('black', 'blue', 'cyan', 'green', 'magenta', 'red', 'white', 'yellow'):
for color in colors:
cli.echo(f'|{color:8}|{{style_dim}}{{fg_{color}}}xxxxxxxx{{style_reset_all}}|{{style_dim}}{{fg_light{color}_ex}}xxxxxxxx{{style_reset_all}}|{{style_dim}}{{bg_{color}}}xxxxxxxx{{style_reset_all}}|{{style_dim}}{{bg_light{color}_ex}}xxxxxxxx{{style_reset_all}}|')

View file

@ -6,6 +6,7 @@ This is a list of breaking changes that have been made to MILC. If your script s
* The `config` subcommand now filters out configuration that has not been explicitly set. The new `--all` flag will allow you to see all possible configuration options and their default values.
* Setting program metadata through environment variables has been deprecated. In its place is the new `set_metadata()` function. See [Metadata](metadata.md) for more detail.
* MILC now tracks whether a script is running interactively or not with `cli.interactive`. You can pass `--interactive` to force a script into interactive mode even when stdout is not a TTY. `milc.questions` will always return the default answer when running non-interactively, unless `--yes` or `--no` are passed.
# Version 1.3.0

View file

@ -56,6 +56,7 @@ class MILC(object):
self.default_arguments = {}
self.platform = platform()
self.prog_name = name
self.interactive = sys.stdout.isatty()
self.release_lock()
# Initialize all the things
@ -223,6 +224,7 @@ class MILC(object):
self.add_argument('--log-file', help='File to write log messages to')
self.add_argument('--color', action='store_boolean', default=ansi_config['color'], help='color in output')
self.add_argument('--unicode', action='store_boolean', default=ansi_config['unicode'], help='unicode loglevels')
self.add_argument('--interactive', action='store_true', help='Force interactive mode even when stdout is not a tty.')
self.add_argument('--config-file', help='The location for the configuration file')
self.arg_only['config_file'] = ['general']
@ -582,6 +584,10 @@ class MILC(object):
colorama.init()
self.parse_args()
self.merge_args_into_config()
if self.config.general.interactive:
self.interactive = True
self.setup_logging()
return self

View file

@ -20,8 +20,8 @@ def yesno(prompt, *args, default=None, **kwargs):
If you add `--yes` and `--no` arguments to your program the user can answer questions by passing command line flags.
```python
@add_argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.')
@add_argument('-n', '--no', action='store_true', arg_only=True, help='Answer no to all questions.')
@cli.argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.')
@cli.argument('-n', '--no', action='store_true', arg_only=True, help='Answer no to all questions.')
```
"""
if not args and kwargs:
@ -33,6 +33,9 @@ def yesno(prompt, *args, default=None, **kwargs):
if 'yes' in cli.args and cli.args.yes:
return True
if not cli.interactive:
return False
if default is None:
prompt = prompt + ' [y/n] '
elif default:
@ -69,6 +72,9 @@ def question(prompt, *args, default=None, confirm=False, answer_type=str, valida
if not args and kwargs:
args = kwargs
if not cli.interactive:
return default
if default is not None:
prompt = '%s [%s] ' % (prompt, default)
@ -118,6 +124,9 @@ def choice(heading, options, *args, default=None, confirm=False, prompt='Please
if not args and kwargs:
args = kwargs
if not cli.interactive:
return default
if prompt and default:
prompt = prompt + ' [%s] ' % (default + 1,)
@ -145,7 +154,7 @@ def choice(heading, options, *args, default=None, confirm=False, prompt='Please
answer = int(answer) - 1
except Exception:
# Normally we would log the exception here, but in the interest of clean UI we do not.
cli.log.error('Invalid choice: %s', answer + 1)
cli.log.error('Invalid choice: %s', answer)
continue
# Validate the answer

29
questions Executable file
View file

@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""Hello World implementation using MILC.
PYTHON_ARGCOMPLETE_OK
"""
from milc import cli
from milc.questions import yesno, choice, question
@cli.argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.')
@cli.argument('-n', '--no', action='store_true', arg_only=True, help='Answer no to all questions.')
@cli.entrypoint('Ask some questions.')
def main(cli):
if yesno('Will you continue?'):
cli.log.info('User has chosen to continue.')
else:
cli.log.info('User has chosen to stop.')
if choice('Will you stop?', ['yes', 'no']) == 'no':
cli.log.info('User is not stopping.')
else:
cli.log.info('User is stopping.')
answer = question('Why? ')
cli.log.info('Interesting answer: %s', answer)
if __name__ == '__main__':
cli()

View file

@ -1,9 +1,9 @@
from milc import cli
def check_command(command, *args):
def check_command(command, *args, input=None):
cmd = [command] + list(args)
return cli.run(cmd, combined_output=True)
return cli.run(cmd, combined_output=True, input=input)
def check_returncode(result, expected=0):

View file

@ -0,0 +1,33 @@
from .common import check_command, check_returncode
def test_questions():
result = check_command('./questions')
check_returncode(result)
assert 'User has chosen to stop.' in result.stdout
assert 'User is stopping.' in result.stdout
assert 'Interesting answer: None' in result.stdout
def test_questions_interactive():
result = check_command('./questions', '--interactive', input='y\n2\nbecause\n')
check_returncode(result)
assert 'User has chosen to continue.' in result.stdout
assert 'User is not stopping.' in result.stdout
assert 'Interesting answer: because' in result.stdout
def test_questions_yes():
result = check_command('./questions', '--yes')
check_returncode(result)
assert 'User has chosen to continue.' in result.stdout
assert 'User is stopping.' in result.stdout
assert 'Interesting answer: None' in result.stdout
def test_questions_no():
result = check_command('./questions', '--no')
check_returncode(result)
assert 'User has chosen to stop.' in result.stdout
assert 'User is stopping.' in result.stdout
assert 'Interesting answer: None' in result.stdout