diff --git a/repologyapp/__init__.py b/repologyapp/__init__.py index 4e10a2af..bccdd91a 100644 --- a/repologyapp/__init__.py +++ b/repologyapp/__init__.py @@ -25,7 +25,8 @@ from repologyapp.config import config from repologyapp.globals import repometadata from repologyapp.template_filters import css_for_versionclass, extract_netloc, maintainer_to_links, maintainers_to_group_mailto -from repologyapp.template_functions import endpoint_like, needs_ipv6_notice, url_for, url_for_self, url_for_static +from repologyapp.template_functions import needs_ipv6_notice, url_for, url_for_self, url_for_static +from repologyapp.template_functions_extra import current_endpoint_group_in from repologyapp.template_tests import for_page, has_flag, has_flag_at, is_fallback_maintainer from repologyapp.views import registry as view_registry @@ -60,6 +61,7 @@ app.jinja_env.tests['has_flag_at'] = has_flag_at # templates: custom global functions +app.jinja_env.globals['current_endpoint_group_in'] = current_endpoint_group_in app.jinja_env.globals['url_for'] = url_for app.jinja_env.globals['url_for_self'] = url_for_self app.jinja_env.globals['url_for_static'] = url_for_static @@ -73,6 +75,5 @@ app.jinja_env.globals['utc'] = zoneinfo.ZoneInfo('UTC') app.jinja_env.globals['now'] = lambda: datetime.datetime.now(zoneinfo.ZoneInfo('UTC')) app.jinja_env.globals['randrange'] = random.randrange -app.jinja_env.globals['endpoint_like'] = endpoint_like view_registry.register_in_flask(app) diff --git a/repologyapp/template_functions.py b/repologyapp/template_functions.py index 5280a6a5..559aade9 100644 --- a/repologyapp/template_functions.py +++ b/repologyapp/template_functions.py @@ -50,21 +50,6 @@ def url_for_static(**args: Any) -> Any: return url_for(endpoint='static', filename=args['file']) -def endpoint_like(*variants: str) -> bool: - python_endpoint = flask.request.endpoint - rust_endpoint = PYTHON_TO_RUST_ENDPOINT_NAMES.get(python_endpoint) - - for variant in variants: - if python_endpoint == variant or rust_endpoint == variant: - return True - elif variant.endswith('*') and python_endpoint and python_endpoint.startswith(variant[:-1]): - return True - elif variant.endswith('*') and rust_endpoint and rust_endpoint.startswith(variant[:-1]): - return True - - return False - - def needs_ipv6_notice(*variants: str) -> bool: now = datetime.datetime.now() return now.month == 6 and now.day == 6 and ':' not in flask.request.environ.REMOTE_ADDR and flask.request.endpoint != 'index' diff --git a/repologyapp/templates/_base.html b/repologyapp/templates/_base.html index 0e28d0a8..88468804 100644 --- a/repologyapp/templates/_base.html +++ b/repologyapp/templates/_base.html @@ -46,19 +46,19 @@ diff --git a/repologyapp/view_registry.py b/repologyapp/view_registry.py index 3e57c82e..4e4b739f 100644 --- a/repologyapp/view_registry.py +++ b/repologyapp/view_registry.py @@ -48,6 +48,7 @@ class ViewRegistrant(): _next: Union['ViewRegistrant', None] _route: str _options: dict[str, Any] + _group: str | None def __init__(self, f: 'ViewRegistrant' | _ViewFunc, route: str, options: dict[str, Any]) -> None: if isinstance(f, ViewRegistrant): @@ -60,6 +61,9 @@ def __init__(self, f: 'ViewRegistrant' | _ViewFunc, route: str, options: dict[st self._route = route self._options = options + self._group = options.get('group') + if self._group: + del options['group'] def __call__(self, *args: Any, **kwargs: Any) -> Any: return self._f(*args, **kwargs) @@ -69,6 +73,9 @@ def register_in_flask(self, app: flask.Flask) -> None: if self._next is not None: self._next.register_in_flask(app) + def get_name_to_group(self) -> tuple[str, str | None]: + return (self._f.__name__, self._group) + class ViewRegistrar(): _route: str @@ -84,6 +91,7 @@ def __call__(self, f: _ViewFunc) -> ViewRegistrant: class ViewRegistry(): _registrants: list[ViewRegistrant] + _groups_by_endpoint: dict[str, str] def __init__(self, pkgname: str, pkgfile: str) -> None: self._registrants = [] @@ -93,6 +101,14 @@ def __init__(self, pkgname: str, pkgfile: str) -> None: if isinstance(member, ViewRegistrant): self._registrants.append(member) + self._group_by_endpoint = {} + for (name, group) in [registrant.get_name_to_group() for registrant in self._registrants]: + if group: + self._group_by_endpoint[name] = group + def register_in_flask(self, app: flask.Flask) -> None: for registrant in self._registrants: registrant.register_in_flask(app) + + def get_current_endpoint_group(self) -> str | None: + return self._group_by_endpoint.get(flask.request.endpoint) diff --git a/repologyapp/views/admin.py b/repologyapp/views/admin.py index 397237e2..89ba030d 100644 --- a/repologyapp/views/admin.py +++ b/repologyapp/views/admin.py @@ -33,7 +33,7 @@ def unauthorized() -> Response: return flask.redirect(flask.url_for('admin')) -@ViewRegistrar('/admin', methods=['GET', 'POST']) +@ViewRegistrar('/admin', methods=['GET', 'POST'], group='Admin') def admin() -> Response: if flask.request.method == 'GET' and flask.session.get('admin'): return flask.redirect(flask.url_for('admin_reports_unprocessed'), 302) @@ -82,17 +82,17 @@ def admin_reports_generic(report_getter: Callable[[], dict[str, Any]]) -> Respon return flask.render_template('admin/reports.html', reports=report_getter()) -@ViewRegistrar('/admin/reports/unprocessed/', methods=['GET', 'POST']) +@ViewRegistrar('/admin/reports/unprocessed/', methods=['GET', 'POST'], group='Admin') def admin_reports_unprocessed() -> Response: return admin_reports_generic(lambda: get_db().get_unprocessed_reports(limit=config['REPORTS_PER_PAGE'])) -@ViewRegistrar('/admin/reports/recent/', methods=['GET', 'POST']) +@ViewRegistrar('/admin/reports/recent/', methods=['GET', 'POST'], group='Admin') def admin_reports_recent() -> Response: return admin_reports_generic(lambda: get_db().get_recently_updated_reports(limit=config['REPORTS_PER_PAGE'])) -@ViewRegistrar('/admin/updates') +@ViewRegistrar('/admin/updates', group='Admin') def admin_updates() -> Response: if not flask.session.get('admin'): return unauthorized() @@ -103,7 +103,7 @@ def admin_updates() -> Response: ) -@ViewRegistrar('/admin/redirects', methods=['GET', 'POST']) +@ViewRegistrar('/admin/redirects', methods=['GET', 'POST'], group='Admin') def admin_redirects() -> Response: if not flask.session.get('admin'): return unauthorized() @@ -159,7 +159,7 @@ def admin_redirects() -> Response: ) -@ViewRegistrar('/admin/name_samples') +@ViewRegistrar('/admin/name_samples', group='Admin') def admin_name_samples() -> Response: if not flask.session.get('admin'): return unauthorized() @@ -284,7 +284,7 @@ def check_required_cpe_fields() -> bool: return flask.redirect(url_for_self(), 302) -@ViewRegistrar('/admin/cpes', methods=['GET', 'POST']) +@ViewRegistrar('/admin/cpes', methods=['GET', 'POST'], group='Admin') def admin_cpes() -> Response: if not flask.session.get('admin'): return unauthorized() @@ -299,7 +299,7 @@ def admin_cpes() -> Response: ) -@ViewRegistrar('/admin/cve_misses', methods=['GET', 'POST']) +@ViewRegistrar('/admin/cve_misses', methods=['GET', 'POST'], group='Admin') def admin_cve_misses() -> Response: if not flask.session.get('admin'): return unauthorized() @@ -314,7 +314,7 @@ def admin_cve_misses() -> Response: ) -@ViewRegistrar('/admin/omni_cves') +@ViewRegistrar('/admin/omni_cves', group='Admin') def admin_omni_cves() -> Response: if not flask.session.get('admin'): return unauthorized() diff --git a/repologyapp/views/api.py b/repologyapp/views/api.py index da8c2aae..d1a3cec7 100644 --- a/repologyapp/views/api.py +++ b/repologyapp/views/api.py @@ -70,8 +70,8 @@ def dump_json(data: Any) -> str: return json.dumps(data, separators=(',', ':')) -@ViewRegistrar('/api') -@ViewRegistrar('/api/v1') +@ViewRegistrar('/api', group='Docs') +@ViewRegistrar('/api/v1', group='Docs') def api_v1() -> Response: return flask.render_template('api.html', per_page=config['METAPACKAGES_PER_PAGE']) diff --git a/repologyapp/views/experimental.py b/repologyapp/views/experimental.py index eb5e5df7..dbb7e69b 100644 --- a/repologyapp/views/experimental.py +++ b/repologyapp/views/experimental.py @@ -23,7 +23,7 @@ from repologyapp.view_registry import Response, ViewRegistrar -@ViewRegistrar('/experimental/', methods=['GET', 'POST']) +@ViewRegistrar('/experimental/', methods=['GET', 'POST'], group='Experimental') def experimental() -> Response: if flask.request.method == 'POST': enabled = flask.request.form.get('experimental') == 'enable' @@ -34,7 +34,7 @@ def experimental() -> Response: return flask.render_template('experimental/index.html') -@ViewRegistrar('/experimental/turnover/maintainers') +@ViewRegistrar('/experimental/turnover/maintainers', group='Experimental') def maintainers_turnover() -> Response: return flask.render_template( 'experimental/maintainers-turnover.html', @@ -43,6 +43,6 @@ def maintainers_turnover() -> Response: ) -@ViewRegistrar('/experimental/distromap') +@ViewRegistrar('/experimental/distromap', group='Experimental') def distromap() -> Response: return flask.render_template('experimental/distromap.html') diff --git a/repologyapp/views/maintainers.py b/repologyapp/views/maintainers.py index 6ce0daa6..83a35de9 100644 --- a/repologyapp/views/maintainers.py +++ b/repologyapp/views/maintainers.py @@ -28,8 +28,8 @@ from repologyapp.views.problems import problems_generic -@ViewRegistrar('/maintainers/') -@ViewRegistrar('/maintainers//') +@ViewRegistrar('/maintainers/', group='Maintainers') +@ViewRegistrar('/maintainers//', group='Maintainers') def maintainers(bound: str | None = None) -> Response: reverse = False if bound and bound.startswith('..'): @@ -81,7 +81,7 @@ def maintainers(bound: str | None = None) -> Response: ) -@ViewRegistrar('/maintainer/') +@ViewRegistrar('/maintainer/', group='Maintainers') def maintainer(maintainer: str) -> Response: maintainer = maintainer.lower() @@ -137,7 +137,7 @@ class RepositoryInfo: ) -@ViewRegistrar('/maintainer//problems-for-repo/') +@ViewRegistrar('/maintainer//problems-for-repo/', group='Maintainers') def maintainer_problems(maintainer: str, repo: str) -> Response: return problems_generic( repo=repo, @@ -147,7 +147,7 @@ def maintainer_problems(maintainer: str, repo: str) -> Response: ) -@ViewRegistrar('/maintainer//feed-for-repo/') +@ViewRegistrar('/maintainer//feed-for-repo/', group='Maintainers') def maintainer_repo_feed(maintainer: str, repo: str) -> Response: autorefresh = flask.request.args.to_dict().get('autorefresh') @@ -166,7 +166,7 @@ def maintainer_repo_feed(maintainer: str, repo: str) -> Response: ) -@ViewRegistrar('/maintainer//feed-for-repo//atom') +@ViewRegistrar('/maintainer//feed-for-repo//atom', group='Maintainers') def maintainer_repo_feed_atom(maintainer: str, repo: str) -> Response: return ( flask.render_template( diff --git a/repologyapp/views/project.py b/repologyapp/views/project.py index be67d63f..487b8c60 100644 --- a/repologyapp/views/project.py +++ b/repologyapp/views/project.py @@ -83,7 +83,7 @@ def handle_nonexisting_project(name: str, metapackage: dict[str, Any]) -> Respon ) -@ViewRegistrar('/project//versions') +@ViewRegistrar('/project//versions', group='Projects') def project_versions(name: str) -> Response: metapackage = get_db().get_metapackage(name) @@ -112,7 +112,7 @@ def project_versions(name: str) -> Response: ) -@ViewRegistrar('/project//versions-compact') +@ViewRegistrar('/project//versions-compact', group='Projects') def project_versions_compact(name: str) -> Response: metapackage = get_db().get_metapackage(name) @@ -142,7 +142,7 @@ def project_versions_compact(name: str) -> Response: ) -@ViewRegistrar('/project//packages') +@ViewRegistrar('/project//packages', group='Projects') def project_packages(name: str) -> Response: metapackage = get_db().get_metapackage(name) @@ -209,7 +209,7 @@ def _link_type_is_raw(link_type: int) -> bool: _LinkKey: TypeAlias = tuple[int] | tuple[int, str] # id, fragment -@ViewRegistrar('/project//information') +@ViewRegistrar('/project//information', group='Projects') def project_information(name: str) -> Response: metapackage = get_db().get_metapackage(name) @@ -308,7 +308,7 @@ def project_information(name: str) -> Response: ) -@ViewRegistrar('/project//history') +@ViewRegistrar('/project//history', group='Projects') def project_history(name: str) -> Response: def prepare_repos(repos: Collection[str]) -> list[str]: if not repos: @@ -401,7 +401,7 @@ def postprocess_history(history: list[dict[str, Any]]) -> Iterable[dict[str, Any ) -@ViewRegistrar('/project//related') +@ViewRegistrar('/project//related', group='Projects') def project_related(name: str) -> Response: metapackage = get_db().get_metapackage(name) @@ -432,7 +432,7 @@ def project_related(name: str) -> Response: ) -@ViewRegistrar('/project//badges') +@ViewRegistrar('/project//badges', group='Projects') def project_badges(name: str) -> Response: metapackage = get_db().get_metapackage(name) @@ -453,7 +453,7 @@ def project_badges(name: str) -> Response: ) -@ViewRegistrar('/project//report', methods=['GET', 'POST']) +@ViewRegistrar('/project//report', methods=['GET', 'POST'], group='Projects') def project_report(name: str) -> Response: metapackage = get_db().get_metapackage(name) @@ -573,7 +573,7 @@ class _CVEAggregation: cpe_other: str -@ViewRegistrar('/project//cves') +@ViewRegistrar('/project//cves', group='Projects') def project_cves(name: str) -> Response: metapackage = get_db().get_metapackage(name) diff --git a/repologyapp/views/projects.py b/repologyapp/views/projects.py index 99543834..aa63ba32 100644 --- a/repologyapp/views/projects.py +++ b/repologyapp/views/projects.py @@ -27,8 +27,8 @@ from repologyapp.view_registry import Response, ViewRegistrar -@ViewRegistrar('/projects/') -@ViewRegistrar('/projects//') +@ViewRegistrar('/projects/', group='Projects') +@ViewRegistrar('/projects//', group='Projects') def projects(bound: str | None = None) -> Response: # process search filterinfo = MetapackagesFilterInfo() diff --git a/repologyapp/views/repositories.py b/repologyapp/views/repositories.py index d0e737e8..1b6d6830 100644 --- a/repologyapp/views/repositories.py +++ b/repologyapp/views/repositories.py @@ -25,8 +25,8 @@ from repologyapp.view_registry import Response, ViewRegistrar -@ViewRegistrar('/repositories/statistics') -@ViewRegistrar('/repositories/statistics/') +@ViewRegistrar('/repositories/statistics', group='Repositories') +@ViewRegistrar('/repositories/statistics/', group='Repositories') def repositories_statistics(sorting: str | None = None) -> Response: autorefresh = flask.request.args.to_dict().get('autorefresh') @@ -62,7 +62,7 @@ def repositories_statistics(sorting: str | None = None) -> Response: ) -@ViewRegistrar('/repositories/packages') +@ViewRegistrar('/repositories/packages', group='Repositories') def repositories_packages() -> Response: autorefresh = flask.request.args.to_dict().get('autorefresh') @@ -77,7 +77,7 @@ def repositories_packages() -> Response: ) -@ViewRegistrar('/repositories/updates') +@ViewRegistrar('/repositories/updates', group='Repositories') def repositories_updates() -> Response: autorefresh = flask.request.args.to_dict().get('autorefresh') @@ -88,7 +88,7 @@ def repositories_updates() -> Response: ) -@ViewRegistrar('/repositories/graphs') +@ViewRegistrar('/repositories/graphs', group='Repositories') def repositories_graphs() -> Response: autorefresh = flask.request.args.to_dict().get('autorefresh') @@ -98,7 +98,7 @@ def repositories_graphs() -> Response: ) -@ViewRegistrar('/repositories/fields') +@ViewRegistrar('/repositories/fields', group='Repositories') def repositories_fields() -> Response: autorefresh = flask.request.args.to_dict().get('autorefresh') diff --git a/repologyapp/views/repository.py b/repologyapp/views/repository.py index 6bad98b5..49564b2f 100644 --- a/repologyapp/views/repository.py +++ b/repologyapp/views/repository.py @@ -27,7 +27,7 @@ from repologyapp.views.problems import problems_generic -@ViewRegistrar('/repository/') +@ViewRegistrar('/repository/', group='Repositories') def repository(repo: str) -> Response: autorefresh = flask.request.args.to_dict().get('autorefresh') @@ -46,7 +46,7 @@ def repository(repo: str) -> Response: ) -@ViewRegistrar('/repository//problems') +@ViewRegistrar('/repository//problems', group='Repositories') def repository_problems(repo: str) -> Response: return problems_generic( repo=repo, @@ -55,7 +55,7 @@ def repository_problems(repo: str) -> Response: ) -@ViewRegistrar('/repository//feed') +@ViewRegistrar('/repository//feed', group='Repositories') def repository_feed(repo: str) -> Response: autorefresh = flask.request.args.to_dict().get('autorefresh') @@ -72,7 +72,7 @@ def repository_feed(repo: str) -> Response: ) -@ViewRegistrar('/repository//feed/atom') +@ViewRegistrar('/repository//feed/atom', group='Repositories') def repository_feed_atom(repo: str) -> Response: return ( flask.render_template( diff --git a/repologyapp/views/security.py b/repologyapp/views/security.py index 0f00f967..b5880b3a 100644 --- a/repologyapp/views/security.py +++ b/repologyapp/views/security.py @@ -25,7 +25,7 @@ from repologyapp.view_registry import Response, ViewRegistrar -@ViewRegistrar('/security/recent-cves') +@ViewRegistrar('/security/recent-cves', group='Security') def security_recent_cves() -> Response: autorefresh = flask.request.args.to_dict().get('autorefresh') @@ -42,7 +42,7 @@ def security_recent_cves() -> Response: ) -@ViewRegistrar('/security/recent-cpes') +@ViewRegistrar('/security/recent-cpes', group='Security') def security_recent_cpes() -> Response: autorefresh = flask.request.args.to_dict().get('autorefresh') diff --git a/repologyapp/views/staticpages.py b/repologyapp/views/staticpages.py index e074ab3a..efb2874b 100644 --- a/repologyapp/views/staticpages.py +++ b/repologyapp/views/staticpages.py @@ -20,32 +20,32 @@ from repologyapp.view_registry import Response, ViewRegistrar -@ViewRegistrar('/news') +@ViewRegistrar('/news', group='News') def news() -> Response: return flask.render_template('news.html') -@ViewRegistrar('/docs') +@ViewRegistrar('/docs', group='Docs') def docs() -> Response: return flask.render_template('docs/index.html') -@ViewRegistrar('/docs/about') +@ViewRegistrar('/docs/about', group='Docs') def docs_about() -> Response: return flask.render_template('docs/about.html') -@ViewRegistrar('/docs/bots') +@ViewRegistrar('/docs/bots', group='Docs') def docs_bots() -> Response: return flask.render_template('docs/bots.html') -@ViewRegistrar('/docs/not_supported') +@ViewRegistrar('/docs/not_supported', group='Docs') def docs_not_supported() -> Response: return flask.render_template('docs/not_supported.html') -@ViewRegistrar('/docs/requirements') +@ViewRegistrar('/docs/requirements', group='Docs') def docs_requirements() -> Response: return flask.render_template('docs/requirements.html') @@ -55,6 +55,6 @@ def favicon() -> Response: return flask.current_app.send_static_file('repology.v1.ico') -@ViewRegistrar('/tools') +@ViewRegistrar('/tools', group='Tools') def tools() -> Response: return flask.render_template('tools/index.html') diff --git a/repologyapp/views/tools.py b/repologyapp/views/tools.py index 000ffb45..09fc2095 100644 --- a/repologyapp/views/tools.py +++ b/repologyapp/views/tools.py @@ -74,7 +74,7 @@ class AllowedTargetPage: } -@ViewRegistrar('/tools/project-by') +@ViewRegistrar('/tools/project-by', group='Tools') def tool_project_by() -> Response: repo = flask.request.args.get('repo') name_type = flask.request.args.get('name_type') @@ -139,7 +139,7 @@ def tool_project_by() -> Response: ) -@ViewRegistrar('/tools/trending') +@ViewRegistrar('/tools/trending', group='Tools') def trending() -> Response: return flask.render_template( 'tools/trending.html', @@ -161,7 +161,7 @@ def trending() -> Response: ) -@ViewRegistrar('/tools/important_updates') +@ViewRegistrar('/tools/important_updates', group='Experimental') def important_updates() -> Response: return flask.render_template( 'tools/important-updates.html',