From 8587e57f1225e9171e6ef22a349c925ab9fa1da7 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 4 Jan 2021 16:39:27 -0800 Subject: [PATCH] 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 Part-of: --- bin/post_version.py | 37 ++++------ bin/post_version_test.py | 151 ++++++++------------------------------ docs/release-calendar.csv | 2 + docs/release-calendar.rst | 12 ++- docs/releasing.rst | 2 +- 5 files changed, 54 insertions(+), 150 deletions(-) create mode 100644 docs/release-calendar.csv diff --git a/bin/post_version.py b/bin/post_version.py index 359cbd9012c..c4779f143d7 100755 --- a/bin/post_version.py +++ b/bin/post_version.py @@ -22,6 +22,7 @@ """Update the main page, release notes, and calendar.""" import argparse +import csv import pathlib import subprocess @@ -52,30 +53,24 @@ def update_release_notes(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: - calendar = f.readlines() + with p.open('r') as f: + calendar = csv.reader(f) - branch = '' - skip_line = False - new_calendar = [] - for line in calendar: - if version in line: - branch = line.split('|')[1].strip() - skip_line = True - elif skip_line: - skip_line = False - 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) + branch = None + for i, line in enumerate(calendar): + if line[2] == version: + if line[0]: + branch = line[0] + break + if branch is not None: + calendar[i + 1][0] = branch + del calendar[i] - with open(p, 'w') as f: - for line in new_calendar: - f.write(line) + with p.open('w') as f: + writer = csv.writer(f) + writer.writerows(calendar) subprocess.run(['git', 'add', p]) diff --git a/bin/post_version_test.py b/bin/post_version_test.py index d0794a609f4..aa008bc4cc8 100644 --- a/bin/post_version_test.py +++ b/bin/post_version_test.py @@ -19,140 +19,49 @@ # SOFTWARE. from unittest import mock -import textwrap -from lxml import html import pytest from . import post_version -# Mock out subprocess.run to avoid having git commits @mock.patch('bin.post_version.subprocess.run', mock.Mock()) class TestUpdateCalendar: - HEAD = textwrap.dedent("""\ - - - - - Release Calendar - - - - """) - - TABLE = textwrap.dedent("""\ - - - - - - - - - """) - - FOOT = "" - - TABLE_FOOT = "
BranchExpected dateReleaseRelease managerNotes
" - - def wrap_table(self, table: str) -> str: - return self.HEAD + self.TABLE + table + self.TABLE_FOOT + self.FOOT + @pytest.fixture(autouse=True) + def mock_sideffects(self) -> None: + """Mock out side effects.""" + with mock.patch('bin.post_version.subprocess.run', mock.Mock()), \ + mock.patch('bin.post_version.pathlib', mock.MagicMock()): + yield def test_basic(self): - data = self.wrap_table(textwrap.dedent("""\ - - 19.2 - 2019-11-06 - 19.2.3 - Dylan Baker - - - 2019-11-20 - 19.2.4 - Dylan Baker - - - 2019-12-04 - 19.2.5 - Dylan Baker - Last planned 19.2.x release - - """)) + data = [ + ['20.3', '2021-01-13', '20.3.3', 'Dylan Baker', None], + [None, '2021-01-27', '20.3.4', 'Dylan Baker', None], + ] - parsed = html.fromstring(data) - parsed.write = mock.Mock() + m = 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', - mock.Mock(return_value=parsed)): - post_version.update_calendar('19.2.3') + m.writerows.assert_called_with([data[1]]) - assert len(parsed.findall('.//tr')) == 3 - # we need the second element becouse the first is the header + def test_two_releases(self): + 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] - tds = tr.findall('.//td') - assert tds[0].get("rowspan") == "2" - assert tds[0].text == "19.2" - assert tds[1].text == "2019-11-20" + m = 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') - @pytest.fixture - def two_releases(self) -> html.etree.ElementTree: - data = self.wrap_table(textwrap.dedent("""\ - - 19.1 - 2019-11-06 - 19.1.8 - Not Dylan Baker - - - 19.2 - 2019-11-06 - 19.2.3 - Dylan Baker - - - 2019-11-20 - 19.2.4 - Dylan Baker - - - 2019-12-04 - 19.2.5 - Dylan Baker - Last planned 19.2.x release - - """)) - - 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" + d = data.copy() + del d[0] + d[0][0] = '20.3' + m.writerows.assert_called_with(d) diff --git a/docs/release-calendar.csv b/docs/release-calendar.csv new file mode 100644 index 00000000000..9eec289f955 --- /dev/null +++ b/docs/release-calendar.csv @@ -0,0 +1,2 @@ +20.3, 2021-01-13, 20.3.3, Dylan Baker, + , 2021-01-27, 20.3.4, Dylan Baker, diff --git a/docs/release-calendar.rst b/docs/release-calendar.rst index 6181410d98a..4b4a854c799 100644 --- a/docs/release-calendar.rst +++ b/docs/release-calendar.rst @@ -27,10 +27,8 @@ nominate a patch in the next stable release. Calendar -------- -+--------+---------------+------------+-----------------+-----------------------------------------+ -| Branch | Expected date | Release | Release manager | Notes | -+========+===============+============+=================+=========================================+ -| 20.3 | 2021-01-13 | 20.3.3 | Dylan Baker | | -| +---------------+------------+-----------------+-----------------------------------------+ -| | 2021-01-27 | 20.3.4 | Dylan Baker | | -+--------+---------------+------------+-----------------+-----------------------------------------+ +.. csv-table:: Calendar + :header: "Branch", "Expected date", "Release", "Release manager", "Notes" + :file: release-calendar.csv + :stub-columns: 1 + :widths: auto diff --git a/docs/releasing.rst b/docs/releasing.rst index 49529f26506..b1f35c597d9 100644 --- a/docs/releasing.rst +++ b/docs/releasing.rst @@ -312,7 +312,7 @@ Then run the ./bin/post_version.py X.Y.Z , 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 push: