rolfedh-doc-utils 0.1.24__py3-none-any.whl → 0.1.26__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/__init__.py +22 -0
- callout_lib/converter_bullets.py +95 -0
- callout_lib/converter_comments.py +295 -0
- callout_lib/converter_deflist.py +79 -0
- callout_lib/detector.py +265 -0
- callout_lib/table_parser.py +437 -0
- convert_callouts_interactive.py +532 -0
- convert_callouts_to_deflist.py +114 -393
- {rolfedh_doc_utils-0.1.24.dist-info → rolfedh_doc_utils-0.1.26.dist-info}/METADATA +1 -1
- {rolfedh_doc_utils-0.1.24.dist-info → rolfedh_doc_utils-0.1.26.dist-info}/RECORD +14 -7
- {rolfedh_doc_utils-0.1.24.dist-info → rolfedh_doc_utils-0.1.26.dist-info}/entry_points.txt +1 -0
- {rolfedh_doc_utils-0.1.24.dist-info → rolfedh_doc_utils-0.1.26.dist-info}/top_level.txt +2 -0
- {rolfedh_doc_utils-0.1.24.dist-info → rolfedh_doc_utils-0.1.26.dist-info}/WHEEL +0 -0
- {rolfedh_doc_utils-0.1.24.dist-info → rolfedh_doc_utils-0.1.26.dist-info}/licenses/LICENSE +0 -0
convert_callouts_to_deflist.py
CHANGED
|
@@ -3,18 +3,24 @@
|
|
|
3
3
|
convert-callouts-to-deflist - Convert AsciiDoc callouts to definition list format
|
|
4
4
|
|
|
5
5
|
Converts code blocks with callout-style annotations (<1>, <2>, etc.) to cleaner
|
|
6
|
-
definition list format with "where:" prefix.
|
|
6
|
+
definition list format with "where:" prefix, bulleted list format, or inline comments.
|
|
7
7
|
|
|
8
8
|
This tool automatically scans all .adoc files in the current directory (recursively)
|
|
9
9
|
by default, or you can specify a specific file or directory.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
import re
|
|
13
12
|
import sys
|
|
14
13
|
import argparse
|
|
15
14
|
from pathlib import Path
|
|
16
|
-
from typing import List,
|
|
17
|
-
|
|
15
|
+
from typing import List, Tuple
|
|
16
|
+
|
|
17
|
+
# Import from callout_lib
|
|
18
|
+
from callout_lib import (
|
|
19
|
+
CalloutDetector,
|
|
20
|
+
DefListConverter,
|
|
21
|
+
BulletListConverter,
|
|
22
|
+
CommentConverter,
|
|
23
|
+
)
|
|
18
24
|
|
|
19
25
|
|
|
20
26
|
# Colors for output
|
|
@@ -30,374 +36,30 @@ def print_colored(message: str, color: str = Colors.NC) -> None:
|
|
|
30
36
|
print(f"{color}{message}{Colors.NC}")
|
|
31
37
|
|
|
32
38
|
|
|
33
|
-
@dataclass
|
|
34
|
-
class Callout:
|
|
35
|
-
"""Represents a callout with its number and explanation text."""
|
|
36
|
-
number: int
|
|
37
|
-
lines: List[str] # List of lines to preserve formatting
|
|
38
|
-
is_optional: bool = False
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@dataclass
|
|
42
|
-
class CalloutGroup:
|
|
43
|
-
"""Represents one or more callouts that share the same code line."""
|
|
44
|
-
code_line: str # The actual code line (without callouts)
|
|
45
|
-
callout_numbers: List[int] # List of callout numbers on this line
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@dataclass
|
|
49
|
-
class CodeBlock:
|
|
50
|
-
"""Represents a code block with its content and metadata."""
|
|
51
|
-
start_line: int
|
|
52
|
-
end_line: int
|
|
53
|
-
delimiter: str
|
|
54
|
-
content: List[str]
|
|
55
|
-
language: Optional[str] = None
|
|
56
|
-
|
|
57
|
-
|
|
58
39
|
class CalloutConverter:
|
|
59
|
-
"""Converts callout-style documentation to
|
|
60
|
-
|
|
61
|
-
# Pattern for code block start: [source,language] or [source] with optional attributes
|
|
62
|
-
# Matches: [source], [source,java], [source,java,subs="..."], [source,java,options="..."], etc.
|
|
63
|
-
CODE_BLOCK_START = re.compile(r'^\[source(?:,\s*(\w+))?(?:[,\s]+[^\]]+)?\]')
|
|
64
|
-
|
|
65
|
-
# Pattern for callout number in code block (can appear multiple times per line)
|
|
66
|
-
CALLOUT_IN_CODE = re.compile(r'<(\d+)>')
|
|
67
|
-
|
|
68
|
-
# Pattern for callout explanation line: <1> Explanation text
|
|
69
|
-
CALLOUT_EXPLANATION = re.compile(r'^<(\d+)>\s+(.+)$')
|
|
40
|
+
"""Converts callout-style documentation to various formats."""
|
|
70
41
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
USER_VALUE_PATTERN = re.compile(r'(?<!<)<([a-zA-Z][^>]*)>')
|
|
74
|
-
|
|
75
|
-
def __init__(self, dry_run: bool = False, verbose: bool = False, output_format: str = 'deflist'):
|
|
42
|
+
def __init__(self, dry_run: bool = False, verbose: bool = False, output_format: str = 'deflist',
|
|
43
|
+
max_comment_length: int = 120):
|
|
76
44
|
self.dry_run = dry_run
|
|
77
45
|
self.verbose = verbose
|
|
78
|
-
self.output_format = output_format # 'deflist' or '
|
|
46
|
+
self.output_format = output_format # 'deflist', 'bullets', or 'comments'
|
|
47
|
+
self.max_comment_length = max_comment_length # Max length for inline comments
|
|
79
48
|
self.changes_made = 0
|
|
80
49
|
self.warnings = [] # Collect warnings for summary
|
|
50
|
+
self.long_comment_warnings = [] # Warnings for comments exceeding max length
|
|
51
|
+
|
|
52
|
+
# Initialize detector and converters
|
|
53
|
+
self.detector = CalloutDetector()
|
|
81
54
|
|
|
82
55
|
def log(self, message: str):
|
|
83
56
|
"""Print message if verbose mode is enabled."""
|
|
84
57
|
if self.verbose:
|
|
85
58
|
print(f"[INFO] {message}")
|
|
86
59
|
|
|
87
|
-
def find_code_blocks(self, lines: List[str]) -> List[CodeBlock]:
|
|
88
|
-
"""Find all code blocks in the document."""
|
|
89
|
-
blocks = []
|
|
90
|
-
i = 0
|
|
91
|
-
|
|
92
|
-
while i < len(lines):
|
|
93
|
-
# Check for [source] prefix first
|
|
94
|
-
match = self.CODE_BLOCK_START.match(lines[i])
|
|
95
|
-
if match:
|
|
96
|
-
language = match.group(1)
|
|
97
|
-
start = i
|
|
98
|
-
i += 1
|
|
99
|
-
|
|
100
|
-
# Find the delimiter line (---- or ....)
|
|
101
|
-
if i < len(lines) and lines[i].strip() in ['----', '....']:
|
|
102
|
-
delimiter = lines[i].strip()
|
|
103
|
-
i += 1
|
|
104
|
-
content_start = i
|
|
105
|
-
|
|
106
|
-
# Find the closing delimiter
|
|
107
|
-
while i < len(lines):
|
|
108
|
-
if lines[i].strip() == delimiter:
|
|
109
|
-
content = lines[content_start:i]
|
|
110
|
-
blocks.append(CodeBlock(
|
|
111
|
-
start_line=start,
|
|
112
|
-
end_line=i,
|
|
113
|
-
delimiter=delimiter,
|
|
114
|
-
content=content,
|
|
115
|
-
language=language
|
|
116
|
-
))
|
|
117
|
-
break
|
|
118
|
-
i += 1
|
|
119
|
-
# Check for plain delimited blocks without [source] prefix
|
|
120
|
-
elif lines[i].strip() in ['----', '....']:
|
|
121
|
-
delimiter = lines[i].strip()
|
|
122
|
-
start = i
|
|
123
|
-
i += 1
|
|
124
|
-
content_start = i
|
|
125
|
-
|
|
126
|
-
# Find the closing delimiter
|
|
127
|
-
while i < len(lines):
|
|
128
|
-
if lines[i].strip() == delimiter:
|
|
129
|
-
content = lines[content_start:i]
|
|
130
|
-
# Only add if block contains callouts
|
|
131
|
-
if any(self.CALLOUT_IN_CODE.search(line) for line in content):
|
|
132
|
-
blocks.append(CodeBlock(
|
|
133
|
-
start_line=start,
|
|
134
|
-
end_line=i,
|
|
135
|
-
delimiter=delimiter,
|
|
136
|
-
content=content,
|
|
137
|
-
language=None
|
|
138
|
-
))
|
|
139
|
-
break
|
|
140
|
-
i += 1
|
|
141
|
-
i += 1
|
|
142
|
-
|
|
143
|
-
return blocks
|
|
144
|
-
|
|
145
|
-
def extract_callouts_from_code(self, content: List[str]) -> List[CalloutGroup]:
|
|
146
|
-
"""
|
|
147
|
-
Extract callout numbers from code block content.
|
|
148
|
-
Returns list of CalloutGroups, where each group contains:
|
|
149
|
-
- The code line (with user-replaceable value if found, or full line)
|
|
150
|
-
- List of callout numbers on that line
|
|
151
|
-
|
|
152
|
-
Multiple callouts on the same line are grouped together to be merged
|
|
153
|
-
in the definition list.
|
|
154
|
-
"""
|
|
155
|
-
groups = []
|
|
156
|
-
|
|
157
|
-
for line in content:
|
|
158
|
-
# Look for all callout numbers on this line
|
|
159
|
-
callout_matches = list(self.CALLOUT_IN_CODE.finditer(line))
|
|
160
|
-
if callout_matches:
|
|
161
|
-
# Remove all callouts from the line to get the actual code
|
|
162
|
-
line_without_callouts = self.CALLOUT_IN_CODE.sub('', line).strip()
|
|
163
|
-
|
|
164
|
-
# Find all angle-bracket enclosed values
|
|
165
|
-
user_values = self.USER_VALUE_PATTERN.findall(line_without_callouts)
|
|
166
|
-
|
|
167
|
-
# Determine what to use as the code line term
|
|
168
|
-
if user_values:
|
|
169
|
-
# Use the rightmost (closest to the callout) user value
|
|
170
|
-
code_line = user_values[-1]
|
|
171
|
-
else:
|
|
172
|
-
# No angle-bracket value found - use the actual code line
|
|
173
|
-
code_line = line_without_callouts
|
|
174
|
-
|
|
175
|
-
# Collect all callout numbers on this line
|
|
176
|
-
callout_nums = [int(m.group(1)) for m in callout_matches]
|
|
177
|
-
|
|
178
|
-
groups.append(CalloutGroup(
|
|
179
|
-
code_line=code_line,
|
|
180
|
-
callout_numbers=callout_nums
|
|
181
|
-
))
|
|
182
|
-
|
|
183
|
-
return groups
|
|
184
|
-
|
|
185
|
-
def extract_callout_explanations(self, lines: List[str], start_line: int) -> Tuple[Dict[int, Callout], int]:
|
|
186
|
-
"""
|
|
187
|
-
Extract callout explanations following a code block.
|
|
188
|
-
Returns dict of callouts and the line number where explanations end.
|
|
189
|
-
"""
|
|
190
|
-
explanations = {}
|
|
191
|
-
i = start_line + 1 # Start after the closing delimiter
|
|
192
|
-
|
|
193
|
-
# Skip blank lines and continuation markers (+)
|
|
194
|
-
while i < len(lines) and (not lines[i].strip() or lines[i].strip() == '+'):
|
|
195
|
-
i += 1
|
|
196
|
-
|
|
197
|
-
# Collect consecutive callout explanation lines
|
|
198
|
-
while i < len(lines):
|
|
199
|
-
match = self.CALLOUT_EXPLANATION.match(lines[i])
|
|
200
|
-
if match:
|
|
201
|
-
num = int(match.group(1))
|
|
202
|
-
first_line = match.group(2).strip()
|
|
203
|
-
explanation_lines = [first_line]
|
|
204
|
-
i += 1
|
|
205
|
-
|
|
206
|
-
# Collect continuation lines (lines that don't start with a new callout)
|
|
207
|
-
# Continue until we hit a blank line, a new callout, or certain patterns
|
|
208
|
-
while i < len(lines):
|
|
209
|
-
line = lines[i]
|
|
210
|
-
# Stop if we hit a blank line, new callout, or list start marker
|
|
211
|
-
if not line.strip() or self.CALLOUT_EXPLANATION.match(line) or line.startswith('[start='):
|
|
212
|
-
break
|
|
213
|
-
# Add continuation line preserving original formatting
|
|
214
|
-
explanation_lines.append(line)
|
|
215
|
-
i += 1
|
|
216
|
-
|
|
217
|
-
# Check if marked as optional (only in first line)
|
|
218
|
-
is_optional = False
|
|
219
|
-
if first_line.lower().startswith('optional.') or first_line.lower().startswith('optional:'):
|
|
220
|
-
is_optional = True
|
|
221
|
-
# Remove "Optional." or "Optional:" from first line
|
|
222
|
-
explanation_lines[0] = first_line[9:].strip()
|
|
223
|
-
elif '(Optional)' in first_line or '(optional)' in first_line:
|
|
224
|
-
is_optional = True
|
|
225
|
-
explanation_lines[0] = re.sub(r'\s*\(optional\)\s*', ' ', first_line, flags=re.IGNORECASE).strip()
|
|
226
|
-
|
|
227
|
-
explanations[num] = Callout(num, explanation_lines, is_optional)
|
|
228
|
-
else:
|
|
229
|
-
break
|
|
230
|
-
|
|
231
|
-
return explanations, i - 1
|
|
232
|
-
|
|
233
|
-
def validate_callouts(self, callout_groups: List[CalloutGroup], explanations: Dict[int, Callout],
|
|
234
|
-
input_file: Path = None, block_start: int = None, block_end: int = None) -> bool:
|
|
235
|
-
"""
|
|
236
|
-
Validate that callout numbers in code match explanation numbers.
|
|
237
|
-
Returns True if valid, False otherwise.
|
|
238
|
-
"""
|
|
239
|
-
# Extract all callout numbers from groups
|
|
240
|
-
code_nums = set()
|
|
241
|
-
for group in callout_groups:
|
|
242
|
-
code_nums.update(group.callout_numbers)
|
|
243
|
-
|
|
244
|
-
explanation_nums = set(explanations.keys())
|
|
245
|
-
|
|
246
|
-
if code_nums != explanation_nums:
|
|
247
|
-
# Format warning message with file and line numbers
|
|
248
|
-
if input_file and block_start is not None and block_end is not None:
|
|
249
|
-
# Line numbers are 1-indexed for display
|
|
250
|
-
line_range = f"{block_start + 1}-{block_end + 1}"
|
|
251
|
-
warning_msg = (
|
|
252
|
-
f"WARNING: {input_file.name} lines {line_range}: Callout mismatch: "
|
|
253
|
-
f"code has {sorted(code_nums)}, explanations have {sorted(explanation_nums)}"
|
|
254
|
-
)
|
|
255
|
-
print_colored(warning_msg, Colors.YELLOW)
|
|
256
|
-
# Store warning for summary
|
|
257
|
-
self.warnings.append(warning_msg)
|
|
258
|
-
else:
|
|
259
|
-
self.log(f"Callout mismatch: code has {code_nums}, explanations have {explanation_nums}")
|
|
260
|
-
return False
|
|
261
|
-
|
|
262
|
-
return True
|
|
263
|
-
|
|
264
|
-
def remove_callouts_from_code(self, content: List[str]) -> List[str]:
|
|
265
|
-
"""Remove callout numbers from code block content (handles multiple callouts per line)."""
|
|
266
|
-
cleaned = []
|
|
267
|
-
for line in content:
|
|
268
|
-
# Remove all callout numbers and trailing whitespace
|
|
269
|
-
cleaned.append(self.CALLOUT_IN_CODE.sub('', line).rstrip())
|
|
270
|
-
return cleaned
|
|
271
|
-
|
|
272
|
-
def create_definition_list(self, callout_groups: List[CalloutGroup], explanations: Dict[int, Callout]) -> List[str]:
|
|
273
|
-
"""
|
|
274
|
-
Create definition list from callout groups and explanations.
|
|
275
|
-
|
|
276
|
-
For callouts with user-replaceable values in angle brackets, uses those.
|
|
277
|
-
For callouts without values, uses the actual code line as the term.
|
|
278
|
-
|
|
279
|
-
When multiple callouts share the same code line (same group), their
|
|
280
|
-
explanations are merged using AsciiDoc list continuation (+).
|
|
281
|
-
"""
|
|
282
|
-
lines = ['\nwhere:']
|
|
283
|
-
|
|
284
|
-
# Process each group (which may contain one or more callouts)
|
|
285
|
-
for group in callout_groups:
|
|
286
|
-
code_line = group.code_line
|
|
287
|
-
callout_nums = group.callout_numbers
|
|
288
|
-
|
|
289
|
-
# Check if this is a user-replaceable value (contains angle brackets but not heredoc)
|
|
290
|
-
# User values are single words/phrases in angle brackets like <my-value>
|
|
291
|
-
user_values = self.USER_VALUE_PATTERN.findall(code_line)
|
|
292
|
-
|
|
293
|
-
if user_values and len(user_values) == 1 and len(code_line) < 100:
|
|
294
|
-
# This looks like a user-replaceable value placeholder
|
|
295
|
-
# Format the value (ensure it has angle brackets)
|
|
296
|
-
user_value = user_values[0]
|
|
297
|
-
if not user_value.startswith('<'):
|
|
298
|
-
user_value = f'<{user_value}>'
|
|
299
|
-
if not user_value.endswith('>'):
|
|
300
|
-
user_value = f'{user_value}>'
|
|
301
|
-
term = f'`{user_value}`'
|
|
302
|
-
else:
|
|
303
|
-
# This is a code line - use it as-is in backticks
|
|
304
|
-
term = f'`{code_line}`'
|
|
305
|
-
|
|
306
|
-
# Add blank line before each term
|
|
307
|
-
lines.append('')
|
|
308
|
-
lines.append(f'{term}::')
|
|
309
|
-
|
|
310
|
-
# Add explanations for all callouts in this group
|
|
311
|
-
for idx, callout_num in enumerate(callout_nums):
|
|
312
|
-
explanation = explanations[callout_num]
|
|
313
|
-
|
|
314
|
-
# If this is not the first explanation in the group, add continuation marker
|
|
315
|
-
if idx > 0:
|
|
316
|
-
lines.append('+')
|
|
317
|
-
|
|
318
|
-
# Add explanation lines, prepending "Optional. " to first line if needed
|
|
319
|
-
for line_idx, line in enumerate(explanation.lines):
|
|
320
|
-
if line_idx == 0 and explanation.is_optional:
|
|
321
|
-
lines.append(f'Optional. {line}')
|
|
322
|
-
else:
|
|
323
|
-
lines.append(line)
|
|
324
|
-
|
|
325
|
-
return lines
|
|
326
|
-
|
|
327
|
-
def create_bulleted_list(self, callout_groups: List[CalloutGroup], explanations: Dict[int, Callout]) -> List[str]:
|
|
328
|
-
"""
|
|
329
|
-
Create bulleted list from callout groups and explanations.
|
|
330
|
-
|
|
331
|
-
Follows Red Hat style guide format:
|
|
332
|
-
- Each bullet starts with `*` followed by backticked code element
|
|
333
|
-
- Colon separates element from explanation
|
|
334
|
-
- Blank line between each bullet point
|
|
335
|
-
|
|
336
|
-
For callouts with user-replaceable values in angle brackets, uses those.
|
|
337
|
-
For callouts without values, uses the actual code line as the term.
|
|
338
|
-
|
|
339
|
-
When multiple callouts share the same code line (same group), their
|
|
340
|
-
explanations are merged with line breaks.
|
|
341
|
-
"""
|
|
342
|
-
lines = [''] # Start with blank line before list
|
|
343
|
-
|
|
344
|
-
# Process each group (which may contain one or more callouts)
|
|
345
|
-
for group in callout_groups:
|
|
346
|
-
code_line = group.code_line
|
|
347
|
-
callout_nums = group.callout_numbers
|
|
348
|
-
|
|
349
|
-
# Check if this is a user-replaceable value (contains angle brackets but not heredoc)
|
|
350
|
-
# User values are single words/phrases in angle brackets like <my-value>
|
|
351
|
-
user_values = self.USER_VALUE_PATTERN.findall(code_line)
|
|
352
|
-
|
|
353
|
-
if user_values and len(user_values) == 1 and len(code_line) < 100:
|
|
354
|
-
# This looks like a user-replaceable value placeholder
|
|
355
|
-
# Format the value (ensure it has angle brackets)
|
|
356
|
-
user_value = user_values[0]
|
|
357
|
-
if not user_value.startswith('<'):
|
|
358
|
-
user_value = f'<{user_value}>'
|
|
359
|
-
if not user_value.endswith('>'):
|
|
360
|
-
user_value = f'{user_value}>'
|
|
361
|
-
term = f'`{user_value}`'
|
|
362
|
-
else:
|
|
363
|
-
# This is a code line - use it as-is in backticks
|
|
364
|
-
term = f'`{code_line}`'
|
|
365
|
-
|
|
366
|
-
# Collect all explanations for this group
|
|
367
|
-
all_explanation_lines = []
|
|
368
|
-
for idx, callout_num in enumerate(callout_nums):
|
|
369
|
-
explanation = explanations[callout_num]
|
|
370
|
-
|
|
371
|
-
# Add explanation lines, prepending "Optional. " to first line if needed
|
|
372
|
-
for line_idx, line in enumerate(explanation.lines):
|
|
373
|
-
if line_idx == 0 and explanation.is_optional:
|
|
374
|
-
all_explanation_lines.append(f'Optional. {line}')
|
|
375
|
-
else:
|
|
376
|
-
all_explanation_lines.append(line)
|
|
377
|
-
|
|
378
|
-
# If there are more callouts in this group, add a line break
|
|
379
|
-
if idx < len(callout_nums) - 1:
|
|
380
|
-
all_explanation_lines.append('')
|
|
381
|
-
|
|
382
|
-
# Format as bullet point: * `term`: explanation
|
|
383
|
-
# First line uses the bullet marker
|
|
384
|
-
lines.append(f'* {term}: {all_explanation_lines[0]}')
|
|
385
|
-
|
|
386
|
-
# Continuation lines (if any) are indented to align with first line
|
|
387
|
-
for continuation_line in all_explanation_lines[1:]:
|
|
388
|
-
if continuation_line: # Skip empty lines for now
|
|
389
|
-
lines.append(f' {continuation_line}')
|
|
390
|
-
else:
|
|
391
|
-
lines.append('')
|
|
392
|
-
|
|
393
|
-
# Add blank line after each bullet point
|
|
394
|
-
lines.append('')
|
|
395
|
-
|
|
396
|
-
return lines
|
|
397
|
-
|
|
398
60
|
def convert_file(self, input_file: Path) -> Tuple[int, bool]:
|
|
399
61
|
"""
|
|
400
|
-
Convert callouts in a file to
|
|
62
|
+
Convert callouts in a file to the specified output format.
|
|
401
63
|
Returns tuple of (number of conversions, whether file was modified).
|
|
402
64
|
"""
|
|
403
65
|
# Read input file
|
|
@@ -411,7 +73,7 @@ class CalloutConverter:
|
|
|
411
73
|
self.log(f"Processing {input_file} ({len(lines)} lines)")
|
|
412
74
|
|
|
413
75
|
# Find all code blocks
|
|
414
|
-
blocks = self.find_code_blocks(lines)
|
|
76
|
+
blocks = self.detector.find_code_blocks(lines)
|
|
415
77
|
self.log(f"Found {len(blocks)} code blocks")
|
|
416
78
|
|
|
417
79
|
if not blocks:
|
|
@@ -423,7 +85,7 @@ class CalloutConverter:
|
|
|
423
85
|
|
|
424
86
|
for block in reversed(blocks):
|
|
425
87
|
# Extract callouts from code (returns list of CalloutGroups)
|
|
426
|
-
callout_groups = self.extract_callouts_from_code(block.content)
|
|
88
|
+
callout_groups = self.detector.extract_callouts_from_code(block.content)
|
|
427
89
|
|
|
428
90
|
if not callout_groups:
|
|
429
91
|
self.log(f"No callouts in block at line {block.start_line + 1}")
|
|
@@ -437,50 +99,96 @@ class CalloutConverter:
|
|
|
437
99
|
self.log(f"Block at line {block.start_line + 1} has callouts: {all_callout_nums}")
|
|
438
100
|
|
|
439
101
|
# Extract explanations
|
|
440
|
-
explanations, explanation_end = self.extract_callout_explanations(new_lines, block.end_line)
|
|
102
|
+
explanations, explanation_end = self.detector.extract_callout_explanations(new_lines, block.end_line)
|
|
441
103
|
|
|
442
104
|
if not explanations:
|
|
443
105
|
self.log(f"No explanations found after block at line {block.start_line + 1}")
|
|
444
106
|
continue
|
|
445
107
|
|
|
446
108
|
# Validate callouts match
|
|
447
|
-
|
|
109
|
+
is_valid, code_nums, explanation_nums = self.detector.validate_callouts(callout_groups, explanations)
|
|
110
|
+
if not is_valid:
|
|
111
|
+
# Format warning message with file and line numbers
|
|
112
|
+
line_range = f"{block.start_line + 1}-{block.end_line + 1}"
|
|
113
|
+
warning_msg = (
|
|
114
|
+
f"WARNING: {input_file.name} lines {line_range}: Callout mismatch: "
|
|
115
|
+
f"code has {sorted(code_nums)}, explanations have {sorted(explanation_nums)}"
|
|
116
|
+
)
|
|
117
|
+
print_colored(warning_msg, Colors.YELLOW)
|
|
118
|
+
self.warnings.append(warning_msg)
|
|
448
119
|
continue
|
|
449
120
|
|
|
450
121
|
self.log(f"Converting block at line {block.start_line + 1}")
|
|
451
122
|
|
|
452
|
-
#
|
|
453
|
-
|
|
123
|
+
# Convert based on format option
|
|
124
|
+
use_deflist_fallback = False
|
|
125
|
+
if self.output_format == 'comments':
|
|
126
|
+
# For comments format, replace callouts inline in the code
|
|
127
|
+
converted_content, long_warnings = CommentConverter.convert(
|
|
128
|
+
block.content, callout_groups, explanations, block.language,
|
|
129
|
+
max_length=self.max_comment_length, shorten_long=False
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# If there are long comment warnings, fall back to definition list
|
|
133
|
+
if long_warnings:
|
|
134
|
+
for lw in long_warnings:
|
|
135
|
+
warning_msg = (
|
|
136
|
+
f"WARNING: {input_file.name} lines {block.start_line + 1}-{block.end_line + 1}: "
|
|
137
|
+
f"Callout <{lw.callout_num}> explanation too long ({lw.length} chars) "
|
|
138
|
+
f"for inline comment (max: {self.max_comment_length}). Falling back to definition list format."
|
|
139
|
+
)
|
|
140
|
+
print_colored(warning_msg, Colors.YELLOW)
|
|
141
|
+
self.warnings.append(warning_msg)
|
|
142
|
+
self.long_comment_warnings.append((input_file.name, block.start_line + 1, lw))
|
|
143
|
+
|
|
144
|
+
# Fall back to definition list
|
|
145
|
+
self.log(f"Falling back to definition list for block at line {block.start_line + 1}")
|
|
146
|
+
converted_content = self.detector.remove_callouts_from_code(block.content)
|
|
147
|
+
output_list = DefListConverter.convert(callout_groups, explanations)
|
|
148
|
+
use_deflist_fallback = True
|
|
149
|
+
else:
|
|
150
|
+
output_list = [] # No separate list after code block for comments
|
|
151
|
+
else:
|
|
152
|
+
# For deflist and bullets, remove callouts from code and create separate list
|
|
153
|
+
converted_content = self.detector.remove_callouts_from_code(block.content)
|
|
454
154
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
output_list = self.create_definition_list(callout_groups, explanations)
|
|
155
|
+
if self.output_format == 'bullets':
|
|
156
|
+
output_list = BulletListConverter.convert(callout_groups, explanations)
|
|
157
|
+
else: # default to 'deflist'
|
|
158
|
+
output_list = DefListConverter.convert(callout_groups, explanations)
|
|
460
159
|
|
|
461
160
|
# Replace in document
|
|
462
|
-
#
|
|
463
|
-
|
|
464
|
-
has_source_prefix = self.CODE_BLOCK_START.match(new_lines[block.start_line])
|
|
161
|
+
# Check if block has [source] prefix
|
|
162
|
+
has_source_prefix = self.detector.CODE_BLOCK_START.match(new_lines[block.start_line])
|
|
465
163
|
if has_source_prefix:
|
|
466
164
|
content_start = block.start_line + 2 # After [source] and ----
|
|
467
165
|
else:
|
|
468
166
|
content_start = block.start_line + 1 # After ---- only
|
|
469
167
|
content_end = block.end_line
|
|
470
168
|
|
|
471
|
-
#
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
169
|
+
# For comments format (without fallback), we keep the explanations section
|
|
170
|
+
# For deflist/bullets format, we remove old explanations and add new list
|
|
171
|
+
if self.output_format == 'comments' and not use_deflist_fallback:
|
|
172
|
+
# Keep everything as-is, just replace code content
|
|
173
|
+
new_section = (
|
|
174
|
+
new_lines[:content_start] +
|
|
175
|
+
converted_content +
|
|
176
|
+
new_lines[content_end:]
|
|
177
|
+
)
|
|
178
|
+
else:
|
|
179
|
+
# Remove old callout explanations and add new list
|
|
180
|
+
explanation_start = block.end_line + 1
|
|
181
|
+
while explanation_start < len(new_lines) and not new_lines[explanation_start].strip():
|
|
182
|
+
explanation_start += 1
|
|
183
|
+
|
|
184
|
+
# Build the new section
|
|
185
|
+
new_section = (
|
|
186
|
+
new_lines[:content_start] +
|
|
187
|
+
converted_content +
|
|
188
|
+
[new_lines[content_end]] + # Keep closing delimiter
|
|
189
|
+
output_list +
|
|
190
|
+
new_lines[explanation_end + 1:]
|
|
191
|
+
)
|
|
484
192
|
|
|
485
193
|
new_lines = new_section
|
|
486
194
|
conversions += 1
|
|
@@ -572,23 +280,29 @@ def load_exclusion_list(exclusion_file: Path) -> Tuple[List[str], List[str]]:
|
|
|
572
280
|
def main():
|
|
573
281
|
"""Main entry point"""
|
|
574
282
|
parser = argparse.ArgumentParser(
|
|
575
|
-
description='Convert AsciiDoc callouts to
|
|
283
|
+
description='Convert AsciiDoc callouts to various formats',
|
|
576
284
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
577
285
|
epilog="""
|
|
578
|
-
Convert AsciiDoc callout-style documentation to
|
|
286
|
+
Convert AsciiDoc callout-style documentation to various formats.
|
|
579
287
|
|
|
580
288
|
This script identifies code blocks with callout numbers (<1>, <2>, etc.) and their
|
|
581
|
-
corresponding explanation lines, then converts them to
|
|
582
|
-
|
|
289
|
+
corresponding explanation lines, then converts them to your chosen format.
|
|
290
|
+
|
|
291
|
+
Formats:
|
|
292
|
+
deflist - Definition list with "where:" prefix (default)
|
|
293
|
+
bullets - Bulleted list format
|
|
294
|
+
comments - Inline comments within code (removes separate explanations)
|
|
583
295
|
|
|
584
296
|
Examples:
|
|
585
297
|
%(prog)s # Process all .adoc files in current directory
|
|
586
298
|
%(prog)s modules/ # Process all .adoc files in modules/
|
|
587
299
|
%(prog)s assemblies/my-guide.adoc # Process single file
|
|
588
300
|
%(prog)s --dry-run modules/ # Preview changes without modifying
|
|
301
|
+
%(prog)s --format bullets modules/ # Convert to bulleted list format
|
|
302
|
+
%(prog)s --format comments src/ # Convert to inline comments
|
|
589
303
|
%(prog)s --exclude-dir .vale modules/ # Exclude .vale directory
|
|
590
304
|
|
|
591
|
-
Example transformation:
|
|
305
|
+
Example transformation (deflist format):
|
|
592
306
|
FROM:
|
|
593
307
|
[source,yaml]
|
|
594
308
|
----
|
|
@@ -633,9 +347,15 @@ Example transformation:
|
|
|
633
347
|
)
|
|
634
348
|
parser.add_argument(
|
|
635
349
|
'-f', '--format',
|
|
636
|
-
choices=['deflist', 'bullets'],
|
|
350
|
+
choices=['deflist', 'bullets', 'comments'],
|
|
637
351
|
default='deflist',
|
|
638
|
-
help='Output format: "deflist" for definition list
|
|
352
|
+
help='Output format: "deflist" for definition list (default), "bullets" for bulleted list, "comments" for inline comments'
|
|
353
|
+
)
|
|
354
|
+
parser.add_argument(
|
|
355
|
+
'--max-comment-length',
|
|
356
|
+
type=int,
|
|
357
|
+
default=120,
|
|
358
|
+
help='Maximum length for inline comments before falling back to definition list (default: 120 characters)'
|
|
639
359
|
)
|
|
640
360
|
parser.add_argument(
|
|
641
361
|
'--exclude-dir',
|
|
@@ -694,7 +414,8 @@ Example transformation:
|
|
|
694
414
|
print(f"Found {len(adoc_files)} AsciiDoc file(s) to process")
|
|
695
415
|
|
|
696
416
|
# Create converter
|
|
697
|
-
converter = CalloutConverter(dry_run=args.dry_run, verbose=args.verbose, output_format=args.format
|
|
417
|
+
converter = CalloutConverter(dry_run=args.dry_run, verbose=args.verbose, output_format=args.format,
|
|
418
|
+
max_comment_length=args.max_comment_length)
|
|
698
419
|
|
|
699
420
|
# Process each file
|
|
700
421
|
files_processed = 0
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
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
6
|
doc_utils_cli.py,sha256=dsMYrkAriYdZUF0_cSPh5DAPrJMPiecuY26xN-p0UJ0,4911
|
|
6
7
|
extract_link_attributes.py,sha256=wR2SmR2la-jR6DzDbas2PoNONgRZ4dZ6aqwzkwEv8Gs,3516
|
|
7
8
|
find_unused_attributes.py,sha256=77CxFdm72wj6SO81w-auMdDjnvF83jWy_qaM7DsAtBw,4263
|
|
8
9
|
format_asciidoc_spacing.py,sha256=nmWpw2dgwhd81LXyznq0rT8w6Z7cNRyGtPJGRyKFRdc,4212
|
|
9
10
|
replace_link_attributes.py,sha256=Cpc4E-j9j-4_y0LOstAKYOPl02Ln_2bGNIeqp3ZVCdA,7624
|
|
10
11
|
validate_links.py,sha256=lWuK8sgfiFdfcUdSVAt_5U9JHVde_oa6peSUlBQtsac,6145
|
|
12
|
+
callout_lib/__init__.py,sha256=8B82N_z4D1LaZVYgd5jZR53QAabtgPzADOyGlnvihj0,665
|
|
13
|
+
callout_lib/converter_bullets.py,sha256=8P92QZ0PylrKRE7V-D3TVZCDH0ct3GRIonc7W5AK5uU,3898
|
|
14
|
+
callout_lib/converter_comments.py,sha256=do0dH8uOyNFpn5CDEzR0jYYCMIPP3oPFM8cEB-Fp22c,9767
|
|
15
|
+
callout_lib/converter_deflist.py,sha256=mQ17Y8gJLv0MzzJscpUL7ujuDRyEVcrb9lcPdUNkIX4,3117
|
|
16
|
+
callout_lib/detector.py,sha256=qMFb8MZGYWhEXqV-Bn9BW0qXlYgJcVwKyPI-50oYl5U,10937
|
|
17
|
+
callout_lib/table_parser.py,sha256=0sngxSXdVxc9vs6umeV6hgOIP2ETC7I_RbrEuSntB6k,15505
|
|
11
18
|
doc_utils/__init__.py,sha256=qqZR3lohzkP63soymrEZPBGzzk6-nFzi4_tSffjmu_0,74
|
|
12
19
|
doc_utils/extract_link_attributes.py,sha256=U0EvPZReJQigNfbT-icBsVT6Li64hYki5W7MQz6qqbc,22743
|
|
13
20
|
doc_utils/file_utils.py,sha256=fpTh3xx759sF8sNocdn_arsP3KAv8XA6cTQTAVIZiZg,4247
|
|
@@ -22,9 +29,9 @@ doc_utils/unused_images.py,sha256=nqn36Bbrmon2KlGlcaruNjJJvTQ8_9H0WU9GvCW7rW8,14
|
|
|
22
29
|
doc_utils/validate_links.py,sha256=iBGXnwdeLlgIT3fo3v01ApT5k0X2FtctsvkrE6E3VMk,19610
|
|
23
30
|
doc_utils/version.py,sha256=5Uc0sAUOkXA6R_PvDGjw2MBYptEKdav5XmeRqukMTo0,203
|
|
24
31
|
doc_utils/version_check.py,sha256=eHJnZmBTbdhhY2fJQW9KnnyD0rWEvCZpMg6oSr0fOmI,7090
|
|
25
|
-
rolfedh_doc_utils-0.1.
|
|
26
|
-
rolfedh_doc_utils-0.1.
|
|
27
|
-
rolfedh_doc_utils-0.1.
|
|
28
|
-
rolfedh_doc_utils-0.1.
|
|
29
|
-
rolfedh_doc_utils-0.1.
|
|
30
|
-
rolfedh_doc_utils-0.1.
|
|
32
|
+
rolfedh_doc_utils-0.1.26.dist-info/licenses/LICENSE,sha256=vLxtwMVOJA_hEy8b77niTkdmQI9kNJskXHq0dBS36e0,1075
|
|
33
|
+
rolfedh_doc_utils-0.1.26.dist-info/METADATA,sha256=ni6hzbVbd9CcWp73tFf688p7g5xaWXqsASphw0Bb4kA,8325
|
|
34
|
+
rolfedh_doc_utils-0.1.26.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
35
|
+
rolfedh_doc_utils-0.1.26.dist-info/entry_points.txt,sha256=vL_LlLKOiurRzchrq8iRUQG19Xi9lSAFVZGjO-xyErk,577
|
|
36
|
+
rolfedh_doc_utils-0.1.26.dist-info/top_level.txt,sha256=J4xtr3zoyCip27b3GnticFVZoyz5HHtgGqHQ-SZONCA,265
|
|
37
|
+
rolfedh_doc_utils-0.1.26.dist-info/RECORD,,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
archive-unused-files = archive_unused_files:main
|
|
3
3
|
archive-unused-images = archive_unused_images:main
|
|
4
4
|
check-scannability = check_scannability:main
|
|
5
|
+
convert-callouts-interactive = convert_callouts_interactive:main
|
|
5
6
|
convert-callouts-to-deflist = convert_callouts_to_deflist:main
|
|
6
7
|
doc-utils = doc_utils_cli:main
|
|
7
8
|
extract-link-attributes = extract_link_attributes:main
|