rolfedh-doc-utils 0.1.25__py3-none-any.whl → 0.1.27__py3-none-any.whl

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.
callout_lib/detector.py CHANGED
@@ -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,95 @@ 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 list-format (<1> text), 2-column table, and 3-column table formats.
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
+ # Check if it's a 3-column table (Item | Value | Description)
167
+ if self.table_parser.is_3column_callout_table(table):
168
+ return self._extract_from_3column_table(table)
169
+ # Check if it's a 2-column table (<callout> | explanation)
170
+ elif self.table_parser.is_callout_table(table):
171
+ return self._extract_from_table(table)
172
+
173
+ # Fall back to list-format extraction
174
+ return self._extract_from_list(lines, start_line)
175
+
176
+ def _extract_from_table(self, table) -> Tuple[Dict[int, Callout], int]:
177
+ """Extract callout explanations from a table format."""
178
+ explanations = {}
179
+ table_data = self.table_parser.extract_callout_explanations_from_table(table)
180
+
181
+ for callout_num, (explanation_lines, conditionals) in table_data.items():
182
+ # Combine explanation lines with conditionals preserved
183
+ all_lines = []
184
+ for line in explanation_lines:
185
+ all_lines.append(line)
186
+
187
+ # Add conditionals as separate lines (they'll be preserved in output)
188
+ all_lines.extend(conditionals)
189
+
190
+ # Check if marked as optional
191
+ is_optional = False
192
+ if all_lines and (all_lines[0].lower().startswith('optional.') or
193
+ all_lines[0].lower().startswith('optional:')):
194
+ is_optional = True
195
+ all_lines[0] = all_lines[0][9:].strip()
196
+ elif all_lines and ('(Optional)' in all_lines[0] or '(optional)' in all_lines[0]):
197
+ is_optional = True
198
+ all_lines[0] = re.sub(r'\s*\(optional\)\s*', ' ', all_lines[0], flags=re.IGNORECASE).strip()
199
+
200
+ explanations[callout_num] = Callout(callout_num, all_lines, is_optional)
201
+
202
+ return explanations, table.end_line
203
+
204
+ def _extract_from_3column_table(self, table) -> Tuple[Dict[int, Callout], int]:
205
+ """
206
+ Extract callout explanations from a 3-column table format.
207
+ Format: Item (number) | Value | Description
208
+ """
209
+ explanations = {}
210
+ table_data = self.table_parser.extract_3column_callout_explanations(table)
211
+
212
+ for callout_num, (value_lines, description_lines, conditionals) in table_data.items():
213
+ # Combine value and description into explanation lines
214
+ # Strategy: Include value as context, then description
215
+ all_lines = []
216
+
217
+ # Add value lines with context
218
+ if value_lines:
219
+ # Format: "Refers to `value`. Description..."
220
+ value_text = value_lines[0] if value_lines else ""
221
+ # If value is code-like (contains backticks or special chars), keep it formatted
222
+ if value_text:
223
+ all_lines.append(f"Refers to {value_text}.")
224
+
225
+ # Add additional value lines if multi-line
226
+ for line in value_lines[1:]:
227
+ all_lines.append(line)
228
+
229
+ # Add description lines
230
+ all_lines.extend(description_lines)
231
+
232
+ # Add conditionals as separate lines (they'll be preserved in output)
233
+ all_lines.extend(conditionals)
234
+
235
+ # Check if marked as optional
236
+ is_optional = False
237
+ if all_lines and (all_lines[0].lower().startswith('optional.') or
238
+ all_lines[0].lower().startswith('optional:') or
239
+ 'optional' in all_lines[0].lower()[:50]): # Check first 50 chars
240
+ is_optional = True
241
+ # Don't remove "optional" text - it's part of the description
242
+
243
+ explanations[callout_num] = Callout(callout_num, all_lines, is_optional)
244
+
245
+ return explanations, table.end_line
246
+
247
+ def _extract_from_list(self, lines: List[str], start_line: int) -> Tuple[Dict[int, Callout], int]:
248
+ """Extract callout explanations from list format (<1> text)."""
156
249
  explanations = {}
157
250
  i = start_line + 1 # Start after the closing delimiter
158
251
 
@@ -0,0 +1,581 @@
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
+
192
+ # Check if there are multiple cells on the same line (e.g., |Cell1 |Cell2 |Cell3)
193
+ if '|' in cell_content:
194
+ # Split by | to get multiple cells
195
+ parts = cell_content.split('|')
196
+ for part in parts:
197
+ part = part.strip()
198
+ if part: # Skip empty parts
199
+ current_row_cells.append(TableCell(
200
+ content=[part],
201
+ conditionals=[]
202
+ ))
203
+ else:
204
+ # Single cell on this line
205
+ if cell_content:
206
+ current_cell_lines.append(cell_content)
207
+ # If empty, just start a new cell with no content yet
208
+
209
+ i += 1
210
+ continue
211
+
212
+ # Regular content line (continuation of current cell)
213
+ if current_cell_lines or current_row_cells:
214
+ current_cell_lines.append(line)
215
+
216
+ i += 1
217
+
218
+ # Return None if we didn't find a proper table end
219
+ return None
220
+
221
+ def is_callout_table(self, table: AsciiDocTable) -> bool:
222
+ """
223
+ Determine if a table is a callout explanation table.
224
+ A callout table has two columns: callout number and explanation.
225
+ """
226
+ if not table.rows:
227
+ return False
228
+
229
+ # Check if all rows have exactly 2 cells
230
+ if not all(len(row.cells) == 2 for row in table.rows):
231
+ return False
232
+
233
+ # Check if first cell of each row is a callout number
234
+ for row in table.rows:
235
+ first_cell = row.cells[0]
236
+ if not first_cell.content:
237
+ return False
238
+
239
+ # First line of first cell should be a callout number
240
+ first_line = first_cell.content[0].strip()
241
+ if not self.CALLOUT_NUMBER.match(first_line):
242
+ return False
243
+
244
+ return True
245
+
246
+ def _has_header_row(self, table: AsciiDocTable) -> bool:
247
+ """
248
+ Check if table has a header row.
249
+ Common header patterns: "Item", "Value", "Description", "Column", etc.
250
+ """
251
+ if not table.rows:
252
+ return False
253
+
254
+ first_row = table.rows[0]
255
+ if not first_row.cells:
256
+ return False
257
+
258
+ # Collect text from all cells in first row
259
+ header_text = ' '.join(
260
+ cell.content[0] if cell.content else ''
261
+ for cell in first_row.cells
262
+ ).lower()
263
+
264
+ # Check for common header keywords
265
+ header_keywords = ['item', 'description', 'value', 'column', 'parameter', 'field', 'name']
266
+ return any(keyword in header_text for keyword in header_keywords)
267
+
268
+ def is_3column_callout_table(self, table: AsciiDocTable) -> bool:
269
+ """
270
+ Determine if a table is a 3-column callout explanation table.
271
+ Format: Item (number) | Value | Description
272
+
273
+ This format is used in some documentation (e.g., Debezium) where:
274
+ - Column 1: Item number (1, 2, 3...) corresponding to callout numbers
275
+ - Column 2: The value/code being explained
276
+ - Column 3: Description/explanation text
277
+ """
278
+ if not table.rows:
279
+ return False
280
+
281
+ # Determine if there's a header row
282
+ has_header = self._has_header_row(table)
283
+ data_rows = table.rows[1:] if has_header else table.rows
284
+
285
+ if not data_rows:
286
+ return False
287
+
288
+ # Check if all data rows have exactly 3 cells
289
+ if not all(len(row.cells) == 3 for row in data_rows):
290
+ return False
291
+
292
+ # Check if first cell of each data row contains a plain number (1, 2, 3...)
293
+ for row in data_rows:
294
+ first_cell = row.cells[0]
295
+ if not first_cell.content:
296
+ return False
297
+
298
+ # First line of first cell should be a number
299
+ first_line = first_cell.content[0].strip()
300
+ if not first_line.isdigit():
301
+ return False
302
+
303
+ return True
304
+
305
+ def extract_callout_explanations_from_table(self, table: AsciiDocTable) -> Dict[int, Tuple[List[str], List[str]]]:
306
+ """
307
+ Extract callout explanations from a table.
308
+ Returns dict mapping callout number to tuple of (explanation_lines, conditionals).
309
+
310
+ The conditionals list includes any ifdef/ifndef/endif statements that should
311
+ be preserved when converting the table to other formats.
312
+ """
313
+ explanations = {}
314
+
315
+ for row in table.rows:
316
+ if len(row.cells) != 2:
317
+ continue
318
+
319
+ callout_cell = row.cells[0]
320
+ explanation_cell = row.cells[1]
321
+
322
+ # Extract callout number
323
+ first_line = callout_cell.content[0].strip()
324
+ match = self.CALLOUT_NUMBER.match(first_line)
325
+ if not match:
326
+ continue
327
+
328
+ callout_num = int(match.group(1))
329
+
330
+ # Collect explanation lines
331
+ explanation_lines = []
332
+ for line in explanation_cell.content:
333
+ # Skip conditional directives in explanation (preserve them separately)
334
+ if not (self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line)):
335
+ explanation_lines.append(line)
336
+
337
+ # Collect all conditionals for this row
338
+ all_conditionals = []
339
+ all_conditionals.extend(row.conditionals_before)
340
+
341
+ # Extract conditionals from explanation cell
342
+ for line in explanation_cell.content:
343
+ if self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line):
344
+ all_conditionals.append(line)
345
+
346
+ all_conditionals.extend(row.conditionals_after)
347
+
348
+ explanations[callout_num] = (explanation_lines, all_conditionals)
349
+
350
+ return explanations
351
+
352
+ def extract_3column_callout_explanations(self, table: AsciiDocTable) -> Dict[int, Tuple[List[str], List[str], List[str]]]:
353
+ """
354
+ Extract callout explanations from a 3-column table.
355
+ Returns dict mapping callout number to tuple of (value_lines, description_lines, conditionals).
356
+
357
+ Format: Item | Value | Description
358
+ - Item: Number (1, 2, 3...) corresponding to callout number
359
+ - Value: The code/value being explained
360
+ - Description: Explanation text
361
+
362
+ The conditionals list includes any ifdef/ifndef/endif statements that should
363
+ be preserved when converting the table to other formats.
364
+ """
365
+ explanations = {}
366
+
367
+ # Determine if there's a header row and skip it
368
+ has_header = self._has_header_row(table)
369
+ data_rows = table.rows[1:] if has_header else table.rows
370
+
371
+ for row in data_rows:
372
+ if len(row.cells) != 3:
373
+ continue
374
+
375
+ item_cell = row.cells[0]
376
+ value_cell = row.cells[1]
377
+ desc_cell = row.cells[2]
378
+
379
+ # Extract item number (maps to callout number)
380
+ if not item_cell.content:
381
+ continue
382
+
383
+ item_num_str = item_cell.content[0].strip()
384
+ if not item_num_str.isdigit():
385
+ continue
386
+
387
+ callout_num = int(item_num_str)
388
+
389
+ # Collect value lines (column 2)
390
+ value_lines = []
391
+ for line in value_cell.content:
392
+ # Skip conditional directives in value (preserve them separately)
393
+ if not (self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line)):
394
+ value_lines.append(line)
395
+
396
+ # Collect description lines (column 3)
397
+ description_lines = []
398
+ for line in desc_cell.content:
399
+ # Skip conditional directives in description (preserve them separately)
400
+ if not (self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line)):
401
+ description_lines.append(line)
402
+
403
+ # Collect all conditionals for this row
404
+ all_conditionals = []
405
+ all_conditionals.extend(row.conditionals_before)
406
+
407
+ # Extract conditionals from value cell
408
+ for line in value_cell.content:
409
+ if self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line):
410
+ all_conditionals.append(line)
411
+
412
+ # Extract conditionals from description cell
413
+ for line in desc_cell.content:
414
+ if self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line):
415
+ all_conditionals.append(line)
416
+
417
+ all_conditionals.extend(row.conditionals_after)
418
+
419
+ explanations[callout_num] = (value_lines, description_lines, all_conditionals)
420
+
421
+ return explanations
422
+
423
+ def find_callout_table_after_code_block(self, lines: List[str], code_block_end: int) -> Optional[AsciiDocTable]:
424
+ """
425
+ Find a callout explanation table that appears after a code block.
426
+
427
+ Args:
428
+ lines: All lines in the document
429
+ code_block_end: Line number where the code block ends
430
+
431
+ Returns:
432
+ AsciiDocTable if a callout table is found, None otherwise
433
+ """
434
+ # Skip blank lines and continuation markers after code block
435
+ i = code_block_end + 1
436
+ while i < len(lines) and (not lines[i].strip() or lines[i].strip() == '+'):
437
+ i += 1
438
+
439
+ # Look for a table starting within the next few lines
440
+ # (allowing for possible text between code block and table)
441
+ search_limit = min(i + 10, len(lines))
442
+
443
+ for j in range(i, search_limit):
444
+ line = lines[j]
445
+
446
+ # If we encounter a list-format callout explanation, stop
447
+ # (list format takes precedence over table format further away)
448
+ if self.CALLOUT_NUMBER.match(line.strip()):
449
+ return None
450
+
451
+ # If we encounter another code block start, stop
452
+ if line.strip() in ['----', '....'] or line.strip().startswith('[source'):
453
+ return None
454
+
455
+ # Check for table delimiter
456
+ if self.TABLE_DELIMITER.match(line):
457
+ # Found a table, parse it
458
+ start_line = j
459
+ if j > 0 and self.TABLE_START.match(lines[j - 1]):
460
+ start_line = j - 1
461
+
462
+ table = self._parse_table(lines, start_line, j)
463
+ if table and (self.is_callout_table(table) or self.is_3column_callout_table(table)):
464
+ return table
465
+
466
+ # If we found a table but it's not a callout table, stop searching
467
+ break
468
+
469
+ return None
470
+
471
+ def convert_table_to_deflist(self, table: AsciiDocTable, preserve_conditionals: bool = True) -> List[str]:
472
+ """
473
+ Convert a two-column table to an AsciiDoc definition list.
474
+
475
+ Args:
476
+ table: The table to convert
477
+ preserve_conditionals: Whether to preserve ifdef/ifndef/endif statements
478
+
479
+ Returns:
480
+ List of lines representing the definition list
481
+ """
482
+ output = []
483
+
484
+ for row in table.rows:
485
+ if len(row.cells) != 2:
486
+ continue
487
+
488
+ # Add conditionals before row
489
+ if preserve_conditionals and row.conditionals_before:
490
+ output.extend(row.conditionals_before)
491
+
492
+ # First cell is the term
493
+ term_lines = row.cells[0].content
494
+ if term_lines:
495
+ output.append(term_lines[0])
496
+
497
+ # Second cell is the definition
498
+ definition_lines = row.cells[1].content
499
+ if definition_lines:
500
+ # Filter out conditionals if needed
501
+ if preserve_conditionals:
502
+ for line in definition_lines:
503
+ if self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line):
504
+ output.append(line)
505
+ else:
506
+ output.append(f" {line}")
507
+ else:
508
+ for line in definition_lines:
509
+ if not (self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line)):
510
+ output.append(f" {line}")
511
+
512
+ # Add conditionals after row
513
+ if preserve_conditionals and row.conditionals_after:
514
+ output.extend(row.conditionals_after)
515
+
516
+ # Add blank line between entries
517
+ output.append("")
518
+
519
+ # Remove trailing blank line
520
+ if output and not output[-1].strip():
521
+ output.pop()
522
+
523
+ return output
524
+
525
+ def convert_table_to_bullets(self, table: AsciiDocTable, preserve_conditionals: bool = True) -> List[str]:
526
+ """
527
+ Convert a two-column table to a bulleted list.
528
+
529
+ Args:
530
+ table: The table to convert
531
+ preserve_conditionals: Whether to preserve ifdef/ifndef/endif statements
532
+
533
+ Returns:
534
+ List of lines representing the bulleted list
535
+ """
536
+ output = []
537
+
538
+ for row in table.rows:
539
+ if len(row.cells) != 2:
540
+ continue
541
+
542
+ # Add conditionals before row
543
+ if preserve_conditionals and row.conditionals_before:
544
+ output.extend(row.conditionals_before)
545
+
546
+ # Get the term (first cell)
547
+ term_lines = row.cells[0].content
548
+ term = term_lines[0] if term_lines else ""
549
+
550
+ # Get the definition (second cell)
551
+ definition_lines = row.cells[1].content
552
+
553
+ # Filter conditionals from definition if needed
554
+ filtered_def_lines = []
555
+ inline_conditionals = []
556
+
557
+ for line in definition_lines:
558
+ if self.IFDEF_PATTERN.match(line) or self.ENDIF_PATTERN.match(line):
559
+ if preserve_conditionals:
560
+ inline_conditionals.append(line)
561
+ else:
562
+ filtered_def_lines.append(line)
563
+
564
+ # Create bullet item
565
+ if filtered_def_lines:
566
+ first_line = filtered_def_lines[0]
567
+ output.append(f"* *{term}*: {first_line}")
568
+
569
+ # Add continuation lines with proper indentation
570
+ for line in filtered_def_lines[1:]:
571
+ output.append(f" {line}")
572
+
573
+ # Add inline conditionals if present
574
+ if preserve_conditionals and inline_conditionals:
575
+ output.extend(inline_conditionals)
576
+
577
+ # Add conditionals after row
578
+ if preserve_conditionals and row.conditionals_after:
579
+ output.extend(row.conditionals_after)
580
+
581
+ return output
@@ -166,14 +166,15 @@ class CalloutConverter:
166
166
  content_start = block.start_line + 1 # After ---- only
167
167
  content_end = block.end_line
168
168
 
169
- # For comments format (without fallback), we keep the explanations section
170
- # For deflist/bullets format, we remove old explanations and add new list
169
+ # For comments format (without fallback), remove explanations but don't add new list
170
+ # For deflist/bullets format, remove old explanations and add new list
171
171
  if self.output_format == 'comments' and not use_deflist_fallback:
172
- # Keep everything as-is, just replace code content
172
+ # Remove old callout explanations (list or table format)
173
173
  new_section = (
174
174
  new_lines[:content_start] +
175
175
  converted_content +
176
- new_lines[content_end:]
176
+ [new_lines[content_end]] + # Keep closing delimiter
177
+ new_lines[explanation_end + 1:] # Skip explanations/table, keep rest
177
178
  )
178
179
  else:
179
180
  # Remove old callout explanations and add new list
@@ -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.27
4
4
  Summary: CLI tools for AsciiDoc documentation projects
5
5
  Author: Rolfe Dlugy-Hegwer
6
6
  License: MIT License
@@ -2,7 +2,7 @@ archive_unused_files.py,sha256=OJZrkqn70hiOXED218jMYPFNFWnsDpjsCYOmBRxYnHU,2274
2
2
  archive_unused_images.py,sha256=fZeyEZtTd72Gbd3YBXTy5xoshAAM9qb4qFPMjhHL1Fg,1864
3
3
  check_scannability.py,sha256=O6ROr-e624jVPvPpASpsWo0gTfuCFpA2mTSX61BjAEI,5478
4
4
  convert_callouts_interactive.py,sha256=hoDKff3jqyJiGZ3IqjcWF7AXM4XUQE-vVg2NpJYECs4,21066
5
- convert_callouts_to_deflist.py,sha256=WRpHmD3DWs7G7qEcL6c9WLYpX4uaPzlMKrWXnPlHQ_s,17720
5
+ convert_callouts_to_deflist.py,sha256=MfNbbTzaODFIK6jdPdCoMCe27KqMjJFjdoIiGazm978,17852
6
6
  doc_utils_cli.py,sha256=dsMYrkAriYdZUF0_cSPh5DAPrJMPiecuY26xN-p0UJ0,4911
7
7
  extract_link_attributes.py,sha256=wR2SmR2la-jR6DzDbas2PoNONgRZ4dZ6aqwzkwEv8Gs,3516
8
8
  find_unused_attributes.py,sha256=77CxFdm72wj6SO81w-auMdDjnvF83jWy_qaM7DsAtBw,4263
@@ -13,7 +13,8 @@ callout_lib/__init__.py,sha256=8B82N_z4D1LaZVYgd5jZR53QAabtgPzADOyGlnvihj0,665
13
13
  callout_lib/converter_bullets.py,sha256=8P92QZ0PylrKRE7V-D3TVZCDH0ct3GRIonc7W5AK5uU,3898
14
14
  callout_lib/converter_comments.py,sha256=do0dH8uOyNFpn5CDEzR0jYYCMIPP3oPFM8cEB-Fp22c,9767
15
15
  callout_lib/converter_deflist.py,sha256=mQ17Y8gJLv0MzzJscpUL7ujuDRyEVcrb9lcPdUNkIX4,3117
16
- callout_lib/detector.py,sha256=douBVXaTM_VWzI5mN8R7QyYzqxD1sVTFPuAbU-blPNo,8806
16
+ callout_lib/detector.py,sha256=yPikWkoZybd5GjooEv8xYd-iLQbHP09cNCp7HrcqWCA,13200
17
+ callout_lib/table_parser.py,sha256=CaFOphhwONF4zQWeVVGZbB-rwF0Ety7R6My3V6ETdig,21119
17
18
  doc_utils/__init__.py,sha256=qqZR3lohzkP63soymrEZPBGzzk6-nFzi4_tSffjmu_0,74
18
19
  doc_utils/extract_link_attributes.py,sha256=U0EvPZReJQigNfbT-icBsVT6Li64hYki5W7MQz6qqbc,22743
19
20
  doc_utils/file_utils.py,sha256=fpTh3xx759sF8sNocdn_arsP3KAv8XA6cTQTAVIZiZg,4247
@@ -28,9 +29,9 @@ doc_utils/unused_images.py,sha256=nqn36Bbrmon2KlGlcaruNjJJvTQ8_9H0WU9GvCW7rW8,14
28
29
  doc_utils/validate_links.py,sha256=iBGXnwdeLlgIT3fo3v01ApT5k0X2FtctsvkrE6E3VMk,19610
29
30
  doc_utils/version.py,sha256=5Uc0sAUOkXA6R_PvDGjw2MBYptEKdav5XmeRqukMTo0,203
30
31
  doc_utils/version_check.py,sha256=eHJnZmBTbdhhY2fJQW9KnnyD0rWEvCZpMg6oSr0fOmI,7090
31
- rolfedh_doc_utils-0.1.25.dist-info/licenses/LICENSE,sha256=vLxtwMVOJA_hEy8b77niTkdmQI9kNJskXHq0dBS36e0,1075
32
- rolfedh_doc_utils-0.1.25.dist-info/METADATA,sha256=MxNbC8OYyyEGXZE_iEipQJHSvpsxzbcyA3e32nD_hsA,8325
33
- rolfedh_doc_utils-0.1.25.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- rolfedh_doc_utils-0.1.25.dist-info/entry_points.txt,sha256=vL_LlLKOiurRzchrq8iRUQG19Xi9lSAFVZGjO-xyErk,577
35
- rolfedh_doc_utils-0.1.25.dist-info/top_level.txt,sha256=J4xtr3zoyCip27b3GnticFVZoyz5HHtgGqHQ-SZONCA,265
36
- rolfedh_doc_utils-0.1.25.dist-info/RECORD,,
32
+ rolfedh_doc_utils-0.1.27.dist-info/licenses/LICENSE,sha256=vLxtwMVOJA_hEy8b77niTkdmQI9kNJskXHq0dBS36e0,1075
33
+ rolfedh_doc_utils-0.1.27.dist-info/METADATA,sha256=Kk9CDC-zzU6sDESsDr_zumWLMycacohrbLUq7_IWHKs,8325
34
+ rolfedh_doc_utils-0.1.27.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
35
+ rolfedh_doc_utils-0.1.27.dist-info/entry_points.txt,sha256=vL_LlLKOiurRzchrq8iRUQG19Xi9lSAFVZGjO-xyErk,577
36
+ rolfedh_doc_utils-0.1.27.dist-info/top_level.txt,sha256=J4xtr3zoyCip27b3GnticFVZoyz5HHtgGqHQ-SZONCA,265
37
+ rolfedh_doc_utils-0.1.27.dist-info/RECORD,,