docs: store the release-calendar information in csv (and fix tests)
Restructured text (and markdown) is painful to programatically manipulate, most python parsers are geared towards writing markdown and generating html. I'd like to move the calendar updates to being scripted, as such using csv to store them will be convenient. This also allows us to simplify our scripting that manipulates the table considerably. Acked-by: Eric Engestrom <eric@engestrom.ch> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/8341>
This commit is contained in:
parent
e1b7c42cc6
commit
8587e57f12
|
@ -22,6 +22,7 @@
|
||||||
"""Update the main page, release notes, and calendar."""
|
"""Update the main page, release notes, and calendar."""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import csv
|
||||||
import pathlib
|
import pathlib
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
@ -52,30 +53,24 @@ def update_release_notes(version: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
def update_calendar(version: str) -> None:
|
def update_calendar(version: str) -> None:
|
||||||
p = pathlib.Path('docs') / 'release-calendar.rst'
|
p = pathlib.Path('docs') / 'release-calendar.csv'
|
||||||
|
|
||||||
with open(p, 'r') as f:
|
with p.open('r') as f:
|
||||||
calendar = f.readlines()
|
calendar = csv.reader(f)
|
||||||
|
|
||||||
branch = ''
|
branch = None
|
||||||
skip_line = False
|
for i, line in enumerate(calendar):
|
||||||
new_calendar = []
|
if line[2] == version:
|
||||||
for line in calendar:
|
if line[0]:
|
||||||
if version in line:
|
branch = line[0]
|
||||||
branch = line.split('|')[1].strip()
|
break
|
||||||
skip_line = True
|
if branch is not None:
|
||||||
elif skip_line:
|
calendar[i + 1][0] = branch
|
||||||
skip_line = False
|
del calendar[i]
|
||||||
elif branch:
|
|
||||||
# Put the branch number back on the next line
|
|
||||||
new_calendar.append(line[:2] + branch + line[len(branch) + 2:])
|
|
||||||
branch = ''
|
|
||||||
else:
|
|
||||||
new_calendar.append(line)
|
|
||||||
|
|
||||||
with open(p, 'w') as f:
|
with p.open('w') as f:
|
||||||
for line in new_calendar:
|
writer = csv.writer(f)
|
||||||
f.write(line)
|
writer.writerows(calendar)
|
||||||
|
|
||||||
subprocess.run(['git', 'add', p])
|
subprocess.run(['git', 'add', p])
|
||||||
|
|
||||||
|
|
|
@ -19,140 +19,49 @@
|
||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
import textwrap
|
|
||||||
|
|
||||||
from lxml import html
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from . import post_version
|
from . import post_version
|
||||||
|
|
||||||
|
|
||||||
# Mock out subprocess.run to avoid having git commits
|
|
||||||
@mock.patch('bin.post_version.subprocess.run', mock.Mock())
|
@mock.patch('bin.post_version.subprocess.run', mock.Mock())
|
||||||
class TestUpdateCalendar:
|
class TestUpdateCalendar:
|
||||||
|
|
||||||
HEAD = textwrap.dedent("""\
|
@pytest.fixture(autouse=True)
|
||||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
def mock_sideffects(self) -> None:
|
||||||
<html lang="en">
|
"""Mock out side effects."""
|
||||||
<head>
|
with mock.patch('bin.post_version.subprocess.run', mock.Mock()), \
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
mock.patch('bin.post_version.pathlib', mock.MagicMock()):
|
||||||
<title>Release Calendar</title>
|
yield
|
||||||
<link rel="stylesheet" type="text/css" href="mesa.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
""")
|
|
||||||
|
|
||||||
TABLE = textwrap.dedent("""\
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th>Branch</th>
|
|
||||||
<th>Expected date</th>
|
|
||||||
<th>Release</th>
|
|
||||||
<th>Release manager</th>
|
|
||||||
<th>Notes</th>
|
|
||||||
</tr>
|
|
||||||
""")
|
|
||||||
|
|
||||||
FOOT = "</body></html>"
|
|
||||||
|
|
||||||
TABLE_FOOT = "</table>"
|
|
||||||
|
|
||||||
def wrap_table(self, table: str) -> str:
|
|
||||||
return self.HEAD + self.TABLE + table + self.TABLE_FOOT + self.FOOT
|
|
||||||
|
|
||||||
def test_basic(self):
|
def test_basic(self):
|
||||||
data = self.wrap_table(textwrap.dedent("""\
|
data = [
|
||||||
<tr>
|
['20.3', '2021-01-13', '20.3.3', 'Dylan Baker', None],
|
||||||
<td rowspan="3">19.2</td>
|
[None, '2021-01-27', '20.3.4', 'Dylan Baker', None],
|
||||||
<td>2019-11-06</td>
|
]
|
||||||
<td>19.2.3</td>
|
|
||||||
<td>Dylan Baker</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>2019-11-20</td>
|
|
||||||
<td>19.2.4</td>
|
|
||||||
<td>Dylan Baker</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>2019-12-04</td>
|
|
||||||
<td>19.2.5</td>
|
|
||||||
<td>Dylan Baker</td>
|
|
||||||
<td>Last planned 19.2.x release</td>
|
|
||||||
</tr>
|
|
||||||
"""))
|
|
||||||
|
|
||||||
parsed = html.fromstring(data)
|
m = mock.Mock()
|
||||||
parsed.write = mock.Mock()
|
with mock.patch('bin.post_version.csv.reader', mock.Mock(return_value=data.copy())), \
|
||||||
|
mock.patch('bin.post_version.csv.writer', mock.Mock(return_value=m)):
|
||||||
|
post_version.update_calendar('20.3.3')
|
||||||
|
|
||||||
with mock.patch('bin.post_version.html.parse',
|
m.writerows.assert_called_with([data[1]])
|
||||||
mock.Mock(return_value=parsed)):
|
|
||||||
post_version.update_calendar('19.2.3')
|
|
||||||
|
|
||||||
assert len(parsed.findall('.//tr')) == 3
|
def test_two_releases(self):
|
||||||
# we need the second element becouse the first is the header
|
data = [
|
||||||
|
['20.3', '2021-01-13', '20.3.3', 'Dylan Baker', None],
|
||||||
|
[None, '2021-01-27', '20.3.4', 'Dylan Baker', None],
|
||||||
|
['21.0', '2021-01-13', '21.0.0', 'Dylan Baker', None],
|
||||||
|
[None, '2021-01-13', '21.0.1', 'Dylan Baker', None],
|
||||||
|
]
|
||||||
|
|
||||||
tr = parsed.findall('.//tr')[1]
|
m = mock.Mock()
|
||||||
tds = tr.findall('.//td')
|
with mock.patch('bin.post_version.csv.reader', mock.Mock(return_value=data.copy())), \
|
||||||
assert tds[0].get("rowspan") == "2"
|
mock.patch('bin.post_version.csv.writer', mock.Mock(return_value=m)):
|
||||||
assert tds[0].text == "19.2"
|
post_version.update_calendar('20.3.3')
|
||||||
assert tds[1].text == "2019-11-20"
|
|
||||||
|
|
||||||
@pytest.fixture
|
d = data.copy()
|
||||||
def two_releases(self) -> html.etree.ElementTree:
|
del d[0]
|
||||||
data = self.wrap_table(textwrap.dedent("""\
|
d[0][0] = '20.3'
|
||||||
<tr>
|
m.writerows.assert_called_with(d)
|
||||||
<td rowspan="1">19.1</td>
|
|
||||||
<td>2019-11-06</td>
|
|
||||||
<td>19.1.8</td>
|
|
||||||
<td>Not Dylan Baker</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="3">19.2</td>
|
|
||||||
<td>2019-11-06</td>
|
|
||||||
<td>19.2.3</td>
|
|
||||||
<td>Dylan Baker</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>2019-11-20</td>
|
|
||||||
<td>19.2.4</td>
|
|
||||||
<td>Dylan Baker</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>2019-12-04</td>
|
|
||||||
<td>19.2.5</td>
|
|
||||||
<td>Dylan Baker</td>
|
|
||||||
<td>Last planned 19.2.x release</td>
|
|
||||||
</tr>
|
|
||||||
"""))
|
|
||||||
|
|
||||||
p = html.fromstring(data)
|
|
||||||
p.write = mock.Mock()
|
|
||||||
return p
|
|
||||||
|
|
||||||
def test_two_releases(self, two_releases: html.etree.ElementTree):
|
|
||||||
with mock.patch('bin.post_version.html.parse',
|
|
||||||
mock.Mock(return_value=two_releases)):
|
|
||||||
post_version.update_calendar('19.2.3')
|
|
||||||
|
|
||||||
assert len(two_releases.findall('.//tr')) == 4
|
|
||||||
# we need the second element becouse the first is the header
|
|
||||||
|
|
||||||
tr = two_releases.findall('.//tr')[2]
|
|
||||||
tds = tr.findall('.//td')
|
|
||||||
assert tds[0].get("rowspan") == "2"
|
|
||||||
assert tds[0].text == "19.2"
|
|
||||||
assert tds[1].text == "2019-11-20"
|
|
||||||
|
|
||||||
def test_last_Release(self, two_releases: html.etree.ElementTree):
|
|
||||||
with mock.patch('bin.post_version.html.parse',
|
|
||||||
mock.Mock(return_value=two_releases)):
|
|
||||||
post_version.update_calendar('19.1.8')
|
|
||||||
|
|
||||||
assert len(two_releases.findall('.//tr')) == 4
|
|
||||||
# we need the second element becouse the first is the header
|
|
||||||
|
|
||||||
tr = two_releases.findall('.//tr')[1]
|
|
||||||
tds = tr.findall('.//td')
|
|
||||||
assert tds[0].get("rowspan") == "3"
|
|
||||||
assert tds[0].text == "19.2"
|
|
||||||
assert tds[1].text == "2019-11-06"
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
20.3, 2021-01-13, 20.3.3, Dylan Baker,
|
||||||
|
, 2021-01-27, 20.3.4, Dylan Baker,
|
|
|
@ -27,10 +27,8 @@ nominate a patch in the next stable release.
|
||||||
Calendar
|
Calendar
|
||||||
--------
|
--------
|
||||||
|
|
||||||
+--------+---------------+------------+-----------------+-----------------------------------------+
|
.. csv-table:: Calendar
|
||||||
| Branch | Expected date | Release | Release manager | Notes |
|
:header: "Branch", "Expected date", "Release", "Release manager", "Notes"
|
||||||
+========+===============+============+=================+=========================================+
|
:file: release-calendar.csv
|
||||||
| 20.3 | 2021-01-13 | 20.3.3 | Dylan Baker | |
|
:stub-columns: 1
|
||||||
| +---------------+------------+-----------------+-----------------------------------------+
|
:widths: auto
|
||||||
| | 2021-01-27 | 20.3.4 | Dylan Baker | |
|
|
||||||
+--------+---------------+------------+-----------------+-----------------------------------------+
|
|
||||||
|
|
|
@ -312,7 +312,7 @@ Then run the
|
||||||
./bin/post_version.py X.Y.Z
|
./bin/post_version.py X.Y.Z
|
||||||
|
|
||||||
, where X.Y.Z is the version you just made. This will update
|
, where X.Y.Z is the version you just made. This will update
|
||||||
docs/relnotes.rst and docs/release-calendar.rst. It will then generate
|
docs/relnotes.rst and docs/release-calendar.csv. It will then generate
|
||||||
a Git commit automatically. Check that everything looks correct and
|
a Git commit automatically. Check that everything looks correct and
|
||||||
push:
|
push:
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue