questions.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. """Functions to collect user input.
  2. """
  3. from milc import cli
  4. try:
  5. from milc import format_ansi
  6. except ImportError:
  7. from milc.ansi import format_ansi
  8. def yesno(prompt, *args, default=None, **kwargs):
  9. """Displays prompt to the user and gets a yes or no response.
  10. Returns True for a yes and False for a no.
  11. If you add `--yes` and `--no` arguments to your program the user can answer questions by passing command line flags.
  12. @add_argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.')
  13. @add_argument('-n', '--no', action='store_true', arg_only=True, help='Answer no to all questions.')
  14. Arguments:
  15. prompt
  16. The prompt to present to the user. Can include ANSI and format strings like milc's `cli.echo()`.
  17. default
  18. Whether to default to a Yes or No when the user presses enter.
  19. None- force the user to enter Y or N
  20. True- Default to yes
  21. False- Default to no
  22. """
  23. if not args and kwargs:
  24. args = kwargs
  25. if 'no' in cli.args and cli.args.no:
  26. return False
  27. if 'yes' in cli.args and cli.args.yes:
  28. return True
  29. if default is not None:
  30. if default:
  31. prompt = prompt + ' [Y/n] '
  32. else:
  33. prompt = prompt + ' [y/N] '
  34. while True:
  35. cli.echo('')
  36. answer = input(format_ansi(prompt % args))
  37. cli.echo('')
  38. if not answer and prompt is not None:
  39. return default
  40. elif answer.lower() in ['y', 'yes']:
  41. return True
  42. elif answer.lower() in ['n', 'no']:
  43. return False
  44. def question(prompt, *args, default=None, confirm=False, answer_type=str, validate=None, **kwargs):
  45. """Prompt the user to answer a question with a free-form input.
  46. Arguments:
  47. prompt
  48. The prompt to present to the user. Can include ANSI and format strings like milc's `cli.echo()`.
  49. default
  50. The value to return when the user doesn't enter any value. Use None to prompt until they enter a value.
  51. confirm
  52. Present the user with a confirmation dialog before accepting their answer.
  53. answer_type
  54. Specify a type function for the answer. Will re-prompt the user if the function raises any errors. Common choices here include int, float, and decimal.Decimal.
  55. validate
  56. This is an optional function that can be used to validate the answer. It should return True or False and have the following signature:
  57. def function_name(answer, *args, **kwargs):
  58. """
  59. if not args and kwargs:
  60. args = kwargs
  61. if default is not None:
  62. prompt = '%s [%s] ' % (prompt, default)
  63. while True:
  64. cli.echo('')
  65. answer = input(format_ansi(prompt % args))
  66. cli.echo('')
  67. if answer:
  68. if validate is not None and not validate(answer, *args, **kwargs):
  69. continue
  70. elif confirm:
  71. if yesno('Is the answer "%s" correct?', answer, default=True):
  72. try:
  73. return answer_type(answer)
  74. except Exception as e:
  75. cli.log.error('Could not convert answer (%s) to type %s: %s', answer, answer_type.__name__, str(e))
  76. else:
  77. try:
  78. return answer_type(answer)
  79. except Exception as e:
  80. cli.log.error('Could not convert answer (%s) to type %s: %s', answer, answer_type.__name__, str(e))
  81. elif default is not None:
  82. return default
  83. def choice(heading, options, *args, default=None, confirm=False, prompt='Please enter your choice: ', **kwargs):
  84. """Present the user with a list of options and let them pick one.
  85. Users can enter either the number or the text of their choice.
  86. This will return the value of the item they choose, not the numerical index.
  87. Arguments:
  88. heading
  89. The text to place above the list of options.
  90. options
  91. A sequence of items to choose from.
  92. default
  93. The index of the item to return when the user doesn't enter any value. Use None to prompt until they enter a value.
  94. confirm
  95. Present the user with a confirmation dialog before accepting their answer.
  96. prompt
  97. The prompt to present to the user. Can include ANSI and format strings like milc's `cli.echo()`.
  98. """
  99. if not args and kwargs:
  100. args = kwargs
  101. if prompt and default:
  102. prompt = prompt + ' [%s] ' % (default + 1,)
  103. while True:
  104. # Prompt for an answer.
  105. cli.echo('')
  106. cli.echo(heading % args)
  107. cli.echo('')
  108. for i, option in enumerate(options, 1):
  109. cli.echo('\t{fg_cyan}%d.{fg_reset} %s', i, option)
  110. cli.echo('')
  111. answer = input(format_ansi(prompt))
  112. cli.echo('')
  113. # If the user types in one of the options exactly use that
  114. if answer in options:
  115. return answer
  116. # Massage the answer into a valid integer
  117. if answer == '' and default:
  118. answer = default
  119. else:
  120. try:
  121. answer = int(answer) - 1
  122. except Exception:
  123. # Normally we would log the exception here, but in the interest of clean UI we do not.
  124. cli.log.error('Invalid choice: %s', answer + 1)
  125. continue
  126. # Validate the answer
  127. if answer >= len(options) or answer < 0:
  128. cli.log.error('Invalid choice: %s', answer + 1)
  129. continue
  130. if confirm and not yesno('Is the answer "%s" correct?', answer + 1, default=True):
  131. continue
  132. # Return the answer they chose.
  133. return options[answer]