Fix column parser (r10)

List table column parser did not dynamically recalc indentation when rebuilding tables. Fixed.

Change-Id: I4ef1013454aa1d0252f5bf5d9fc8f33a44a021e7
Signed-off-by: Ron Stone <ronald.stone@windriver.com>
This commit is contained in:
Ron Stone
2025-06-10 11:31:02 +00:00
parent c9f9f2809f
commit 10b385e726
2 changed files with 159 additions and 131 deletions

View File

@@ -3,151 +3,175 @@ import re
import sys import sys
def parse_meta_directives(lines): def parse_meta_directives(lines):
directives = {} directives = {}
for line in lines: for line in lines:
if ":remove-column-from-html-table:" in line: if ":remove-column-from-html-table:" in line:
directives["remove_column"] = line.split(":", 2)[2].strip() directives["remove_column"] = line.split(":", 2)[2].strip()
if ":remove-column-emptied-row:" in line: if ":remove-column-emptied-row:" in line:
directives["remove_emptied_row"] = line.split(":", 2)[2].strip() == "1" directives["remove_emptied_row"] = line.split(":", 2)[2].strip() == "1"
return directives if ":docs-build-context:" in line:
directives["context"] = line.split(":", 2)[2].strip()
return directives
def extract_table_blocks(lines): def extract_table_blocks(lines):
blocks = [] blocks = []
current = [] current = []
inside = False inside = False
for line in lines: for line in lines:
if line.strip().startswith(".. list-table::"): if line.strip().startswith(".. list-table::"):
inside = True inside = True
current = [line] current = [line]
elif inside and line.startswith(" ") or line.strip() == "": elif inside and line.startswith(" ") or line.strip() == "":
current.append(line) current.append(line)
elif inside: elif inside:
blocks.append(current) blocks.append(current)
inside = False inside = False
if inside: if inside:
blocks.append(current) blocks.append(current)
return blocks return blocks
def split_table_row(row_lines): def split_table_row(row_lines):
"""Splits a table row (beginning with '*') into a list of cells.""" """Splits a table row (beginning with '*') into a list of cells."""
cells = [] cells = []
current_cell = [] current_cell = []
for line in row_lines: for line in row_lines:
if re.match(r'^\s*\*\s+-', line): # First cell in row if re.match(r'^\s*\*\s+-', line): # First cell in row
parts = re.split(r'\s*\*\s+-\s*', line, maxsplit=1) parts = re.split(r'\s*\*\s+-\s*', line, maxsplit=1)
current_cell = [parts[1]] current_cell = [parts[1]]
elif re.match(r'^\s*-\s+', line): # New cell elif re.match(r'^\s*-\s+', line): # New cell
cells.append(current_cell) cells.append(current_cell)
current_cell = [line.strip()[2:]] current_cell = [line.strip()[2:]]
else: else:
current_cell.append(line.strip()) current_cell.append(line.strip())
cells.append(current_cell) cells.append(current_cell)
return cells return cells
def join_cells(cells, base_indent=" "): def join_cells(cells, base_indent):
"""Reconstructs a list-table row from cell lists.""" """Reconstructs a list-table row from cell lists."""
line = f"{base_indent}* - " + cells[0][0] line = f"{base_indent}* - " + cells[0][0]
lines = [line] lines = [line]
for line in cells[0][1:]: for line in cells[0][1:]:
lines.append(base_indent + " " + line) lines.append(base_indent + " " + line)
for cell in cells[1:]: for cell in cells[1:]:
lines.append(base_indent + " - " + cell[0]) lines.append(base_indent + " - " + cell[0])
for l in cell[1:]: for l in cell[1:]:
lines.append(base_indent + " " + l) lines.append(base_indent + " " + l)
return lines return lines
def process_table(table_lines, col_to_remove, remove_empty_row=False): def process_table(table_lines, cols_to_remove_str, remove_empty_row=False):
processed = [] # Parse comma-separated column names
table_rows = [] cols_to_remove = [col.strip() for col in cols_to_remove_str.split(',')]
header_index = -1
header_row = [] processed = []
buffer = [] table_rows = []
header_indices = []
header_row = []
buffer = []
for line in table_lines: for line in table_lines:
if re.match(r'\s*\*\s+-', line): if re.match(r'\s*\*\s+-', line):
if buffer: match = re.match(r'(\s*)\*\s+-', line)
table_rows.append(buffer) indentation = match.group(1)
buffer = [line] if buffer:
elif buffer != [] and (line.strip() == "" or re.match(r'^\s*(-|[^*].*)$', line)): table_rows.append(buffer)
buffer.append(line) buffer = [line]
else: elif buffer != [] and (line.strip() == "" or re.match(r'^\s*(-|[^*].*)$', line)):
if buffer: buffer.append(line)
table_rows.append(buffer) else:
buffer = [] if buffer:
processed.append(line) table_rows.append(buffer)
buffer = []
processed.append(line)
if buffer: if buffer:
table_rows.append(buffer) table_rows.append(buffer)
# Parse header row # Parse header row and find all matching column indices
for i, row in enumerate(table_rows): for i, row in enumerate(table_rows):
if i == 0: if i == 0:
cells = split_table_row(row) cells = split_table_row(row)
flat_cells = [' '.join(c).strip() for c in cells] flat_cells = [' '.join(c).strip() for c in cells]
if col_to_remove not in flat_cells:
return table_lines # Don't modify # Find indices of all columns to remove
header_index = flat_cells.index(col_to_remove) for col_name in cols_to_remove:
header_row = cells if col_name in flat_cells:
break header_indices.append(flat_cells.index(col_name))
# If no columns found, don't modify
if not header_indices:
return table_lines
# Sort indices in descending order so we can remove from right to left
header_indices.sort(reverse=True)
header_row = cells
break
if header_index == -1: if not header_indices:
return table_lines # Don't modify return table_lines # Don't modify
# Remove the column from each row # Remove the columns from each row
new_rows = [] new_rows = []
for row in table_rows: for row in table_rows:
cells = split_table_row(row) cells = split_table_row(row)
if header_index >= len(cells):
continue # Remove columns from right to left to preserve indices
if remove_empty_row and all(not ''.join(cell).strip() for cell in cells[:header_index] + cells[header_index+1:]): for header_index in header_indices:
continue if header_index < len(cells):
del cells[header_index] del cells[header_index]
new_rows.append(join_cells(cells))
# Check if row should be removed (if it's empty after column removal)
if remove_empty_row and all(not ''.join(cell).strip() for cell in cells):
continue
new_rows.append(join_cells(cells, indentation))
return processed + [""] + [line for row in new_rows for line in row] return processed + [""] + [line for row in new_rows for line in row]
def process_file(path): def process_file(path):
with open(path, 'r', encoding='utf-8') as f: with open(path, 'r', encoding='utf-8') as f:
lines = f.readlines() lines = f.readlines()
directives = parse_meta_directives(lines) directives = parse_meta_directives(lines)
if "remove_column" not in directives: if "remove_column" not in directives:
return return
if directives["context"] != os.environ.get('DOCS_BUILD_CONTEXT'):
print("Not in", directives["context"], "- Skipping")
return
table_blocks = extract_table_blocks(lines) table_blocks = extract_table_blocks(lines)
output_lines = [] output_lines = []
i = 0 i = 0
while i < len(lines): while i < len(lines):
line = lines[i] line = lines[i]
if line.strip().startswith(".. list-table::"): if line.strip().startswith(".. list-table::"):
# Find the table block and replace # Find the table block and replace
for block in table_blocks: for block in table_blocks:
if lines[i:i+len(block)] == block: if lines[i:i+len(block)] == block:
processed = process_table( processed = process_table(
block, block,
directives["remove_column"], directives["remove_column"],
directives.get("remove_emptied_row", False) directives.get("remove_emptied_row", False)
) )
output_lines.extend(processed) output_lines.extend(processed)
i += len(block) i += len(block)
break break
else: else:
output_lines.append(line) output_lines.append(line)
i += 1 i += 1
with open(path, 'w', encoding='utf-8') as f: with open(path, 'w', encoding='utf-8') as f:
f.writelines(l + ("\n" if not l.endswith("\n") else "") for l in output_lines) f.writelines(l + ("\n" if not l.endswith("\n") else "") for l in output_lines)
# not used currently. We get a list of files from grep and loop
def scan_dir(directory): def scan_dir(directory):
for root, _, files in os.walk(directory): for root, _, files in os.walk(directory):
for name in files: for name in files:
if name.endswith(".rst"): if name.endswith(".rst"):
process_file(os.path.join(root, name)) process_file(os.path.join(root, name))
if __name__ == "__main__": if __name__ == "__main__":
if len(sys.argv) != 2: if len(sys.argv) != 2:
print("Usage: python remove-columns.py <directory>") print("Usage: python remove-columns.py <directory>")
sys.exit(1) sys.exit(1)
process_file(sys.argv[1]) process_file(sys.argv[1])
# scan_dir(sys.argv[1])

12
tox.ini
View File

@@ -11,8 +11,10 @@ setenv = VIRTUAL_ENV={envdir}
OS_TEST_TIMEOUT=60 OS_TEST_TIMEOUT=60
LC_ALL=C LC_ALL=C
DOCS_BUILD_CONTEXT=starlingx DOCS_BUILD_CONTEXT=starlingx
drop_table_cols = {env:DROP_TABLE_COLS:bash -c 'for f in $(grep -rl -e :remove-column-from-html-table: doc/source/*); do python3 remove-list-columns.py "$f" -o "$f"; python3 remove-grid-columns.py "$f" -o "$f"; done'} drop_table_cols_list = {env:DROP_TABLE_COLS_LIST:bash -c 'for f in $(grep -rl -e :remove-column-from-html-table: doc/source/* --exclude-dir="doc/source/contributor"); do python3 remove-list-columns.py "$f"; done'}
drop_table_rows = {env:DROP_TABLE_ROWS:bash -c 'for f in $(grep -rl -e :remove-empty-table-rows: doc/source/*); do python3 remove_empty-list_rows.py "$f" -o "$f"; python3 remove_empty-grid_rows.py "$f" -o "$f"; done'} drop_table_cols_grid = {env:DROP_TABLE_COLS_GRID:bash -c 'for f in $(grep -rl -e :remove-column-from-html-table: doc/source/* --exclude-dir="doc/source/contributor"); do python3 remove-grid-columns.py "$f" -o "$f"; done'}
drop_table_rows_list = {env:DROP_TABLE_ROWS_LIST:bash -c 'for f in $(grep -rl -e :remove-empty-table-rows: doc/source/* --exclude-dir="doc/source/contributor"); do python3 remove_empty-list_rows.py "$f" -o "$f"; done'}
drop_table_rows_grid = {env:DROP_TABLE_ROWS_GRID:bash -c 'for f in $(grep -rl -e :remove-empty-table-rows: doc/source/* --exclude-dir="doc/source/contributor"); do python3 remove_empty-grid_rows.py "$f" -o "$f"; done'}
deps = -r{toxinidir}/test-requirements.txt deps = -r{toxinidir}/test-requirements.txt
[testenv:prebuild-docs] [testenv:prebuild-docs]
@@ -29,8 +31,10 @@ commands =
bash ./fetch-ports-files.sh bash ./fetch-ports-files.sh
python py_2_xlsx.py tmp/platform_firewall.py tmp/constants.py tmp/FW_PORTS.xlsx python py_2_xlsx.py tmp/platform_firewall.py tmp/constants.py tmp/FW_PORTS.xlsx
python xlst_2_csv.py tmp/FW_PORTS.xlsx doc/source/shared/FW_PORTS.csv --columns Source Port Protocol Network Desc HTTPS Note _stx --sort_orders Port=asc --filters _stx=y python xlst_2_csv.py tmp/FW_PORTS.xlsx doc/source/shared/FW_PORTS.csv --columns Source Port Protocol Network Desc HTTPS Note _stx --sort_orders Port=asc --filters _stx=y
{[testenv]drop_table_cols} {[testenv]drop_table_cols_list}
{[testenv]drop_table_rows} {[testenv]drop_table_cols_grid}
{[testenv]drop_table_rows_list}
{[testenv]drop_table_rows_grid}
[testenv:postbuild-docs] [testenv:postbuild-docs]
commands = commands =