rolfedh-doc-utils 0.1.25__tar.gz → 0.1.26__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rolfedh_doc_utils-0.1.25/rolfedh_doc_utils.egg-info → rolfedh_doc_utils-0.1.26}/PKG-INFO +1 -1
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/callout_lib/detector.py +45 -0
- rolfedh_doc_utils-0.1.26/callout_lib/table_parser.py +437 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/pyproject.toml +1 -1
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26/rolfedh_doc_utils.egg-info}/PKG-INFO +1 -1
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/rolfedh_doc_utils.egg-info/SOURCES.txt +3 -0
- rolfedh_doc_utils-0.1.26/tests/test_table_callout_conversion.py +261 -0
- rolfedh_doc_utils-0.1.26/tests/test_table_parser.py +330 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/LICENSE +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/README.md +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/archive_unused_files.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/archive_unused_images.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/callout_lib/__init__.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/callout_lib/converter_bullets.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/callout_lib/converter_comments.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/callout_lib/converter_deflist.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/check_scannability.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/convert_callouts_interactive.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/convert_callouts_to_deflist.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/__init__.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/extract_link_attributes.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/file_utils.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/format_asciidoc_spacing.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/replace_link_attributes.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/scannability.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/spinner.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/topic_map_parser.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/unused_adoc.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/unused_attributes.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/unused_images.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/validate_links.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/version.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/version_check.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils_cli.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/extract_link_attributes.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/find_unused_attributes.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/format_asciidoc_spacing.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/replace_link_attributes.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/rolfedh_doc_utils.egg-info/dependency_links.txt +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/rolfedh_doc_utils.egg-info/entry_points.txt +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/rolfedh_doc_utils.egg-info/requires.txt +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/rolfedh_doc_utils.egg-info/top_level.txt +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/setup.cfg +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/setup.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_archive_unused_files.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_archive_unused_images.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_auto_discovery.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_check_scannability.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_cli_entry_points.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_extract_link_attributes.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_file_utils.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_fixture_archive_unused_files.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_fixture_archive_unused_images.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_fixture_check_scannability.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_parse_exclude_list.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_replace_link_attributes.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_symlink_handling.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_topic_map_parser.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_unused_attributes.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_validate_links.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_version_check.py +0 -0
- {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/validate_links.py +0 -0
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
Callout Detection Module
|
|
3
3
|
|
|
4
4
|
Detects code blocks with callouts and extracts callout information from AsciiDoc files.
|
|
5
|
+
Supports both list-format and table-format callout explanations.
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
import re
|
|
8
9
|
from typing import List, Dict, Tuple, Optional
|
|
9
10
|
from dataclasses import dataclass
|
|
11
|
+
from .table_parser import TableParser
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
@dataclass
|
|
@@ -50,6 +52,10 @@ class CalloutDetector:
|
|
|
50
52
|
# Excludes heredoc syntax (<<) and comparison operators
|
|
51
53
|
USER_VALUE_PATTERN = re.compile(r'(?<!<)<([a-zA-Z][^>]*)>')
|
|
52
54
|
|
|
55
|
+
def __init__(self):
|
|
56
|
+
"""Initialize detector with table parser."""
|
|
57
|
+
self.table_parser = TableParser()
|
|
58
|
+
|
|
53
59
|
def find_code_blocks(self, lines: List[str]) -> List[CodeBlock]:
|
|
54
60
|
"""Find all code blocks in the document."""
|
|
55
61
|
blocks = []
|
|
@@ -151,8 +157,47 @@ class CalloutDetector:
|
|
|
151
157
|
def extract_callout_explanations(self, lines: List[str], start_line: int) -> Tuple[Dict[int, Callout], int]:
|
|
152
158
|
"""
|
|
153
159
|
Extract callout explanations following a code block.
|
|
160
|
+
Supports both list-format (<1> text) and table-format explanations.
|
|
154
161
|
Returns dict of callouts and the line number where explanations end.
|
|
155
162
|
"""
|
|
163
|
+
# First, try to find a table-format callout explanation
|
|
164
|
+
table = self.table_parser.find_callout_table_after_code_block(lines, start_line)
|
|
165
|
+
if table:
|
|
166
|
+
return self._extract_from_table(table)
|
|
167
|
+
|
|
168
|
+
# Fall back to list-format extraction
|
|
169
|
+
return self._extract_from_list(lines, start_line)
|
|
170
|
+
|
|
171
|
+
def _extract_from_table(self, table) -> Tuple[Dict[int, Callout], int]:
|
|
172
|
+
"""Extract callout explanations from a table format."""
|
|
173
|
+
explanations = {}
|
|
174
|
+
table_data = self.table_parser.extract_callout_explanations_from_table(table)
|
|
175
|
+
|
|
176
|
+
for callout_num, (explanation_lines, conditionals) in table_data.items():
|
|
177
|
+
# Combine explanation lines with conditionals preserved
|
|
178
|
+
all_lines = []
|
|
179
|
+
for line in explanation_lines:
|
|
180
|
+
all_lines.append(line)
|
|
181
|
+
|
|
182
|
+
# Add conditionals as separate lines (they'll be preserved in output)
|
|
183
|
+
all_lines.extend(conditionals)
|
|
184
|
+
|
|
185
|
+
# Check if marked as optional
|
|
186
|
+
is_optional = False
|
|
187
|
+
if all_lines and (all_lines[0].lower().startswith('optional.') or
|
|
188
|
+
all_lines[0].lower().startswith('optional:')):
|
|
189
|
+
is_optional = True
|
|
190
|
+
all_lines[0] = all_lines[0][9:].strip()
|
|
191
|
+
elif all_lines and ('(Optional)' in all_lines[0] or '(optional)' in all_lines[0]):
|
|
192
|
+
is_optional = True
|
|
193
|
+
all_lines[0] = re.sub(r'\s*\(optional\)\s*', ' ', all_lines[0], flags=re.IGNORECASE).strip()
|
|
194
|
+
|
|
195
|
+
explanations[callout_num] = Callout(callout_num, all_lines, is_optional)
|
|
196
|
+
|
|
197
|
+
return explanations, table.end_line
|
|
198
|
+
|
|
199
|
+
def _extract_from_list(self, lines: List[str], start_line: int) -> Tuple[Dict[int, Callout], int]:
|
|
200
|
+
"""Extract callout explanations from list format (<1> text)."""
|
|
156
201
|
explanations = {}
|
|
157
202
|
i = start_line + 1 # Start after the closing delimiter
|
|
158
203
|
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AsciiDoc Table Parser Module
|
|
3
|
+
|
|
4
|
+
Parses AsciiDoc tables and extracts structured data. Designed to be reusable
|
|
5
|
+
for various table conversion tasks (not just callout explanations).
|
|
6
|
+
|
|
7
|
+
Handles:
|
|
8
|
+
- Two-column tables with callout numbers and explanations
|
|
9
|
+
- Conditional statements (ifdef, ifndef, endif) within table cells
|
|
10
|
+
- Multi-line table cells
|
|
11
|
+
- Table attributes and formatting
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from typing import List, Dict, Tuple, Optional
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class TableCell:
|
|
21
|
+
"""Represents a single table cell with its content and any conditional blocks."""
|
|
22
|
+
content: List[str] # Lines of content in the cell
|
|
23
|
+
conditionals: List[str] # Any ifdef/ifndef/endif lines associated with this cell
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class TableRow:
|
|
28
|
+
"""Represents a table row with cells."""
|
|
29
|
+
cells: List[TableCell]
|
|
30
|
+
conditionals_before: List[str] # Conditional statements before this row
|
|
31
|
+
conditionals_after: List[str] # Conditional statements after this row
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class AsciiDocTable:
|
|
36
|
+
"""Represents a complete AsciiDoc table."""
|
|
37
|
+
start_line: int
|
|
38
|
+
end_line: int
|
|
39
|
+
attributes: str # Table attributes like [cols="1,3"]
|
|
40
|
+
rows: List[TableRow]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TableParser:
|
|
44
|
+
"""Parses AsciiDoc tables and extracts structured data."""
|
|
45
|
+
|
|
46
|
+
# Pattern for table start delimiter with optional attributes
|
|
47
|
+
TABLE_START = re.compile(r'^\[.*?\]$')
|
|
48
|
+
TABLE_DELIMITER = re.compile(r'^\|===\s*$')
|
|
49
|
+
|
|
50
|
+
# Pattern for table cell separator
|
|
51
|
+
CELL_SEPARATOR = re.compile(r'^\|')
|
|
52
|
+
|
|
53
|
+
# Pattern for conditional directives
|
|
54
|
+
IFDEF_PATTERN = re.compile(r'^(ifdef::|ifndef::).+\[\]\s*$')
|
|
55
|
+
ENDIF_PATTERN = re.compile(r'^endif::\[\]\s*$')
|
|
56
|
+
|
|
57
|
+
# Pattern for callout number (used for callout table detection)
|
|
58
|
+
CALLOUT_NUMBER = re.compile(r'^<(\d+)>\s*$')
|
|
59
|
+
|
|
60
|
+
def find_tables(self, lines: List[str]) -> List[AsciiDocTable]:
|
|
61
|
+
"""Find all tables in the document."""
|
|
62
|
+
tables = []
|
|
63
|
+
i = 0
|
|
64
|
+
|
|
65
|
+
while i < len(lines):
|
|
66
|
+
# Look for table delimiter
|
|
67
|
+
if self.TABLE_DELIMITER.match(lines[i]):
|
|
68
|
+
# Check if there are attributes on the line before
|
|
69
|
+
attributes = ""
|
|
70
|
+
start_line = i
|
|
71
|
+
|
|
72
|
+
if i > 0 and self.TABLE_START.match(lines[i - 1]):
|
|
73
|
+
attributes = lines[i - 1]
|
|
74
|
+
start_line = i - 1
|
|
75
|
+
|
|
76
|
+
# Parse table content
|
|
77
|
+
table = self._parse_table(lines, start_line, i)
|
|
78
|
+
if table:
|
|
79
|
+
tables.append(table)
|
|
80
|
+
i = table.end_line + 1
|
|
81
|
+
continue
|
|
82
|
+
i += 1
|
|
83
|
+
|
|
84
|
+
return tables
|
|
85
|
+
|
|
86
|
+
def _parse_table(self, lines: List[str], start_line: int, delimiter_line: int) -> Optional[AsciiDocTable]:
|
|
87
|
+
"""
|
|
88
|
+
Parse a single table starting at the delimiter.
|
|
89
|
+
|
|
90
|
+
AsciiDoc table format:
|
|
91
|
+
|===
|
|
92
|
+
|Cell1
|
|
93
|
+
|Cell2
|
|
94
|
+
(blank line separates rows)
|
|
95
|
+
|Cell3
|
|
96
|
+
|Cell4
|
|
97
|
+
|===
|
|
98
|
+
"""
|
|
99
|
+
i = delimiter_line + 1
|
|
100
|
+
rows = []
|
|
101
|
+
current_row_cells = []
|
|
102
|
+
current_cell_lines = []
|
|
103
|
+
conditionals_before_row = []
|
|
104
|
+
conditionals_after_row = []
|
|
105
|
+
|
|
106
|
+
while i < len(lines):
|
|
107
|
+
line = lines[i]
|
|
108
|
+
|
|
109
|
+
# Check for table end
|
|
110
|
+
if self.TABLE_DELIMITER.match(line):
|
|
111
|
+
# Save any pending cell
|
|
112
|
+
if current_cell_lines:
|
|
113
|
+
current_row_cells.append(TableCell(
|
|
114
|
+
content=current_cell_lines.copy(),
|
|
115
|
+
conditionals=[]
|
|
116
|
+
))
|
|
117
|
+
current_cell_lines = []
|
|
118
|
+
|
|
119
|
+
# Save any pending row
|
|
120
|
+
if current_row_cells:
|
|
121
|
+
rows.append(TableRow(
|
|
122
|
+
cells=current_row_cells.copy(),
|
|
123
|
+
conditionals_before=conditionals_before_row.copy(),
|
|
124
|
+
conditionals_after=conditionals_after_row.copy()
|
|
125
|
+
))
|
|
126
|
+
|
|
127
|
+
# Get attributes if present
|
|
128
|
+
attributes = ""
|
|
129
|
+
if start_line < delimiter_line:
|
|
130
|
+
attributes = lines[start_line]
|
|
131
|
+
|
|
132
|
+
return AsciiDocTable(
|
|
133
|
+
start_line=start_line,
|
|
134
|
+
end_line=i,
|
|
135
|
+
attributes=attributes,
|
|
136
|
+
rows=rows
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Check for conditional directives
|
|
140
|
+
if self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line):
|
|
141
|
+
if not current_row_cells:
|
|
142
|
+
# Conditional before any cells in this row
|
|
143
|
+
conditionals_before_row.append(line)
|
|
144
|
+
else:
|
|
145
|
+
# Conditional after cells started - treat as part of current context
|
|
146
|
+
if current_cell_lines:
|
|
147
|
+
# Inside a cell
|
|
148
|
+
current_cell_lines.append(line)
|
|
149
|
+
else:
|
|
150
|
+
# Between cells in the same row
|
|
151
|
+
conditionals_after_row.append(line)
|
|
152
|
+
i += 1
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
# Blank line separates rows
|
|
156
|
+
if not line.strip():
|
|
157
|
+
# Save pending cell if exists
|
|
158
|
+
if current_cell_lines:
|
|
159
|
+
current_row_cells.append(TableCell(
|
|
160
|
+
content=current_cell_lines.copy(),
|
|
161
|
+
conditionals=[]
|
|
162
|
+
))
|
|
163
|
+
current_cell_lines = []
|
|
164
|
+
|
|
165
|
+
# Save row if we have cells
|
|
166
|
+
if current_row_cells:
|
|
167
|
+
rows.append(TableRow(
|
|
168
|
+
cells=current_row_cells.copy(),
|
|
169
|
+
conditionals_before=conditionals_before_row.copy(),
|
|
170
|
+
conditionals_after=conditionals_after_row.copy()
|
|
171
|
+
))
|
|
172
|
+
current_row_cells = []
|
|
173
|
+
conditionals_before_row = []
|
|
174
|
+
conditionals_after_row = []
|
|
175
|
+
|
|
176
|
+
i += 1
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
# Check for cell separator (|)
|
|
180
|
+
if self.CELL_SEPARATOR.match(line):
|
|
181
|
+
# Save previous cell if exists
|
|
182
|
+
if current_cell_lines:
|
|
183
|
+
current_row_cells.append(TableCell(
|
|
184
|
+
content=current_cell_lines.copy(),
|
|
185
|
+
conditionals=[]
|
|
186
|
+
))
|
|
187
|
+
current_cell_lines = []
|
|
188
|
+
|
|
189
|
+
# Extract cell content from this line (text after |)
|
|
190
|
+
cell_content = line[1:].strip() # Remove leading |
|
|
191
|
+
if cell_content:
|
|
192
|
+
current_cell_lines.append(cell_content)
|
|
193
|
+
# If empty, just start a new cell with no content yet
|
|
194
|
+
|
|
195
|
+
i += 1
|
|
196
|
+
continue
|
|
197
|
+
|
|
198
|
+
# Regular content line (continuation of current cell)
|
|
199
|
+
if current_cell_lines or current_row_cells:
|
|
200
|
+
current_cell_lines.append(line)
|
|
201
|
+
|
|
202
|
+
i += 1
|
|
203
|
+
|
|
204
|
+
# Return None if we didn't find a proper table end
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
def is_callout_table(self, table: AsciiDocTable) -> bool:
|
|
208
|
+
"""
|
|
209
|
+
Determine if a table is a callout explanation table.
|
|
210
|
+
A callout table has two columns: callout number and explanation.
|
|
211
|
+
"""
|
|
212
|
+
if not table.rows:
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
# Check if all rows have exactly 2 cells
|
|
216
|
+
if not all(len(row.cells) == 2 for row in table.rows):
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
# Check if first cell of each row is a callout number
|
|
220
|
+
for row in table.rows:
|
|
221
|
+
first_cell = row.cells[0]
|
|
222
|
+
if not first_cell.content:
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
# First line of first cell should be a callout number
|
|
226
|
+
first_line = first_cell.content[0].strip()
|
|
227
|
+
if not self.CALLOUT_NUMBER.match(first_line):
|
|
228
|
+
return False
|
|
229
|
+
|
|
230
|
+
return True
|
|
231
|
+
|
|
232
|
+
def extract_callout_explanations_from_table(self, table: AsciiDocTable) -> Dict[int, Tuple[List[str], List[str]]]:
|
|
233
|
+
"""
|
|
234
|
+
Extract callout explanations from a table.
|
|
235
|
+
Returns dict mapping callout number to tuple of (explanation_lines, conditionals).
|
|
236
|
+
|
|
237
|
+
The conditionals list includes any ifdef/ifndef/endif statements that should
|
|
238
|
+
be preserved when converting the table to other formats.
|
|
239
|
+
"""
|
|
240
|
+
explanations = {}
|
|
241
|
+
|
|
242
|
+
for row in table.rows:
|
|
243
|
+
if len(row.cells) != 2:
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
callout_cell = row.cells[0]
|
|
247
|
+
explanation_cell = row.cells[1]
|
|
248
|
+
|
|
249
|
+
# Extract callout number
|
|
250
|
+
first_line = callout_cell.content[0].strip()
|
|
251
|
+
match = self.CALLOUT_NUMBER.match(first_line)
|
|
252
|
+
if not match:
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
callout_num = int(match.group(1))
|
|
256
|
+
|
|
257
|
+
# Collect explanation lines
|
|
258
|
+
explanation_lines = []
|
|
259
|
+
for line in explanation_cell.content:
|
|
260
|
+
# Skip conditional directives in explanation (preserve them separately)
|
|
261
|
+
if not (self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line)):
|
|
262
|
+
explanation_lines.append(line)
|
|
263
|
+
|
|
264
|
+
# Collect all conditionals for this row
|
|
265
|
+
all_conditionals = []
|
|
266
|
+
all_conditionals.extend(row.conditionals_before)
|
|
267
|
+
|
|
268
|
+
# Extract conditionals from explanation cell
|
|
269
|
+
for line in explanation_cell.content:
|
|
270
|
+
if self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line):
|
|
271
|
+
all_conditionals.append(line)
|
|
272
|
+
|
|
273
|
+
all_conditionals.extend(row.conditionals_after)
|
|
274
|
+
|
|
275
|
+
explanations[callout_num] = (explanation_lines, all_conditionals)
|
|
276
|
+
|
|
277
|
+
return explanations
|
|
278
|
+
|
|
279
|
+
def find_callout_table_after_code_block(self, lines: List[str], code_block_end: int) -> Optional[AsciiDocTable]:
|
|
280
|
+
"""
|
|
281
|
+
Find a callout explanation table that appears after a code block.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
lines: All lines in the document
|
|
285
|
+
code_block_end: Line number where the code block ends
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
AsciiDocTable if a callout table is found, None otherwise
|
|
289
|
+
"""
|
|
290
|
+
# Skip blank lines and continuation markers after code block
|
|
291
|
+
i = code_block_end + 1
|
|
292
|
+
while i < len(lines) and (not lines[i].strip() or lines[i].strip() == '+'):
|
|
293
|
+
i += 1
|
|
294
|
+
|
|
295
|
+
# Look for a table starting within the next few lines
|
|
296
|
+
# (allowing for possible text between code block and table)
|
|
297
|
+
search_limit = min(i + 10, len(lines))
|
|
298
|
+
|
|
299
|
+
for j in range(i, search_limit):
|
|
300
|
+
line = lines[j]
|
|
301
|
+
|
|
302
|
+
# If we encounter a list-format callout explanation, stop
|
|
303
|
+
# (list format takes precedence over table format further away)
|
|
304
|
+
if self.CALLOUT_NUMBER.match(line.strip()):
|
|
305
|
+
return None
|
|
306
|
+
|
|
307
|
+
# If we encounter another code block start, stop
|
|
308
|
+
if line.strip() in ['----', '....'] or line.strip().startswith('[source'):
|
|
309
|
+
return None
|
|
310
|
+
|
|
311
|
+
# Check for table delimiter
|
|
312
|
+
if self.TABLE_DELIMITER.match(line):
|
|
313
|
+
# Found a table, parse it
|
|
314
|
+
start_line = j
|
|
315
|
+
if j > 0 and self.TABLE_START.match(lines[j - 1]):
|
|
316
|
+
start_line = j - 1
|
|
317
|
+
|
|
318
|
+
table = self._parse_table(lines, start_line, j)
|
|
319
|
+
if table and self.is_callout_table(table):
|
|
320
|
+
return table
|
|
321
|
+
|
|
322
|
+
# If we found a table but it's not a callout table, stop searching
|
|
323
|
+
break
|
|
324
|
+
|
|
325
|
+
return None
|
|
326
|
+
|
|
327
|
+
def convert_table_to_deflist(self, table: AsciiDocTable, preserve_conditionals: bool = True) -> List[str]:
|
|
328
|
+
"""
|
|
329
|
+
Convert a two-column table to an AsciiDoc definition list.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
table: The table to convert
|
|
333
|
+
preserve_conditionals: Whether to preserve ifdef/ifndef/endif statements
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
List of lines representing the definition list
|
|
337
|
+
"""
|
|
338
|
+
output = []
|
|
339
|
+
|
|
340
|
+
for row in table.rows:
|
|
341
|
+
if len(row.cells) != 2:
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
# Add conditionals before row
|
|
345
|
+
if preserve_conditionals and row.conditionals_before:
|
|
346
|
+
output.extend(row.conditionals_before)
|
|
347
|
+
|
|
348
|
+
# First cell is the term
|
|
349
|
+
term_lines = row.cells[0].content
|
|
350
|
+
if term_lines:
|
|
351
|
+
output.append(term_lines[0])
|
|
352
|
+
|
|
353
|
+
# Second cell is the definition
|
|
354
|
+
definition_lines = row.cells[1].content
|
|
355
|
+
if definition_lines:
|
|
356
|
+
# Filter out conditionals if needed
|
|
357
|
+
if preserve_conditionals:
|
|
358
|
+
for line in definition_lines:
|
|
359
|
+
if self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line):
|
|
360
|
+
output.append(line)
|
|
361
|
+
else:
|
|
362
|
+
output.append(f" {line}")
|
|
363
|
+
else:
|
|
364
|
+
for line in definition_lines:
|
|
365
|
+
if not (self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line)):
|
|
366
|
+
output.append(f" {line}")
|
|
367
|
+
|
|
368
|
+
# Add conditionals after row
|
|
369
|
+
if preserve_conditionals and row.conditionals_after:
|
|
370
|
+
output.extend(row.conditionals_after)
|
|
371
|
+
|
|
372
|
+
# Add blank line between entries
|
|
373
|
+
output.append("")
|
|
374
|
+
|
|
375
|
+
# Remove trailing blank line
|
|
376
|
+
if output and not output[-1].strip():
|
|
377
|
+
output.pop()
|
|
378
|
+
|
|
379
|
+
return output
|
|
380
|
+
|
|
381
|
+
def convert_table_to_bullets(self, table: AsciiDocTable, preserve_conditionals: bool = True) -> List[str]:
|
|
382
|
+
"""
|
|
383
|
+
Convert a two-column table to a bulleted list.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
table: The table to convert
|
|
387
|
+
preserve_conditionals: Whether to preserve ifdef/ifndef/endif statements
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
List of lines representing the bulleted list
|
|
391
|
+
"""
|
|
392
|
+
output = []
|
|
393
|
+
|
|
394
|
+
for row in table.rows:
|
|
395
|
+
if len(row.cells) != 2:
|
|
396
|
+
continue
|
|
397
|
+
|
|
398
|
+
# Add conditionals before row
|
|
399
|
+
if preserve_conditionals and row.conditionals_before:
|
|
400
|
+
output.extend(row.conditionals_before)
|
|
401
|
+
|
|
402
|
+
# Get the term (first cell)
|
|
403
|
+
term_lines = row.cells[0].content
|
|
404
|
+
term = term_lines[0] if term_lines else ""
|
|
405
|
+
|
|
406
|
+
# Get the definition (second cell)
|
|
407
|
+
definition_lines = row.cells[1].content
|
|
408
|
+
|
|
409
|
+
# Filter conditionals from definition if needed
|
|
410
|
+
filtered_def_lines = []
|
|
411
|
+
inline_conditionals = []
|
|
412
|
+
|
|
413
|
+
for line in definition_lines:
|
|
414
|
+
if self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line):
|
|
415
|
+
if preserve_conditionals:
|
|
416
|
+
inline_conditionals.append(line)
|
|
417
|
+
else:
|
|
418
|
+
filtered_def_lines.append(line)
|
|
419
|
+
|
|
420
|
+
# Create bullet item
|
|
421
|
+
if filtered_def_lines:
|
|
422
|
+
first_line = filtered_def_lines[0]
|
|
423
|
+
output.append(f"* *{term}*: {first_line}")
|
|
424
|
+
|
|
425
|
+
# Add continuation lines with proper indentation
|
|
426
|
+
for line in filtered_def_lines[1:]:
|
|
427
|
+
output.append(f" {line}")
|
|
428
|
+
|
|
429
|
+
# Add inline conditionals if present
|
|
430
|
+
if preserve_conditionals and inline_conditionals:
|
|
431
|
+
output.extend(inline_conditionals)
|
|
432
|
+
|
|
433
|
+
# Add conditionals after row
|
|
434
|
+
if preserve_conditionals and row.conditionals_after:
|
|
435
|
+
output.extend(row.conditionals_after)
|
|
436
|
+
|
|
437
|
+
return output
|
{rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/rolfedh_doc_utils.egg-info/SOURCES.txt
RENAMED
|
@@ -18,6 +18,7 @@ callout_lib/converter_bullets.py
|
|
|
18
18
|
callout_lib/converter_comments.py
|
|
19
19
|
callout_lib/converter_deflist.py
|
|
20
20
|
callout_lib/detector.py
|
|
21
|
+
callout_lib/table_parser.py
|
|
21
22
|
doc_utils/__init__.py
|
|
22
23
|
doc_utils/extract_link_attributes.py
|
|
23
24
|
doc_utils/file_utils.py
|
|
@@ -51,6 +52,8 @@ tests/test_fixture_check_scannability.py
|
|
|
51
52
|
tests/test_parse_exclude_list.py
|
|
52
53
|
tests/test_replace_link_attributes.py
|
|
53
54
|
tests/test_symlink_handling.py
|
|
55
|
+
tests/test_table_callout_conversion.py
|
|
56
|
+
tests/test_table_parser.py
|
|
54
57
|
tests/test_topic_map_parser.py
|
|
55
58
|
tests/test_unused_attributes.py
|
|
56
59
|
tests/test_validate_links.py
|