def _rule_to_regex(rule):
    """
    Parse a rule and return a regular expression, as well as converters for
    value conversion and default values.
    """
    regex, converters, names, namegen, domain, path = "", [], [], "", "", ""

    # Make sure we have at least one /.
    if not '/' in rule:
        rule = '/' + rule

    in_domain = True

    # Handle the beginning of the string.
    if rule[0] == '.':
        regex += '[^./]+?'
    elif rule[0] == '/':
        regex += '[^/]+?'

    # Find the first <.
    ind = rule.find("<")
    if ind == -1 or RULE_PARSER.match(rule[ind:]) is None:
        if rule in '/':
            domain, _, path = rule.partition('/')
            path = '/' + path
            regex += re.escape(domain) + "(?::\d+)?" + re.escape(path) + "$"
        else:
            regex += re.escape(rule) + "$"

        # There are no variables to match. Tough luck.
        return "^" + regex, tuple(), tuple(), rule, domain, path
    elif ind > 0:
        text = rule[:ind]
        rule = rule[ind:]

        if '/' in text:
            in_domain = False
            domain, _, path = text.partition('/')
            path = '/' + path
            regex += re.escape(domain) + "(?::\d+)?" + re.escape(path)
        else:
            regex += re.escape(text)
        namegen += text

    has_default = 0

    # Iterate through the matches.
    for match in RULE_PARSER.finditer(rule):
        converter, options, name, default, text = match.groups()
        names.append(name)

        if default is not None:
            has_default += 1
            if not in_domain:
                regex += "(?:"

        # If we're still in the domain, use a special converter that doesn't
        # match the period.
        if converter:
            converter = converter.lower()

        if converter == 'str':
            converter = 'string'
        if in_domain and (not converter or converter == 'string'):
            converter = 'domainpart'
        elif not in_domain and (not converter or converter == 'domainpart'):
            converter = 'string'

        converter = converter.strip()
        if not converter in CONVERTER_TYPES:
            raise ValueError("No such converter %r." % converter)

        # Make the converter.
        converter = CONVERTER_TYPES[converter](options, default)
        converters.append(converter)

        regex += converter.regex
        namegen += '%s' + text

        if in_domain and '/' in text:
            in_domain = False
            domain, _, path = text.partition('/')
            path = '/' + path
            regex += re.escape(domain) + "(?::\d+)?" + re.escape(path)

            if default:
                regex += ")?"

            while has_default > 0:
                regex = "(?:" + regex
                has_default -= 1
        else:
            regex += re.escape(text)
            if default and in_domain:
                regex += ")?"

    while has_default > 0:
        regex += ")?"
        has_default -= 1

    return "^" + regex + "$", tuple(converters), tuple(names), \
           namegen, domain, path


###############################################################################
# Public Helper Functions
###############################################################################
dataset/ETHPy150Open ecdavis/pants/pants/web/application.py/_rule_to_regex/swapped operands of `in`