5
5
import subprocess
6
6
import pkg_resources
7
7
from pathlib import Path
8
- from typing import Dict , Any
8
+ from typing import Dict , Any , Optional
9
9
from sphinx .application import Sphinx
10
+ from sphinx .errors import ExtensionError
10
11
11
-
12
- __version__ = "0.0.1b7"
12
+ __version__ = "0.0.1b8"
13
13
__author__ = 'Adam Korn <hello@dailykitten.net>'
14
14
15
-
16
15
from .add_linkcode_class import add_linkcode_node_class
17
16
from .meth_lexer import TDKMethLexer
18
17
from .github_style import TDKStyle
19
18
20
19
21
20
def setup (app : Sphinx ) -> Dict [str , Any ]:
22
- # Package Info
23
21
modpath = os .path .abspath ('../' )
24
22
modname = os .path .basename (modpath )
25
23
pkg = pkg_resources .require (modname )[0 ]
26
24
pkg_name = pkg .get_metadata ('top_level.txt' ).strip ()
27
25
28
- app .add_config_value ('pkg_name' , pkg_name , 'html' )
29
26
app .connect ("builder-inited" , get_static_path )
30
27
# app.connect('build-finished', save_generated_rst_files)
31
28
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
+
33
32
app .setup_extension ('sphinx_github_style.add_linkcode_class' )
34
33
app .setup_extension ('sphinx_github_style.github_style' )
35
34
app .setup_extension ('sphinx_github_style.meth_lexer' )
35
+ app .setup_extension ('sphinx.ext.linkcode' )
36
36
37
- app .add_config_value ('linkcode_default_blob' , 'master' , 'html' )
38
37
app .config .html_context ['github_version' ] = get_linkcode_revision (app )
38
+
39
39
linkcode_url = get_linkcode_url (app )
40
+ linkcode_func = getattr (app .config , 'linkcode_resolve' , None )
40
41
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``" )
43
45
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
48
56
49
- modname = info ['module' ]
50
- fullname = info ['fullname' ]
57
+ modname = info ['module' ]
58
+ fullname = info ['fullname' ]
51
59
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
55
70
56
- obj = submod
57
- for part in fullname .split ('.' ):
58
71
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
61
75
except Exception :
62
76
return None
63
77
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
70
85
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
93
101
return {'version' : sphinx .__display_version__ , 'parallel_read_safe' : True }
94
102
95
103
@@ -99,45 +107,70 @@ def get_static_path(app):
99
107
)
100
108
101
109
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
+
102
122
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
105
140
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"
106
148
try :
107
- # lock to commit number
108
- cmd = "git log -n1 --pretty=%H"
149
+ # get most recent commit hash
109
150
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"
117
151
118
- # if we have a tag, use tag as reference
152
+ # if head is a tag, use tag as reference
119
153
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
130
157
131
- except subprocess .CalledProcessError :
132
- linkcode_revision = "master"
158
+ except subprocess .CalledProcessError :
159
+ return head
133
160
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
135
167
136
168
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' )
140
174
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