Skip to content

WIP: Support additional forges #2215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/_templates/edit-this-page.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{% if sourcename is defined and theme_use_edit_page_button and page_source_suffix %}
{% set src = sourcename.split('.') %}
<div class="tocsection editthispage">
<a href="{{ to_main(get_edit_provider_and_url()[1]) }}">
{% set provider, url = get_edit_provider_and_url() %}
<a href="{{ to_main(url) }}">
<i class="fa-solid fa-pencil"></i>
{% set provider = get_edit_provider_and_url()[0] %}
{% block edit_this_page_text %}
{% if provider %}
{% trans provider=provider %}Edit on {{ provider }}{% endtrans %}
Expand Down
2 changes: 2 additions & 0 deletions docs/user_guide/header-links.rst
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ These may be removed in a future release in favor of ``icon_links``:
"github_url": "https://github.com/<your-org>/<your-repo>",
"gitlab_url": "https://gitlab.com/<your-org>/<your-repo>",
"bitbucket_url": "https://bitbucket.org/<your-org>/<your-repo>",
"forgejo_url": "https://codeberg.org/<your-handle>/<your-repo>",
"gitea_url": "https://gitea.com/<your-handle>/<your-repo>",
"twitter_url": "https://twitter.com/<your-handle>",
...
}
Expand Down
31 changes: 29 additions & 2 deletions docs/user_guide/source-buttons.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ your ``conf.py`` file in 'html_theme_options':
}

A number of providers are available for building *Edit this Page* links, including
GitHub, GitLab, and Bitbucket. For each, the default public instance URL can be
GitHub, GitLab, Bitbucket, Forgejo and Gitea. For each, the default public instance URL (should it exist) can be
replaced with a self-hosted instance.


Expand Down Expand Up @@ -65,6 +65,33 @@ Bitbucket
}


Forgejo
---------

.. code:: python

html_context = {
# "forgejo_url": "https://codeberg.org", # or your self-hosted Forgejo
"forgejo_user": "<your-forgejo-org>",
"forgejo_repo": "<your-forgejo-repo>",
"forgejo_version": "<your-branch>",
"doc_path": "<path-from-root-to-your-docs>",
}


Gitea
---------

.. code:: python

html_context = {
# "gitea_url": "https://gitea.com", # or your self-hosted Gitea
"gitea_user": "<your-gitea-org>",
"gitea_repo": "<your-gitea-repo>",
"gitea_version": "<your-branch>",
"doc_path": "<path-from-root-to-your-docs>",
}

Custom Edit URL
---------------

Expand All @@ -80,7 +107,7 @@ any other context values.
"some_other_arg": "?some-other-arg"
}

With the predefined providers, the link text reads "Edit on GitHub/GitLab/Bitbucket".
With the predefined providers, the link text reads "Edit on GitHub/GitLab/Bitbucket/Codeberg/Forgejo/Gitea".
By default, a simple "Edit" is used if you use a custom URL. However, you can set
a provider name like this:

Expand Down
35 changes: 29 additions & 6 deletions src/pydata_sphinx_theme/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,27 @@ def update_config(app):

# Handle icon link shortcuts
shortcuts = [
("twitter_url", "fa-brands fa-square-twitter", "Twitter"),
("bitbucket_url", "fa-brands fa-bitbucket", "Bitbucket"),
("gitlab_url", "fa-brands fa-square-gitlab", "GitLab"),
("github_url", "fa-brands fa-square-github", "GitHub"),
("twitter_url", "fa-brands fa-square-twitter", "Twitter", "fontawesome"),
("bitbucket_url", "fa-brands fa-bitbucket", "Bitbucket", "fontawesome"),
("gitlab_url", "fa-brands fa-square-gitlab", "GitLab", "fontawesome"),
("github_url", "fa-brands fa-square-github", "GitHub", "fontawesome"),
(
"forgejo_url",
r"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 212 212'%3E%3Cstyle%3Ecircle,path%7Bfill:none;stroke:%23000;stroke-width:15%7Dpath%7Bstroke-width:25%7D.orange%7Bstroke:%23f60%7D.red%7Bstroke:%23d40000%7D%3C/style%3E%3Cg transform='translate(6 6)'%3E%3Cpath d='M58 168V70a50 50 0 0 1 50-50h20' class='orange'/%3E%3Cpath d='M58 168v-30a50 50 0 0 1 50-50h20' class='red'/%3E%3Ccircle cx='142' cy='20' r='18' class='orange'/%3E%3Ccircle cx='142' cy='88' r='18' class='red'/%3E%3Ccircle cx='58' cy='180' r='18' class='red'/%3E%3C/g%3E%3C/svg%3E",
"Forgejo",
"local",
),
(
"gitea_url",
r"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 640'%3E%3Cpath fill='%23fff' d='m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12z'/%3E%3Cg fill='%23609926'%3E%3Cpath d='M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6zM125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1zm300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1z'/%3E%3Cpath d='M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8-1.9 8 2 16.3 9.1 20 7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3 7.8 4 17.4 1.7 22.5-5.3 5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8l-24.6 50.4z'/%3E%3C/g%3E%3C/svg%3E",
"Gitea",
"local",
),
]
# Add extra icon links entries if there were shortcuts present
# TODO: Deprecate this at some point in the future?
icon_links = theme_options.get("icon_links", [])
for url, icon, name in shortcuts:
for url, icon, name, icon_type in shortcuts:
if theme_options.get(url):
# This defaults to an empty list so we can always insert
icon_links.insert(
Expand All @@ -149,9 +161,10 @@ def update_config(app):
"url": theme_options.get(url),
"icon": icon,
"name": name,
"type": "fontawesome",
"type": icon_type,
},
)
icon_links[0] = adjust_known_instances(icon_links[0])
theme_options["icon_links"] = icon_links

# Prepare the logo config dictionary
Expand Down Expand Up @@ -275,6 +288,16 @@ def _fix_canonical_url(
context["pageurl"] = app.config.html_baseurl + target


def adjust_known_instances(icon_link: dict[str, str]) -> dict[str, str]:
"""Adjust icon data for supported self-hostable forge instances."""
if icon_link["url"].startswith("https://codeberg.org"):
icon_link["icon"] = (
r"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 4.233 4.233'%3E%3Cdefs%3E%3ClinearGradient xlink:href='%23a' id='b' x1='42519.285' x2='42575.336' y1='-7078.789' y2='-6966.931' gradientUnits='userSpaceOnUse'/%3E%3ClinearGradient id='a'%3E%3Cstop offset='0' style='stop-color:%232185d0;stop-opacity:0'/%3E%3Cstop offset='.495' style='stop-color:%232185d0;stop-opacity:.30000001'/%3E%3Cstop offset='1' style='stop-color:%232185d0;stop-opacity:.30000001'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M42519.285-7078.79a.76.568 0 0 0-.738.675l33.586 125.888a87.182 87.182 0 0 0 39.381-33.763l-71.565-92.52a.76.568 0 0 0-.664-.28z' style='font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(%23b);fill-opacity:1;stroke:none;stroke-width:3.67846;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill;stop-color:%23000;stop-opacity:1' transform='translate(-1030.156 172.97) scale(.02428)'/%3E%3Cpath d='M11249.461-1883.696c-12.74 0-23.067 10.327-23.067 23.067 0 4.333 1.22 8.58 3.522 12.251l19.232-24.863c.138-.18.486-.18.624 0l19.233 24.864a23.068 23.068 0 0 0 3.523-12.252c0-12.74-10.327-23.067-23.067-23.067z' style='opacity:1;fill:%232185d0;fill-opacity:1;stroke-width:17.0055;paint-order:markers fill stroke;stop-color:%23000' transform='translate(-1030.156 172.97) scale(.09176)'/%3E%3C/svg%3E"
)
icon_link["name"] = "Codeberg"
return icon_link


def setup(app: Sphinx) -> Dict[str, str]:
"""Setup the Sphinx application."""
here = Path(__file__).parent.resolve()
Expand Down
61 changes: 56 additions & 5 deletions src/pydata_sphinx_theme/edit_this_page.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
"""Create an "edit this page" url compatible with bitbucket, gitlab and github."""

import urllib

import jinja2

from sphinx.application import Sphinx
from sphinx.errors import ExtensionError

from .utils import get_theme_options_dict


def setup_edit_url(
app: Sphinx, pagename: str, templatename: str, context, doctree
Expand All @@ -20,11 +24,24 @@ def get_edit_provider_and_url() -> None:
if doc_path and not doc_path.endswith("/"):
doc_path = f"{doc_path}/"

default_provider_urls = {
provider_urls = {
"bitbucket_url": "https://bitbucket.org",
"forgejo_url": "https://codeberg.org",
"gitea_url": "https://gitea.com",
"github_url": "https://github.com",
"gitlab_url": "https://gitlab.com",
}
provider_labels = {
"bitbucket": "Bitbucket",
"forgejo": "Forgejo",
"gitea": "Gitea",
"github": "GitHub",
"gitlab": "GitLab",
}
theme_options = get_theme_options_dict(app)
provider_urls, provider_labels = adjust_forge_params(
provider_urls, provider_labels, theme_options
)

edit_attrs = {}

Expand All @@ -44,25 +61,35 @@ def get_edit_provider_and_url() -> None:
edit_attrs.update(
{
("bitbucket_user", "bitbucket_repo", "bitbucket_version"): (
"Bitbucket",
provider_labels["bitbucket"],
"{{ bitbucket_url }}/{{ bitbucket_user }}/{{ bitbucket_repo }}"
"/src/{{ bitbucket_version }}"
"/{{ doc_path }}{{ file_name }}?mode=edit",
),
("forgejo_user", "forgejo_repo", "forgejo_version"): (
provider_labels["forgejo"],
"{{ forgejo_url }}/{{ forgejo_user }}/{{ forgejo_repo }}"
"/_edit/{{ forgejo_version }}/{{ doc_path }}{{ file_name }}",
),
("gitea_user", "gitea_repo", "gitea_version"): (
provider_labels["gitea"],
"{{ gitea_url }}/{{ gitea_user }}/{{ gitea_repo }}"
"/_edit/{{ gitea_version }}/{{ doc_path }}{{ file_name }}",
),
("github_user", "github_repo", "github_version"): (
"GitHub",
provider_labels["github"],
"{{ github_url }}/{{ github_user }}/{{ github_repo }}"
"/edit/{{ github_version }}/{{ doc_path }}{{ file_name }}",
),
("gitlab_user", "gitlab_repo", "gitlab_version"): (
"GitLab",
provider_labels["gitlab"],
"{{ gitlab_url }}/{{ gitlab_user }}/{{ gitlab_repo }}"
"/-/edit/{{ gitlab_version }}/{{ doc_path }}{{ file_name }}",
),
}
)

doc_context = dict(default_provider_urls)
doc_context = dict(provider_urls)
doc_context.update(context)
doc_context.update(doc_path=doc_path, file_name=file_name)

Expand All @@ -80,3 +107,27 @@ def get_edit_provider_and_url() -> None:

# Ensure that the max TOC level is an integer
context["theme_show_toc_level"] = int(context.get("theme_show_toc_level", 1))


def adjust_forge_params(
forge_urls: dict[str, str],
forge_labels: dict[str, str],
theme_options: dict[str, str],
) -> (dict[str, str], dict[str, str]):
"""Adjust labels and URLs for some of the more decentralized forges."""
# use *_urls given in html_theme_options as authority (netloc)
# instead of default if available
for url_key in forge_urls:
forge_url = theme_options.get(url_key)
if forge_url:
forge_url = urllib.parse.urlsplit(forge_url, allow_fragments=False)
forge_url = f"{forge_url.scheme}://{forge_url.netloc}"
forge_urls[url_key] = forge_url

# use rebranded forge label instead of generic SW name where known
if "forgejo_url" in theme_options:
url = theme_options["forgejo_url"]
if url.startswith("https://codeberg.org"):
forge_labels["forgejo"] = "Codeberg"

return forge_urls, forge_labels
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
{% if sourcename is defined and theme_use_edit_page_button==true and page_source_suffix %}
{% set src = sourcename.split('.') %}
<div class="tocsection editthispage">
<a href="{{ get_edit_provider_and_url()[1] }}">
{% set provider, url = get_edit_provider_and_url() %}
<a href="{{ url }}">
<i class="fa-solid fa-pencil"></i>
{% set provider = get_edit_provider_and_url()[0] %}
{% block edit_this_page_text %}
{% if provider %}
{% trans provider=provider %}Edit on {{ provider }}{% endtrans %}
Expand Down
2 changes: 2 additions & 0 deletions src/pydata_sphinx_theme/theme/pydata_sphinx_theme/theme.conf
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ external_links =
bitbucket_url =
github_url =
gitlab_url =
forgejo_url =
gitea_url =
twitter_url =
icon_links_label = Icon Links
icon_links =
Expand Down
56 changes: 52 additions & 4 deletions tests/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,44 @@ def test_footer(sphinx_build_factory) -> None:
"https://bitbucket.org/foo/bar/src/HEAD/docs/index.rst?mode=edit",
),
],
[
{
"forgejo_user": "foo",
"forgejo_repo": "bar",
"forgejo_version": "HEAD",
"doc_path": "docs",
"forgejo_url": "https://my-forgejo.com",
},
(
"Edit on Forgejo",
"https://my-forgejo.com/foo/bar/_edit/HEAD/docs/index.rst",
),
],
[
{
"forgejo_user": "foo",
"forgejo_repo": "bar",
"forgejo_version": "HEAD",
"doc_path": "docs",
"forgejo_url": "https://codeberg.org",
},
(
"Edit on Codeberg",
"https://codeberg.org/foo/bar/_edit/HEAD/docs/index.rst",
),
],
[
{
"gitea_user": "foo",
"gitea_repo": "bar",
"gitea_version": "HEAD",
"doc_path": "docs",
},
(
"Edit on Gitea",
"https://gitea.com/foo/bar/_edit/HEAD/docs/index.rst",
),
],
]


Expand All @@ -608,16 +646,22 @@ def test_footer(sphinx_build_factory) -> None:
dict(
# copy all the values
**html_context,
# add a provider url
**{f"{provider}_url": f"https://{provider}.example.com"},
# add a provider url if not specified in html_context
**(
{f"{provider}_url": f"https://{provider}.example.com"}
if f"{provider}_url" not in html_context
else {}
),
),
(
text,
f"""https://{provider}.example.com/foo/{url.split("/foo/")[1]}""",
f"""https://{provider}.example.com/foo/{url.split("/foo/")[1]}"""
if f"{provider}_url" not in html_context
else f"{html_context[f'{provider}_url']}/foo/{url.split('/foo/')[1]}",
),
]
for html_context, (text, url) in good_edits
for provider in ["github", "gitlab", "bitbucket"]
for provider in ["github", "gitlab", "bitbucket", "forgejo", "gitea"]
if provider in text.lower()
]

Expand Down Expand Up @@ -687,6 +731,10 @@ def test_edit_page_url(sphinx_build_factory, html_context, edit_text_and_url) ->
"html_theme_options.use_edit_page_button": True,
"html_context": html_context,
}
if html_context.get("forgejo_url"):
confoverrides.update(
{"html_theme_options.forgejo_url": html_context["forgejo_url"]}
)
sphinx_build = sphinx_build_factory("base", confoverrides=confoverrides)

if edit_text_and_url is None:
Expand Down
Loading