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',