0001"""Input and output functions
0002"""
0003import sys, os, os.path
0004from terminate.abstract import color, stdout, stderr
0005
0006__all__ = ["rl", "casts", "queries",
0007           "input_object","query","query_list","file_chooser","error_message"]
0008
0009# this constant is here because float() and int() give error messages
0010# that would confuse most sane users.
0011
0012ERROR_MESSAGE = ( color('bright','red') + 'Error: ' + color('default') +
0013                 '%s' + '\a' + os.linesep )
0014NICE_INPUT_ERRORS = {
0015    float: "The input ('%s') must be a number",
0016    int: "The input ('%s') must be an integer (-1, 0, 1, 2, etc.)"
0017}
0018
0019DEFAULT_INPUT_ERRORS = "Bad input (%s)"
0020
0021def input_object(prompt_text, cast, default=None, castarg=[], castkwarg={}):
0022    """Gets input from the command line and validates it.
0023    
0024    prompt_text
0025        A string. Used to prompt the user.
0026    cast
0027        This can be any callable object (class, function, type, etc). It
0028        simply calls the cast with the given arguements and returns the 
0029        result. If a ValueError is raised, it
0030        will output an error message and prompt the user again.
0031
0032        Because some builtin python objects don't do casting in the way
0033        that we might like you can easily write a wrapper function that
0034        looks and the input and returns the appropriate object or exception.
0035        Look in the cast submodule for examples.
0036    default
0037        function returns this value if the user types nothing in. This is
0038        can be used to cancel the input so-to-speek
0039    castarg, castkwarg
0040        list and dictionary. Extra arguments passed on to the cast.
0041    """
0042    while True:
0043        try:
0044            t = raw_input(prompt_text)
0045            if t == '': return default
0046            value = cast(t, *castarg, **castkwarg)
0047        except ValueError, details:
0048            if cast in NICE_INPUT_ERRORS: # see comment above this constant
0049                stdout.write(ERROR_MESSAGE % (NICE_INPUT_ERRORS[cast] % t))
0050            else: stdout.write(ERROR_MESSAGE % (DEFAULT_INPUT_ERRORS % str(details)))
0051            continue
0052        return value
0053
0054def query(prompt_text, answers, default=None, list_values = False, ignorecase = True ):
0055    """Preset a few options
0056    
0057    The prompt_text argument is a string, nothing magical.
0058    
0059    The answers argument accepts input in two different forms. The simpler form
0060    (a tuple with strings) looks like:
0061    
0062        .. code-block:: Python
0063        
0064            ('Male','Female')
0065    
0066    And it will pop up a question asking the user for a gender and requiring
0067    the user to enter either 'male' or 'female' (case doesn't matter unless
0068    you set the third arguement to false).
0069    The other form is something like:
0070    
0071        .. code-block:: Python
0072        
0073            ({'values':('Male','M'),'fg':'cyan'},
0074            {'values':('Female','F'),'fg':'magenta'})
0075    
0076    This will pop up a question with Male/Female (each with appropriate
0077    colouring). Additionally, if the user types in just 'M', it will be
0078    treated as if 'Male' was typed in. The first item in the 'values' tuple
0079    is treated as default and is the one that is returned by the function
0080    if the user chooses one in that group.
0081    In addition the function can handle non-string objects quite fine. It
0082    simple displays the output object.__str__() and compares the user's input
0083    against that. So the the code
0084    
0085        .. code-block:: Python
0086        
0087            query("Python rocks? ",(True, False))
0088    
0089    will return a bool (True) when the user types in the string 'True' (Of
0090    course there isn't any other reasonable answer than True anyways :P)
0091    
0092    ``default`` is the value function returns if the user types nothing in. This is
0093    can be used to cancel the input so-to-speek
0094    
0095    Using list_values = False will display a list, with descriptions printed out
0096    from the 'desc' keyword
0097    """
0098    answers = list(answers)
0099    for i in range(len(answers)):
0100        if not isinstance(answers[i], dict):
0101            answers[i] = {'values': [answers[i]]}
0102    try:
0103        import readline, rl
0104        wordlist = [ str(values) for answer in answers
0105                    for values in answer['values']]
0106        completer = rl.ListCompleter(wordlist, ignorecase)
0107        readline.parse_and_bind("tab: complete")
0108        readline.set_completer(completer.complete)
0109    except ImportError:
0110        pass
0111    answerslist = []
0112    if list_values:
0113        for item in answers:
0114            answerslist.append(
0115                color('bright', fg=item.get('fg'), bg=item.get('bg')) +
0116                str(item['values'][0]) +
0117                color('default') +
0118                ' : ' + item['desc'])
0119        prompt_text += os.linesep + os.linesep.join(answerslist) + os.linesep
0120    else:
0121        for item in answers:
0122            answerslist.append(
0123                color('bright', fg=item.get('fg'), bg=item.get('bg')) +
0124                str(item['values'][0]) +
0125                color('default'))
0126        prompt_text += '[' + '/'.join(answerslist) + ']'
0127    while True:
0128        stdout.write(prompt_text)
0129        t = raw_input(': ')
0130        if t == '': return default
0131        if ignorecase: response = t.lower()
0132        else: response = t
0133        for item in answers:
0134            for a in item['values']:
0135                if ignorecase and (response == str(a).lower()):
0136                    return item['values'][0]
0137                if response == a:
0138                    return item['values'][0]
0139        stdout.write(ERROR_MESSAGE % (
0140                      "Response '%s' not understood, please try again." % t))
0141
0142def file_chooser(prompt_text = "Enter File: ", default=None, filearg=[], filekwarg={}):
0143    """A simple tool to get a file from the user. Takes keyworded arguemnts
0144    and passes them to open().
0145    
0146    If the user enters nothing the function will return the ``default`` value.
0147    Otherwise it continues to prompt the user until it get's a decent response.
0148    
0149    filekwarg may contain arguements passed on to ``open()``.
0150    """
0151    try:
0152        import readline, rl
0153        completer = rl.PathCompleter()
0154        readline.set_completer_delims(completer.delims)
0155        readline.parse_and_bind("tab: complete")
0156        readline.set_completer(completer.complete)
0157    except ImportError:
0158        pass
0159    while True:
0160        f = raw_input(prompt_text)
0161        if f == '': return default
0162        f = os.path.expanduser(f)
0163        if len(f) != 0 and f[0] == os.path.sep:
0164            f = os.path.abspath(f)
0165        try:
0166            return open(f, *filearg, **filekwarg)
0167        except IOError, e:
0168            stdout.write(ERROR_MESSAGE % ("unable to open %s : %s" % (f, e)))