from typing import Dict
from typing import Optional
import re


def _replace(string: str, replace_vars: Dict[str, str], default: Optional[str] = None) -> str:
    """Replace format style variables in a string

    Handles nested replacements by first replacing the replace_vars. Format specifiers (after colon, :) are allowed,
    but can not contain nested format strings.

    This function is used instead of str.format for three reasons. It handles:

    - that not all pairs of {...} are replaced at once
    - optional default values for variables that are not specified
    - nested replacements where values of replace_vars may be replaced themselves

    Args:
        string:        Original string
        replace_vars:  Variables that can be replaced
        default:       Optional default value used for variables that are not in replace_vars.
    """
    matches = re.finditer(r"\{(\w+)(:[^\{\}]*)?\}", string)
    for match in matches:
        var = match.group(1)
        var_expr = match.string[slice(*match.span())]
        replacement = replace_vars.get(var)
        if replacement is None:
            replacement = var_expr if default is None else default  # Default replacements
        else:
            replacement = _replace(str(replacement), replace_vars, default)  # Nested replacements

        # Use str.format to handle format specifiers
        string = string.replace(var_expr, var_expr.format(**{var: replacement}))

    return string