rolfedh-doc-utils 0.1.29__py3-none-any.whl → 0.1.31__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.
@@ -16,7 +16,7 @@ class BulletListConverter:
16
16
  USER_VALUE_PATTERN = re.compile(r'(?<!<)<([a-zA-Z][^>]*)>')
17
17
 
18
18
  @staticmethod
19
- def convert(callout_groups: List[CalloutGroup], explanations: Dict[int, Callout]) -> List[str]:
19
+ def convert(callout_groups: List[CalloutGroup], explanations: Dict[int, Callout], table_title: str = "") -> List[str]:
20
20
  """
21
21
  Create bulleted list from callout groups and explanations.
22
22
 
@@ -33,12 +33,20 @@ class BulletListConverter:
33
33
 
34
34
  Args:
35
35
  callout_groups: List of CalloutGroup objects from code block
36
+ table_title: Optional table title (e.g., ".Descriptions of delete event")
37
+ Will be converted to lead-in sentence
36
38
  explanations: Dict mapping callout numbers to Callout objects
37
39
 
38
40
  Returns:
39
41
  List of strings representing the bulleted list
40
42
  """
41
- lines = [''] # Start with blank line before list
43
+ # Convert table title to lead-in sentence if present
44
+ if table_title:
45
+ # Remove leading dot and trailing period if present
46
+ title_text = table_title.lstrip('.').rstrip('.')
47
+ lines = [f'\n{title_text}:'] # Use colon for bulleted list lead-in
48
+ else:
49
+ lines = [''] # Start with blank line before list
42
50
 
43
51
  # Process each group (which may contain one or more callouts)
44
52
  for group in callout_groups:
@@ -16,7 +16,7 @@ class DefListConverter:
16
16
  USER_VALUE_PATTERN = re.compile(r'(?<!<)<([a-zA-Z][^>]*)>')
17
17
 
18
18
  @staticmethod
19
- def convert(callout_groups: List[CalloutGroup], explanations: Dict[int, Callout]) -> List[str]:
19
+ def convert(callout_groups: List[CalloutGroup], explanations: Dict[int, Callout], table_title: str = "") -> List[str]:
20
20
  """
21
21
  Create definition list from callout groups and explanations.
22
22
 
@@ -29,11 +29,19 @@ class DefListConverter:
29
29
  Args:
30
30
  callout_groups: List of CalloutGroup objects from code block
31
31
  explanations: Dict mapping callout numbers to Callout objects
32
+ table_title: Optional table title (e.g., ".Descriptions of delete event")
33
+ Will be converted to lead-in sentence (e.g., "Descriptions of delete event, where:")
32
34
 
33
35
  Returns:
34
36
  List of strings representing the definition list
35
37
  """
36
- lines = ['\nwhere:']
38
+ # Convert table title to lead-in sentence if present
39
+ if table_title:
40
+ # Remove leading dot and trailing period if present
41
+ title_text = table_title.lstrip('.').rstrip('.')
42
+ lines = [f'\n{title_text}, where:']
43
+ else:
44
+ lines = ['\nwhere:']
37
45
 
38
46
  # Process each group (which may contain one or more callouts)
39
47
  for group in callout_groups:
@@ -70,10 +78,37 @@ class DefListConverter:
70
78
  lines.append('+')
71
79
 
72
80
  # Add explanation lines, prepending "Optional. " to first line if needed
81
+ # Handle blank lines and conditionals by inserting continuation markers
82
+ need_continuation = False
83
+ had_content = False # Track if we've output any non-conditional content
84
+
73
85
  for line_idx, line in enumerate(explanation.lines):
86
+ stripped = line.strip()
87
+
88
+ # Check if this is a blank line
89
+ if stripped == '':
90
+ # Next non-blank line will need a continuation marker
91
+ need_continuation = True
92
+ continue # Skip blank lines
93
+
94
+ # Check if this is a conditional directive
95
+ is_conditional = stripped.startswith(('ifdef::', 'ifndef::', 'endif::'))
96
+
97
+ # Add continuation marker if:
98
+ # 1. Previous line was blank (need_continuation=True), OR
99
+ # 2. This is a conditional and we've had content before (need separator)
100
+ if need_continuation or (is_conditional and had_content and line_idx > 0):
101
+ lines.append('+')
102
+ need_continuation = False
103
+
104
+ # Add the line
74
105
  if line_idx == 0 and explanation.is_optional:
75
106
  lines.append(f'Optional. {line}')
76
107
  else:
77
108
  lines.append(line)
78
109
 
110
+ # Track that we've output content (not just conditionals)
111
+ if not is_conditional:
112
+ had_content = True
113
+
79
114
  return lines
callout_lib/detector.py CHANGED
@@ -60,6 +60,8 @@ class CalloutDetector:
60
60
  def __init__(self):
61
61
  """Initialize detector with table parser."""
62
62
  self.table_parser = TableParser()
63
+ self.last_table_title = "" # Track title from most recent table extraction
64
+ self.last_table = None # Track last table found for validation diagnostics
63
65
 
64
66
  def find_code_blocks(self, lines: List[str]) -> List[CodeBlock]:
65
67
  """Find all code blocks in the document."""
@@ -181,17 +183,24 @@ class CalloutDetector:
181
183
 
182
184
  def _extract_from_table(self, table) -> Tuple[Dict[int, Callout], int]:
183
185
  """Extract callout explanations from a table format."""
186
+ # Store table for use by converters and validation
187
+ self.last_table = table
188
+ self.last_table_title = table.title if hasattr(table, 'title') else ""
189
+
184
190
  explanations = {}
185
191
  table_data = self.table_parser.extract_callout_explanations_from_table(table)
186
192
 
187
- for callout_num, (explanation_lines, conditionals) in table_data.items():
188
- # Combine explanation lines with conditionals preserved
193
+ for callout_num, (explanation_lines, row_conditionals) in table_data.items():
194
+ # explanation_lines now includes blank lines and conditionals inline
195
+ # row_conditionals are before/after the entire row (rarely used)
189
196
  all_lines = []
190
- for line in explanation_lines:
191
- all_lines.append(line)
192
197
 
193
- # Add conditionals as separate lines (they'll be preserved in output)
194
- all_lines.extend(conditionals)
198
+ # Add any row-level conditionals before
199
+ if row_conditionals:
200
+ all_lines.extend(row_conditionals)
201
+
202
+ # Add explanation lines (already includes inline conditionals and blank lines)
203
+ all_lines.extend(explanation_lines)
195
204
 
196
205
  # Check if marked as optional
197
206
  is_optional = False
@@ -212,14 +221,22 @@ class CalloutDetector:
212
221
  Extract callout explanations from a 3-column table format.
213
222
  Format: Item (number) | Value | Description
214
223
  """
224
+ # Store table for use by converters and validation
225
+ self.last_table = table
226
+ self.last_table_title = table.title if hasattr(table, 'title') else ""
227
+
215
228
  explanations = {}
216
229
  table_data = self.table_parser.extract_3column_callout_explanations(table)
217
230
 
218
- for callout_num, (value_lines, description_lines, conditionals) in table_data.items():
231
+ for callout_num, (value_lines, description_lines, row_conditionals) in table_data.items():
219
232
  # Combine value and description into explanation lines
220
- # Strategy: Include value as context, then description
233
+ # Both value_lines and description_lines now include conditionals and blank lines inline
221
234
  all_lines = []
222
235
 
236
+ # Add any row-level conditionals before
237
+ if row_conditionals:
238
+ all_lines.extend(row_conditionals)
239
+
223
240
  # Add value lines with context
224
241
  if value_lines:
225
242
  # Format: "`value`:"
@@ -228,16 +245,13 @@ class CalloutDetector:
228
245
  if value_text:
229
246
  all_lines.append(f"{value_text}:")
230
247
 
231
- # Add additional value lines if multi-line
248
+ # Add additional value lines if multi-line (includes conditionals and blank lines)
232
249
  for line in value_lines[1:]:
233
250
  all_lines.append(line)
234
251
 
235
- # Add description lines
252
+ # Add description lines (already includes conditionals and blank lines)
236
253
  all_lines.extend(description_lines)
237
254
 
238
- # Add conditionals as separate lines (they'll be preserved in output)
239
- all_lines.extend(conditionals)
240
-
241
255
  # Check if marked as optional
242
256
  is_optional = False
243
257
  if all_lines and (all_lines[0].lower().startswith('optional.') or
@@ -252,6 +266,10 @@ class CalloutDetector:
252
266
 
253
267
  def _extract_from_list(self, lines: List[str], start_line: int) -> Tuple[Dict[int, Callout], int]:
254
268
  """Extract callout explanations from list format (<1> text)."""
269
+ # Clear table data since list format doesn't have tables
270
+ self.last_table = None
271
+ self.last_table_title = ""
272
+
255
273
  explanations = {}
256
274
  i = start_line + 1 # Start after the closing delimiter
257
275
 
@@ -306,17 +324,33 @@ class CalloutDetector:
306
324
  cleaned.append(self.CALLOUT_WITH_COMMENT.sub('', line).rstrip())
307
325
  return cleaned
308
326
 
309
- def validate_callouts(self, callout_groups: List[CalloutGroup], explanations: Dict[int, Callout]) -> Tuple[bool, set, set]:
327
+ def validate_callouts(self, callout_groups: List[CalloutGroup], explanations: Dict[int, Callout]) -> Tuple[bool, List[int], List[int]]:
310
328
  """
311
329
  Validate that callout numbers in code match explanation numbers.
312
- Returns tuple of (is_valid, code_nums, explanation_nums).
330
+ Returns tuple of (is_valid, code_nums_list, explanation_nums_list).
331
+
332
+ Returns:
333
+ - is_valid: True if unique callout numbers match
334
+ - code_nums_list: List of callout numbers from code (unique, sorted)
335
+ - explanation_nums_list: List of callout numbers from explanations
336
+ (preserves duplicates if from table, sorted)
313
337
  """
314
- # Extract all callout numbers from groups
315
- code_nums = set()
338
+ # Extract unique callout numbers from code groups
339
+ code_nums_set = set()
316
340
  for group in callout_groups:
317
- code_nums.update(group.callout_numbers)
341
+ code_nums_set.update(group.callout_numbers)
342
+
343
+ # Get explanation numbers, preserving duplicates if from a table
344
+ if self.last_table:
345
+ # Use table parser to get raw callout numbers (with duplicates)
346
+ explanation_nums_list = self.table_parser.get_table_callout_numbers(self.last_table)
347
+ else:
348
+ # List format: dict keys are already unique
349
+ explanation_nums_list = list(explanations.keys())
350
+
351
+ explanation_nums_set = set(explanation_nums_list)
318
352
 
319
- explanation_nums = set(explanations.keys())
353
+ # Validation compares unique numbers only
354
+ is_valid = code_nums_set == explanation_nums_set
320
355
 
321
- is_valid = code_nums == explanation_nums
322
- return is_valid, code_nums, explanation_nums
356
+ return is_valid, sorted(code_nums_set), sorted(explanation_nums_list)