""" Processing of docstrings, which means parsing for types. """

import re

import evaluate
import parsing

DOCSTRING_PARAM_PATTERNS = [
    r'\s*:type\s+%s:\s*([^\n]+)', # Sphinx
    r'\s*@type\s+%s:\s*([^\n]+)', # Epidoc
]

DOCSTRING_RETURN_PATTERNS = [
        re.compile(r'\s*:rtype:\s*([^\n]+)', re.M), # Sphinx
        re.compile(r'\s*@rtype:\s*([^\n]+)', re.M), # Epidoc
]

#@cache.memoize_default()  # TODO add
def follow_param(param):
    func = param.parent_function
    #print func, param, param.parent_function
    param_str = search_param_in_docstr(func.docstr, str(param.get_name()))
    user_position = (1, 0)

    if param_str is not None:

        # Try to import module part in dotted name.
        # (e.g., 'threading' in 'threading.Thread').
        if '.' in param_str:
            param_str = 'import %s\n%s' % (
                param_str.rsplit('.', 1)[0],
                param_str)
            user_position = (2, 0)

        p = parsing.PyFuzzyParser(param_str, None, user_position,
                                  no_docstr=True)
        return evaluate.follow_statement(p.user_stmt)
    return []


def search_param_in_docstr(docstr, param_str):
    """
    Search `docstr` for a type of `param_str`.

    >>> search_param_in_docstr(':type param: int', 'param')
    'int'
    >>> search_param_in_docstr('@type param: int', 'param')
    'int'
    >>> search_param_in_docstr(
    ...   ':type param: :class:`threading.Thread`', 'param')
    'threading.Thread'
    >>> search_param_in_docstr('no document', 'param') is None
    True

    """
    # look at #40 to see definitions of those params
    patterns = [ re.compile(p % re.escape(param_str)) for p in DOCSTRING_PARAM_PATTERNS ]
    for pattern in patterns:
        match = pattern.search(docstr)
        if match:
            type_str = match.group(1)
            if type_str.startswith(':class:'):
                type_str = type_str[len(':class:'):].strip('`')
            return type_str

    return None


def find_return_types(func):
    if isinstance(func, evaluate.InstanceElement):
        func = func.var

    if isinstance(func, evaluate.Function):
        func = func.base_func

    type_str = search_return_in_docstr(func.docstr)
    if not type_str:
        return []

    p = parsing.PyFuzzyParser(type_str, None, (1, 0), no_docstr=True)
    p.user_stmt.parent = func
    return list(evaluate.follow_statement(p.user_stmt))

def search_return_in_docstr(code):
    for p in DOCSTRING_RETURN_PATTERNS:
        match = p.search(code)
        if match:
            return match.group(1)

@@ -375,16 +375,69 @@
 idoc%0A%5D%0A%0A
+REST_ROLE_PATTERN = re.compile(r':%5B%5E%60%5D+:%60(%5B%5E%60%5D+)%60')%0A%0A
 #@cache.
@@ -1857,27 +1857,39 @@
         
-type_str = 
+return strip_rest_role(
 match.gr
@@ -1898,149 +1898,591 @@
 p(1)
+)%0A
 %0A    
-        if type_str.startswith(':class:'):%0A                type_str = type_str%5B
+return None%0A%0A%0Adef strip_rest_role(type_str):%0A    %22%22%22%0A    Strip off the part looks like a ReST role in %60type_str%60.%0A%0A    %3E%3E%3E strip_rest_role(':class:%60ClassName%60')  # strip off :class:%0A    'ClassName'%0A    %3E%3E%3E strip_rest_role(':py:obj:%60module.Object%60')  # works with domain%0A    'module.Object'%0A    %3E%3E%3E strip_rest_ro
 le
-n
 ('
-:c
+C
 lass
-:'):%5D.strip('%60')%0A            return type_str%0A%0A
+Name')  # do nothing when not ReST role%0A    'ClassName'%0A%0A    See also:%0A    http://sphinx-doc.org/domains.html#cross-referencing-python-objects%0A%0A    %22%22%22%0A    match = REST_ROLE_PATTERN.match(type_str)%0A    if match:%0A        return match.group(1)%0A    else:%0A    
     
@@ -2484,28 +2484,32 @@
      return 
-None
+type_str
 %0A%0A%0Adef find_

Make :ROLE:`TYPE` handling more general