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.
Files changed (62) hide show
  1. {rolfedh_doc_utils-0.1.25/rolfedh_doc_utils.egg-info → rolfedh_doc_utils-0.1.26}/PKG-INFO +1 -1
  2. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/callout_lib/detector.py +45 -0
  3. rolfedh_doc_utils-0.1.26/callout_lib/table_parser.py +437 -0
  4. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/pyproject.toml +1 -1
  5. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26/rolfedh_doc_utils.egg-info}/PKG-INFO +1 -1
  6. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/rolfedh_doc_utils.egg-info/SOURCES.txt +3 -0
  7. rolfedh_doc_utils-0.1.26/tests/test_table_callout_conversion.py +261 -0
  8. rolfedh_doc_utils-0.1.26/tests/test_table_parser.py +330 -0
  9. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/LICENSE +0 -0
  10. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/README.md +0 -0
  11. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/archive_unused_files.py +0 -0
  12. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/archive_unused_images.py +0 -0
  13. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/callout_lib/__init__.py +0 -0
  14. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/callout_lib/converter_bullets.py +0 -0
  15. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/callout_lib/converter_comments.py +0 -0
  16. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/callout_lib/converter_deflist.py +0 -0
  17. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/check_scannability.py +0 -0
  18. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/convert_callouts_interactive.py +0 -0
  19. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/convert_callouts_to_deflist.py +0 -0
  20. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/__init__.py +0 -0
  21. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/extract_link_attributes.py +0 -0
  22. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/file_utils.py +0 -0
  23. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/format_asciidoc_spacing.py +0 -0
  24. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/replace_link_attributes.py +0 -0
  25. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/scannability.py +0 -0
  26. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/spinner.py +0 -0
  27. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/topic_map_parser.py +0 -0
  28. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/unused_adoc.py +0 -0
  29. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/unused_attributes.py +0 -0
  30. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/unused_images.py +0 -0
  31. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/validate_links.py +0 -0
  32. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/version.py +0 -0
  33. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils/version_check.py +0 -0
  34. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/doc_utils_cli.py +0 -0
  35. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/extract_link_attributes.py +0 -0
  36. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/find_unused_attributes.py +0 -0
  37. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/format_asciidoc_spacing.py +0 -0
  38. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/replace_link_attributes.py +0 -0
  39. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/rolfedh_doc_utils.egg-info/dependency_links.txt +0 -0
  40. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/rolfedh_doc_utils.egg-info/entry_points.txt +0 -0
  41. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/rolfedh_doc_utils.egg-info/requires.txt +0 -0
  42. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/rolfedh_doc_utils.egg-info/top_level.txt +0 -0
  43. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/setup.cfg +0 -0
  44. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/setup.py +0 -0
  45. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_archive_unused_files.py +0 -0
  46. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_archive_unused_images.py +0 -0
  47. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_auto_discovery.py +0 -0
  48. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_check_scannability.py +0 -0
  49. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_cli_entry_points.py +0 -0
  50. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_extract_link_attributes.py +0 -0
  51. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_file_utils.py +0 -0
  52. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_fixture_archive_unused_files.py +0 -0
  53. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_fixture_archive_unused_images.py +0 -0
  54. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_fixture_check_scannability.py +0 -0
  55. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_parse_exclude_list.py +0 -0
  56. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_replace_link_attributes.py +0 -0
  57. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_symlink_handling.py +0 -0
  58. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_topic_map_parser.py +0 -0
  59. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_unused_attributes.py +0 -0
  60. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_validate_links.py +0 -0
  61. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/tests/test_version_check.py +0 -0
  62. {rolfedh_doc_utils-0.1.25 → rolfedh_doc_utils-0.1.26}/validate_links.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rolfedh-doc-utils
3
- Version: 0.1.25
3
+ Version: 0.1.26
4
4
  Summary: CLI tools for AsciiDoc documentation projects
5
5
  Author: Rolfe Dlugy-Hegwer
6
6
  License: MIT License
@@ -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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rolfedh-doc-utils"
7
- version = "0.1.25"
7
+ version = "0.1.26"
8
8
  description = "CLI tools for AsciiDoc documentation projects"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rolfedh-doc-utils
3
- Version: 0.1.25
3
+ Version: 0.1.26
4
4
  Summary: CLI tools for AsciiDoc documentation projects
5
5
  Author: Rolfe Dlugy-Hegwer
6
6
  License: MIT License
@@ -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