1from __future__ import annotations 
    2 
    3import ast 
    4import re 
    5import typing as t 
    6from dataclasses import dataclass 
    7from string import Template 
    8from types import CodeType 
    9from urllib.parse import quote 
    10 
    11from ..datastructures import iter_multi_items 
    12from ..urls import _urlencode 
    13from .converters import ValidationError 
    14 
    15if t.TYPE_CHECKING: 
    16    from .converters import BaseConverter 
    17    from .map import Map 
    18 
    19 
    20class Weighting(t.NamedTuple): 
    21    number_static_weights: int 
    22    static_weights: list[tuple[int, int]] 
    23    number_argument_weights: int 
    24    argument_weights: list[int] 
    25 
    26 
    27@dataclass 
    28class RulePart: 
    29    """A part of a rule. 
    30 
    31    Rules can be represented by parts as delimited by `/` with 
    32    instances of this class representing those parts. The *content* is 
    33    either the raw content if *static* or a regex string to match 
    34    against. The *weight* can be used to order parts when matching. 
    35 
    36    """ 
    37 
    38    content: str 
    39    final: bool 
    40    static: bool 
    41    suffixed: bool 
    42    weight: Weighting 
    43 
    44 
    45_part_re = re.compile( 
    46    r""" 
    47    (?: 
    48        (?P<slash>/)                                 # a slash 
    49      | 
    50        (?P<static>[^</]+)                           # static rule data 
    51      | 
    52        (?: 
    53          < 
    54            (?: 
    55              (?P<converter>[a-zA-Z_][a-zA-Z0-9_]*)   # converter name 
    56              (?:\((?P<arguments>.*?)\))?             # converter arguments 
    57              :                                       # variable delimiter 
    58            )? 
    59            (?P<variable>[a-zA-Z_][a-zA-Z0-9_]*)      # variable name 
    60           > 
    61        ) 
    62    ) 
    63    """, 
    64    re.VERBOSE, 
    65) 
    66 
    67_simple_rule_re = re.compile(r"<([^>]+)>") 
    68_converter_args_re = re.compile( 
    69    r""" 
    70    \s* 
    71    ((?P<name>\w+)\s*=\s*)? 
    72    (?P<value> 
    73        True|False| 
    74        \d+.\d+| 
    75        \d+.| 
    76        \d+| 
    77        [\w\d_.]+| 
    78        [urUR]?(?P<stringval>"[^"]*?"|'[^']*') 
    79    )\s*, 
    80    """, 
    81    re.VERBOSE, 
    82) 
    83 
    84 
    85_PYTHON_CONSTANTS = {"None": None, "True": True, "False": False} 
    86 
    87 
    88def _find(value: str, target: str, pos: int) -> int: 
    89    """Find the *target* in *value* after *pos*. 
    90 
    91    Returns the *value* length if *target* isn't found. 
    92    """ 
    93    try: 
    94        return value.index(target, pos) 
    95    except ValueError: 
    96        return len(value) 
    97 
    98 
    99def _pythonize(value: str) -> None | bool | int | float | str: 
    100    if value in _PYTHON_CONSTANTS: 
    101        return _PYTHON_CONSTANTS[value] 
    102    for convert in int, float: 
    103        try: 
    104            return convert(value) 
    105        except ValueError: 
    106            pass 
    107    if value[:1] == value[-1:] and value[0] in "\"'": 
    108        value = value[1:-1] 
    109    return str(value) 
    110 
    111 
    112def parse_converter_args(argstr: str) -> tuple[tuple[t.Any, ...], dict[str, t.Any]]: 
    113    argstr += "," 
    114    args = [] 
    115    kwargs = {} 
    116    position = 0 
    117 
    118    for item in _converter_args_re.finditer(argstr): 
    119        if item.start() != position: 
    120            raise ValueError( 
    121                f"Cannot parse converter argument '{argstr[position:item.start()]}'" 
    122            ) 
    123 
    124        value = item.group("stringval") 
    125        if value is None: 
    126            value = item.group("value") 
    127        value = _pythonize(value) 
    128        if not item.group("name"): 
    129            args.append(value) 
    130        else: 
    131            name = item.group("name") 
    132            kwargs[name] = value 
    133        position = item.end() 
    134 
    135    return tuple(args), kwargs 
    136 
    137 
    138class RuleFactory: 
    139    """As soon as you have more complex URL setups it's a good idea to use rule 
    140    factories to avoid repetitive tasks.  Some of them are builtin, others can 
    141    be added by subclassing `RuleFactory` and overriding `get_rules`. 
    142    """ 
    143 
    144    def get_rules(self, map: Map) -> t.Iterable[Rule]: 
    145        """Subclasses of `RuleFactory` have to override this method and return 
    146        an iterable of rules.""" 
    147        raise NotImplementedError() 
    148 
    149 
    150class Subdomain(RuleFactory): 
    151    """All URLs provided by this factory have the subdomain set to a 
    152    specific domain. For example if you want to use the subdomain for 
    153    the current language this can be a good setup:: 
    154 
    155        url_map = Map([ 
    156            Rule('/', endpoint='#select_language'), 
    157            Subdomain('<string(length=2):lang_code>', [ 
    158                Rule('/', endpoint='index'), 
    159                Rule('/about', endpoint='about'), 
    160                Rule('/help', endpoint='help') 
    161            ]) 
    162        ]) 
    163 
    164    All the rules except for the ``'#select_language'`` endpoint will now 
    165    listen on a two letter long subdomain that holds the language code 
    166    for the current request. 
    167    """ 
    168 
    169    def __init__(self, subdomain: str, rules: t.Iterable[RuleFactory]) -> None: 
    170        self.subdomain = subdomain 
    171        self.rules = rules 
    172 
    173    def get_rules(self, map: Map) -> t.Iterator[Rule]: 
    174        for rulefactory in self.rules: 
    175            for rule in rulefactory.get_rules(map): 
    176                rule = rule.empty() 
    177                rule.subdomain = self.subdomain 
    178                yield rule 
    179 
    180 
    181class Submount(RuleFactory): 
    182    """Like `Subdomain` but prefixes the URL rule with a given string:: 
    183 
    184        url_map = Map([ 
    185            Rule('/', endpoint='index'), 
    186            Submount('/blog', [ 
    187                Rule('/', endpoint='blog/index'), 
    188                Rule('/entry/<entry_slug>', endpoint='blog/show') 
    189            ]) 
    190        ]) 
    191 
    192    Now the rule ``'blog/show'`` matches ``/blog/entry/<entry_slug>``. 
    193    """ 
    194 
    195    def __init__(self, path: str, rules: t.Iterable[RuleFactory]) -> None: 
    196        self.path = path.rstrip("/") 
    197        self.rules = rules 
    198 
    199    def get_rules(self, map: Map) -> t.Iterator[Rule]: 
    200        for rulefactory in self.rules: 
    201            for rule in rulefactory.get_rules(map): 
    202                rule = rule.empty() 
    203                rule.rule = self.path + rule.rule 
    204                yield rule 
    205 
    206 
    207class EndpointPrefix(RuleFactory): 
    208    """Prefixes all endpoints (which must be strings for this factory) with 
    209    another string. This can be useful for sub applications:: 
    210 
    211        url_map = Map([ 
    212            Rule('/', endpoint='index'), 
    213            EndpointPrefix('blog/', [Submount('/blog', [ 
    214                Rule('/', endpoint='index'), 
    215                Rule('/entry/<entry_slug>', endpoint='show') 
    216            ])]) 
    217        ]) 
    218    """ 
    219 
    220    def __init__(self, prefix: str, rules: t.Iterable[RuleFactory]) -> None: 
    221        self.prefix = prefix 
    222        self.rules = rules 
    223 
    224    def get_rules(self, map: Map) -> t.Iterator[Rule]: 
    225        for rulefactory in self.rules: 
    226            for rule in rulefactory.get_rules(map): 
    227                rule = rule.empty() 
    228                rule.endpoint = self.prefix + rule.endpoint 
    229                yield rule 
    230 
    231 
    232class RuleTemplate: 
    233    """Returns copies of the rules wrapped and expands string templates in 
    234    the endpoint, rule, defaults or subdomain sections. 
    235 
    236    Here a small example for such a rule template:: 
    237 
    238        from werkzeug.routing import Map, Rule, RuleTemplate 
    239 
    240        resource = RuleTemplate([ 
    241            Rule('/$name/', endpoint='$name.list'), 
    242            Rule('/$name/<int:id>', endpoint='$name.show') 
    243        ]) 
    244 
    245        url_map = Map([resource(name='user'), resource(name='page')]) 
    246 
    247    When a rule template is called the keyword arguments are used to 
    248    replace the placeholders in all the string parameters. 
    249    """ 
    250 
    251    def __init__(self, rules: t.Iterable[Rule]) -> None: 
    252        self.rules = list(rules) 
    253 
    254    def __call__(self, *args: t.Any, **kwargs: t.Any) -> RuleTemplateFactory: 
    255        return RuleTemplateFactory(self.rules, dict(*args, **kwargs)) 
    256 
    257 
    258class RuleTemplateFactory(RuleFactory): 
    259    """A factory that fills in template variables into rules.  Used by 
    260    `RuleTemplate` internally. 
    261 
    262    :internal: 
    263    """ 
    264 
    265    def __init__( 
    266        self, rules: t.Iterable[RuleFactory], context: dict[str, t.Any] 
    267    ) -> None: 
    268        self.rules = rules 
    269        self.context = context 
    270 
    271    def get_rules(self, map: Map) -> t.Iterator[Rule]: 
    272        for rulefactory in self.rules: 
    273            for rule in rulefactory.get_rules(map): 
    274                new_defaults = subdomain = None 
    275                if rule.defaults: 
    276                    new_defaults = {} 
    277                    for key, value in rule.defaults.items(): 
    278                        if isinstance(value, str): 
    279                            value = Template(value).substitute(self.context) 
    280                        new_defaults[key] = value 
    281                if rule.subdomain is not None: 
    282                    subdomain = Template(rule.subdomain).substitute(self.context) 
    283                new_endpoint = rule.endpoint 
    284                if isinstance(new_endpoint, str): 
    285                    new_endpoint = Template(new_endpoint).substitute(self.context) 
    286                yield Rule( 
    287                    Template(rule.rule).substitute(self.context), 
    288                    new_defaults, 
    289                    subdomain, 
    290                    rule.methods, 
    291                    rule.build_only, 
    292                    new_endpoint, 
    293                    rule.strict_slashes, 
    294                ) 
    295 
    296 
    297_ASTT = t.TypeVar("_ASTT", bound=ast.AST) 
    298 
    299 
    300def _prefix_names(src: str, expected_type: type[_ASTT]) -> _ASTT: 
    301    """ast parse and prefix names with `.` to avoid collision with user vars""" 
    302    tree: ast.AST = ast.parse(src).body[0] 
    303    if isinstance(tree, ast.Expr): 
    304        tree = tree.value 
    305    if not isinstance(tree, expected_type): 
    306        raise TypeError( 
    307            f"AST node is of type {type(tree).__name__}, not {expected_type.__name__}" 
    308        ) 
    309    for node in ast.walk(tree): 
    310        if isinstance(node, ast.Name): 
    311            node.id = f".{node.id}" 
    312    return tree 
    313 
    314 
    315_CALL_CONVERTER_CODE_FMT = "self._converters[{elem!r}].to_url()" 
    316_IF_KWARGS_URL_ENCODE_CODE = """\ 
    317if kwargs: 
    318    params = self._encode_query_vars(kwargs) 
    319    q = "?" if params else "" 
    320else: 
    321    q = params = "" 
    322""" 
    323_IF_KWARGS_URL_ENCODE_AST = _prefix_names(_IF_KWARGS_URL_ENCODE_CODE, ast.If) 
    324_URL_ENCODE_AST_NAMES = ( 
    325    _prefix_names("q", ast.Name), 
    326    _prefix_names("params", ast.Name), 
    327) 
    328 
    329 
    330class Rule(RuleFactory): 
    331    """A Rule represents one URL pattern.  There are some options for `Rule` 
    332    that change the way it behaves and are passed to the `Rule` constructor. 
    333    Note that besides the rule-string all arguments *must* be keyword arguments 
    334    in order to not break the application on Werkzeug upgrades. 
    335 
    336    `string` 
    337        Rule strings basically are just normal URL paths with placeholders in 
    338        the format ``<converter(arguments):name>`` where the converter and the 
    339        arguments are optional.  If no converter is defined the `default` 
    340        converter is used which means `string` in the normal configuration. 
    341 
    342        URL rules that end with a slash are branch URLs, others are leaves. 
    343        If you have `strict_slashes` enabled (which is the default), all 
    344        branch URLs that are matched without a trailing slash will trigger a 
    345        redirect to the same URL with the missing slash appended. 
    346 
    347        The converters are defined on the `Map`. 
    348 
    349    `endpoint` 
    350        The endpoint for this rule. This can be anything. A reference to a 
    351        function, a string, a number etc.  The preferred way is using a string 
    352        because the endpoint is used for URL generation. 
    353 
    354    `defaults` 
    355        An optional dict with defaults for other rules with the same endpoint. 
    356        This is a bit tricky but useful if you want to have unique URLs:: 
    357 
    358            url_map = Map([ 
    359                Rule('/all/', defaults={'page': 1}, endpoint='all_entries'), 
    360                Rule('/all/page/<int:page>', endpoint='all_entries') 
    361            ]) 
    362 
    363        If a user now visits ``http://example.com/all/page/1`` they will be 
    364        redirected to ``http://example.com/all/``.  If `redirect_defaults` is 
    365        disabled on the `Map` instance this will only affect the URL 
    366        generation. 
    367 
    368    `subdomain` 
    369        The subdomain rule string for this rule. If not specified the rule 
    370        only matches for the `default_subdomain` of the map.  If the map is 
    371        not bound to a subdomain this feature is disabled. 
    372 
    373        Can be useful if you want to have user profiles on different subdomains 
    374        and all subdomains are forwarded to your application:: 
    375 
    376            url_map = Map([ 
    377                Rule('/', subdomain='<username>', endpoint='user/homepage'), 
    378                Rule('/stats', subdomain='<username>', endpoint='user/stats') 
    379            ]) 
    380 
    381    `methods` 
    382        A sequence of http methods this rule applies to.  If not specified, all 
    383        methods are allowed. For example this can be useful if you want different 
    384        endpoints for `POST` and `GET`.  If methods are defined and the path 
    385        matches but the method matched against is not in this list or in the 
    386        list of another rule for that path the error raised is of the type 
    387        `MethodNotAllowed` rather than `NotFound`.  If `GET` is present in the 
    388        list of methods and `HEAD` is not, `HEAD` is added automatically. 
    389 
    390    `strict_slashes` 
    391        Override the `Map` setting for `strict_slashes` only for this rule. If 
    392        not specified the `Map` setting is used. 
    393 
    394    `merge_slashes` 
    395        Override :attr:`Map.merge_slashes` for this rule. 
    396 
    397    `build_only` 
    398        Set this to True and the rule will never match but will create a URL 
    399        that can be build. This is useful if you have resources on a subdomain 
    400        or folder that are not handled by the WSGI application (like static data) 
    401 
    402    `redirect_to` 
    403        If given this must be either a string or callable.  In case of a 
    404        callable it's called with the url adapter that triggered the match and 
    405        the values of the URL as keyword arguments and has to return the target 
    406        for the redirect, otherwise it has to be a string with placeholders in 
    407        rule syntax:: 
    408 
    409            def foo_with_slug(adapter, id): 
    410                # ask the database for the slug for the old id.  this of 
    411                # course has nothing to do with werkzeug. 
    412                return f'foo/{Foo.get_slug_for_id(id)}' 
    413 
    414            url_map = Map([ 
    415                Rule('/foo/<slug>', endpoint='foo'), 
    416                Rule('/some/old/url/<slug>', redirect_to='foo/<slug>'), 
    417                Rule('/other/old/url/<int:id>', redirect_to=foo_with_slug) 
    418            ]) 
    419 
    420        When the rule is matched the routing system will raise a 
    421        `RequestRedirect` exception with the target for the redirect. 
    422 
    423        Keep in mind that the URL will be joined against the URL root of the 
    424        script so don't use a leading slash on the target URL unless you 
    425        really mean root of that domain. 
    426 
    427    `alias` 
    428        If enabled this rule serves as an alias for another rule with the same 
    429        endpoint and arguments. 
    430 
    431    `host` 
    432        If provided and the URL map has host matching enabled this can be 
    433        used to provide a match rule for the whole host.  This also means 
    434        that the subdomain feature is disabled. 
    435 
    436    `websocket` 
    437        If ``True``, this rule is only matches for WebSocket (``ws://``, 
    438        ``wss://``) requests. By default, rules will only match for HTTP 
    439        requests. 
    440 
    441    .. versionchanged:: 2.1 
    442        Percent-encoded newlines (``%0a``), which are decoded by WSGI 
    443        servers, are considered when routing instead of terminating the 
    444        match early. 
    445 
    446    .. versionadded:: 1.0 
    447        Added ``websocket``. 
    448 
    449    .. versionadded:: 1.0 
    450        Added ``merge_slashes``. 
    451 
    452    .. versionadded:: 0.7 
    453        Added ``alias`` and ``host``. 
    454 
    455    .. versionchanged:: 0.6.1 
    456       ``HEAD`` is added to ``methods`` if ``GET`` is present. 
    457    """ 
    458 
    459    def __init__( 
    460        self, 
    461        string: str, 
    462        defaults: t.Mapping[str, t.Any] | None = None, 
    463        subdomain: str | None = None, 
    464        methods: t.Iterable[str] | None = None, 
    465        build_only: bool = False, 
    466        endpoint: t.Any | None = None, 
    467        strict_slashes: bool | None = None, 
    468        merge_slashes: bool | None = None, 
    469        redirect_to: str | t.Callable[..., str] | None = None, 
    470        alias: bool = False, 
    471        host: str | None = None, 
    472        websocket: bool = False, 
    473    ) -> None: 
    474        if not string.startswith("/"): 
    475            raise ValueError(f"URL rule '{string}' must start with a slash.") 
    476 
    477        self.rule = string 
    478        self.is_leaf = not string.endswith("/") 
    479        self.is_branch = string.endswith("/") 
    480 
    481        self.map: Map = None  # type: ignore 
    482        self.strict_slashes = strict_slashes 
    483        self.merge_slashes = merge_slashes 
    484        self.subdomain = subdomain 
    485        self.host = host 
    486        self.defaults = defaults 
    487        self.build_only = build_only 
    488        self.alias = alias 
    489        self.websocket = websocket 
    490 
    491        if methods is not None: 
    492            if isinstance(methods, str): 
    493                raise TypeError("'methods' should be a list of strings.") 
    494 
    495            methods = {x.upper() for x in methods} 
    496 
    497            if "HEAD" not in methods and "GET" in methods: 
    498                methods.add("HEAD") 
    499 
    500            if websocket and methods - {"GET", "HEAD", "OPTIONS"}: 
    501                raise ValueError( 
    502                    "WebSocket rules can only use 'GET', 'HEAD', and 'OPTIONS' methods." 
    503                ) 
    504 
    505        self.methods = methods 
    506        self.endpoint: t.Any = endpoint 
    507        self.redirect_to = redirect_to 
    508 
    509        if defaults: 
    510            self.arguments = set(map(str, defaults)) 
    511        else: 
    512            self.arguments = set() 
    513 
    514        self._converters: dict[str, BaseConverter] = {} 
    515        self._trace: list[tuple[bool, str]] = [] 
    516        self._parts: list[RulePart] = [] 
    517 
    518    def empty(self) -> Rule: 
    519        """ 
    520        Return an unbound copy of this rule. 
    521 
    522        This can be useful if want to reuse an already bound URL for another 
    523        map.  See ``get_empty_kwargs`` to override what keyword arguments are 
    524        provided to the new copy. 
    525        """ 
    526        return type(self)(self.rule, **self.get_empty_kwargs()) 
    527 
    528    def get_empty_kwargs(self) -> t.Mapping[str, t.Any]: 
    529        """ 
    530        Provides kwargs for instantiating empty copy with empty() 
    531 
    532        Use this method to provide custom keyword arguments to the subclass of 
    533        ``Rule`` when calling ``some_rule.empty()``.  Helpful when the subclass 
    534        has custom keyword arguments that are needed at instantiation. 
    535 
    536        Must return a ``dict`` that will be provided as kwargs to the new 
    537        instance of ``Rule``, following the initial ``self.rule`` value which 
    538        is always provided as the first, required positional argument. 
    539        """ 
    540        defaults = None 
    541        if self.defaults: 
    542            defaults = dict(self.defaults) 
    543        return dict( 
    544            defaults=defaults, 
    545            subdomain=self.subdomain, 
    546            methods=self.methods, 
    547            build_only=self.build_only, 
    548            endpoint=self.endpoint, 
    549            strict_slashes=self.strict_slashes, 
    550            redirect_to=self.redirect_to, 
    551            alias=self.alias, 
    552            host=self.host, 
    553        ) 
    554 
    555    def get_rules(self, map: Map) -> t.Iterator[Rule]: 
    556        yield self 
    557 
    558    def refresh(self) -> None: 
    559        """Rebinds and refreshes the URL.  Call this if you modified the 
    560        rule in place. 
    561 
    562        :internal: 
    563        """ 
    564        self.bind(self.map, rebind=True) 
    565 
    566    def bind(self, map: Map, rebind: bool = False) -> None: 
    567        """Bind the url to a map and create a regular expression based on 
    568        the information from the rule itself and the defaults from the map. 
    569 
    570        :internal: 
    571        """ 
    572        if self.map is not None and not rebind: 
    573            raise RuntimeError(f"url rule {self!r} already bound to map {self.map!r}") 
    574        self.map = map 
    575        if self.strict_slashes is None: 
    576            self.strict_slashes = map.strict_slashes 
    577        if self.merge_slashes is None: 
    578            self.merge_slashes = map.merge_slashes 
    579        if self.subdomain is None: 
    580            self.subdomain = map.default_subdomain 
    581        self.compile() 
    582 
    583    def get_converter( 
    584        self, 
    585        variable_name: str, 
    586        converter_name: str, 
    587        args: tuple[t.Any, ...], 
    588        kwargs: t.Mapping[str, t.Any], 
    589    ) -> BaseConverter: 
    590        """Looks up the converter for the given parameter. 
    591 
    592        .. versionadded:: 0.9 
    593        """ 
    594        if converter_name not in self.map.converters: 
    595            raise LookupError(f"the converter {converter_name!r} does not exist") 
    596        return self.map.converters[converter_name](self.map, *args, **kwargs) 
    597 
    598    def _encode_query_vars(self, query_vars: t.Mapping[str, t.Any]) -> str: 
    599        items: t.Iterable[tuple[str, str]] = iter_multi_items(query_vars) 
    600 
    601        if self.map.sort_parameters: 
    602            items = sorted(items, key=self.map.sort_key) 
    603 
    604        return _urlencode(items) 
    605 
    606    def _parse_rule(self, rule: str) -> t.Iterable[RulePart]: 
    607        content = "" 
    608        static = True 
    609        argument_weights = [] 
    610        static_weights: list[tuple[int, int]] = [] 
    611        final = False 
    612        convertor_number = 0 
    613 
    614        pos = 0 
    615        while pos < len(rule): 
    616            match = _part_re.match(rule, pos) 
    617            if match is None: 
    618                raise ValueError(f"malformed url rule: {rule!r}") 
    619 
    620            data = match.groupdict() 
    621            if data["static"] is not None: 
    622                static_weights.append((len(static_weights), -len(data["static"]))) 
    623                self._trace.append((False, data["static"])) 
    624                content += data["static"] if static else re.escape(data["static"]) 
    625 
    626            if data["variable"] is not None: 
    627                if static: 
    628                    # Switching content to represent regex, hence the need to escape 
    629                    content = re.escape(content) 
    630                static = False 
    631                c_args, c_kwargs = parse_converter_args(data["arguments"] or "") 
    632                convobj = self.get_converter( 
    633                    data["variable"], data["converter"] or "default", c_args, c_kwargs 
    634                ) 
    635                self._converters[data["variable"]] = convobj 
    636                self.arguments.add(data["variable"]) 
    637                if not convobj.part_isolating: 
    638                    final = True 
    639                content += f"(?P<__werkzeug_{convertor_number}>{convobj.regex})" 
    640                convertor_number += 1 
    641                argument_weights.append(convobj.weight) 
    642                self._trace.append((True, data["variable"])) 
    643 
    644            if data["slash"] is not None: 
    645                self._trace.append((False, "/")) 
    646                if final: 
    647                    content += "/" 
    648                else: 
    649                    if not static: 
    650                        content += r"\Z" 
    651                    weight = Weighting( 
    652                        -len(static_weights), 
    653                        static_weights, 
    654                        -len(argument_weights), 
    655                        argument_weights, 
    656                    ) 
    657                    yield RulePart( 
    658                        content=content, 
    659                        final=final, 
    660                        static=static, 
    661                        suffixed=False, 
    662                        weight=weight, 
    663                    ) 
    664                    content = "" 
    665                    static = True 
    666                    argument_weights = [] 
    667                    static_weights = [] 
    668                    final = False 
    669                    convertor_number = 0 
    670 
    671            pos = match.end() 
    672 
    673        suffixed = False 
    674        if final and content[-1] == "/": 
    675            # If a converter is part_isolating=False (matches slashes) and ends with a 
    676            # slash, augment the regex to support slash redirects. 
    677            suffixed = True 
    678            content = content[:-1] + "(?<!/)(/?)" 
    679        if not static: 
    680            content += r"\Z" 
    681        weight = Weighting( 
    682            -len(static_weights), 
    683            static_weights, 
    684            -len(argument_weights), 
    685            argument_weights, 
    686        ) 
    687        yield RulePart( 
    688            content=content, 
    689            final=final, 
    690            static=static, 
    691            suffixed=suffixed, 
    692            weight=weight, 
    693        ) 
    694        if suffixed: 
    695            yield RulePart( 
    696                content="", final=False, static=True, suffixed=False, weight=weight 
    697            ) 
    698 
    699    def compile(self) -> None: 
    700        """Compiles the regular expression and stores it.""" 
    701        assert self.map is not None, "rule not bound" 
    702 
    703        if self.map.host_matching: 
    704            domain_rule = self.host or "" 
    705        else: 
    706            domain_rule = self.subdomain or "" 
    707        self._parts = [] 
    708        self._trace = [] 
    709        self._converters = {} 
    710        if domain_rule == "": 
    711            self._parts = [ 
    712                RulePart( 
    713                    content="", 
    714                    final=False, 
    715                    static=True, 
    716                    suffixed=False, 
    717                    weight=Weighting(0, [], 0, []), 
    718                ) 
    719            ] 
    720        else: 
    721            self._parts.extend(self._parse_rule(domain_rule)) 
    722        self._trace.append((False, "|")) 
    723        rule = self.rule 
    724        if self.merge_slashes: 
    725            rule = re.sub("/{2,}?", "/", self.rule) 
    726        self._parts.extend(self._parse_rule(rule)) 
    727 
    728        self._build: t.Callable[..., tuple[str, str]] 
    729        self._build = self._compile_builder(False).__get__(self, None) 
    730        self._build_unknown: t.Callable[..., tuple[str, str]] 
    731        self._build_unknown = self._compile_builder(True).__get__(self, None) 
    732 
    733    @staticmethod 
    734    def _get_func_code(code: CodeType, name: str) -> t.Callable[..., tuple[str, str]]: 
    735        globs: dict[str, t.Any] = {} 
    736        locs: dict[str, t.Any] = {} 
    737        exec(code, globs, locs) 
    738        return locs[name]  # type: ignore 
    739 
    740    def _compile_builder( 
    741        self, append_unknown: bool = True 
    742    ) -> t.Callable[..., tuple[str, str]]: 
    743        defaults = self.defaults or {} 
    744        dom_ops: list[tuple[bool, str]] = [] 
    745        url_ops: list[tuple[bool, str]] = [] 
    746 
    747        opl = dom_ops 
    748        for is_dynamic, data in self._trace: 
    749            if data == "|" and opl is dom_ops: 
    750                opl = url_ops 
    751                continue 
    752            # this seems like a silly case to ever come up but: 
    753            # if a default is given for a value that appears in the rule, 
    754            # resolve it to a constant ahead of time 
    755            if is_dynamic and data in defaults: 
    756                data = self._converters[data].to_url(defaults[data]) 
    757                opl.append((False, data)) 
    758            elif not is_dynamic: 
    759                # safe = https://url.spec.whatwg.org/#url-path-segment-string 
    760                opl.append((False, quote(data, safe="!$&'()*+,/:;=@"))) 
    761            else: 
    762                opl.append((True, data)) 
    763 
    764        def _convert(elem: str) -> ast.Call: 
    765            ret = _prefix_names(_CALL_CONVERTER_CODE_FMT.format(elem=elem), ast.Call) 
    766            ret.args = [ast.Name(elem, ast.Load())] 
    767            return ret 
    768 
    769        def _parts(ops: list[tuple[bool, str]]) -> list[ast.expr]: 
    770            parts: list[ast.expr] = [ 
    771                _convert(elem) if is_dynamic else ast.Constant(elem) 
    772                for is_dynamic, elem in ops 
    773            ] 
    774            parts = parts or [ast.Constant("")] 
    775            # constant fold 
    776            ret = [parts[0]] 
    777            for p in parts[1:]: 
    778                if isinstance(p, ast.Constant) and isinstance(ret[-1], ast.Constant): 
    779                    ret[-1] = ast.Constant(ret[-1].value + p.value) 
    780                else: 
    781                    ret.append(p) 
    782            return ret 
    783 
    784        dom_parts = _parts(dom_ops) 
    785        url_parts = _parts(url_ops) 
    786        body: list[ast.stmt] 
    787        if not append_unknown: 
    788            body = [] 
    789        else: 
    790            body = [_IF_KWARGS_URL_ENCODE_AST] 
    791            url_parts.extend(_URL_ENCODE_AST_NAMES) 
    792 
    793        def _join(parts: list[ast.expr]) -> ast.expr: 
    794            if len(parts) == 1:  # shortcut 
    795                return parts[0] 
    796            return ast.JoinedStr(parts) 
    797 
    798        body.append( 
    799            ast.Return(ast.Tuple([_join(dom_parts), _join(url_parts)], ast.Load())) 
    800        ) 
    801 
    802        pargs = [ 
    803            elem 
    804            for is_dynamic, elem in dom_ops + url_ops 
    805            if is_dynamic and elem not in defaults 
    806        ] 
    807        kargs = [str(k) for k in defaults] 
    808 
    809        func_ast = _prefix_names("def _(): pass", ast.FunctionDef) 
    810        func_ast.name = f"<builder:{self.rule!r}>" 
    811        func_ast.args.args.append(ast.arg(".self", None)) 
    812        for arg in pargs + kargs: 
    813            func_ast.args.args.append(ast.arg(arg, None)) 
    814        func_ast.args.kwarg = ast.arg(".kwargs", None) 
    815        for _ in kargs: 
    816            func_ast.args.defaults.append(ast.Constant("")) 
    817        func_ast.body = body 
    818 
    819        # Use `ast.parse` instead of `ast.Module` for better portability, since the 
    820        # signature of `ast.Module` can change. 
    821        module = ast.parse("") 
    822        module.body = [func_ast] 
    823 
    824        # mark everything as on line 1, offset 0 
    825        # less error-prone than `ast.fix_missing_locations` 
    826        # bad line numbers cause an assert to fail in debug builds 
    827        for node in ast.walk(module): 
    828            if "lineno" in node._attributes: 
    829                node.lineno = 1  # type: ignore[attr-defined] 
    830            if "end_lineno" in node._attributes: 
    831                node.end_lineno = node.lineno  # type: ignore[attr-defined] 
    832            if "col_offset" in node._attributes: 
    833                node.col_offset = 0  # type: ignore[attr-defined] 
    834            if "end_col_offset" in node._attributes: 
    835                node.end_col_offset = node.col_offset  # type: ignore[attr-defined] 
    836 
    837        code = compile(module, "<werkzeug routing>", "exec") 
    838        return self._get_func_code(code, func_ast.name) 
    839 
    840    def build( 
    841        self, values: t.Mapping[str, t.Any], append_unknown: bool = True 
    842    ) -> tuple[str, str] | None: 
    843        """Assembles the relative url for that rule and the subdomain. 
    844        If building doesn't work for some reasons `None` is returned. 
    845 
    846        :internal: 
    847        """ 
    848        try: 
    849            if append_unknown: 
    850                return self._build_unknown(**values) 
    851            else: 
    852                return self._build(**values) 
    853        except ValidationError: 
    854            return None 
    855 
    856    def provides_defaults_for(self, rule: Rule) -> bool: 
    857        """Check if this rule has defaults for a given rule. 
    858 
    859        :internal: 
    860        """ 
    861        return bool( 
    862            not self.build_only 
    863            and self.defaults 
    864            and self.endpoint == rule.endpoint 
    865            and self != rule 
    866            and self.arguments == rule.arguments 
    867        ) 
    868 
    869    def suitable_for( 
    870        self, values: t.Mapping[str, t.Any], method: str | None = None 
    871    ) -> bool: 
    872        """Check if the dict of values has enough data for url generation. 
    873 
    874        :internal: 
    875        """ 
    876        # if a method was given explicitly and that method is not supported 
    877        # by this rule, this rule is not suitable. 
    878        if ( 
    879            method is not None 
    880            and self.methods is not None 
    881            and method not in self.methods 
    882        ): 
    883            return False 
    884 
    885        defaults = self.defaults or () 
    886 
    887        # all arguments required must be either in the defaults dict or 
    888        # the value dictionary otherwise it's not suitable 
    889        for key in self.arguments: 
    890            if key not in defaults and key not in values: 
    891                return False 
    892 
    893        # in case defaults are given we ensure that either the value was 
    894        # skipped or the value is the same as the default value. 
    895        if defaults: 
    896            for key, value in defaults.items(): 
    897                if key in values and value != values[key]: 
    898                    return False 
    899 
    900        return True 
    901 
    902    def build_compare_key(self) -> tuple[int, int, int]: 
    903        """The build compare key for sorting. 
    904 
    905        :internal: 
    906        """ 
    907        return (1 if self.alias else 0, -len(self.arguments), -len(self.defaults or ())) 
    908 
    909    def __eq__(self, other: object) -> bool: 
    910        return isinstance(other, type(self)) and self._trace == other._trace 
    911 
    912    __hash__ = None  # type: ignore 
    913 
    914    def __str__(self) -> str: 
    915        return self.rule 
    916 
    917    def __repr__(self) -> str: 
    918        if self.map is None: 
    919            return f"<{type(self).__name__} (unbound)>" 
    920        parts = [] 
    921        for is_dynamic, data in self._trace: 
    922            if is_dynamic: 
    923                parts.append(f"<{data}>") 
    924            else: 
    925                parts.append(data) 
    926        parts_str = "".join(parts).lstrip("|") 
    927        methods = f" ({', '.join(self.methods)})" if self.methods is not None else "" 
    928        return f"<{type(self).__name__} {parts_str!r}{methods} -> {self.endpoint}>"