rolfedh-doc-utils 0.1.23__py3-none-any.whl → 0.1.25__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 +220 -0
- convert_callouts_interactive.py +532 -0
- convert_callouts_to_deflist.py +118 -316
- {rolfedh_doc_utils-0.1.23.dist-info → rolfedh_doc_utils-0.1.25.dist-info}/METADATA +1 -1
- {rolfedh_doc_utils-0.1.23.dist-info → rolfedh_doc_utils-0.1.25.dist-info}/RECORD +13 -7
- {rolfedh_doc_utils-0.1.23.dist-info → rolfedh_doc_utils-0.1.25.dist-info}/entry_points.txt +1 -0
- {rolfedh_doc_utils-0.1.23.dist-info → rolfedh_doc_utils-0.1.25.dist-info}/top_level.txt +2 -0
- {rolfedh_doc_utils-0.1.23.dist-info → rolfedh_doc_utils-0.1.25.dist-info}/WHEEL +0 -0
- {rolfedh_doc_utils-0.1.23.dist-info → rolfedh_doc_utils-0.1.25.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,302 +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+)>')
|
|
40
|
+
"""Converts callout-style documentation to various formats."""
|
|
67
41
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# Pattern to detect user-replaceable values in angle brackets
|
|
72
|
-
# Excludes heredoc syntax (<<) and comparison operators
|
|
73
|
-
USER_VALUE_PATTERN = re.compile(r'(?<!<)<([a-zA-Z][^>]*)>')
|
|
74
|
-
|
|
75
|
-
def __init__(self, dry_run: bool = False, verbose: bool = False):
|
|
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
|
|
46
|
+
self.output_format = output_format # 'deflist', 'bullets', or 'comments'
|
|
47
|
+
self.max_comment_length = max_comment_length # Max length for inline comments
|
|
78
48
|
self.changes_made = 0
|
|
79
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()
|
|
80
54
|
|
|
81
55
|
def log(self, message: str):
|
|
82
56
|
"""Print message if verbose mode is enabled."""
|
|
83
57
|
if self.verbose:
|
|
84
58
|
print(f"[INFO] {message}")
|
|
85
59
|
|
|
86
|
-
def find_code_blocks(self, lines: List[str]) -> List[CodeBlock]:
|
|
87
|
-
"""Find all code blocks in the document."""
|
|
88
|
-
blocks = []
|
|
89
|
-
i = 0
|
|
90
|
-
|
|
91
|
-
while i < len(lines):
|
|
92
|
-
# Check for [source] prefix first
|
|
93
|
-
match = self.CODE_BLOCK_START.match(lines[i])
|
|
94
|
-
if match:
|
|
95
|
-
language = match.group(1)
|
|
96
|
-
start = i
|
|
97
|
-
i += 1
|
|
98
|
-
|
|
99
|
-
# Find the delimiter line (---- or ....)
|
|
100
|
-
if i < len(lines) and lines[i].strip() in ['----', '....']:
|
|
101
|
-
delimiter = lines[i].strip()
|
|
102
|
-
i += 1
|
|
103
|
-
content_start = i
|
|
104
|
-
|
|
105
|
-
# Find the closing delimiter
|
|
106
|
-
while i < len(lines):
|
|
107
|
-
if lines[i].strip() == delimiter:
|
|
108
|
-
content = lines[content_start:i]
|
|
109
|
-
blocks.append(CodeBlock(
|
|
110
|
-
start_line=start,
|
|
111
|
-
end_line=i,
|
|
112
|
-
delimiter=delimiter,
|
|
113
|
-
content=content,
|
|
114
|
-
language=language
|
|
115
|
-
))
|
|
116
|
-
break
|
|
117
|
-
i += 1
|
|
118
|
-
# Check for plain delimited blocks without [source] prefix
|
|
119
|
-
elif lines[i].strip() in ['----', '....']:
|
|
120
|
-
delimiter = lines[i].strip()
|
|
121
|
-
start = i
|
|
122
|
-
i += 1
|
|
123
|
-
content_start = i
|
|
124
|
-
|
|
125
|
-
# Find the closing delimiter
|
|
126
|
-
while i < len(lines):
|
|
127
|
-
if lines[i].strip() == delimiter:
|
|
128
|
-
content = lines[content_start:i]
|
|
129
|
-
# Only add if block contains callouts
|
|
130
|
-
if any(self.CALLOUT_IN_CODE.search(line) for line in content):
|
|
131
|
-
blocks.append(CodeBlock(
|
|
132
|
-
start_line=start,
|
|
133
|
-
end_line=i,
|
|
134
|
-
delimiter=delimiter,
|
|
135
|
-
content=content,
|
|
136
|
-
language=None
|
|
137
|
-
))
|
|
138
|
-
break
|
|
139
|
-
i += 1
|
|
140
|
-
i += 1
|
|
141
|
-
|
|
142
|
-
return blocks
|
|
143
|
-
|
|
144
|
-
def extract_callouts_from_code(self, content: List[str]) -> List[CalloutGroup]:
|
|
145
|
-
"""
|
|
146
|
-
Extract callout numbers from code block content.
|
|
147
|
-
Returns list of CalloutGroups, where each group contains:
|
|
148
|
-
- The code line (with user-replaceable value if found, or full line)
|
|
149
|
-
- List of callout numbers on that line
|
|
150
|
-
|
|
151
|
-
Multiple callouts on the same line are grouped together to be merged
|
|
152
|
-
in the definition list.
|
|
153
|
-
"""
|
|
154
|
-
groups = []
|
|
155
|
-
|
|
156
|
-
for line in content:
|
|
157
|
-
# Look for all callout numbers on this line
|
|
158
|
-
callout_matches = list(self.CALLOUT_IN_CODE.finditer(line))
|
|
159
|
-
if callout_matches:
|
|
160
|
-
# Remove all callouts from the line to get the actual code
|
|
161
|
-
line_without_callouts = self.CALLOUT_IN_CODE.sub('', line).strip()
|
|
162
|
-
|
|
163
|
-
# Find all angle-bracket enclosed values
|
|
164
|
-
user_values = self.USER_VALUE_PATTERN.findall(line_without_callouts)
|
|
165
|
-
|
|
166
|
-
# Determine what to use as the code line term
|
|
167
|
-
if user_values:
|
|
168
|
-
# Use the rightmost (closest to the callout) user value
|
|
169
|
-
code_line = user_values[-1]
|
|
170
|
-
else:
|
|
171
|
-
# No angle-bracket value found - use the actual code line
|
|
172
|
-
code_line = line_without_callouts
|
|
173
|
-
|
|
174
|
-
# Collect all callout numbers on this line
|
|
175
|
-
callout_nums = [int(m.group(1)) for m in callout_matches]
|
|
176
|
-
|
|
177
|
-
groups.append(CalloutGroup(
|
|
178
|
-
code_line=code_line,
|
|
179
|
-
callout_numbers=callout_nums
|
|
180
|
-
))
|
|
181
|
-
|
|
182
|
-
return groups
|
|
183
|
-
|
|
184
|
-
def extract_callout_explanations(self, lines: List[str], start_line: int) -> Tuple[Dict[int, Callout], int]:
|
|
185
|
-
"""
|
|
186
|
-
Extract callout explanations following a code block.
|
|
187
|
-
Returns dict of callouts and the line number where explanations end.
|
|
188
|
-
"""
|
|
189
|
-
explanations = {}
|
|
190
|
-
i = start_line + 1 # Start after the closing delimiter
|
|
191
|
-
|
|
192
|
-
# Skip blank lines and continuation markers (+)
|
|
193
|
-
while i < len(lines) and (not lines[i].strip() or lines[i].strip() == '+'):
|
|
194
|
-
i += 1
|
|
195
|
-
|
|
196
|
-
# Collect consecutive callout explanation lines
|
|
197
|
-
while i < len(lines):
|
|
198
|
-
match = self.CALLOUT_EXPLANATION.match(lines[i])
|
|
199
|
-
if match:
|
|
200
|
-
num = int(match.group(1))
|
|
201
|
-
first_line = match.group(2).strip()
|
|
202
|
-
explanation_lines = [first_line]
|
|
203
|
-
i += 1
|
|
204
|
-
|
|
205
|
-
# Collect continuation lines (lines that don't start with a new callout)
|
|
206
|
-
# Continue until we hit a blank line, a new callout, or certain patterns
|
|
207
|
-
while i < len(lines):
|
|
208
|
-
line = lines[i]
|
|
209
|
-
# Stop if we hit a blank line, new callout, or list start marker
|
|
210
|
-
if not line.strip() or self.CALLOUT_EXPLANATION.match(line) or line.startswith('[start='):
|
|
211
|
-
break
|
|
212
|
-
# Add continuation line preserving original formatting
|
|
213
|
-
explanation_lines.append(line)
|
|
214
|
-
i += 1
|
|
215
|
-
|
|
216
|
-
# Check if marked as optional (only in first line)
|
|
217
|
-
is_optional = False
|
|
218
|
-
if first_line.lower().startswith('optional.') or first_line.lower().startswith('optional:'):
|
|
219
|
-
is_optional = True
|
|
220
|
-
# Remove "Optional." or "Optional:" from first line
|
|
221
|
-
explanation_lines[0] = first_line[9:].strip()
|
|
222
|
-
elif '(Optional)' in first_line or '(optional)' in first_line:
|
|
223
|
-
is_optional = True
|
|
224
|
-
explanation_lines[0] = re.sub(r'\s*\(optional\)\s*', ' ', first_line, flags=re.IGNORECASE).strip()
|
|
225
|
-
|
|
226
|
-
explanations[num] = Callout(num, explanation_lines, is_optional)
|
|
227
|
-
else:
|
|
228
|
-
break
|
|
229
|
-
|
|
230
|
-
return explanations, i - 1
|
|
231
|
-
|
|
232
|
-
def validate_callouts(self, callout_groups: List[CalloutGroup], explanations: Dict[int, Callout],
|
|
233
|
-
input_file: Path = None, block_start: int = None, block_end: int = None) -> bool:
|
|
234
|
-
"""
|
|
235
|
-
Validate that callout numbers in code match explanation numbers.
|
|
236
|
-
Returns True if valid, False otherwise.
|
|
237
|
-
"""
|
|
238
|
-
# Extract all callout numbers from groups
|
|
239
|
-
code_nums = set()
|
|
240
|
-
for group in callout_groups:
|
|
241
|
-
code_nums.update(group.callout_numbers)
|
|
242
|
-
|
|
243
|
-
explanation_nums = set(explanations.keys())
|
|
244
|
-
|
|
245
|
-
if code_nums != explanation_nums:
|
|
246
|
-
# Format warning message with file and line numbers
|
|
247
|
-
if input_file and block_start is not None and block_end is not None:
|
|
248
|
-
# Line numbers are 1-indexed for display
|
|
249
|
-
line_range = f"{block_start + 1}-{block_end + 1}"
|
|
250
|
-
warning_msg = (
|
|
251
|
-
f"WARNING: {input_file.name} lines {line_range}: Callout mismatch: "
|
|
252
|
-
f"code has {sorted(code_nums)}, explanations have {sorted(explanation_nums)}"
|
|
253
|
-
)
|
|
254
|
-
print_colored(warning_msg, Colors.YELLOW)
|
|
255
|
-
# Store warning for summary
|
|
256
|
-
self.warnings.append(warning_msg)
|
|
257
|
-
else:
|
|
258
|
-
self.log(f"Callout mismatch: code has {code_nums}, explanations have {explanation_nums}")
|
|
259
|
-
return False
|
|
260
|
-
|
|
261
|
-
return True
|
|
262
|
-
|
|
263
|
-
def remove_callouts_from_code(self, content: List[str]) -> List[str]:
|
|
264
|
-
"""Remove callout numbers from code block content (handles multiple callouts per line)."""
|
|
265
|
-
cleaned = []
|
|
266
|
-
for line in content:
|
|
267
|
-
# Remove all callout numbers and trailing whitespace
|
|
268
|
-
cleaned.append(self.CALLOUT_IN_CODE.sub('', line).rstrip())
|
|
269
|
-
return cleaned
|
|
270
|
-
|
|
271
|
-
def create_definition_list(self, callout_groups: List[CalloutGroup], explanations: Dict[int, Callout]) -> List[str]:
|
|
272
|
-
"""
|
|
273
|
-
Create definition list from callout groups and explanations.
|
|
274
|
-
|
|
275
|
-
For callouts with user-replaceable values in angle brackets, uses those.
|
|
276
|
-
For callouts without values, uses the actual code line as the term.
|
|
277
|
-
|
|
278
|
-
When multiple callouts share the same code line (same group), their
|
|
279
|
-
explanations are merged using AsciiDoc list continuation (+).
|
|
280
|
-
"""
|
|
281
|
-
lines = ['\nwhere:']
|
|
282
|
-
|
|
283
|
-
# Process each group (which may contain one or more callouts)
|
|
284
|
-
for group in callout_groups:
|
|
285
|
-
code_line = group.code_line
|
|
286
|
-
callout_nums = group.callout_numbers
|
|
287
|
-
|
|
288
|
-
# Check if this is a user-replaceable value (contains angle brackets but not heredoc)
|
|
289
|
-
# User values are single words/phrases in angle brackets like <my-value>
|
|
290
|
-
user_values = self.USER_VALUE_PATTERN.findall(code_line)
|
|
291
|
-
|
|
292
|
-
if user_values and len(user_values) == 1 and len(code_line) < 100:
|
|
293
|
-
# This looks like a user-replaceable value placeholder
|
|
294
|
-
# Format the value (ensure it has angle brackets)
|
|
295
|
-
user_value = user_values[0]
|
|
296
|
-
if not user_value.startswith('<'):
|
|
297
|
-
user_value = f'<{user_value}>'
|
|
298
|
-
if not user_value.endswith('>'):
|
|
299
|
-
user_value = f'{user_value}>'
|
|
300
|
-
term = f'`{user_value}`'
|
|
301
|
-
else:
|
|
302
|
-
# This is a code line - use it as-is in backticks
|
|
303
|
-
term = f'`{code_line}`'
|
|
304
|
-
|
|
305
|
-
# Add blank line before each term
|
|
306
|
-
lines.append('')
|
|
307
|
-
lines.append(f'{term}::')
|
|
308
|
-
|
|
309
|
-
# Add explanations for all callouts in this group
|
|
310
|
-
for idx, callout_num in enumerate(callout_nums):
|
|
311
|
-
explanation = explanations[callout_num]
|
|
312
|
-
|
|
313
|
-
# If this is not the first explanation in the group, add continuation marker
|
|
314
|
-
if idx > 0:
|
|
315
|
-
lines.append('+')
|
|
316
|
-
|
|
317
|
-
# Add explanation lines, prepending "Optional. " to first line if needed
|
|
318
|
-
for line_idx, line in enumerate(explanation.lines):
|
|
319
|
-
if line_idx == 0 and explanation.is_optional:
|
|
320
|
-
lines.append(f'Optional. {line}')
|
|
321
|
-
else:
|
|
322
|
-
lines.append(line)
|
|
323
|
-
|
|
324
|
-
return lines
|
|
325
|
-
|
|
326
60
|
def convert_file(self, input_file: Path) -> Tuple[int, bool]:
|
|
327
61
|
"""
|
|
328
|
-
Convert callouts in a file to
|
|
62
|
+
Convert callouts in a file to the specified output format.
|
|
329
63
|
Returns tuple of (number of conversions, whether file was modified).
|
|
330
64
|
"""
|
|
331
65
|
# Read input file
|
|
@@ -339,7 +73,7 @@ class CalloutConverter:
|
|
|
339
73
|
self.log(f"Processing {input_file} ({len(lines)} lines)")
|
|
340
74
|
|
|
341
75
|
# Find all code blocks
|
|
342
|
-
blocks = self.find_code_blocks(lines)
|
|
76
|
+
blocks = self.detector.find_code_blocks(lines)
|
|
343
77
|
self.log(f"Found {len(blocks)} code blocks")
|
|
344
78
|
|
|
345
79
|
if not blocks:
|
|
@@ -351,7 +85,7 @@ class CalloutConverter:
|
|
|
351
85
|
|
|
352
86
|
for block in reversed(blocks):
|
|
353
87
|
# Extract callouts from code (returns list of CalloutGroups)
|
|
354
|
-
callout_groups = self.extract_callouts_from_code(block.content)
|
|
88
|
+
callout_groups = self.detector.extract_callouts_from_code(block.content)
|
|
355
89
|
|
|
356
90
|
if not callout_groups:
|
|
357
91
|
self.log(f"No callouts in block at line {block.start_line + 1}")
|
|
@@ -365,47 +99,96 @@ class CalloutConverter:
|
|
|
365
99
|
self.log(f"Block at line {block.start_line + 1} has callouts: {all_callout_nums}")
|
|
366
100
|
|
|
367
101
|
# Extract explanations
|
|
368
|
-
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)
|
|
369
103
|
|
|
370
104
|
if not explanations:
|
|
371
105
|
self.log(f"No explanations found after block at line {block.start_line + 1}")
|
|
372
106
|
continue
|
|
373
107
|
|
|
374
108
|
# Validate callouts match
|
|
375
|
-
|
|
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)
|
|
376
119
|
continue
|
|
377
120
|
|
|
378
121
|
self.log(f"Converting block at line {block.start_line + 1}")
|
|
379
122
|
|
|
380
|
-
#
|
|
381
|
-
|
|
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
|
+
)
|
|
382
131
|
|
|
383
|
-
|
|
384
|
-
|
|
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)
|
|
154
|
+
|
|
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)
|
|
385
159
|
|
|
386
160
|
# Replace in document
|
|
387
|
-
#
|
|
388
|
-
|
|
389
|
-
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])
|
|
390
163
|
if has_source_prefix:
|
|
391
164
|
content_start = block.start_line + 2 # After [source] and ----
|
|
392
165
|
else:
|
|
393
166
|
content_start = block.start_line + 1 # After ---- only
|
|
394
167
|
content_end = block.end_line
|
|
395
168
|
|
|
396
|
-
#
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
+
)
|
|
409
192
|
|
|
410
193
|
new_lines = new_section
|
|
411
194
|
conversions += 1
|
|
@@ -497,23 +280,29 @@ def load_exclusion_list(exclusion_file: Path) -> Tuple[List[str], List[str]]:
|
|
|
497
280
|
def main():
|
|
498
281
|
"""Main entry point"""
|
|
499
282
|
parser = argparse.ArgumentParser(
|
|
500
|
-
description='Convert AsciiDoc callouts to
|
|
283
|
+
description='Convert AsciiDoc callouts to various formats',
|
|
501
284
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
502
285
|
epilog="""
|
|
503
|
-
Convert AsciiDoc callout-style documentation to
|
|
286
|
+
Convert AsciiDoc callout-style documentation to various formats.
|
|
504
287
|
|
|
505
288
|
This script identifies code blocks with callout numbers (<1>, <2>, etc.) and their
|
|
506
|
-
corresponding explanation lines, then converts them to
|
|
507
|
-
|
|
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)
|
|
508
295
|
|
|
509
296
|
Examples:
|
|
510
297
|
%(prog)s # Process all .adoc files in current directory
|
|
511
298
|
%(prog)s modules/ # Process all .adoc files in modules/
|
|
512
299
|
%(prog)s assemblies/my-guide.adoc # Process single file
|
|
513
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
|
|
514
303
|
%(prog)s --exclude-dir .vale modules/ # Exclude .vale directory
|
|
515
304
|
|
|
516
|
-
Example transformation:
|
|
305
|
+
Example transformation (deflist format):
|
|
517
306
|
FROM:
|
|
518
307
|
[source,yaml]
|
|
519
308
|
----
|
|
@@ -556,6 +345,18 @@ Example transformation:
|
|
|
556
345
|
action='store_true',
|
|
557
346
|
help='Enable verbose output'
|
|
558
347
|
)
|
|
348
|
+
parser.add_argument(
|
|
349
|
+
'-f', '--format',
|
|
350
|
+
choices=['deflist', 'bullets', 'comments'],
|
|
351
|
+
default='deflist',
|
|
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)'
|
|
359
|
+
)
|
|
559
360
|
parser.add_argument(
|
|
560
361
|
'--exclude-dir',
|
|
561
362
|
action='append',
|
|
@@ -613,7 +414,8 @@ Example transformation:
|
|
|
613
414
|
print(f"Found {len(adoc_files)} AsciiDoc file(s) to process")
|
|
614
415
|
|
|
615
416
|
# Create converter
|
|
616
|
-
converter = CalloutConverter(dry_run=args.dry_run, verbose=args.verbose
|
|
417
|
+
converter = CalloutConverter(dry_run=args.dry_run, verbose=args.verbose, output_format=args.format,
|
|
418
|
+
max_comment_length=args.max_comment_length)
|
|
617
419
|
|
|
618
420
|
# Process each file
|
|
619
421
|
files_processed = 0
|
|
@@ -1,13 +1,19 @@
|
|
|
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=douBVXaTM_VWzI5mN8R7QyYzqxD1sVTFPuAbU-blPNo,8806
|
|
11
17
|
doc_utils/__init__.py,sha256=qqZR3lohzkP63soymrEZPBGzzk6-nFzi4_tSffjmu_0,74
|
|
12
18
|
doc_utils/extract_link_attributes.py,sha256=U0EvPZReJQigNfbT-icBsVT6Li64hYki5W7MQz6qqbc,22743
|
|
13
19
|
doc_utils/file_utils.py,sha256=fpTh3xx759sF8sNocdn_arsP3KAv8XA6cTQTAVIZiZg,4247
|
|
@@ -22,9 +28,9 @@ doc_utils/unused_images.py,sha256=nqn36Bbrmon2KlGlcaruNjJJvTQ8_9H0WU9GvCW7rW8,14
|
|
|
22
28
|
doc_utils/validate_links.py,sha256=iBGXnwdeLlgIT3fo3v01ApT5k0X2FtctsvkrE6E3VMk,19610
|
|
23
29
|
doc_utils/version.py,sha256=5Uc0sAUOkXA6R_PvDGjw2MBYptEKdav5XmeRqukMTo0,203
|
|
24
30
|
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.
|
|
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,,
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|