Skip to content

Commit 21d2d64

Browse files
committed
🚀 Only build release notes up to current version
1 parent f5189fa commit 21d2d64

File tree

2 files changed

+141
-54
lines changed

2 files changed

+141
-54
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ commands:
1515
command: |
1616
apt-get update && apt-get -y install git python-dev graphviz libgraphviz-dev pkg-config python3-sphinx
1717
pip install --user -r https://raw.githubusercontent.com/FCP-INDI/C-PAC/<< parameters.version >>/requirements.txt
18-
pip install --user git+https://github.com/${CIRCLE_PROJECT_USERNAME}/C-PAC.git@<< parameters.version >> sphinx m2r numpydoc PyGithub sphinxcontrib-fulltoc sphinxcontrib-programoutput torch
18+
pip install --user git+https://github.com/${CIRCLE_PROJECT_USERNAME}/C-PAC.git@<< parameters.version >> sphinx m2r numpydoc PyGithub sphinxcontrib-fulltoc sphinxcontrib-programoutput semver torch
1919
git clone https://github.com/${CIRCLE_PROJECT_USERNAME}/C-PAC.git /build/C-PAC
2020
cd /build/C-PAC
2121
git checkout << parameters.version >>

docs/_sources/conf.py

Lines changed: 140 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
import m2r
1515
import os
16+
import re
17+
import semver
1618
import sys
1719

1820
from CPAC import __version__
@@ -21,6 +23,76 @@
2123
from github.GithubException import RateLimitExceededException, \
2224
UnknownObjectException
2325

26+
# "Dealing with Invalid Versions" from
27+
# https://python-semver.readthedocs.io/en/latest/usage.html
28+
29+
30+
def coerce(version):
31+
"""
32+
Convert an incomplete version string into a semver-compatible VersionInfo
33+
object
34+
35+
* Tries to detect a "basic" version string (``major.minor.patch``).
36+
* If not enough components can be found, missing components are
37+
set to zero to obtain a valid semver version.
38+
39+
:param str version: the version string to convert
40+
:return: a tuple with a :class:`VersionInfo` instance (or ``None``
41+
if it's not a version) and the rest of the string which doesn't
42+
belong to a basic version.
43+
:rtype: tuple(:class:`VersionInfo` | None, str)
44+
"""
45+
BASEVERSION = re.compile(
46+
r"""[vV]?
47+
(?P<major>0|[1-9]\d*)
48+
(\.
49+
(?P<minor>0|[1-9]\d*)
50+
(\.
51+
(?P<patch>0|[1-9]\d*)
52+
)?
53+
)?
54+
""",
55+
re.VERBOSE,
56+
)
57+
58+
match = BASEVERSION.search(version)
59+
if not match:
60+
return (None, version)
61+
62+
ver = {
63+
key: 0 if value is None else value for key, value in match.groupdict().items()
64+
}
65+
ver = semver.VersionInfo(**ver)
66+
rest = match.string[match.end() :] # noqa:E203
67+
return ver, rest
68+
69+
70+
def compare_versions(new, old):
71+
"""
72+
Function to compare two versions.
73+
74+
Parameters
75+
----------
76+
new: str
77+
78+
old: str
79+
80+
Returns
81+
-------
82+
bool
83+
Is the "new" at least as new as "old"?
84+
"""
85+
comparisons = list(zip(coerce(new), coerce(old)))
86+
if any([v is None for v in comparisons[0]]):
87+
return(False)
88+
outright = semver.compare(str(comparisons[0][0]), str(comparisons[0][1]))
89+
return (
90+
bool(outright == 1) or bool(
91+
(outright == 0) and comparisons[1][0] >= comparisons[1][1]
92+
)
93+
)
94+
95+
2496
# If extensions (or modules to document with autodoc) are in another directory,
2597
# add these directories to sys.path here. If the directory is relative to the
2698
# documentation root, use os.path.abspath to make it absolute, like shown here.
@@ -74,7 +146,7 @@
74146

75147
# Get tags from GitHub
76148
# Set GITHUBTOKEN to your API token in your environment to increase rate limit.
77-
g = Github(os.environ.get("GITHUBTOKEN"))
149+
g = Github(os.environ.get('GITHUBTOKEN'))
78150

79151
def _gh_rate_limit():
80152
print("""Release notes not updated due to GitHub API rate limit.
@@ -100,21 +172,32 @@ def _gh_rate_limit():
100172
""")
101173

102174
try:
103-
gh_cpac = g.get_user("FCP-INDI").get_repo("C-PAC")
175+
gh_cpac = g.get_user('FCP-INDI').get_repo('C-PAC')
104176
gh_tags = [t.name for t in gh_cpac.get_tags()]
105177
except RateLimitExceededException:
106178
_gh_rate_limit()
107179
gh_tags = []
108180
gh_tags.sort(reverse=True)
109181

182+
build_version_path = os.path.abspath(os.path.join(
183+
__file__, os.pardir, os.pardir, os.pardir, 'build_version.txt'
184+
))
185+
# don't build release notes for newer releases
186+
if os.path.exists(build_version_path):
187+
with open(build_version_path, 'r') as bvf:
188+
build_version = bvf.read().strip()
189+
gh_tags = [gh_tag for gh_tag in gh_tags if compare_versions(
190+
build_version, gh_tag
191+
)]
192+
110193
# Try to get release notes from GitHub
111194
try:
112195
gh_releases = []
113196
for t in gh_tags:
114197
try:
115198
gh_releases.append(gh_cpac.get_release(t).raw_data)
116199
except (AttributeError, UnknownObjectException):
117-
print(f"No notes for {t}")
200+
print(f'No notes for {t}')
118201
gh_releaseNotes = {r['tag_name']: {
119202
'name': r['name'],
120203
'body': r['body'],
@@ -124,10 +207,13 @@ def _gh_rate_limit():
124207
_gh_rate_limit()
125208
gh_releaseNotes = {
126209
t: {
127-
"name": t,
128-
"body": f"See https://github.com/FCP-INDI/C-PAC/releases/tag/{t} for "
129-
"release notes.",
130-
"published_at": None
210+
'name': t,
211+
'body': ''.join([
212+
'See https://github.com/FCP-INDI/C-PAC/releases/tag/',
213+
t,
214+
' for release notes.'
215+
]),
216+
'published_at': None
131217
} for t in gh_tags
132218
}
133219

@@ -141,66 +227,66 @@ def _unireplace(release_note, unireplace):
141227
e2 = str(e[2:])
142228
release_note = release_note.replace(
143229
e,
144-
f" |u{e2}| "
230+
f' |u{e2}| '
145231
)
146232
unireplace[e2] = e
147233
return(_unireplace(release_note, unireplace))
148234
return(
149235
release_note,
150-
"\n\n".join([
151-
f".. |u{u}| unicode:: {v}"
236+
'\n\n'.join([
237+
f'.. |u{u}| unicode:: {v}'
152238
for u, v in list(unireplace.items())])
153239
)
154240

155241
this_dir = os.path.dirname(os.path.abspath(__file__))
156-
release_notes_dir = os.path.join(this_dir, "user", "release_notes")
242+
release_notes_dir = os.path.join(this_dir, 'user', 'release_notes')
157243
if not os.path.exists(release_notes_dir):
158244
os.makedirs(release_notes_dir)
159-
latest_path = os.path.join(release_notes_dir, "latest.rst")
245+
latest_path = os.path.join(release_notes_dir, 'latest.rst')
160246
# all_release_notes = ""
161247
for t in gh_tags:
162248
if t in gh_releaseNotes:
163-
tag_header = "{}{}{}".format(
164-
"Latest Release: " if t==gh_tags[0] else "",
249+
tag_header = '{}{}{}'.format(
250+
'Latest Release: ' if t==gh_tags[0] else '',
165251
(
166252
gh_releaseNotes[t]['name'][4:] if (
167-
gh_releaseNotes[t]['name'].startswith("CPAC")
253+
gh_releaseNotes[t]['name'].startswith('CPAC')
168254
) else gh_releaseNotes[t]['name'][5:] if (
169-
gh_releaseNotes[t]['name'].startswith("C-PAC")
255+
gh_releaseNotes[t]['name'].startswith('C-PAC')
170256
) else gh_releaseNotes[t]['name']
171257
).strip(),
172-
" ({})".format(
258+
' ({})'.format(
173259
dparser.parse(gh_releaseNotes[t]['published_at']).date(
174-
).strftime("%b %w, %Y")
175-
) if gh_releaseNotes[t]['published_at'] else ""
260+
).strftime('%b %d, %Y')
261+
) if gh_releaseNotes[t]['published_at'] else ''
176262
)
177-
release_note = "\n".join(_unireplace(
263+
release_note = '\n'.join(_unireplace(
178264
"""{}
179265
{}
180266
{}
181267
""".format(
182268
tag_header,
183-
"^"*len(tag_header),
269+
'^'*len(tag_header),
184270
m2r.convert(gh_releaseNotes[t]['body'].encode(
185-
"ascii",
186-
errors="backslashreplace"
187-
).decode("utf-8"))
271+
'ascii',
272+
errors='backslashreplace'
273+
).decode('utf-8'))
188274
),
189275
{}
190276
))
191277

192-
release_notes_path = os.path.join(release_notes_dir, "{}.rst".format(t))
278+
release_notes_path = os.path.join(release_notes_dir, f'{t}.rst')
193279
if gh_releaseNotes[t]['published_at'] and not os.path.exists(
194280
release_notes_path
195281
) and not os.path.exists(
196-
os.path.join(release_notes_dir, "v{}.rst".format(t))
282+
os.path.join(release_notes_dir, f'v{t}.rst')
197283
):
198284
with open(release_notes_path, 'w+') as f:
199285
f.write(release_note)
200286
else:
201287
print(release_notes_path)
202288

203-
if tag_header.startswith("Latest") and not os.path.exists(latest_path):
289+
if tag_header.startswith('Latest') and not os.path.exists(latest_path):
204290
with open(latest_path, 'w+') as f:
205291
f.write(
206292
"""
@@ -216,8 +302,8 @@ def _unireplace(release_note, unireplace):
216302

217303
rnd = [
218304
d for d in os.listdir(release_notes_dir) if d not in [
219-
"index.rst",
220-
"latest.rst"
305+
'index.rst',
306+
'latest.rst'
221307
]
222308
]
223309
rnd.sort(key=sort_tag, reverse=True)
@@ -231,18 +317,18 @@ def _unireplace(release_note, unireplace):
231317
{}
232318
233319
""".format(
234-
"\n".join([
235-
".. include:: /user/release_notes/{}".format(fp) for fp in rnd
320+
'\n'.join([
321+
f'.. include:: /user/release_notes/{fp}' for fp in rnd
236322
]),
237-
"\n ".join([
238-
"/user/release_notes/{}".format(d) for d in rnd
323+
'\n '.join([
324+
f'/user/release_notes/{d}' for d in rnd
239325
]))
240326
with open(os.path.join(release_notes_dir, 'index.rst'), 'w+') as f:
241327
f.write(all_release_notes.strip())
242328

243329

244330
# The full version, including alpha/beta/rc tags.
245-
release = '{} Beta'.format(__version__)
331+
release = f'{__version__} Beta'
246332

247333
# The language for content autogenerated by Sphinx. Refer to documentation
248334
# for a list of supported languages.
@@ -256,7 +342,7 @@ def _unireplace(release_note, unireplace):
256342

257343
# List of patterns, relative to source directory, that match files and
258344
# directories to ignore when looking for source files.
259-
exclude_patterns = ["futuredocs/*"]
345+
exclude_patterns = ['futuredocs/*']
260346

261347
# The reST default role (used for this markup: `text`) to use for all documents.
262348
#default_role = None
@@ -289,40 +375,40 @@ def _unireplace(release_note, unireplace):
289375
# further. For a list of options available for each theme, see the
290376
# documentation.
291377
html_theme_options = {
292-
"relbarbgcolor": "#0067a0",
293-
"sidebarbgcolor": "#f0f0f0",
294-
"sidebartextcolor": "#000000",
295-
"sidebarlinkcolor": "#0067a0",
296-
"headbgcolor": "#919d9d",
297-
"headtextcolor": "#e4e4e4"
378+
'relbarbgcolor': '#0067a0',
379+
'sidebarbgcolor': '#f0f0f0',
380+
'sidebartextcolor': '#000000',
381+
'sidebarlinkcolor': '#0067a0',
382+
'headbgcolor': '#919d9d',
383+
'headtextcolor': '#e4e4e4'
298384
}
299385

300386
# Add any paths that contain custom themes here, relative to this directory.
301-
html_theme_path = ["../Themes"]
387+
html_theme_path = ['../Themes']
302388

303389
html_css_files = [
304390
'custom.css',
305391
]
306392

307393
# The name for this set of Sphinx documents. If None, it defaults to
308-
# "<project> v<release> documentation".
394+
# '<project> v<release> documentation'.
309395
#html_title = None
310396

311397
# A shorter title for the navigation bar. Default is the same as html_title.
312398
#html_short_title = None
313399

314400
# The name of an image file (relative to this directory) to place at the top
315401
# of the sidebar.
316-
html_logo = "_static/cpac_logo_vertical.png"
402+
html_logo = '_static/cpac_logo_vertical.png'
317403

318404
# The name of an image file (within the static path) to use as favicon of the
319405
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
320406
# pixels large.
321-
html_favicon = "favicon.ico"
407+
html_favicon = 'favicon.ico'
322408

323409
# Add any paths that contain custom static files (such as style sheets) here,
324410
# relative to this directory. They are copied after the builtin static files,
325-
# so a file named "default.css" will overwrite the builtin "default.css".
411+
# so a file named 'default.css' will overwrite the builtin 'default.css'.
326412
html_static_path = ['_static']
327413

328414
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
@@ -358,23 +444,23 @@ def _unireplace(release_note, unireplace):
358444
# If true, links to the reST sources are added to the pages.
359445
#html_show_sourcelink = True
360446

361-
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
447+
# If true, 'Created using Sphinx' is shown in the HTML footer. Default is True.
362448
#html_show_sphinx = True
363449

364-
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
450+
# If true, '(C) Copyright ...' is shown in the HTML footer. Default is True.
365451
#html_show_copyright = True
366452

367453
# If true, an OpenSearch description file will be output, and all pages will
368454
# contain a <link> tag referring to it. The value of this option must be the
369455
# base URL from which the finished HTML is served.
370456
#html_use_opensearch = ''
371457

372-
# This is the file name suffix for HTML files (e.g. ".xhtml").
373-
html_file_suffix = ".html"
458+
# This is the file name suffix for HTML files (e.g. '.xhtml').
459+
html_file_suffix = '.html'
374460

375461
# Suffix for generated links to HTML files
376-
html_link_suffix = ""
377-
link_suffix = ""
462+
html_link_suffix = ''
463+
link_suffix = ''
378464

379465
# Output file base name for HTML help builder.
380466
htmlhelp_basename = 'C-PACdoc'
@@ -404,7 +490,7 @@ def _unireplace(release_note, unireplace):
404490
# the title page.
405491
#latex_logo = None
406492

407-
# For "manual" documents, if this is true, then toplevel headings are parts,
493+
# For 'manual' documents, if this is true, then toplevel headings are parts,
408494
# not chapters.
409495
#latex_use_parts = False
410496

@@ -461,3 +547,4 @@ def _unireplace(release_note, unireplace):
461547
""".format(
462548
versions=", ".join(gh_tags[:5])
463549
)
550+

0 commit comments

Comments
 (0)