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
callout_lib/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Callout Library - Shared modules for AsciiDoc callout conversion
|
|
3
|
+
|
|
4
|
+
This library provides reusable components for converting AsciiDoc callouts
|
|
5
|
+
to various formats including definition lists, bulleted lists, and inline comments.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .detector import CalloutDetector, CodeBlock, CalloutGroup, Callout
|
|
9
|
+
from .converter_deflist import DefListConverter
|
|
10
|
+
from .converter_bullets import BulletListConverter
|
|
11
|
+
from .converter_comments import CommentConverter, LongCommentWarning
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'CalloutDetector',
|
|
15
|
+
'CodeBlock',
|
|
16
|
+
'CalloutGroup',
|
|
17
|
+
'Callout',
|
|
18
|
+
'DefListConverter',
|
|
19
|
+
'BulletListConverter',
|
|
20
|
+
'CommentConverter',
|
|
21
|
+
'LongCommentWarning',
|
|
22
|
+
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bulleted List Converter Module
|
|
3
|
+
|
|
4
|
+
Converts callouts to bulleted list format following Red Hat style guide.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import List, Dict
|
|
9
|
+
from .detector import CalloutGroup, Callout
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BulletListConverter:
|
|
13
|
+
"""Converts callouts to bulleted list format."""
|
|
14
|
+
|
|
15
|
+
# Pattern to detect user-replaceable values in angle brackets
|
|
16
|
+
USER_VALUE_PATTERN = re.compile(r'(?<!<)<([a-zA-Z][^>]*)>')
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def convert(callout_groups: List[CalloutGroup], explanations: Dict[int, Callout]) -> List[str]:
|
|
20
|
+
"""
|
|
21
|
+
Create bulleted list from callout groups and explanations.
|
|
22
|
+
|
|
23
|
+
Follows Red Hat style guide format:
|
|
24
|
+
- Each bullet starts with `*` followed by backticked code element
|
|
25
|
+
- Colon separates element from explanation
|
|
26
|
+
- Blank line between each bullet point
|
|
27
|
+
|
|
28
|
+
For callouts with user-replaceable values in angle brackets, uses those.
|
|
29
|
+
For callouts without values, uses the actual code line as the term.
|
|
30
|
+
|
|
31
|
+
When multiple callouts share the same code line (same group), their
|
|
32
|
+
explanations are merged with line breaks.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
callout_groups: List of CalloutGroup objects from code block
|
|
36
|
+
explanations: Dict mapping callout numbers to Callout objects
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
List of strings representing the bulleted list
|
|
40
|
+
"""
|
|
41
|
+
lines = [''] # Start with blank line before list
|
|
42
|
+
|
|
43
|
+
# Process each group (which may contain one or more callouts)
|
|
44
|
+
for group in callout_groups:
|
|
45
|
+
code_line = group.code_line
|
|
46
|
+
callout_nums = group.callout_numbers
|
|
47
|
+
|
|
48
|
+
# Check if this is a user-replaceable value (contains angle brackets but not heredoc)
|
|
49
|
+
# User values are single words/phrases in angle brackets like <my-value>
|
|
50
|
+
user_values = BulletListConverter.USER_VALUE_PATTERN.findall(code_line)
|
|
51
|
+
|
|
52
|
+
if user_values and len(user_values) == 1 and len(code_line) < 100:
|
|
53
|
+
# This looks like a user-replaceable value placeholder
|
|
54
|
+
# Format the value (ensure it has angle brackets)
|
|
55
|
+
user_value = user_values[0]
|
|
56
|
+
if not user_value.startswith('<'):
|
|
57
|
+
user_value = f'<{user_value}>'
|
|
58
|
+
if not user_value.endswith('>'):
|
|
59
|
+
user_value = f'{user_value}>'
|
|
60
|
+
term = f'`{user_value}`'
|
|
61
|
+
else:
|
|
62
|
+
# This is a code line - use it as-is in backticks
|
|
63
|
+
term = f'`{code_line}`'
|
|
64
|
+
|
|
65
|
+
# Collect all explanations for this group
|
|
66
|
+
all_explanation_lines = []
|
|
67
|
+
for idx, callout_num in enumerate(callout_nums):
|
|
68
|
+
explanation = explanations[callout_num]
|
|
69
|
+
|
|
70
|
+
# Add explanation lines, prepending "Optional. " to first line if needed
|
|
71
|
+
for line_idx, line in enumerate(explanation.lines):
|
|
72
|
+
if line_idx == 0 and explanation.is_optional:
|
|
73
|
+
all_explanation_lines.append(f'Optional. {line}')
|
|
74
|
+
else:
|
|
75
|
+
all_explanation_lines.append(line)
|
|
76
|
+
|
|
77
|
+
# If there are more callouts in this group, add a line break
|
|
78
|
+
if idx < len(callout_nums) - 1:
|
|
79
|
+
all_explanation_lines.append('')
|
|
80
|
+
|
|
81
|
+
# Format as bullet point: * `term`: explanation
|
|
82
|
+
# First line uses the bullet marker
|
|
83
|
+
lines.append(f'* {term}: {all_explanation_lines[0]}')
|
|
84
|
+
|
|
85
|
+
# Continuation lines (if any) are indented to align with first line
|
|
86
|
+
for continuation_line in all_explanation_lines[1:]:
|
|
87
|
+
if continuation_line: # Skip empty lines for now
|
|
88
|
+
lines.append(f' {continuation_line}')
|
|
89
|
+
else:
|
|
90
|
+
lines.append('')
|
|
91
|
+
|
|
92
|
+
# Add blank line after each bullet point
|
|
93
|
+
lines.append('')
|
|
94
|
+
|
|
95
|
+
return lines
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Inline Comments Converter Module
|
|
3
|
+
|
|
4
|
+
Converts callouts to inline comments within code blocks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import List, Dict, Optional, Tuple
|
|
9
|
+
from .detector import CalloutGroup, Callout
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LongCommentWarning:
|
|
13
|
+
"""Represents a warning about a comment that exceeds the length threshold."""
|
|
14
|
+
def __init__(self, callout_num: int, length: int, text: str, line_num: int = None):
|
|
15
|
+
self.callout_num = callout_num
|
|
16
|
+
self.length = length
|
|
17
|
+
self.text = text
|
|
18
|
+
self.line_num = line_num
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CommentConverter:
|
|
22
|
+
"""Converts callouts to inline comments in code."""
|
|
23
|
+
|
|
24
|
+
# Map of programming language identifiers to their comment syntax
|
|
25
|
+
COMMENT_SYNTAX = {
|
|
26
|
+
# Single-line comment languages
|
|
27
|
+
'java': '//',
|
|
28
|
+
'javascript': '//',
|
|
29
|
+
'js': '//',
|
|
30
|
+
'typescript': '//',
|
|
31
|
+
'ts': '//',
|
|
32
|
+
'c': '//',
|
|
33
|
+
'cpp': '//',
|
|
34
|
+
'c++': '//',
|
|
35
|
+
'csharp': '//',
|
|
36
|
+
'cs': '//',
|
|
37
|
+
'go': '//',
|
|
38
|
+
'rust': '//',
|
|
39
|
+
'swift': '//',
|
|
40
|
+
'kotlin': '//',
|
|
41
|
+
'scala': '//',
|
|
42
|
+
'groovy': '//',
|
|
43
|
+
|
|
44
|
+
# Hash comment languages
|
|
45
|
+
'python': '#',
|
|
46
|
+
'py': '#',
|
|
47
|
+
'ruby': '#',
|
|
48
|
+
'rb': '#',
|
|
49
|
+
'perl': '#',
|
|
50
|
+
'bash': '#',
|
|
51
|
+
'sh': '#',
|
|
52
|
+
'shell': '#',
|
|
53
|
+
'yaml': '#',
|
|
54
|
+
'yml': '#',
|
|
55
|
+
'properties': '#',
|
|
56
|
+
'r': '#',
|
|
57
|
+
'powershell': '#',
|
|
58
|
+
'ps1': '#',
|
|
59
|
+
|
|
60
|
+
# SQL variants
|
|
61
|
+
'sql': '--',
|
|
62
|
+
'plsql': '--',
|
|
63
|
+
'tsql': '--',
|
|
64
|
+
|
|
65
|
+
# Lua
|
|
66
|
+
'lua': '--',
|
|
67
|
+
|
|
68
|
+
# Lisp-like
|
|
69
|
+
'lisp': ';;',
|
|
70
|
+
'scheme': ';;',
|
|
71
|
+
'clojure': ';;',
|
|
72
|
+
|
|
73
|
+
# Markup languages
|
|
74
|
+
'html': '<!--',
|
|
75
|
+
'xml': '<!--',
|
|
76
|
+
'svg': '<!--',
|
|
77
|
+
|
|
78
|
+
# Other
|
|
79
|
+
'matlab': '%',
|
|
80
|
+
'tex': '%',
|
|
81
|
+
'latex': '%',
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Languages that need closing comment syntax
|
|
85
|
+
COMMENT_CLOSING = {
|
|
86
|
+
'html': '-->',
|
|
87
|
+
'xml': '-->',
|
|
88
|
+
'svg': '-->',
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def get_comment_syntax(language: Optional[str]) -> tuple[str, str]:
|
|
93
|
+
"""
|
|
94
|
+
Get comment syntax for a given language.
|
|
95
|
+
Returns tuple of (opening, closing) comment markers.
|
|
96
|
+
For single-line comments, closing is empty string.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
language: Language identifier (e.g., 'java', 'python')
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Tuple of (opening_marker, closing_marker)
|
|
103
|
+
"""
|
|
104
|
+
if not language:
|
|
105
|
+
# Default to generic comment marker
|
|
106
|
+
return '#', ''
|
|
107
|
+
|
|
108
|
+
lang_lower = language.lower()
|
|
109
|
+
opening = CommentConverter.COMMENT_SYNTAX.get(lang_lower, '#')
|
|
110
|
+
closing = CommentConverter.COMMENT_CLOSING.get(lang_lower, '')
|
|
111
|
+
|
|
112
|
+
return opening, closing
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def format_comment(text: str, opening: str, closing: str = '') -> str:
|
|
116
|
+
"""
|
|
117
|
+
Format text as a comment using the given markers.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
text: Comment text
|
|
121
|
+
opening: Opening comment marker (e.g., '//', '#', '<!--')
|
|
122
|
+
closing: Closing comment marker (e.g., '-->')
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Formatted comment string
|
|
126
|
+
"""
|
|
127
|
+
if closing:
|
|
128
|
+
return f'{opening} {text} {closing}'
|
|
129
|
+
else:
|
|
130
|
+
return f'{opening} {text}'
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def get_comment_length(explanation: Callout, opening: str, closing: str = '') -> int:
|
|
134
|
+
"""
|
|
135
|
+
Calculate the total length of a comment including markers and text.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
explanation: Callout explanation
|
|
139
|
+
opening: Opening comment marker
|
|
140
|
+
closing: Closing comment marker (if any)
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Total character length of the formatted comment
|
|
144
|
+
"""
|
|
145
|
+
# Combine all explanation lines into single comment text
|
|
146
|
+
comment_parts = []
|
|
147
|
+
for exp_line in explanation.lines:
|
|
148
|
+
comment_parts.append(exp_line.strip())
|
|
149
|
+
comment_text = ' '.join(comment_parts)
|
|
150
|
+
|
|
151
|
+
# Add "Optional:" prefix if needed
|
|
152
|
+
if explanation.is_optional:
|
|
153
|
+
comment_text = f'Optional: {comment_text}'
|
|
154
|
+
|
|
155
|
+
# Calculate total length with markers
|
|
156
|
+
if closing:
|
|
157
|
+
total = len(f'{opening} {comment_text} {closing}')
|
|
158
|
+
else:
|
|
159
|
+
total = len(f'{opening} {comment_text}')
|
|
160
|
+
|
|
161
|
+
return total
|
|
162
|
+
|
|
163
|
+
@staticmethod
|
|
164
|
+
def shorten_comment(explanation: Callout) -> str:
|
|
165
|
+
"""
|
|
166
|
+
Shorten a comment to its first sentence or clause.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
explanation: Callout explanation
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Shortened comment text
|
|
173
|
+
"""
|
|
174
|
+
# Get first line
|
|
175
|
+
first_line = explanation.lines[0] if explanation.lines else ''
|
|
176
|
+
|
|
177
|
+
# Find first sentence-ending punctuation
|
|
178
|
+
for delimiter in ['. ', '! ', '? ']:
|
|
179
|
+
if delimiter in first_line:
|
|
180
|
+
short = first_line.split(delimiter)[0] + delimiter.strip()
|
|
181
|
+
return short
|
|
182
|
+
|
|
183
|
+
# No sentence delimiter found, return first line
|
|
184
|
+
return first_line
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def check_comment_lengths(explanations: Dict[int, Callout], language: Optional[str] = None,
|
|
188
|
+
max_length: int = 100) -> List[LongCommentWarning]:
|
|
189
|
+
"""
|
|
190
|
+
Check if any comments exceed the maximum length threshold.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
explanations: Dict of callout explanations
|
|
194
|
+
language: Programming language for comment syntax
|
|
195
|
+
max_length: Maximum allowed comment length (default: 100)
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
List of LongCommentWarning objects for comments exceeding threshold
|
|
199
|
+
"""
|
|
200
|
+
opening, closing = CommentConverter.get_comment_syntax(language)
|
|
201
|
+
warnings = []
|
|
202
|
+
|
|
203
|
+
for num, explanation in explanations.items():
|
|
204
|
+
length = CommentConverter.get_comment_length(explanation, opening, closing)
|
|
205
|
+
if length > max_length:
|
|
206
|
+
# Combine all lines for warning text
|
|
207
|
+
full_text = ' '.join(exp_line.strip() for exp_line in explanation.lines)
|
|
208
|
+
warnings.append(LongCommentWarning(num, length, full_text))
|
|
209
|
+
|
|
210
|
+
return warnings
|
|
211
|
+
|
|
212
|
+
@staticmethod
|
|
213
|
+
def convert(code_content: List[str], callout_groups: List[CalloutGroup],
|
|
214
|
+
explanations: Dict[int, Callout], language: Optional[str] = None,
|
|
215
|
+
max_length: Optional[int] = None, shorten_long: bool = False) -> Tuple[List[str], List[LongCommentWarning]]:
|
|
216
|
+
"""
|
|
217
|
+
Convert callouts to inline comments within code.
|
|
218
|
+
|
|
219
|
+
This replaces callout markers (<1>, <2>, etc.) with actual comments containing
|
|
220
|
+
the explanation text. The comment syntax is determined by the code block's language.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
code_content: Original code block content with callout markers
|
|
224
|
+
callout_groups: List of CalloutGroup objects (not used for inline conversion)
|
|
225
|
+
explanations: Dict mapping callout numbers to Callout objects
|
|
226
|
+
language: Programming language identifier for comment syntax
|
|
227
|
+
max_length: Maximum comment length before triggering warning (default: None = no limit)
|
|
228
|
+
shorten_long: If True, automatically shorten long comments to first sentence
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Tuple of (converted lines, list of LongCommentWarning objects)
|
|
232
|
+
"""
|
|
233
|
+
opening, closing = CommentConverter.get_comment_syntax(language)
|
|
234
|
+
warnings = []
|
|
235
|
+
|
|
236
|
+
# Check for long comments if max_length specified
|
|
237
|
+
if max_length:
|
|
238
|
+
warnings = CommentConverter.check_comment_lengths(explanations, language, max_length)
|
|
239
|
+
|
|
240
|
+
# Pattern for callout number in code block
|
|
241
|
+
CALLOUT_IN_CODE = re.compile(r'<(\d+)>')
|
|
242
|
+
|
|
243
|
+
result_lines = []
|
|
244
|
+
|
|
245
|
+
for line in code_content:
|
|
246
|
+
# Find all callout markers on this line
|
|
247
|
+
matches = list(CALLOUT_IN_CODE.finditer(line))
|
|
248
|
+
|
|
249
|
+
if not matches:
|
|
250
|
+
# No callouts on this line - keep as-is
|
|
251
|
+
result_lines.append(line)
|
|
252
|
+
continue
|
|
253
|
+
|
|
254
|
+
# Process line with callouts
|
|
255
|
+
# Start from the end to maintain positions during replacement
|
|
256
|
+
modified_line = line
|
|
257
|
+
|
|
258
|
+
for match in reversed(matches):
|
|
259
|
+
callout_num = int(match.group(1))
|
|
260
|
+
|
|
261
|
+
if callout_num not in explanations:
|
|
262
|
+
# Callout number not found in explanations - skip
|
|
263
|
+
continue
|
|
264
|
+
|
|
265
|
+
explanation = explanations[callout_num]
|
|
266
|
+
|
|
267
|
+
# Check if we should shorten this comment
|
|
268
|
+
if shorten_long and any(w.callout_num == callout_num for w in warnings):
|
|
269
|
+
# Use shortened version
|
|
270
|
+
comment_text = CommentConverter.shorten_comment(explanation)
|
|
271
|
+
if explanation.is_optional:
|
|
272
|
+
comment_text = f'Optional: {comment_text}'
|
|
273
|
+
else:
|
|
274
|
+
# Build comment text from explanation lines
|
|
275
|
+
# For inline comments, combine multi-line explanations with spaces
|
|
276
|
+
comment_parts = []
|
|
277
|
+
for exp_line in explanation.lines:
|
|
278
|
+
comment_parts.append(exp_line.strip())
|
|
279
|
+
|
|
280
|
+
comment_text = ' '.join(comment_parts)
|
|
281
|
+
|
|
282
|
+
# Add "Optional:" prefix if needed
|
|
283
|
+
if explanation.is_optional:
|
|
284
|
+
comment_text = f'Optional: {comment_text}'
|
|
285
|
+
|
|
286
|
+
# Format as comment
|
|
287
|
+
comment = CommentConverter.format_comment(comment_text, opening, closing)
|
|
288
|
+
|
|
289
|
+
# Replace the callout marker with the comment
|
|
290
|
+
start, end = match.span()
|
|
291
|
+
modified_line = modified_line[:start] + comment + modified_line[end:]
|
|
292
|
+
|
|
293
|
+
result_lines.append(modified_line)
|
|
294
|
+
|
|
295
|
+
return result_lines, warnings
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Definition List Converter Module
|
|
3
|
+
|
|
4
|
+
Converts callouts to AsciiDoc definition list format with "where:" prefix.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import List, Dict
|
|
9
|
+
from .detector import CalloutGroup, Callout
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DefListConverter:
|
|
13
|
+
"""Converts callouts to definition list format."""
|
|
14
|
+
|
|
15
|
+
# Pattern to detect user-replaceable values in angle brackets
|
|
16
|
+
USER_VALUE_PATTERN = re.compile(r'(?<!<)<([a-zA-Z][^>]*)>')
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def convert(callout_groups: List[CalloutGroup], explanations: Dict[int, Callout]) -> List[str]:
|
|
20
|
+
"""
|
|
21
|
+
Create definition list from callout groups and explanations.
|
|
22
|
+
|
|
23
|
+
For callouts with user-replaceable values in angle brackets, uses those.
|
|
24
|
+
For callouts without values, uses the actual code line as the term.
|
|
25
|
+
|
|
26
|
+
When multiple callouts share the same code line (same group), their
|
|
27
|
+
explanations are merged using AsciiDoc list continuation (+).
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
callout_groups: List of CalloutGroup objects from code block
|
|
31
|
+
explanations: Dict mapping callout numbers to Callout objects
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
List of strings representing the definition list
|
|
35
|
+
"""
|
|
36
|
+
lines = ['\nwhere:']
|
|
37
|
+
|
|
38
|
+
# Process each group (which may contain one or more callouts)
|
|
39
|
+
for group in callout_groups:
|
|
40
|
+
code_line = group.code_line
|
|
41
|
+
callout_nums = group.callout_numbers
|
|
42
|
+
|
|
43
|
+
# Check if this is a user-replaceable value (contains angle brackets but not heredoc)
|
|
44
|
+
# User values are single words/phrases in angle brackets like <my-value>
|
|
45
|
+
user_values = DefListConverter.USER_VALUE_PATTERN.findall(code_line)
|
|
46
|
+
|
|
47
|
+
if user_values and len(user_values) == 1 and len(code_line) < 100:
|
|
48
|
+
# This looks like a user-replaceable value placeholder
|
|
49
|
+
# Format the value (ensure it has angle brackets)
|
|
50
|
+
user_value = user_values[0]
|
|
51
|
+
if not user_value.startswith('<'):
|
|
52
|
+
user_value = f'<{user_value}>'
|
|
53
|
+
if not user_value.endswith('>'):
|
|
54
|
+
user_value = f'{user_value}>'
|
|
55
|
+
term = f'`{user_value}`'
|
|
56
|
+
else:
|
|
57
|
+
# This is a code line - use it as-is in backticks
|
|
58
|
+
term = f'`{code_line}`'
|
|
59
|
+
|
|
60
|
+
# Add blank line before each term
|
|
61
|
+
lines.append('')
|
|
62
|
+
lines.append(f'{term}::')
|
|
63
|
+
|
|
64
|
+
# Add explanations for all callouts in this group
|
|
65
|
+
for idx, callout_num in enumerate(callout_nums):
|
|
66
|
+
explanation = explanations[callout_num]
|
|
67
|
+
|
|
68
|
+
# If this is not the first explanation in the group, add continuation marker
|
|
69
|
+
if idx > 0:
|
|
70
|
+
lines.append('+')
|
|
71
|
+
|
|
72
|
+
# Add explanation lines, prepending "Optional. " to first line if needed
|
|
73
|
+
for line_idx, line in enumerate(explanation.lines):
|
|
74
|
+
if line_idx == 0 and explanation.is_optional:
|
|
75
|
+
lines.append(f'Optional. {line}')
|
|
76
|
+
else:
|
|
77
|
+
lines.append(line)
|
|
78
|
+
|
|
79
|
+
return lines
|