#
# Copyright (c) 2010 Testrepository Contributors
# 
# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
# license at the users choice. A copy of both licenses are available in the
# project source as Apache-2.0 and BSD. You may not use this file except in
# compliance with one of these two licences.
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# license you chose for the specific language governing permissions and
# limitations under that license.

"""'Arguments' for testr.

This is a small typed arguments concept - which is perhaps obsoleted by
argparse in Python 2.7, but for testrepository is an extension used with
optparse.

The code in this module contains the AbstractArgument base class. Individual
argument types are present in e.g. testrepository.arguments.string.

See testrepository.commands.Command for usage of Arguments.

Plugins and extensions wanting to add argument types should either define them
internally or install into testrepository.arguments as somename (perhaps by
extending the testrepository.arguments __path__ to include a directory
containing their argument types - no __init__ is needed in that directory.)
"""

import sys

from testtools.compat import reraise


class AbstractArgument(object):
    """A argument that a command may need.

    Arguments can be converted into a summary for describing the UI to users,
    and provide validator/parsers for the arguments.

    :ivar: The name of the argument. This is used for retrieving the argument
        from UI objects, and for generating the summary.
    """

    def __init__(self, name, min=1, max=1):
        """Create an AbstractArgument.

        While conceptually a separate SequenceArgument could be used, all
        arguments support sequencing to avoid unnecessary boilerplate in user
        code.

        :param name: The name for the argument.
        :param min: The minimum number of occurences permitted.
        :param max: The maximum number of occurences permitted. None for
            unlimited.
        """
        self.name = name
        self.minimum_count = min
        self.maximum_count = max

    def summary(self):
        """Get a regex-like summary of this argument."""
        result = self.name
        if (self.minimum_count == self.maximum_count and
            self.minimum_count == 1):
                return result
        minmax = (self.minimum_count, self.maximum_count)
        if minmax == (0, 1):
            return result + '?'
        if minmax == (1, None):
            return result + '+'
        if minmax == (0, None):
            return result + '*'
        if minmax[1] == None:
            minmax = (minmax[0], '')
        return result + '{%s,%s}' % minmax

    def parse(self, argv):
        """Evaluate arguments in argv.

        Used arguments are removed from argv.

        :param argv: The arguments to parse.
        :return: The parsed results as a list.
        """
        count = 0
        result = []
        error = None
        while len(argv) > count and (
            self.maximum_count is None or count < self.maximum_count):
            arg = argv[count]
            count += 1
            try:
                result.append(self._parse_one(arg))
            except ValueError:
                # argument rejected this element
                error = sys.exc_info()
                count -= 1
                break
        if count < self.minimum_count:
            if error is not None:
                reraise(error[0], error[1], error[2])
            raise ValueError('not enough arguments present/matched in %s' % argv)
        del argv[:count]
        return result

    def _parse_one(self, arg):
        """Parse a single argument.
        
        :param arg: An arg from an argv.
        :result: The parsed argument.
        :raises ValueError: If the arg cannot be parsed/validated.
        """
        raise NotImplementedError(self._parse_one)
