From b99f79c98e619afa4347514e1e669b00aec4c0d8 Mon Sep 17 00:00:00 2001 From: Reto Aebersold Date: Thu, 8 May 2014 17:18:10 -0600 Subject: [PATCH] Add a simple query system - Extend clint.textui.prompt with a query function. - Add clint.textui.validators to validate the user input. - Add a prompt example showing different usages. - Update the README to demo the query function. --- README.rst | 6 ++- clint/textui/prompt.py | 36 +++++++++++++ clint/textui/validators.py | 104 +++++++++++++++++++++++++++++++++++++ examples/prompt.py | 21 ++++++++ 4 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 clint/textui/validators.py create mode 100644 examples/prompt.py diff --git a/README.rst b/README.rst index f718d00..bddda80 100644 --- a/README.rst +++ b/README.rst @@ -45,7 +45,6 @@ Future Features: ---------------- - Documentation! - Simple choice system ``Are you sure? [Yn]`` -- Default query system ``Installation Path [/usr/local/bin/]`` - Suggestions welcome. @@ -113,6 +112,11 @@ I want to force color output even if stdout is not a TTY: $ export CLINT_FORCE_COLOR=1 +I want to ask for input. :: + + >>> from clint.textui import prompt, validators + >>> path = prompt.query('Installation Path', default='/usr/local/bin/', validators=[validators.PathValidator()]) + Installation ------------ diff --git a/clint/textui/prompt.py b/clint/textui/prompt.py index 41e9293..5041207 100644 --- a/clint/textui/prompt.py +++ b/clint/textui/prompt.py @@ -11,6 +11,11 @@ Module for simple interactive prompts handling from __future__ import absolute_import, print_function from re import match, I + +from .core import puts +from .colored import yellow +from .validators import RegexValidator + try: raw_input except NameError: @@ -52,3 +57,34 @@ def yn(prompt, default='y', batch=False): # then return True, False otherwise elif match('n(?:o)?', input, I): return True if default == 'n' else False + + +def query(prompt, default='', validators=None, batch=False): + # Set the nonempty validator as default + if validators is None: + validators = [RegexValidator(r'.+')] + + # Let's build the prompt + if prompt[-1] is not ' ': + prompt += ' ' + + if default: + prompt += '[' + default + '] ' + + # If input is not valid keep asking + while True: + # If batch option is True then auto reply + # with default input + if not batch: + user_input = raw_input(prompt).strip() or default + else: + print(prompt) + user_input = '' + + # Validate the user input + try: + for validator in validators: + user_input = validator(user_input) + return user_input + except Exception, e: + puts(yellow(e.message)) \ No newline at end of file diff --git a/clint/textui/validators.py b/clint/textui/validators.py new file mode 100644 index 0000000..7a12094 --- /dev/null +++ b/clint/textui/validators.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +""" +clint.textui.validators +~~~~~~~~~~~~~~~~~~~~~~~ + +Core TextUI functionality for input validation. + +""" + +from __future__ import absolute_import + +import os +import sys +import re + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + +if PY3: + string_types = str, +else: + string_types = basestring, + + +class ValidationError(Exception): + """An error while validating data.""" + + def __init__(self, message): + self.message = message + self.error_list = [self] + + +class RegexValidator(object): + regex = '' + message = 'Enter a valid value.' + + def __init__(self, regex=None, message=None): + if regex is not None: + self.regex = regex + if message is not None: + self.message = message + + # Compile the regex if it was not passed pre-compiled. + if isinstance(self.regex, string_types): + self.regex = re.compile(self.regex) + + def __call__(self, value): + """ + Validates that the input matches the regular expression. + """ + if not self.regex.search(value): + raise ValidationError(self.message) + return value + + +class PathValidator(object): + message = 'Enter a valid path.' + + def __init__(self, message=None): + if message is not None: + self.message = message + + def __call__(self, value): + """ + Validates that the input is a valid directory. + """ + if not os.path.isdir(value): + raise ValidationError(self.message) + return value + + +class FileValidator(object): + message = 'Enter a valid file.' + + def __init__(self, message=None): + if message is not None: + self.message = message + + def __call__(self, value): + """ + Validates that the input is a valid file. + """ + if not os.path.isfile(value): + raise ValidationError(self.message) + return value + + +class IntegerValidator(object): + message = 'Enter a valid number.' + + def __init__(self, message=None): + if message is not None: + self.message = message + + def __call__(self, value): + """ + Validates that the input is a integer. + """ + try: + return int(value) + except (TypeError, ValueError): + raise ValidationError(self.message) diff --git a/examples/prompt.py b/examples/prompt.py new file mode 100644 index 0000000..f14b108 --- /dev/null +++ b/examples/prompt.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +import os + +sys.path.insert(0, os.path.abspath('..')) + +from clint.textui import prompt, puts, colored, validators + +if __name__ == '__main__': + # Standard non-empty input + name = prompt.query("What's your name?") + + # Set validators to an empty list for an optional input + language = prompt.query("Your favorite tool (optional)?", validators=[]) + + # Use a default value and a validator + path = prompt.query('Installation Path', default='/usr/local/bin/', validators=[validators.PathValidator()]) + + puts(colored.blue('Hi {0}. Install {1} to {2}'.format(name, language or 'nothing', path)))