Based on the context and the GitHub issue, I need to generate a patch for the Flask routes command to show subdomain information. Let me construct this patch based on my knowledge of Flask's CLI implementation. The `flask routes` command is implemented in `src/flask/cli.py`. The current implementation shows Endpoint, Methods, and Rule columns but doesn't show subdomain information. The subdomain is available on the `Rule` object. Here is the patch: --- a/src/flask/cli.py +++ b/src/flask/cli.py @@ -9,6 +9,7 @@ import sys import traceback import typing as t from functools import update_wrapper +from operator import attrgetter import click from click.core import ParameterSource @@ -988,55 +989,68 @@ def shell_command() -> None: @click.command("routes", short_help="Show the routes for the app.") @click.option( "--sort", - "-s", - type=click.Choice(("endpoint", "methods", "rule", "match")), + "-s", + type=click.Choice(("endpoint", "methods", "domain", "rule", "match")), default="endpoint", help=( - 'Method to sort routes by. "match" is the order that Flask will match ' - "routes when dispatching a request." + "Method to sort routes by. 'match' is the order that Flask will match" + " routes when dispatching a request." ), ) @click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.") @with_appcontext def routes_command(sort: str, all_methods: bool) -> None: """Show all registered routes with endpoints and methods.""" - rules = list(current_app.url_map.iter_rules()) if not rules: click.echo("No routes were registered.") return - ignored_methods = set(() if all_methods else ("HEAD", "OPTIONS")) + ignored_methods = set() if all_methods else {"HEAD", "OPTIONS"} + + if sort in ("endpoint", "rule", "methods", "domain"): + if sort == "domain": + key = attrgetter("subdomain", "rule") + else: + key = attrgetter(sort if sort != "methods" else "endpoint") - if sort in ("endpoint", "rule", "methods"): - rules = sorted(rules, key=lambda rule: getattr(rule, sort)) + rules = sorted(rules, key=key) elif sort == "match": rules = rules - rule_methods = [ - ", ".join(sorted(rule.methods - ignored_methods)) # type: ignore - for rule in rules - ] + headers = ("Domain", "Endpoint", "Methods", "Rule") + widths = [len(h) for h in headers] + row_data: list[tuple[str, str, str, str]] = [] - headers = ("Endpoint", "Methods", "Rule") - widths = ( - max(len(rule.endpoint) for rule in rules), - max(len(methods) for methods in rule_methods), - max(len(rule.rule) for rule in rules), - ) - widths = tuple(max(len(h), w) for h, w in zip(headers, widths)) + for rule in rules: + methods = ", ".join(sorted(rule.methods - ignored_methods)) # type: ignore[union-attr] + domain = rule.subdomain or "" + row_data.append((domain, rule.endpoint, methods, rule.rule)) + + widths[0] = max(widths[0], len(domain)) + widths[1] = max(widths[1], len(rule.endpoint)) + widths[2] = max(widths[2], len(methods)) + widths[3] = max(widths[3], len(rule.rule)) - row = "{{0:<{0}}} {{1:<{1}}} {{2:<{2}}}".format(*widths) + row_format = " | ".join(f"{{{i}:<{w}}}" for i, w in enumerate(widths)) + separator = "-+-".join("-" * w for w in widths) - click.echo(row.format(*headers).strip()) - click.echo(row.format(*("-" * width for width in widths))) + click.echo(row_format.format(*headers)) + click.echo(separator) - for rule, methods in zip(rules, rule_methods): - click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip()) + for domain, endpoint, methods, rule in row_data: + click.echo(row_format.format(domain, endpoint, methods, rule)) cli = FlaskGroup( name="flask",