import argparse
import inspect
import re


def parse_docstring(docstring):
    docstring = docstring or ''
    lines = docstring.split('\n')
    description = []
    while lines:
        line = lines[0].strip()
        lines = lines[1:]
        if not line:
            continue

        if line.startswith(':'):
            lines.insert(0, line)
            break

        description.append(line)

    params = []

    while lines:
        line = lines[0].strip()
        lines = lines[1:]
        if not line:
            continue

        if not line.startswith(':') and params:
            params[-1]['description'] += '\n' + line.strip()
            continue

        match = re.match('^:param\s+(?P<type>.+?)?(?(type)\s+|)(?P<name>.+?):\s*(?P<description>.+)?', line)
        if match:
            params.append(match.groupdict())

    return '\n'.join(description), params


def autospec(parser, func, argument_overrides=None):
    docstring = inspect.getdoc(func) or ""
    parser.description, params_docs = parse_docstring(docstring)
    argument_overrides = argument_overrides or dict()

    signature = inspect.signature(func)
    for param_name, param in signature.parameters.items():
        if param_name == 'self' or param_name == 'cls':
            # https://bitbucket.org/ned/coveragepy/issues/198/continue-marked-as-not-covered
            continue  # pragma: no cover

        kwargs = dict(
            action='store',
            help=param_name
        )

        for param_doc in params_docs:
            if param_doc['name'] == param_name:
                kwargs['help'] = param_doc['description']

        if param.kind == inspect.Parameter.VAR_POSITIONAL:
            continue
        elif param.kind == inspect.Parameter.VAR_KEYWORD:
            kwargs['nargs'] = '*'
            if kwargs['help'] == param_name:
                kwargs['help'] = 'Optional keyword arguments. Specify them as key=value'

        if param.default is not inspect._empty:
            param_name = '--' + param_name
            kwargs['default'] = param.default

        if isinstance(param.default, bool):
            if param.default:
                kwargs['action'] = 'store_false'
            else:
                kwargs['action'] = 'store_true'

        if param_name in argument_overrides:
            kwargs.update(argument_overrides[param_name])

        parser.add_argument(param_name, **kwargs)


class EndpointParser(argparse.ArgumentParser):
    subparsers = None
    internal_keys = {'__func__', '__endpoint__'}

    def clear_internal_keys(self, args):
        """
        :param dict|argparse.Namespace args:
        :rtype: dict
        """
        if not isinstance(args, dict):
            args = vars(args)
        for key in self.internal_keys:
            args.pop(key, None)
        kwargs = args.pop('kwargs', [])
        for item in kwargs:
            key, value = item.split('=')
            args[key] = value
        return args

    def add_subparsers(self, **kwargs):
        self.subparsers = super(EndpointParser, self).add_subparsers(**kwargs)
        return self.subparsers

    # noinspection PyProtectedMember
    def get_endpoint_parser(self, path):
        """
        :param str|list|tuple path:
        :rtype: argparse.ArgumentParser
        """
        if not isinstance(path, (list, tuple)):
            path = path.split('.')
            if len(path) == 1 and path[0]:
                path = path[0].split(' ')
            elif len(path) == 1 and not path[0]:
                path = []

        if not path:
            return self

        parser = self

        for key in path:
            if parser.subparsers is None:
                parser.add_subparsers()
            if key in parser.subparsers._name_parser_map:
                parser = parser.subparsers._name_parser_map[key]
            else:
                parser = parser.subparsers.add_parser(key)

        parser.path = path

        return parser

    def add_endpoint(self, path, func=None, autospec=True, argument_overrides=None):
        parser = self.get_endpoint_parser(path)

        if func:
            if autospec:
                globals()['autospec'](parser, func, argument_overrides=argument_overrides)
            parser.set_defaults(__func__=func)

        parser.set_defaults(__endpoint__=path)

        return parser

    def parse_and_call(self, *args, **kwargs):
        args = self.parse_args(*args, **kwargs)
        return self.call(args)

    def call(self, args):
        if not hasattr(args, '__func__'):
            self.error('Invalid endpoint')

        func = args.__func__
        args = self.clear_internal_keys(args)

        return func(**args)

@@ -68,16 +68,297 @@
 tring):%0A
+    %22%22%22%0A    Parse docstring and return tuple of description string and list of params dicts.%0A    %60:param%60 are supported only.%0A    For %60:param str foo: help%60 this will generate dict %60%7B'type': 'str', 'name': 'foo', 'description': 'help'%7D%60%0A%0A    :rtype: tuple%5Bstr, list%5Bdict%5D%5D%0A    %22%22%22%0A
     docs
@@ -381,16 +381,16 @@
 g or ''%0A
-
     line
@@ -1206,24 +1206,284 @@
 ides=None):%0A
+    %22%22%22%0A    Generate parser arguments from %60func%60 signature.%0A%0A    :param argparse.ArgumentParser parser: Target parser%0A    :param func: Function to get signature from%0A    :param None%7Cdict%5Bstr, dict%5D argument_overrides: passed to add_argument for param%0A    %22%22%22%0A
     docstrin
@@ -3113,32 +3113,123 @@
 s):%0A        %22%22%22%0A
+        Deletes all keys specified in %60internal_keys%60 field from args (Namespace or dict)%0A%0A
         :param d
@@ -3622,32 +3622,187 @@
 elf, **kwargs):%0A
+        %22%22%22%0A        Create subparsers action for current parser and store it at %60subparsers%60 field%0A%0A        :rtype: argparse._SubParsersAction%0A        %22%22%22%0A
         self.sub
@@ -3978,32 +3978,69 @@
 h):%0A        %22%22%22%0A
+        Return a parser for %60path%60.%0A%0A
         :param s
@@ -5161,32 +5161,32 @@
  return parser%0A%0A
-
     def parse_an
@@ -5208,32 +5208,106 @@
 rgs, **kwargs):%0A
+        %22%22%22%0A        Shortcut function to parse args and call.%0A        %22%22%22%0A
         args = s

Add docs