I wrote an extension for Sphinx to read code coverage files and present them as a table in a Sphinx generated HTML documentation.
Currently the table has a single header row with e.g. 3 columns for statement related values and 4 columns for branch related data. I would like to create a 2 row table header, so multiple columns are grouped.
In pure HTML it would be done by adding colspan=3. But how to solve that question with docutils?
The full sources can be found here: https://github.com/pyTooling/sphinx-reports/blob/main/sphinx_reports/CodeCoverage.py#L169
Interesting code is this:
def _PrepareTable(self, columns: Dict[str, int], identifier: str, classes: List[str]) -> Tuple[nodes.table, nodes.tgroup]:
table = nodes.table("", identifier=identifier, classes=classes)
tableGroup = nodes.tgroup(cols=(len(columns)))
table += tableGroup
tableRow = nodes.row()
for columnTitle, width in columns.items():
tableGroup += nodes.colspec(colwidth=width)
tableRow += nodes.entry("", nodes.paragraph(text=columnTitle))
tableGroup += nodes.thead("", tableRow)
return table, tableGroup
def _GenerateCoverageTable(self) -> nodes.table:
# Create a table and table header with 5 columns
table, tableGroup = self._PrepareTable(
identifier=self._packageID,
columns={
"Module": 500,
"Total Statements": 100,
"Excluded Statements": 100,
"Covered Statements": 100,
"Missing Statements": 100,
"Total Branches": 100,
"Covered Branches": 100,
"Partial Branches": 100,
"Missing Branches": 100,
"Coverage in %": 100
},
classes=["report-doccov-table"]
)
tableBody = nodes.tbody()
tableGroup += tableBody

The magic of multiple cells spanning rows or columns is done by
morerowsandmorecols. In addition, merged cells need to be set asNone. I found it by investigating the code for the table parser.Like always with Sphinx and docutils, such features are not documented (but isn't docutils and Sphinx meant to document code/itself?).
Anyhow, I created a helper method which returns a table node with header rows in it. I used a simple approach to describe header columns in the primary rows that are divided into more columns in a secondary row. Alternatively, @Bhav-Bhela demonstrated a description technique for deeper nesting.
The method expects a list of primary column descriptions, which is a tuple of column title, optional list of secondary columns, column width. If the secondary column list is present, then no column width is needed for the primary row. In the secondary row, a tuple of title and column width is used.
It's then used like that:
The full code can be found here: Sphinx.py:BaseDirective._PrepareTable
The result looks like this:

Link to example: https://pytooling.github.io/sphinx-reports/coverage/index.html