""" Advanced Datatypes to add as type to entrypoint. Or any parser, really. """
from __future__ import print_function
import abc
import sys
import six
# for testing purposes:
from os.path import abspath, join, dirname, pardir
sys.path.append(abspath(join(dirname(__file__), pardir)))
from utils.entrypoint import entrypoint, EntryPointParameters
# Meta Class Helper ############################################################
# 'Merge' Classes ##############################################################
[docs]def get_multi_class(*classes):
    """ Create a class 'behaving' like all classes in `classes`.
    In case a value needs to be converted to a class in this list,
    it is attempted to cast the input to the classes in the given order
    (i.e. string-classes need to go to the end, as they 'always' succeed).
    """
    class MultiClass(object):
        __metaclass__ = get_instance_faker_meta(*classes)
        @classmethod
        def _convert_to_a_type(cls, value):
            for c in classes:
                try:
                    return c.__new__(c, value)
                except ValueError:
                    pass
            else:
                raise ValueError(
                    "The value '{}' cant be converted to any of the classes '{}' ".format(
                        value,
                        ",".join([c.__name__ for c in classes]),
                    )
                )
        def __new__(cls, value):
            if isinstance(value, six.string_types) or not isinstance(value, classes):
                return cls._convert_to_a_type(value)
            return value
    return MultiClass 
# More Fake Classes ############################################################
[docs]class DictAsString(object):
    """ Use dicts in command line like {"key":value} """
    __metaclass__ = get_instance_faker_meta(str, dict)
    def __new__(cls, s):
        if isinstance(s, dict):
            return s
        d = eval(s)
        if not isinstance(d, dict):
            raise ValueError("'{}' can't be converted to a dictionary.".format(s))
        return d 
[docs]class BoolOrString(object):
    """ A class that behaves like a boolean when possible, otherwise like a string."""
    __metaclass__ = get_instance_faker_meta(bool, str)
    def __new__(cls, value):
        if value in ["True", "1", True, 1]:
            return bool.__new__(bool, True)
        elif value in ["False", "0", False, 0]:
            return bool.__new__(bool, False)
        else:
            return str.__new__(str, value) 
[docs]class BoolOrList(object):
    """ A class that behaves like a boolean when possible, otherwise like a list.
    Hint: 'list.__new__(list, value)' returns an empty list."""
    __metaclass__ = get_instance_faker_meta(bool, list)
    def __new__(cls, value):
        if value in ["True", "1", True, 1]:
            return bool.__new__(bool, True)
        elif value in ["False", "0", False, 0]:
            return bool.__new__(bool, False)
        else:
            return list(value) 
# Test #########################################################################
def get_params():
    params = EntryPointParameters()
    params.add_parameter(
        flags="--dict",
        name="dict",
        type=DictAsString,
        help="Use a dictionary!",
    )
    params.add_parameter(
        flags="--int_or_str",
        name="int_or_str",
        type=get_multi_class(int, str),
        help="either int or string",
    )
    params.add_parameter(
        flags="--bool_or_str",
        name="bool_or_str",
        type=BoolOrString,
        help="either bool or string",
    )
    params.add_parameter(
        flags="--bool_or_list",
        name="bool_or_list",
        type=BoolOrList,
        help="either bool or list",
    )
    return params
@entrypoint(get_params(), strict=False)
def tester(opt, other):
    print("Opt:")
    print(opt)
    print("Other:")
    print(other)
    print("\n")
if __name__ == '__main__':
    if len(sys.argv) < 2:
        tester(dict={"test": 5}, bool_or_str="test", int_or_str="test")
        tester(dict={"test": 5}, bool_or_str=False, int_or_str=10)
        tester(bool_or_list=True)
        tester(bool_or_list=["This", "Is", "A", 1])
    else:
        tester()
        # e.g.
        # --dict '{"hello":4, "there":{"General":"Kenobi"}}' --int_or_str hello --bool_or_str there
        # --dict '{"hello":4, "there":{"General":"Kenobi"}}' --int_or_str 5 --bool_or_str 0
        # --dict '{"hello":4, "there":{"General":"Kenobi"}}' --int_or_str 0 --bool_or_str True