11"""A custom Transform object to shorten github and gitlab links."""
22
3- from typing import ClassVar
3+ from typing import ClassVar , Literal
44from urllib .parse import ParseResult , urlparse , urlunparse
55
66from docutils import nodes
77from sphinx .transforms .post_transforms import SphinxPostTransform
88from sphinx .util .nodes import NodeMatcher
99
10- from .utils import traverse_or_findall
10+ from .utils import get_theme_options_dict , traverse_or_findall
1111
1212
1313class ShortenLinkTransform (SphinxPostTransform ):
1414 """
15- Shorten link when they are coming from github or gitlab and add an extra class to
16- the tag for further styling.
15+ Shorten links leading to supported forges.
16+ Also attempt to identify self-hosted forge instances.
17+ Add an extra class to the tag for further styling.
1718
1819 Before:
1920 .. code-block:: html
@@ -35,6 +36,8 @@ class ShortenLinkTransform(SphinxPostTransform):
3536 default_priority = 400
3637 formats = ("html" ,)
3738 supported_platform : ClassVar [dict [str , str ]] = {
39+ "codeberg.org" : "codeberg" ,
40+ "gitea.com" : "gitea" ,
3841 "github.com" : "github" ,
3942 "gitlab.com" : "gitlab" ,
4043 }
@@ -53,10 +56,54 @@ def run(self, **kwargs):
5356 uri = urlparse (uri )
5457 # only do something if the platform is identified
5558 self .platform = self .supported_platform .get (uri .netloc )
59+ # or we can make a reasonable guess about self-hosted forges
60+ if self .platform is None :
61+ html_theme_options = get_theme_options_dict (self .app )
62+ self .platform = self .identify_selfhosted (uri , html_theme_options )
5663 if self .platform is not None :
5764 node .attributes ["classes" ].append (self .platform )
5865 node .children [0 ] = nodes .Text (self .parse_url (uri ))
5966
67+ def identify_selfhosted (
68+ self , uri : ParseResult , html_theme_options : dict [str , str ]
69+ ) -> Literal ["forgejo" , "gitea" , "gitlab" ] | None :
70+ """Try to identify what self-hosted forge uri leads to (if any).
71+
72+ Args:
73+ uri: the link to the platform content
74+ html_theme_options: varia
75+
76+ Returns:
77+ likely platform if one matches, None otherwise
78+ """
79+ # forge name in authority and known url part in the right place
80+ # unreliable but may catch any number of hosts
81+ path_parts = uri .path .strip ("/" ).split ("/" )
82+ if len (path_parts ) > 2 and path_parts [2 ] in ("pulls" , "issues" , "projects" ):
83+ if "forgejo" in uri .netloc :
84+ return "forgejo"
85+ elif "gitea" in uri .netloc :
86+ return "gitea"
87+ if (
88+ len (path_parts ) > 3
89+ and path_parts [2 ] == "-"
90+ and path_parts [3 ] in ("issues" , "merge_requests" )
91+ ):
92+ if "gitlab" in uri .netloc :
93+ return "gitlab"
94+
95+ # url passed in *_url option
96+ # will only match project's own forge but that's
97+ # likely where most doc links will lead anyway
98+ str_url = f"{ uri .scheme } ://{ uri .netloc } "
99+ selfhosted = ("forgejo" , "gitea" , "gitlab" )
100+ for forge in selfhosted :
101+ known_url = html_theme_options .get (f"{ forge } _url" )
102+ if known_url and known_url .startswith (str_url ):
103+ return forge
104+
105+ return None
106+
60107 def parse_url (self , uri : ParseResult ) -> str :
61108 """Parse the content of the url with respect to the selected platform.
62109
@@ -119,5 +166,14 @@ def parse_url(self, uri: ParseResult) -> str:
119166 # for example "<group>/<subgroup1>/<subgroup2>/<repository>"
120167 text = uri ._replace (netloc = "" , scheme = "" ) # remove platform
121168 text = urlunparse (text )[1 :] # combine to string and strip leading "/"
169+ elif self .platform in ("codeberg" , "forgejo" , "gitea" ):
170+ parts = path .rstrip ("/" ).split ("/" )
171+ if len (parts ) == 4 and parts [2 ] in ("issues" , "pulls" ):
172+ text = f"{ parts [0 ]} /{ parts [1 ]} #{ parts [3 ]} " # element number
173+ elif parts == ["" ]:
174+ text = self .platform
175+ else :
176+ text = uri ._replace (netloc = "" , scheme = "" ) # remove platform
177+ text = urlunparse (text )[1 :] # combine to string and strip leading "/"
122178
123179 return text
0 commit comments