r'''
A set of cooperating classes which allow command implementations to
specify and parse the various options which Gerrit supports.

In addition, every option can be assigned a semantic-version compatible
specification which identifies the Gerrit versions on which the option
is supported. Thus, a command implementation can not only parse its
option list with ease, but also identify combinations which are not
supported by the site they are being sent to.

Implementing a versioned command has the following requirements (see
the `ProjectList` command for a full implementation).

* Create an OptionSet instance, specify all the supported options. This
  is best done as a class attribute, as the information is constant for
  all instances.

* Pass the OptionSet, and list of options to be parsed, to the constructor
  for the abstract SiteCommand class.

* When the `execute_on` method is invoked, call check_support_for() in the
  base class, which will verify all options (and the command itself) are
  supported in the Gerrit version for the given site.


A complete framework for a command implementation, supported on Gerrit versions
2.7 and above, might look like::

    class SomeCommand(SiteCommand):

        __options = OptionSet(
                        Option.flag(...),
                        Option.choice(...),
                        Option.valued(...),
                        Option.flag('needed'))

        def __init__(self, options_string):
            super(SomeCommand,self).__init__('>=2.7',
                                             SomeCommand.__options,
                                             options_string)

        def execute_on(self, site):
            # We always need to specify --needed for some reason
            self.__parsed.needed = True

            # Check the support *after* overriding options
            self.check_support_for(site)

            return site.execute(' '.join(['some-command',
                                        self.__parsed]).strip())

'''

import collections
import shlex
import argparse
import logging

import semantic_version as SV


_logger = logging.getLogger(__name__)


class OptionRepr(collections.namedtuple('__OptionRepr',
                                        'type key args kwargs spec')):
    pass


class CmdOptionParser(object):
    '''
    A parser configured for a given OptionSet

    :param option_set: An initialized OptionSet instance

    :raises: TypeError if option_set is not an OptionSet

    '''

    def __init__(self, option_set):
        self.__results = None

        if not isinstance(option_set, OptionSet):
            raise TypeError('CmdOptionParser requires an OptionSet instance')

        self._option_set = option_set
        self._parser = argparse.ArgumentParser()

        for opt in option_set:
            kwargs = dict(opt.kwargs, **{'dest': opt.key})
            self._parser.add_argument(*opt.args, **kwargs)

    def parse(self, opt_str):
        '''
        Parse the provided option string.

        :param opt_str:
            A string containing the list of options to be parsed against the
            OptionSet the instiance was initialized with.

        :returns: A ParsedOptions object with the results of parsing the string

        '''
        self.__parser_input = opt_str
        self.__results = ParsedOptions(self._option_set,
                                       self._parser,
                                       opt_str)
        return self.__results

    @property
    def results(self):
        '''
        The ParsedOptions object from the last call to parse

        '''
        return self.__results


class ParsedOptions(object):
    '''
    Results of parsing a list of options

    The value of each option is available as an attribute of the
    instance, allowing setting, getting, and deleting of options.

    This is an internal class, not meant to be instantiated outside
    of this module. Instances are returned by CmdOptionParser.parse().

    :param option_set: