Skip to content

Commit fa75340

Browse files
committed
More __init__.py helper functions
1 parent ba3bc48 commit fa75340

File tree

1 file changed

+115
-82
lines changed

1 file changed

+115
-82
lines changed

sphinx_github_style/__init__.py

Lines changed: 115 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -5,91 +5,99 @@
55
import subprocess
66
import pkg_resources
77
from pathlib import Path
8-
from typing import Dict, Any
8+
from typing import Dict, Any, Optional
99
from sphinx.application import Sphinx
10+
from sphinx.errors import ExtensionError
1011

11-
12-
__version__ = "0.0.1b7"
12+
__version__ = "0.0.1b8"
1313
__author__ = 'Adam Korn <hello@dailykitten.net>'
1414

15-
1615
from .add_linkcode_class import add_linkcode_node_class
1716
from .meth_lexer import TDKMethLexer
1817
from .github_style import TDKStyle
1918

2019

2120
def setup(app: Sphinx) -> Dict[str, Any]:
22-
# Package Info
2321
modpath = os.path.abspath('../')
2422
modname = os.path.basename(modpath)
2523
pkg = pkg_resources.require(modname)[0]
2624
pkg_name = pkg.get_metadata('top_level.txt').strip()
2725

28-
app.add_config_value('pkg_name', pkg_name, 'html')
2926
app.connect("builder-inited", get_static_path)
3027
# app.connect('build-finished', save_generated_rst_files)
3128

32-
app.setup_extension('sphinx.ext.linkcode')
29+
app.add_config_value('pkg_name', pkg_name, 'html')
30+
app.add_config_value('linkcode_blob', 'master', True)
31+
3332
app.setup_extension('sphinx_github_style.add_linkcode_class')
3433
app.setup_extension('sphinx_github_style.github_style')
3534
app.setup_extension('sphinx_github_style.meth_lexer')
35+
app.setup_extension('sphinx.ext.linkcode')
3636

37-
app.add_config_value('linkcode_default_blob', 'master', 'html')
3837
app.config.html_context['github_version'] = get_linkcode_revision(app)
38+
3939
linkcode_url = get_linkcode_url(app)
40+
linkcode_func = getattr(app.config, 'linkcode_resolve', None)
4041

41-
def linkcode_resolve(domain, info):
42-
"""Returns a link to the source code on GitHub, with appropriate lines highlighted
42+
if not linkcode_func or not callable(linkcode_func):
43+
print("Function `linkcode_resolve` is not given in conf.py; "
44+
"using default function from ``sphinx_github_style``")
4345

44-
Adapted from https://github.com/nlgranger
45-
"""
46-
if domain != 'py' or not info['module']:
47-
return None
46+
def linkcode_resolve(domain, info):
47+
"""Returns a link to the source code on GitHub, with appropriate lines highlighted
48+
49+
:By:
50+
Adam Korn (https://github.com/tdkorn)
51+
:Adapted From:
52+
nlgranger/SeqTools (https://github.com/nlgranger/seqtools/blob/master/docs/conf.py)
53+
"""
54+
if domain != 'py' or not info['module']:
55+
return None
4856

49-
modname = info['module']
50-
fullname = info['fullname']
57+
modname = info['module']
58+
fullname = info['fullname']
5159

52-
submod = sys.modules.get(modname)
53-
if submod is None:
54-
return None
60+
submod = sys.modules.get(modname)
61+
if submod is None:
62+
return None
63+
64+
obj = submod
65+
for part in fullname.split('.'):
66+
try:
67+
obj = getattr(obj, part)
68+
except Exception:
69+
return None
5570

56-
obj = submod
57-
for part in fullname.split('.'):
5871
try:
59-
obj = getattr(obj, part)
60-
print(obj)
72+
filepath = os.path.relpath(inspect.getsourcefile(obj), modpath)
73+
if filepath is None:
74+
return
6175
except Exception:
6276
return None
6377

64-
try:
65-
filepath = os.path.relpath(inspect.getsourcefile(obj), modpath)
66-
if filepath is None:
67-
return
68-
except Exception:
69-
return None
78+
try:
79+
source, lineno = inspect.getsourcelines(obj)
80+
except OSError:
81+
print(f'failed to get source lines for {obj}')
82+
return None
83+
else:
84+
linestart, linestop = lineno, lineno + len(source) - 1
7085

71-
try:
72-
source, lineno = inspect.getsourcelines(obj)
73-
except OSError:
74-
print(f'failed to get source lines for {obj}')
75-
return None
76-
else:
77-
linestart, linestop = lineno, lineno + len(source) - 1
78-
79-
# Format link using the filepath of the source file plus the line numbers
80-
# Fix links with "../../../" or "..\\..\\..\\"
81-
filepath = '/'.join(filepath[filepath.find(pkg_name):].split('\\'))
82-
83-
# Example of final link: # https://github.com/tdkorn/my-magento/blob/sphinx-docs/magento/utils.py#L355-L357
84-
final_link = linkcode_url.format(
85-
filepath=filepath,
86-
linestart=linestart,
87-
linestop=linestop
88-
)
89-
print(f"Final Link for {fullname}: {final_link}")
90-
return final_link
91-
92-
app.config.linkcode_resolve = linkcode_resolve
86+
# Fix links with "../../../" or "..\\..\\..\\"
87+
filepath = '/'.join(filepath[filepath.find(pkg_name):].split('\\'))
88+
89+
# Example: https://github.com/TDKorn/my-magento/blob/docs/magento/models/model.py#L28-L59
90+
final_link = linkcode_url.format(
91+
filepath=filepath,
92+
linestart=linestart,
93+
linestop=linestop
94+
)
95+
print(f"Final Link for {fullname}: {final_link}")
96+
return final_link
97+
98+
linkcode_func = linkcode_resolve
99+
100+
app.config.linkcode_resolve = linkcode_func
93101
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
94102

95103

@@ -99,45 +107,70 @@ def get_static_path(app):
99107
)
100108

101109

110+
def get_linkcode_url(app: Sphinx) -> str:
111+
"""Template for linking to highlighted GitHub source code
112+
113+
Formatted into a final link by :meth:`~.linkcode_resolve`
114+
"""
115+
if (url := app.config._raw_config.get("linkcode_url")) is None:
116+
raise ExtensionError("Config value ``linkcode_url`` is missing")
117+
url = f"{url.rstrip('/')}/blob/{get_linkcode_revision(app)}/"
118+
url += "{filepath}#L{linestart}-L{linestop}"
119+
return url
120+
121+
102122
def get_linkcode_revision(app: Sphinx):
103-
# Get the blob to link to on GitHub
104-
linkcode_revision = "master"
123+
"""Get the blob to link to on GitHub
124+
125+
.. admonition:: Linkcode Blobs
126+
127+
The generated links will use the conf.py value of ``linkcode_blob``
128+
129+
* ``"head"`` - most recent commit hash; if this commit is tagged, uses the tag instead
130+
* ``"last_tag" - the most recently tagged commit
131+
* "{blob}" - any blob (ex. ``"master"``, ``"v2.1.0b0"``)
132+
"""
133+
blob = getattr(app.config, "linkcode_blob", "master")
134+
if blob == "head":
135+
return get_head()
136+
if blob == 'last_tag':
137+
return get_last_tag()
138+
# Link to the branch/tree/blob you provided, ex. "master"
139+
return blob
105140

141+
142+
def get_head(errors: bool = False) -> Optional[str]:
143+
"""Gets the most recent commit hash or tag
144+
145+
:raises subprocess.CalledProcessError: if the commit can't be found and ``errors`` is set to ``True``
146+
"""
147+
cmd = "git log -n1 --pretty=%H"
106148
try:
107-
# lock to commit number
108-
cmd = "git log -n1 --pretty=%H"
149+
# get most recent commit hash
109150
head = subprocess.check_output(cmd.split()).strip().decode('utf-8')
110-
linkcode_revision = head
111-
112-
# if we are on master's HEAD, use master as reference
113-
cmd = "git log --first-parent master -n1 --pretty=%H"
114-
master = subprocess.check_output(cmd.split()).strip().decode('utf-8')
115-
if head == master:
116-
linkcode_revision = "master"
117151

118-
# if we have a tag, use tag as reference
152+
# if head is a tag, use tag as reference
119153
cmd = "git describe --exact-match --tags " + head
120-
tag = subprocess.check_output(cmd.split(" ")).strip().decode('utf-8')
121-
linkcode_revision = tag
122-
123-
except subprocess.CalledProcessError:
124-
if app.config._raw_config.get('linkcode_default_blob') == 'last_tag':
125-
# Get the most recent tag to link to on GitHub
126-
try:
127-
cmd = "git describe --tags --abbrev=0"
128-
last_tag = subprocess.check_output(cmd.split(" ")).strip().decode('utf-8')
129-
linkcode_revision = last_tag
154+
try:
155+
tag = subprocess.check_output(cmd.split(" ")).strip().decode('utf-8')
156+
return tag
130157

131-
except subprocess.CalledProcessError:
132-
linkcode_revision = "master"
158+
except subprocess.CalledProcessError:
159+
return head
133160

134-
return linkcode_revision
161+
except subprocess.CalledProcessError as e:
162+
# Raise error
163+
if errors:
164+
raise RuntimeError from e
165+
else:
166+
return None
135167

136168

137-
def get_linkcode_url(app):
138-
if not (url := app.config._raw_config.get("linkcode_url")):
139-
raise ValueError("conf.py missing value for ``linkcode_url``")
169+
def get_last_tag():
170+
"""Get the most recent commit tag"""
171+
try:
172+
cmd = "git describe --tags --abbrev=0"
173+
return subprocess.check_output(cmd.split(" ")).strip().decode('utf-8')
140174

141-
# Source link template; formatted by linkcode_resolve
142-
return f"{url.rstrip('/')}/blob/{get_linkcode_revision(app)}/" + \
143-
"{filepath}#L{linestart}-L{linestop}"
175+
except subprocess.CalledProcessError as e:
176+
raise RuntimeError("No tags exist for the repo...(?)") from e

0 commit comments

Comments
 (0)