rolfedh-doc-utils 0.1.26__tar.gz → 0.1.27__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.26 → rolfedh_doc_utils-0.1.27}/PKG-INFO +1 -1
  2. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/callout_lib/detector.py +50 -2
  3. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/callout_lib/table_parser.py +148 -4
  4. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/convert_callouts_to_deflist.py +5 -4
  5. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/pyproject.toml +1 -1
  6. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/rolfedh_doc_utils.egg-info/PKG-INFO +1 -1
  7. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/LICENSE +0 -0
  8. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/README.md +0 -0
  9. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/archive_unused_files.py +0 -0
  10. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/archive_unused_images.py +0 -0
  11. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/callout_lib/__init__.py +0 -0
  12. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/callout_lib/converter_bullets.py +0 -0
  13. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/callout_lib/converter_comments.py +0 -0
  14. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/callout_lib/converter_deflist.py +0 -0
  15. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/check_scannability.py +0 -0
  16. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/convert_callouts_interactive.py +0 -0
  17. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils/__init__.py +0 -0
  18. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils/extract_link_attributes.py +0 -0
  19. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils/file_utils.py +0 -0
  20. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils/format_asciidoc_spacing.py +0 -0
  21. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils/replace_link_attributes.py +0 -0
  22. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils/scannability.py +0 -0
  23. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils/spinner.py +0 -0
  24. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils/topic_map_parser.py +0 -0
  25. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils/unused_adoc.py +0 -0
  26. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils/unused_attributes.py +0 -0
  27. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils/unused_images.py +0 -0
  28. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils/validate_links.py +0 -0
  29. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils/version.py +0 -0
  30. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils/version_check.py +0 -0
  31. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/doc_utils_cli.py +0 -0
  32. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/extract_link_attributes.py +0 -0
  33. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/find_unused_attributes.py +0 -0
  34. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/format_asciidoc_spacing.py +0 -0
  35. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/replace_link_attributes.py +0 -0
  36. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/rolfedh_doc_utils.egg-info/SOURCES.txt +0 -0
  37. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/rolfedh_doc_utils.egg-info/dependency_links.txt +0 -0
  38. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/rolfedh_doc_utils.egg-info/entry_points.txt +0 -0
  39. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/rolfedh_doc_utils.egg-info/requires.txt +0 -0
  40. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/rolfedh_doc_utils.egg-info/top_level.txt +0 -0
  41. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/setup.cfg +0 -0
  42. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/setup.py +0 -0
  43. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_archive_unused_files.py +0 -0
  44. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_archive_unused_images.py +0 -0
  45. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_auto_discovery.py +0 -0
  46. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_check_scannability.py +0 -0
  47. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_cli_entry_points.py +0 -0
  48. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_extract_link_attributes.py +0 -0
  49. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_file_utils.py +0 -0
  50. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_fixture_archive_unused_files.py +0 -0
  51. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_fixture_archive_unused_images.py +0 -0
  52. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_fixture_check_scannability.py +0 -0
  53. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_parse_exclude_list.py +0 -0
  54. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_replace_link_attributes.py +0 -0
  55. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_symlink_handling.py +0 -0
  56. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_table_callout_conversion.py +0 -0
  57. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_table_parser.py +0 -0
  58. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_topic_map_parser.py +0 -0
  59. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_unused_attributes.py +0 -0
  60. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_validate_links.py +0 -0
  61. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/tests/test_version_check.py +0 -0
  62. {rolfedh_doc_utils-0.1.26 → rolfedh_doc_utils-0.1.27}/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.26
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
@@ -157,13 +157,18 @@ class CalloutDetector:
157
157
  def extract_callout_explanations(self, lines: List[str], start_line: int) -> Tuple[Dict[int, Callout], int]:
158
158
  """
159
159
  Extract callout explanations following a code block.
160
- Supports both list-format (<1> text) and table-format explanations.
160
+ Supports list-format (<1> text), 2-column table, and 3-column table formats.
161
161
  Returns dict of callouts and the line number where explanations end.
162
162
  """
163
163
  # First, try to find a table-format callout explanation
164
164
  table = self.table_parser.find_callout_table_after_code_block(lines, start_line)
165
165
  if table:
166
- return self._extract_from_table(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)
167
172
 
168
173
  # Fall back to list-format extraction
169
174
  return self._extract_from_list(lines, start_line)
@@ -196,6 +201,49 @@ class CalloutDetector:
196
201
 
197
202
  return explanations, table.end_line
198
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
+
199
247
  def _extract_from_list(self, lines: List[str], start_line: int) -> Tuple[Dict[int, Callout], int]:
200
248
  """Extract callout explanations from list format (<1> text)."""
201
249
  explanations = {}
@@ -188,9 +188,23 @@ class TableParser:
188
188
 
189
189
  # Extract cell content from this line (text after |)
190
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
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
194
208
 
195
209
  i += 1
196
210
  continue
@@ -229,6 +243,65 @@ class TableParser:
229
243
 
230
244
  return True
231
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
+
232
305
  def extract_callout_explanations_from_table(self, table: AsciiDocTable) -> Dict[int, Tuple[List[str], List[str]]]:
233
306
  """
234
307
  Extract callout explanations from a table.
@@ -276,6 +349,77 @@ class TableParser:
276
349
 
277
350
  return explanations
278
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
+
279
423
  def find_callout_table_after_code_block(self, lines: List[str], code_block_end: int) -> Optional[AsciiDocTable]:
280
424
  """
281
425
  Find a callout explanation table that appears after a code block.
@@ -316,7 +460,7 @@ class TableParser:
316
460
  start_line = j - 1
317
461
 
318
462
  table = self._parse_table(lines, start_line, j)
319
- if table and self.is_callout_table(table):
463
+ if table and (self.is_callout_table(table) or self.is_3column_callout_table(table)):
320
464
  return table
321
465
 
322
466
  # If we found a table but it's not a callout table, stop searching
@@ -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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rolfedh-doc-utils"
7
- version = "0.1.26"
7
+ version = "0.1.27"
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.26
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