Converts a template and a dict of parameters to a path fragment.

    Converts a template, such as /{name}/ and a dictionary of parameter
    values to a URL path (string).

    Parameter values that are used for buildig the path are converted to
    strings using `str()` and URI-escaped, then validated against the their
    range. Unused parameters are ignored.

    Any optional ([]) blocks in the template are skipped unless they contain at
    least one parameter and all parameters needed to fill the block (including
    nested blocks) are present in `params`.

    Example:

    >>> import rhino.mapper
    >>> rhino.mapper.template2path("/{name}", {'name': 'fred'})
    '/fred'

def template2path(template, params, ranges=None):
    """Converts a template and a dict of parameters to a path fragment.

    Converts a template, such as /{name}/ and a dictionary of parameter
    values to a URL path (string).

    Parameter values that are used for buildig the path are converted to
    strings using `str()` and URI-escaped, then validated against the their
    range. Unused parameters are ignored.

    Any optional ([]) blocks in the template are skipped unless they contain at
    least one parameter and all parameters needed to fill the block (including
    nested blocks) are present in `params`.

    Example:

    >>> import rhino.mapper
    >>> rhino.mapper.template2path("/{name}", {'name': 'fred'})
    '/fred'

    """
    if len(template) and -1 < template.find('|') < len(template) - 1:
        raise InvalidTemplateError("'|' may only appear at the end, found at position %d in %s" % (template.find('|'), template))
    if ranges is None:
        ranges = DEFAULT_RANGES

    # Stack for path components. A new list is added for each '[]' block
    # encountered. When the closing ']' is reached, the last element is
    # removed and either merged into the previous one (we keep the
    # block) or discarded (we skip the block). At the end, this should
    # contain a flat list of strings as its single element.
    stack = [[]]
    pattern = "[^/]+"    # default range
    name = ""            # name of the current parameter
    bracketdepth = 0     # current level of nested brackets
    skip_to_depth = 0    # if > 1, skip until we're back at this bracket level
    state = S_PATH
    rangename = None     # range name for the current parameter
    seen_name = [False]  # have we seen a named param in bracket level (index)?

    for c in template_splitter.split(template):
        if state == S_PATH:
            if len(c) > 1:
                stack[-1].append(c)
            elif c == '[':
                bracketdepth += 1
                stack.append([])
                seen_name.append(False)
            elif c == ']':
                bracketdepth -= 1
                if bracketdepth < 0:
                    raise InvalidTemplateError("Mismatched brackets in %s" % template)
                last_elem = stack.pop()
                if seen_name.pop():
                    stack[-1].extend(last_elem)
                    seen_name[-1] = True
            elif c == '{':
                name = ""
                state = S_TEMPLATE
            elif c == '}':
                raise InvalidTemplateError("Mismatched braces in %s" % template)
            elif c == '|':
                pass
            else:
                stack[-1].append(c)
        elif state == S_SKIP:
            if c == '[':
                bracketdepth += 1
                seen_name.append(False)
            elif c == ']':
                if bracketdepth == skip_to_depth:
                    stack.pop()
                    skip_to_depth = 0
                    state = S_PATH
                bracketdepth -= 1
                seen_name.pop()
        else:  # state == S_TEMPLATE
            if c == '}':
                if name not in params:
                    if bracketdepth:
                        # We're missing a parameter, but it's ok since
                        # we're inside a '[]' block. Skip everything
                        # until we reach the end of the current block.
                        skip_to_depth = bracketdepth
                        state = S_SKIP
                    else:
                        raise InvalidArgumentError("Missing parameter '%s' in %s" % (name, template))
                else:
                    if rangename and rangename in ranges:
                        regex = ranges[rangename]
                    else:
                        regex = pattern
                    value_bytes = unicode(params[name]).encode('utf-8')
                    value = urllib.quote(value_bytes, safe='/:;')
                    if not re.match('^' + regex + '$', value):
                        raise InvalidArgumentError("Value '%s' for parameter '%s' does not match '^%s$' in %s" % (value, name, regex, template))
                    stack[-1].append(value)
                    state = S_PATH
                rangename = None
            else:
                name = c
                if name.find(":") > -1:
                    name, rangename = name.split(":")
                seen_name[bracketdepth] = True

    if bracketdepth != 0:
        raise InvalidTemplateError("Mismatched brackets in %s" % template)
    if state == S_TEMPLATE:
        raise InvalidTemplateError("Mismatched braces in %s" % template)
    # None of these Should Ever Happen [TM]
    if state == S_SKIP:     # pragma: no cover
        raise MapperException("Internal error: end state is S_SKIP")
    if len(stack) > 1:      # pragma: no cover
        raise MapperException("Internal error: stack not empty")
    if len(seen_name) != 1: # pragma: no cover
        raise MapperException("Internal error: seen_name not empty")

    return "".join(stack[0])