You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2026-10-23 21:28:56 +03:00
Add expandable details to the release notes.
Release items from 2.60 on put the paragraphs after the first and the links to their issues, pull requests, and commits in a details element that is closed by default. The toggle is drawn in place of the bullet: a plus when the details have text and a chevron when they have only links. An item with more than one commit has a line for each commit with its subject. A button on each release opens or closes all of its details. Older releases still join extra paragraphs into one line. The version pattern for bug fix releases also matched x.y.0, so the commits of a release reached back to v2.53, and 2.54.0 through 2.59.0 were only checked while each was the most recent release. A bug fix release is now x.y.z with z > 0, so a release keeps its commit links after a newer release is added. The 2.55.0 PostgreSQL 18 item now has a commit element because the note was reworded after the commit. The GCS Workload Identity Federation item now has a commit element because its text does not match the commit subject.
This commit is contained in:
@@ -7,6 +7,7 @@ Each item is checked against the git history: a release item with no commit behi
|
||||
commit whose subject no longer matches what the release reports it did."""
|
||||
|
||||
####################################################################################################################################
|
||||
import html
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@@ -59,7 +60,21 @@ _CONTRIBUTOR_LIST = (
|
||||
("release-item-ideator", "Reported", "Suggested"),
|
||||
)
|
||||
|
||||
_VERSION_BUG_FIX_EXP = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+$")
|
||||
# Links to an issue and to a pull request, in the order they are listed
|
||||
_REF_LIST = (
|
||||
("github-issue", "Issue", "{[github-url-issues]}/%s"),
|
||||
("github-pull-request", "Pull request", "{[github-url-base]}/pull/%s"),
|
||||
)
|
||||
|
||||
# Link to a commit and the length of the hash it is named by
|
||||
_COMMIT_URL = "{[github-url-base]}/commit/%s"
|
||||
_COMMIT_SIZE = 7
|
||||
|
||||
# Releases with version >= this have the paragraphs after the first and the links of an item in details
|
||||
_DETAIL_VERSION_MIN = "2.60"
|
||||
|
||||
# A bug fix release is x.y.z with z > 0
|
||||
_VERSION_BUG_FIX_EXP = re.compile(r"^[0-9]+\.[0-9]+\.[1-9][0-9]*$")
|
||||
_VERSION_DATE_EXP = re.compile(r"^(XXXX-XX-XX)|([0-9]{4}-[0-9]{2}-[0-9]{2})$")
|
||||
|
||||
|
||||
@@ -317,34 +332,110 @@ class _Release:
|
||||
return commit_list, remaining_list, commit_map, check
|
||||
|
||||
################################################################################################################################
|
||||
def _item_check(self, item, text, version, commit_list, remaining_list, commit_map):
|
||||
"""Check that every commit a release item claims is in the history of the release."""
|
||||
def _item_commit(self, item, text, version, commit_list, remaining_list, commit_map, check):
|
||||
"""Find every commit a release item claims in the history of the release.
|
||||
|
||||
Each commit is returned with the commit element that names it. The commit element is None when the item names no commit and
|
||||
the commit is None when it is not found. A commit that is not found is an error when check is true."""
|
||||
|
||||
result = []
|
||||
commit_xml_list = xml_node_child_list(item, "commit")
|
||||
|
||||
# An item with no commit named uses what it says as the subject to look for
|
||||
if len(commit_xml_list) == 0:
|
||||
subject = self.render.process_text(text)
|
||||
commit = self._commit_find(commit_list, subject, False)
|
||||
|
||||
if self._commit_find(commit_list, subject, False) is None:
|
||||
if commit is None and check:
|
||||
self._commit_error(
|
||||
"unable to find commit or no subject match for release %s item '%s'" % (version, subject),
|
||||
remaining_list,
|
||||
commit_map,
|
||||
)
|
||||
|
||||
result.append((None, commit))
|
||||
|
||||
for commit_xml in commit_xml_list:
|
||||
subject = xml_node_attribute(commit_xml, "subject", True)
|
||||
commit = self._commit_find(commit_list, subject, False)
|
||||
|
||||
if commit is None:
|
||||
self._commit_error(
|
||||
"unable to find release %s commit subject '%s' in list" % (version, subject), remaining_list, commit_map
|
||||
)
|
||||
|
||||
if commit["commit"] in remaining_list:
|
||||
if check:
|
||||
self._commit_error(
|
||||
"unable to find release %s commit subject '%s' in list" % (version, subject), remaining_list, commit_map
|
||||
)
|
||||
elif commit["commit"] in remaining_list:
|
||||
remaining_list.remove(commit["commit"])
|
||||
|
||||
result.append((commit_xml, commit))
|
||||
|
||||
return result
|
||||
|
||||
################################################################################################################################
|
||||
@staticmethod
|
||||
def _ref_add(ref, label, url, value, code=False):
|
||||
"""Add a link to an issue, a pull request, or a commit."""
|
||||
|
||||
ref_item = xml_node_add(ref, "list-detail-ref-item")
|
||||
|
||||
if label is not None:
|
||||
xml_node_content_add(ref_item, "%s " % label)
|
||||
|
||||
link = xml_node_add(ref_item, "link", {"url": url})
|
||||
xml_node_content_add(xml_node_add(link, "code") if code else link, value)
|
||||
|
||||
return ref_item
|
||||
|
||||
################################################################################################################################
|
||||
def _ref_list_add(self, ref, node):
|
||||
"""Add a link to each issue and pull request a node names."""
|
||||
|
||||
for type, label, url in _REF_LIST:
|
||||
for ref_xml in xml_node_child_list(node, type):
|
||||
id = xml_node_attribute(ref_xml, "id", True)
|
||||
self._ref_add(ref, label, url % id, "#%s" % id)
|
||||
|
||||
################################################################################################################################
|
||||
def _detail_add(self, list_item, item, item_commit_list):
|
||||
"""Add the paragraphs after the first and the issues, pull requests, and commits of an item as details.
|
||||
|
||||
An item with one commit has the commit on the same line as its issues and pull requests, after them. An item with more than
|
||||
one commit has a line for each commit, with the subject of the commit and the issues and pull requests the commit element
|
||||
names."""
|
||||
|
||||
detail = xml_node_add(list_item, "list-detail")
|
||||
|
||||
for paragraph in xml_node_child_list(item, "p")[1:]:
|
||||
xml_node_child_add(xml_node_add(detail, "list-detail-text"), paragraph)
|
||||
|
||||
ref = xml_node_add(detail, "list-detail-ref")
|
||||
self._ref_list_add(ref, item)
|
||||
|
||||
for commit_xml, commit in item_commit_list:
|
||||
url = None if commit is None else _COMMIT_URL % commit["commit"]
|
||||
hash = None if commit is None else commit["commit"][:_COMMIT_SIZE]
|
||||
|
||||
if len(item_commit_list) > 1:
|
||||
ref = xml_node_add(detail, "list-detail-ref")
|
||||
|
||||
# Escape the subject for html
|
||||
if commit is not None:
|
||||
xml_node_content_add(self._ref_add(ref, None, url, hash, True), " %s" % html.escape(commit["subject"]))
|
||||
|
||||
if commit_xml is not None:
|
||||
self._ref_list_add(ref, commit_xml)
|
||||
|
||||
if len(item_commit_list) == 1 and commit is not None:
|
||||
self._ref_add(ref, "Commit", url, hash, True)
|
||||
|
||||
# Remove lines with no links and details with nothing in them
|
||||
for ref in xml_node_child_list(detail, "list-detail-ref"):
|
||||
if len(ref) == 0:
|
||||
detail.remove(ref)
|
||||
|
||||
if len(detail) == 0:
|
||||
list_item.remove(detail)
|
||||
|
||||
################################################################################################################################
|
||||
def render_document(self):
|
||||
"""Build the release document."""
|
||||
@@ -436,6 +527,7 @@ class _Release:
|
||||
"""Render what one release changed."""
|
||||
|
||||
note = False
|
||||
detail = version >= _DETAIL_VERSION_MIN
|
||||
|
||||
for section_type, section_title in _SECTION_LIST:
|
||||
type_node = xml_node_child(release, section_type)
|
||||
@@ -470,14 +562,19 @@ class _Release:
|
||||
|
||||
for item in xml_node_child_list(item_node, "release-item"):
|
||||
text = xml_node_child_list(item, "p")[0]
|
||||
item_check = check and item_type != "release-development-list"
|
||||
item_commit_list = None
|
||||
|
||||
if check and item_type != "release-development-list":
|
||||
self._item_check(item, text, version, commit_list, remaining_list, commit_map)
|
||||
if item_check or detail:
|
||||
item_commit_list = self._item_commit(
|
||||
item, text, version, commit_list, remaining_list, commit_map, item_check
|
||||
)
|
||||
|
||||
# An item written as more than one paragraph reads as one line in the release notes
|
||||
for extra in xml_node_child_list(item, "p")[1:]:
|
||||
xml_node_content_add(text, " ")
|
||||
xml_node_child_add(text, extra)
|
||||
# An item written as more than one paragraph reads as one line in the release notes when version < 2.60
|
||||
if not detail:
|
||||
for extra in xml_node_child_list(item, "p")[1:]:
|
||||
xml_node_content_add(text, " ")
|
||||
xml_node_child_add(text, extra)
|
||||
|
||||
contributor_text = self._contributor_text(item, item_name)
|
||||
|
||||
@@ -486,7 +583,11 @@ class _Release:
|
||||
xml_node_content_add(xml_node_add(text, "i"), contributor_text)
|
||||
xml_node_content_add(text, ")")
|
||||
|
||||
xml_node_child_add(xml_node_text_add(xml_node_add(list_node, "list-item")), text)
|
||||
list_item = xml_node_text_add(xml_node_add(list_node, "list-item"))
|
||||
xml_node_child_add(list_item, text)
|
||||
|
||||
if detail:
|
||||
self._detail_add(list_item, item, item_commit_list)
|
||||
|
||||
|
||||
####################################################################################################################################
|
||||
|
||||
@@ -30,7 +30,7 @@ SECTION_ANCHOR = "anchor"
|
||||
SECTION_ANCHOR_NO_INHERIT = "no-inherit"
|
||||
|
||||
# Tags whose content is text and markup rather than markup alone, so what they hold is rendered as text
|
||||
_TAG_TEXT = ("admonition", "code-block", "list-item", "p", "summary", "title")
|
||||
_TAG_TEXT = ("admonition", "code-block", "list-detail-ref-item", "list-detail-text", "list-item", "p", "summary", "title")
|
||||
|
||||
# Tags that are content however little they hold, so they are never read as a property of the node holding them
|
||||
_TAG_CONTENT = _TAG_TEXT + ("table-cell", "table-column")
|
||||
@@ -102,6 +102,13 @@ _TAG_RENDER = {
|
||||
"i": ("<i>", "</i>"),
|
||||
"id": ('<span class="id">', "</span>"),
|
||||
"list": ('<ul class="list-unordered">', "</ul>"),
|
||||
"list-detail": (
|
||||
'\n<details class="list-detail"><summary class="list-detail-toggle" title="Details" aria-label="Details"></summary>',
|
||||
"</details>",
|
||||
),
|
||||
"list-detail-ref": ('\n<div class="list-detail-ref">', "</div>"),
|
||||
"list-detail-ref-item": ('<span class="list-detail-ref-item">', "</span>"),
|
||||
"list-detail-text": ('\n<div class="list-detail-text">', "</div>"),
|
||||
"list-item": ('<li class="list-unordered">', "</li>"),
|
||||
"p": ("", ""),
|
||||
"param": ('<span class="br-option">', "</span>"),
|
||||
|
||||
@@ -747,6 +747,158 @@ li.list-unordered::marker
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
List Details
|
||||
|
||||
An item with details has a toggle in place of the bullet. The toggle is a plus when the details have text and a chevron when they
|
||||
have only links.
|
||||
***********************************************************************************************************************************/
|
||||
li.list-unordered:has(> .list-detail)
|
||||
{
|
||||
position: relative;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
/* The toggle fills the space the bullet is drawn in, level with the first line of the item */
|
||||
.list-detail-toggle
|
||||
{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -1.4rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.3rem;
|
||||
height: 1.7em;
|
||||
border-radius: var(--radius);
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.list-detail-toggle::-webkit-details-marker
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.list-detail-toggle:hover
|
||||
{
|
||||
background-color: var(--accent-soft);
|
||||
color: var(--accent-hover);
|
||||
}
|
||||
|
||||
.list-detail-toggle:focus-visible
|
||||
{
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* A chevron that points right when closed and down when open */
|
||||
.list-detail-toggle::before
|
||||
{
|
||||
content: "";
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-right: 1.5px solid currentColor;
|
||||
border-bottom: 1.5px solid currentColor;
|
||||
transform: translateX(-1px) rotate(-45deg);
|
||||
}
|
||||
|
||||
.list-detail[open] > .list-detail-toggle::before
|
||||
{
|
||||
transform: translateY(-2px) rotate(45deg);
|
||||
}
|
||||
|
||||
/* A plus that is a minus when open */
|
||||
.list-detail:has(> .list-detail-text) > .list-detail-toggle
|
||||
{
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.list-detail:has(> .list-detail-text) > .list-detail-toggle::before
|
||||
{
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border: none;
|
||||
background:
|
||||
linear-gradient(currentColor, currentColor) center / 100% 2px no-repeat,
|
||||
linear-gradient(currentColor, currentColor) center / 2px 100% no-repeat;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.list-detail[open]:has(> .list-detail-text) > .list-detail-toggle::before
|
||||
{
|
||||
background: linear-gradient(currentColor, currentColor) center / 100% 2px no-repeat;
|
||||
}
|
||||
|
||||
.list-detail[open]
|
||||
{
|
||||
margin: .35rem 0 .25rem 0;
|
||||
border-left: 2px solid var(--border);
|
||||
padding-left: .75rem;
|
||||
}
|
||||
|
||||
.list-detail-text + .list-detail-text
|
||||
{
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
.list-detail-text + .list-detail-ref
|
||||
{
|
||||
margin-top: .4rem;
|
||||
}
|
||||
|
||||
.list-detail-ref
|
||||
{
|
||||
color: var(--text-muted);
|
||||
font-size: .875rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* A dot between the links on a line */
|
||||
.list-detail-ref-item + .list-detail-ref-item::before
|
||||
{
|
||||
content: "\00b7";
|
||||
margin: 0 .45em;
|
||||
}
|
||||
|
||||
/* The hash of a commit is drawn in the code font without the box of a keyword */
|
||||
.list-detail-ref .id
|
||||
{
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
font-size: .9375em;
|
||||
}
|
||||
|
||||
/* The button that opens or closes every details of a section, which the script adds */
|
||||
.list-detail-all
|
||||
{
|
||||
display: block;
|
||||
margin-top: .5rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background-color: var(--bg-surface);
|
||||
padding: .1rem .65rem;
|
||||
color: var(--text-muted);
|
||||
font-family: inherit;
|
||||
font-size: .8125rem;
|
||||
line-height: 1.6;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.list-detail-all:hover
|
||||
{
|
||||
border-color: var(--border-strong);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.list-detail-all:focus-visible
|
||||
{
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Table Elements
|
||||
***********************************************************************************************************************************/
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/***********************************************************************************************************************************
|
||||
Documentation Script
|
||||
|
||||
Four things that a page reads better with and works without. A browser that runs no script loses them and loses nothing it needs:
|
||||
Five things that a page reads better with and works without. A browser that runs no script loses them and loses nothing it needs:
|
||||
the contents are links and work as links, every block that can scroll is already reachable from the keyboard, a menu too wide for
|
||||
the window scrolls whether or not it says so, and the button that copies a command or a config file is not shown until there is a
|
||||
script to make it work.
|
||||
the window scrolls whether or not it says so, the button that copies a command or a config file is not shown until there is a
|
||||
script to make it work, and each details of a list still opens on its own.
|
||||
|
||||
The first marks the section being read in the contents beside the text, so a long page says where the reader is rather than only
|
||||
where they can go. The section being read is the last one that begins above the menu bar, which is what a reader means by the
|
||||
@@ -381,3 +381,77 @@ cannot copy never shows it at all, since a button that does nothing is worse tha
|
||||
for (var buttonIdx = 0; buttonIdx < buttonList.length; buttonIdx++)
|
||||
ready(buttonList[buttonIdx]);
|
||||
})();
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Opening every details of a section
|
||||
|
||||
A section with details in its lists has a button after its subtitles that opens every details of the section and then closes them
|
||||
when pressed again. Opening or closing one details does not change the button. The button is added here, so a page with no script
|
||||
has no button.
|
||||
***********************************************************************************************************************************/
|
||||
(function()
|
||||
{
|
||||
var SECTION = ".section1, .section2, .section3";
|
||||
|
||||
var sectionList = document.querySelectorAll(SECTION);
|
||||
|
||||
/*******************************************************************************************************************************
|
||||
Add the button to a section
|
||||
*******************************************************************************************************************************/
|
||||
function ready(section, detailList)
|
||||
{
|
||||
var button = document.createElement("button");
|
||||
|
||||
// Did the button last open the details?
|
||||
var open = false;
|
||||
|
||||
button.type = "button";
|
||||
button.className = "list-detail-all";
|
||||
|
||||
// The label is what pressing the button does
|
||||
function label()
|
||||
{
|
||||
button.textContent = open ? "Hide details" : "Show details";
|
||||
}
|
||||
|
||||
function toggle()
|
||||
{
|
||||
open = !open;
|
||||
|
||||
for (var idx = 0; idx < detailList.length; idx++)
|
||||
detailList[idx].open = open;
|
||||
|
||||
label();
|
||||
}
|
||||
|
||||
button.addEventListener("click", toggle);
|
||||
|
||||
label();
|
||||
|
||||
// After the subtitles of the section, or first in its body when it has none
|
||||
var body = section.querySelector(":scope > .section-body");
|
||||
var subtitleList = body.querySelectorAll(":scope > [class$='subtitle']");
|
||||
|
||||
if (subtitleList.length > 0)
|
||||
subtitleList[subtitleList.length - 1].after(button);
|
||||
else
|
||||
body.prepend(button);
|
||||
}
|
||||
|
||||
for (var sectionIdx = 0; sectionIdx < sectionList.length; sectionIdx++)
|
||||
{
|
||||
var section = sectionList[sectionIdx];
|
||||
var detailAll = section.querySelectorAll(".list-detail");
|
||||
var detailList = [];
|
||||
|
||||
// Details in a section below this one belong to that section
|
||||
for (var detailIdx = 0; detailIdx < detailAll.length; detailIdx++)
|
||||
{
|
||||
if (detailAll[detailIdx].closest(SECTION) === section)
|
||||
detailList.push(detailAll[detailIdx]);
|
||||
}
|
||||
|
||||
if (detailList.length > 0)
|
||||
ready(section, detailList);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -93,7 +93,9 @@
|
||||
</release-item>
|
||||
|
||||
<release-item>
|
||||
<github-pull-request id="2603"/>
|
||||
<commit subject="PostgreSQL 18 experimental support.">
|
||||
<github-pull-request id="2603"/>
|
||||
</commit>
|
||||
|
||||
<release-item-contributor-list>
|
||||
<release-item-contributor id="david.steele"/>
|
||||
|
||||
@@ -22,8 +22,10 @@
|
||||
</release-item>
|
||||
|
||||
<release-item>
|
||||
<github-issue id="2838"/>
|
||||
<github-pull-request id="2839"/>
|
||||
<commit subject="Add web-id key type for GCS Workload Identity Federation.">
|
||||
<github-issue id="2838"/>
|
||||
<github-pull-request id="2839"/>
|
||||
</commit>
|
||||
|
||||
<release-item-contributor-list>
|
||||
<release-item-contributor id="adol.rodriguez"/>
|
||||
@@ -249,6 +251,7 @@
|
||||
<commit subject="Fade an end the menu carries on past rather than cutting it flat."/>
|
||||
<commit subject="Put the bar of a section2 header under the title rather than beside it."/>
|
||||
<commit subject="Center the page title beside the logo and left align it when wrapped."/>
|
||||
<commit subject="Add expandable details to the release notes."/>
|
||||
|
||||
<release-item-contributor-list>
|
||||
<release-item-contributor id="david.steele"/>
|
||||
|
||||
@@ -15,6 +15,7 @@ from common.exec import exec_one
|
||||
from common.storage import file_read, file_write
|
||||
from common.xml import xml_node_attribute, xml_node_child, xml_node_child_list, xml_node_content, xml_parse
|
||||
from command.render.release import release_current_stable, release_history_update, release_last, release_list, release_render
|
||||
from command.render.render import DocRender
|
||||
|
||||
# The history a release is checked against, newest first, which is the order git writes it in
|
||||
HISTORY = [
|
||||
@@ -135,6 +136,85 @@ RELEASE_LIST = """
|
||||
"""
|
||||
|
||||
|
||||
# Releases with details and the history they are checked against
|
||||
DETAIL_HISTORY = [
|
||||
("d0", "Improve speed."),
|
||||
("d1", "Add a feature."),
|
||||
("d2", "Fix a bug."),
|
||||
("d3", 'Fix a "quoted" <bug>.'),
|
||||
("d4", "Begin v2.60.0 development."),
|
||||
("d5", "v2.59.0: Prior."),
|
||||
("d6", "Improve a thing."),
|
||||
("d7", "v1.00: Initial."),
|
||||
]
|
||||
|
||||
DETAIL_RELEASE_LIST = """
|
||||
<release date="2026-10-01" version="2.60.0" title="Detail">
|
||||
<release-core-list>
|
||||
<release-feature-list>
|
||||
<release-item>
|
||||
<github-issue id="1"/>
|
||||
<github-pull-request id="2"/>
|
||||
|
||||
<p>Add a feature.</p>
|
||||
|
||||
<p>Say more about it.</p>
|
||||
|
||||
<p>And <id>more</id>.</p>
|
||||
</release-item>
|
||||
|
||||
<release-item>
|
||||
<commit subject="Fix a bug.">
|
||||
<github-pull-request id="3"/>
|
||||
</commit>
|
||||
<commit subject="Fix a "quoted""/>
|
||||
|
||||
<p>Fix bugs.</p>
|
||||
</release-item>
|
||||
|
||||
<release-item>
|
||||
<commit subject="Improve speed.">
|
||||
<github-issue id="5"/>
|
||||
<github-pull-request id="6"/>
|
||||
</commit>
|
||||
|
||||
<p>Speed things up.</p>
|
||||
</release-item>
|
||||
</release-feature-list>
|
||||
</release-core-list>
|
||||
</release>
|
||||
|
||||
<release date="2026-09-15" version="2.59.1" title="Patch">
|
||||
<release-core-list>
|
||||
<release-bug-list>
|
||||
<release-item>
|
||||
<p>Fix a bug on a branch.</p>
|
||||
</release-item>
|
||||
</release-bug-list>
|
||||
</release-core-list>
|
||||
</release>
|
||||
|
||||
<release date="2026-09-01" version="2.59.0" title="Prior">
|
||||
<release-core-list>
|
||||
<release-improvement-list>
|
||||
<release-item>
|
||||
<github-pull-request id="9"/>
|
||||
|
||||
<p>Improve a thing.</p>
|
||||
|
||||
<p>Say more.</p>
|
||||
</release-item>
|
||||
</release-improvement-list>
|
||||
</release-core-list>
|
||||
</release>
|
||||
|
||||
<release date="2025-01-01" version="1.00" title="Initial"/>
|
||||
"""
|
||||
|
||||
# Html of the details up to the first paragraph or line of links
|
||||
DETAIL_HTML = '\n<details class="list-detail"><summary class="list-detail-toggle" title="Details" aria-label="Details"></summary>'
|
||||
|
||||
|
||||
####################################################################################################################################
|
||||
def _doc(release_list=RELEASE_LIST, contributor=CONTRIBUTOR):
|
||||
"""The release document, which is the list of releases and who worked on them."""
|
||||
@@ -178,6 +258,19 @@ def _section_list(root):
|
||||
return [xml_node_attribute(section, "id") for section in xml_node_child_list(root, "section")]
|
||||
|
||||
|
||||
####################################################################################################################################
|
||||
def _item_html(release):
|
||||
"""The html of each item of a release."""
|
||||
|
||||
render = DocRender("html")
|
||||
|
||||
return [
|
||||
render.process_text(item)
|
||||
for list_node in xml_node_child_list(release, "list")
|
||||
for item in xml_node_child_list(list_node, "list-item")
|
||||
]
|
||||
|
||||
|
||||
####################################################################################################################################
|
||||
def test_release_list():
|
||||
"""The releases are listed newest first, which is the order they are read in."""
|
||||
@@ -435,6 +528,135 @@ def test_release_render_commit_ambiguous():
|
||||
assert_in("subject prefix 'Fix a bug.' already found in commit", str(raised.exception))
|
||||
|
||||
|
||||
####################################################################################################################################
|
||||
def test_release_render_detail():
|
||||
"""Releases with version >= 2.60 have the paragraphs after the first and the links of an item in details."""
|
||||
|
||||
root = _render(_doc(DETAIL_RELEASE_LIST), history=DETAIL_HISTORY)
|
||||
|
||||
assert_equal(_section_list(root), ["introduction", "current", "supported"])
|
||||
|
||||
release = xml_node_child_list(xml_node_child_list(root, "section")[1], "section")[0]
|
||||
|
||||
assert_equal(
|
||||
_item_html(release),
|
||||
[
|
||||
"Add a feature."
|
||||
+ DETAIL_HTML
|
||||
+ '\n<div class="list-detail-text">Say more about it.</div>'
|
||||
+ '\n<div class="list-detail-text">And <span class="id">more</span>.</div>'
|
||||
+ '\n<div class="list-detail-ref">'
|
||||
+ '<span class="list-detail-ref-item">Issue <a href="{[github-url-issues]}/1">#1</a></span>'
|
||||
+ '<span class="list-detail-ref-item">Pull request <a href="{[github-url-base]}/pull/2">#2</a></span>'
|
||||
+ '<span class="list-detail-ref-item">Commit '
|
||||
+ '<a href="{[github-url-base]}/commit/d1"><span class="id">d1</span></a></span>'
|
||||
+ "</div></details>",
|
||||
# An item with more than one commit has a line for each commit
|
||||
"Fix bugs."
|
||||
+ DETAIL_HTML
|
||||
+ '\n<div class="list-detail-ref">'
|
||||
+ '<span class="list-detail-ref-item"><a href="{[github-url-base]}/commit/d2"><span class="id">d2</span></a>'
|
||||
+ " Fix a bug.</span>"
|
||||
+ '<span class="list-detail-ref-item">Pull request <a href="{[github-url-base]}/pull/3">#3</a></span></div>'
|
||||
+ '\n<div class="list-detail-ref">'
|
||||
+ '<span class="list-detail-ref-item"><a href="{[github-url-base]}/commit/d3"><span class="id">d3</span></a>'
|
||||
+ " Fix a "quoted" <bug>.</span></div></details>",
|
||||
# An item with one commit has the commit after the issues and pull requests the commit element names
|
||||
"Speed things up."
|
||||
+ DETAIL_HTML
|
||||
+ '\n<div class="list-detail-ref">'
|
||||
+ '<span class="list-detail-ref-item">Issue <a href="{[github-url-issues]}/5">#5</a></span>'
|
||||
+ '<span class="list-detail-ref-item">Pull request <a href="{[github-url-base]}/pull/6">#6</a></span>'
|
||||
+ '<span class="list-detail-ref-item">Commit '
|
||||
+ '<a href="{[github-url-base]}/commit/d0"><span class="id">d0</span></a></span>'
|
||||
+ "</div></details>",
|
||||
],
|
||||
)
|
||||
|
||||
# Releases with version < 2.60 have no details
|
||||
supported = xml_node_child_list(root, "section")[2]
|
||||
|
||||
assert_equal(_item_html(xml_node_child_list(supported, "section")[1]), ["Improve a thing. Say more."])
|
||||
|
||||
|
||||
####################################################################################################################################
|
||||
def test_release_render_detail_dev():
|
||||
"""A development release has links for the commits that are found and no error for the commits that are not."""
|
||||
|
||||
root = _render(
|
||||
_doc(
|
||||
DETAIL_RELEASE_LIST.replace('date="2026-10-01" version="2.60.0"', 'date="XXXX-XX-XX" version="2.60.0dev"').replace(
|
||||
"</release-feature-list>",
|
||||
"""<release-item>
|
||||
<p>Do a thing.</p>
|
||||
</release-item>
|
||||
|
||||
<release-item>
|
||||
<commit subject="Do another thing.">
|
||||
<github-pull-request id="4"/>
|
||||
</commit>
|
||||
|
||||
<p>Do another thing.</p>
|
||||
</release-item>
|
||||
|
||||
<release-item>
|
||||
<commit subject="Do a third thing.">
|
||||
<github-pull-request id="7"/>
|
||||
</commit>
|
||||
<commit subject="Improve speed."/>
|
||||
|
||||
<p>Do a third thing.</p>
|
||||
</release-item>
|
||||
</release-feature-list>""",
|
||||
1,
|
||||
)
|
||||
),
|
||||
history=DETAIL_HISTORY,
|
||||
)
|
||||
|
||||
development = xml_node_child_list(xml_node_child_list(root, "section")[1], "section")[0]
|
||||
item_list = _item_html(development)
|
||||
|
||||
assert_equal(len(item_list), 6)
|
||||
assert_equal(item_list[3], "Do a thing.")
|
||||
assert_equal(
|
||||
item_list[4],
|
||||
"Do another thing."
|
||||
+ DETAIL_HTML
|
||||
+ '\n<div class="list-detail-ref">'
|
||||
+ '<span class="list-detail-ref-item">Pull request <a href="{[github-url-base]}/pull/4">#4</a></span></div></details>',
|
||||
)
|
||||
|
||||
# A line for a commit that is not found has only the links the commit element names
|
||||
assert_equal(
|
||||
item_list[5],
|
||||
"Do a third thing."
|
||||
+ DETAIL_HTML
|
||||
+ '\n<div class="list-detail-ref">'
|
||||
+ '<span class="list-detail-ref-item">Pull request <a href="{[github-url-base]}/pull/7">#7</a></span></div>'
|
||||
+ '\n<div class="list-detail-ref">'
|
||||
+ '<span class="list-detail-ref-item"><a href="{[github-url-base]}/commit/d0"><span class="id">d0</span></a>'
|
||||
+ " Improve speed.</span></div></details>",
|
||||
)
|
||||
|
||||
|
||||
####################################################################################################################################
|
||||
def test_release_render_bug_fix():
|
||||
"""A bug fix release x.y.z with z > 0 is skipped when it is not the most recent and a release x.y.0 is checked."""
|
||||
|
||||
# The commits of 2.60.0 end at the commit that closed 2.59.0
|
||||
with assert_raises(ToolError) as raised:
|
||||
_render(_doc(DETAIL_RELEASE_LIST.replace("<p>Add a feature.</p>", "<p>Improve a thing.</p>")), history=DETAIL_HISTORY)
|
||||
|
||||
assert_in("unable to find commit or no subject match for release 2.60.0 item 'Improve a thing.'", str(raised.exception))
|
||||
|
||||
# 2.59.0 is checked
|
||||
with assert_raises(ToolError) as raised:
|
||||
_render(_doc(DETAIL_RELEASE_LIST.replace("<p>Improve a thing.</p>", "<p>Improve two things.</p>")), history=DETAIL_HISTORY)
|
||||
|
||||
assert_in("unable to find commit or no subject match for release 2.59.0 item 'Improve two things.'", str(raised.exception))
|
||||
|
||||
|
||||
####################################################################################################################################
|
||||
def test_release_history_update():
|
||||
"""The history is written from git when HEAD is not the newest commit in it."""
|
||||
|
||||
Reference in New Issue
Block a user