rolfedh-doc-utils 0.1.4__py3-none-any.whl → 0.1.41__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.
- archive_unused_files.py +18 -5
- archive_unused_images.py +9 -2
- callout_lib/__init__.py +22 -0
- callout_lib/converter_bullets.py +103 -0
- callout_lib/converter_comments.py +295 -0
- callout_lib/converter_deflist.py +134 -0
- callout_lib/detector.py +364 -0
- callout_lib/table_parser.py +804 -0
- check_published_links.py +1083 -0
- check_scannability.py +6 -0
- check_source_directives.py +101 -0
- convert_callouts_interactive.py +567 -0
- convert_callouts_to_deflist.py +628 -0
- convert_freemarker_to_asciidoc.py +288 -0
- convert_tables_to_deflists.py +479 -0
- doc_utils/convert_freemarker_to_asciidoc.py +708 -0
- doc_utils/duplicate_content.py +409 -0
- doc_utils/duplicate_includes.py +347 -0
- doc_utils/extract_link_attributes.py +618 -0
- doc_utils/format_asciidoc_spacing.py +285 -0
- doc_utils/insert_abstract_role.py +220 -0
- doc_utils/inventory_conditionals.py +164 -0
- doc_utils/missing_source_directive.py +211 -0
- doc_utils/replace_link_attributes.py +187 -0
- doc_utils/spinner.py +119 -0
- doc_utils/unused_adoc.py +150 -22
- doc_utils/unused_attributes.py +218 -6
- doc_utils/unused_images.py +81 -9
- doc_utils/validate_links.py +576 -0
- doc_utils/version.py +8 -0
- doc_utils/version_check.py +243 -0
- doc_utils/warnings_report.py +237 -0
- doc_utils_cli.py +158 -0
- extract_link_attributes.py +120 -0
- find_duplicate_content.py +209 -0
- find_duplicate_includes.py +198 -0
- find_unused_attributes.py +84 -6
- format_asciidoc_spacing.py +134 -0
- insert_abstract_role.py +163 -0
- inventory_conditionals.py +53 -0
- replace_link_attributes.py +214 -0
- rolfedh_doc_utils-0.1.41.dist-info/METADATA +246 -0
- rolfedh_doc_utils-0.1.41.dist-info/RECORD +52 -0
- {rolfedh_doc_utils-0.1.4.dist-info → rolfedh_doc_utils-0.1.41.dist-info}/WHEEL +1 -1
- rolfedh_doc_utils-0.1.41.dist-info/entry_points.txt +20 -0
- rolfedh_doc_utils-0.1.41.dist-info/top_level.txt +21 -0
- validate_links.py +213 -0
- rolfedh_doc_utils-0.1.4.dist-info/METADATA +0 -285
- rolfedh_doc_utils-0.1.4.dist-info/RECORD +0 -17
- rolfedh_doc_utils-0.1.4.dist-info/entry_points.txt +0 -5
- rolfedh_doc_utils-0.1.4.dist-info/top_level.txt +0 -5
- {rolfedh_doc_utils-0.1.4.dist-info → rolfedh_doc_utils-0.1.41.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
convert-callouts-to-deflist - Convert AsciiDoc callouts to definition list format
|
|
4
|
+
|
|
5
|
+
Converts code blocks with callout-style annotations (<1>, <2>, etc.) to cleaner
|
|
6
|
+
definition list format with "where:" prefix, bulleted list format, or inline comments.
|
|
7
|
+
|
|
8
|
+
This tool automatically scans all .adoc files in the current directory (recursively)
|
|
9
|
+
by default, or you can specify a specific file or directory.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import argparse
|
|
14
|
+
from pathlib import Path
|
|
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
|
+
)
|
|
24
|
+
|
|
25
|
+
# Import warnings report generator
|
|
26
|
+
from doc_utils.warnings_report import generate_warnings_report
|
|
27
|
+
|
|
28
|
+
# Import version
|
|
29
|
+
from doc_utils.version import __version__
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Colors for output
|
|
33
|
+
class Colors:
|
|
34
|
+
RED = '\033[0;31m'
|
|
35
|
+
GREEN = '\033[0;32m'
|
|
36
|
+
YELLOW = '\033[1;33m'
|
|
37
|
+
NC = '\033[0m' # No Color
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def print_colored(message: str, color: str = Colors.NC) -> None:
|
|
41
|
+
"""Print message with color"""
|
|
42
|
+
print(f"{color}{message}{Colors.NC}")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CalloutConverter:
|
|
46
|
+
"""Converts callout-style documentation to various formats."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, dry_run: bool = False, verbose: bool = False, output_format: str = 'deflist',
|
|
49
|
+
max_comment_length: int = 120, force: bool = False, definition_prefix: str = ""):
|
|
50
|
+
self.dry_run = dry_run
|
|
51
|
+
self.verbose = verbose
|
|
52
|
+
self.output_format = output_format # 'deflist', 'bullets', or 'comments'
|
|
53
|
+
self.max_comment_length = max_comment_length # Max length for inline comments
|
|
54
|
+
self.force = force # Force strip callouts even with warnings
|
|
55
|
+
self.definition_prefix = definition_prefix # Prefix to add before definitions (e.g., "Specifies ")
|
|
56
|
+
self.changes_made = 0
|
|
57
|
+
self.warnings = [] # Collect warnings for summary
|
|
58
|
+
self.long_comment_warnings = [] # Warnings for comments exceeding max length
|
|
59
|
+
|
|
60
|
+
# Initialize detector and converters
|
|
61
|
+
self.detector = CalloutDetector()
|
|
62
|
+
|
|
63
|
+
def log(self, message: str):
|
|
64
|
+
"""Print message if verbose mode is enabled."""
|
|
65
|
+
if self.verbose:
|
|
66
|
+
print(f"[INFO] {message}")
|
|
67
|
+
|
|
68
|
+
def convert_file(self, input_file: Path) -> Tuple[int, bool]:
|
|
69
|
+
"""
|
|
70
|
+
Convert callouts in a file to the specified output format.
|
|
71
|
+
Returns tuple of (number of conversions, whether file was modified).
|
|
72
|
+
"""
|
|
73
|
+
# Read input file
|
|
74
|
+
try:
|
|
75
|
+
with open(input_file, 'r', encoding='utf-8') as f:
|
|
76
|
+
lines = [line.rstrip('\n') for line in f]
|
|
77
|
+
except Exception as e:
|
|
78
|
+
print_colored(f"Error reading {input_file}: {e}", Colors.RED)
|
|
79
|
+
return 0, False
|
|
80
|
+
|
|
81
|
+
self.log(f"Processing {input_file} ({len(lines)} lines)")
|
|
82
|
+
|
|
83
|
+
# Find all code blocks
|
|
84
|
+
blocks = self.detector.find_code_blocks(lines)
|
|
85
|
+
self.log(f"Found {len(blocks)} code blocks")
|
|
86
|
+
|
|
87
|
+
if not blocks:
|
|
88
|
+
return 0, False
|
|
89
|
+
|
|
90
|
+
# Process blocks in reverse order to maintain line numbers
|
|
91
|
+
new_lines = lines.copy()
|
|
92
|
+
conversions = 0
|
|
93
|
+
|
|
94
|
+
for block in reversed(blocks):
|
|
95
|
+
# Extract callouts from code (returns list of CalloutGroups)
|
|
96
|
+
callout_groups = self.detector.extract_callouts_from_code(block.content)
|
|
97
|
+
|
|
98
|
+
if not callout_groups:
|
|
99
|
+
self.log(f"No callouts in block at line {block.start_line + 1}")
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
# Extract all callout numbers for logging
|
|
103
|
+
all_callout_nums = []
|
|
104
|
+
for group in callout_groups:
|
|
105
|
+
all_callout_nums.extend(group.callout_numbers)
|
|
106
|
+
|
|
107
|
+
self.log(f"Block at line {block.start_line + 1} has callouts: {all_callout_nums}")
|
|
108
|
+
|
|
109
|
+
# Extract explanations
|
|
110
|
+
explanations, explanation_end = self.detector.extract_callout_explanations(new_lines, block.end_line)
|
|
111
|
+
|
|
112
|
+
if not explanations:
|
|
113
|
+
self.log(f"No explanations found after block at line {block.start_line + 1}")
|
|
114
|
+
# Warn user about code blocks with callouts but no explanations
|
|
115
|
+
warning_msg = (
|
|
116
|
+
f"WARNING: {input_file.name} line {block.start_line + 1}: "
|
|
117
|
+
f"Code block has callouts {sorted(set(all_callout_nums))} but no explanations found after it. "
|
|
118
|
+
f"This may indicate: explanations are shared with another code block, "
|
|
119
|
+
f"explanations are in an unexpected location, or documentation error (missing explanations). "
|
|
120
|
+
f"Consider reviewing this block manually."
|
|
121
|
+
)
|
|
122
|
+
print_colored(warning_msg, Colors.YELLOW)
|
|
123
|
+
self.warnings.append(warning_msg)
|
|
124
|
+
|
|
125
|
+
# In force mode, strip callouts anyway
|
|
126
|
+
if not self.force:
|
|
127
|
+
continue
|
|
128
|
+
else:
|
|
129
|
+
self.log(f"FORCE MODE: Stripping callouts from block at line {block.start_line + 1} despite missing explanations")
|
|
130
|
+
|
|
131
|
+
# Just strip callouts without creating explanation list
|
|
132
|
+
converted_content = self.detector.remove_callouts_from_code(block.content)
|
|
133
|
+
|
|
134
|
+
# Replace in document
|
|
135
|
+
has_source_prefix = self.detector.CODE_BLOCK_START.match(new_lines[block.start_line])
|
|
136
|
+
if has_source_prefix:
|
|
137
|
+
content_start = block.start_line + 2 # After [source] and ----
|
|
138
|
+
else:
|
|
139
|
+
content_start = block.start_line + 1 # After ---- only
|
|
140
|
+
content_end = block.end_line
|
|
141
|
+
|
|
142
|
+
# Build new section with just code (no explanations)
|
|
143
|
+
new_section = (
|
|
144
|
+
new_lines[:content_start] +
|
|
145
|
+
converted_content +
|
|
146
|
+
[new_lines[content_end]] + # Keep closing delimiter
|
|
147
|
+
new_lines[content_end + 1:] # Keep rest of file
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
new_lines = new_section
|
|
151
|
+
conversions += 1
|
|
152
|
+
self.changes_made += 1
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
# Validate callouts match
|
|
156
|
+
is_valid, code_nums, explanation_nums = self.detector.validate_callouts(callout_groups, explanations)
|
|
157
|
+
if not is_valid and explanations: # Only validate if we have explanations
|
|
158
|
+
# Format warning message with file and line numbers
|
|
159
|
+
line_range = f"{block.start_line + 1}-{block.end_line + 1}"
|
|
160
|
+
warning_msg = (
|
|
161
|
+
f"WARNING: {input_file.name} lines {line_range}: Callout mismatch: "
|
|
162
|
+
f"code has {sorted(code_nums)}, explanations have {sorted(explanation_nums)}"
|
|
163
|
+
)
|
|
164
|
+
print_colored(warning_msg, Colors.YELLOW)
|
|
165
|
+
self.warnings.append(warning_msg)
|
|
166
|
+
|
|
167
|
+
# In force mode, convert anyway
|
|
168
|
+
if not self.force:
|
|
169
|
+
continue
|
|
170
|
+
else:
|
|
171
|
+
self.log(f"FORCE MODE: Converting block at line {block.start_line + 1} despite callout mismatch")
|
|
172
|
+
|
|
173
|
+
self.log(f"Converting block at line {block.start_line + 1}")
|
|
174
|
+
|
|
175
|
+
# Convert based on format option
|
|
176
|
+
use_deflist_fallback = False
|
|
177
|
+
if self.output_format == 'comments':
|
|
178
|
+
# For comments format, replace callouts inline in the code
|
|
179
|
+
converted_content, long_warnings = CommentConverter.convert(
|
|
180
|
+
block.content, callout_groups, explanations, block.language,
|
|
181
|
+
max_length=self.max_comment_length, shorten_long=False
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# If there are long comment warnings, fall back to definition list
|
|
185
|
+
if long_warnings:
|
|
186
|
+
for lw in long_warnings:
|
|
187
|
+
warning_msg = (
|
|
188
|
+
f"WARNING: {input_file.name} lines {block.start_line + 1}-{block.end_line + 1}: "
|
|
189
|
+
f"Callout <{lw.callout_num}> explanation too long ({lw.length} chars) "
|
|
190
|
+
f"for inline comment (max: {self.max_comment_length}). Falling back to definition list format."
|
|
191
|
+
)
|
|
192
|
+
print_colored(warning_msg, Colors.YELLOW)
|
|
193
|
+
self.warnings.append(warning_msg)
|
|
194
|
+
self.long_comment_warnings.append((input_file.name, block.start_line + 1, lw))
|
|
195
|
+
|
|
196
|
+
# Fall back to definition list
|
|
197
|
+
self.log(f"Falling back to definition list for block at line {block.start_line + 1}")
|
|
198
|
+
converted_content = self.detector.remove_callouts_from_code(block.content)
|
|
199
|
+
output_list = DefListConverter.convert(callout_groups, explanations, self.detector.last_table_title, self.definition_prefix)
|
|
200
|
+
use_deflist_fallback = True
|
|
201
|
+
else:
|
|
202
|
+
output_list = [] # No separate list after code block for comments
|
|
203
|
+
else:
|
|
204
|
+
# For deflist and bullets, remove callouts from code and create separate list
|
|
205
|
+
converted_content = self.detector.remove_callouts_from_code(block.content)
|
|
206
|
+
|
|
207
|
+
if self.output_format == 'bullets':
|
|
208
|
+
output_list = BulletListConverter.convert(callout_groups, explanations, self.detector.last_table_title)
|
|
209
|
+
else: # default to 'deflist'
|
|
210
|
+
output_list = DefListConverter.convert(callout_groups, explanations, self.detector.last_table_title, self.definition_prefix)
|
|
211
|
+
|
|
212
|
+
# Replace in document
|
|
213
|
+
# Check if block has [source] prefix
|
|
214
|
+
has_source_prefix = self.detector.CODE_BLOCK_START.match(new_lines[block.start_line])
|
|
215
|
+
if has_source_prefix:
|
|
216
|
+
content_start = block.start_line + 2 # After [source] and ----
|
|
217
|
+
else:
|
|
218
|
+
content_start = block.start_line + 1 # After ---- only
|
|
219
|
+
content_end = block.end_line
|
|
220
|
+
|
|
221
|
+
# For comments format (without fallback), remove explanations but don't add new list
|
|
222
|
+
# For deflist/bullets format, remove old explanations and add new list
|
|
223
|
+
if self.output_format == 'comments' and not use_deflist_fallback:
|
|
224
|
+
# Remove old callout explanations (list or table format)
|
|
225
|
+
# Find where explanations/table actually starts to preserve content in between
|
|
226
|
+
if self.detector.last_table:
|
|
227
|
+
explanation_start_line = self.detector.last_table.start_line
|
|
228
|
+
else:
|
|
229
|
+
# List format: skip blank lines after code block
|
|
230
|
+
explanation_start_line = block.end_line + 1
|
|
231
|
+
while explanation_start_line < len(new_lines) and not new_lines[explanation_start_line].strip():
|
|
232
|
+
explanation_start_line += 1
|
|
233
|
+
|
|
234
|
+
new_section = (
|
|
235
|
+
new_lines[:content_start] +
|
|
236
|
+
converted_content +
|
|
237
|
+
[new_lines[content_end]] + # Keep closing delimiter
|
|
238
|
+
new_lines[content_end + 1:explanation_start_line] + # Preserve content between code and explanations
|
|
239
|
+
new_lines[explanation_end + 1:] # Skip explanations/table, keep rest
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
# Remove old callout explanations and add new list
|
|
243
|
+
# Find where explanations/table actually starts
|
|
244
|
+
if self.detector.last_table:
|
|
245
|
+
# Table format: preserve content between code block and table start
|
|
246
|
+
explanation_start_line = self.detector.last_table.start_line
|
|
247
|
+
else:
|
|
248
|
+
# List format: skip blank lines after code block
|
|
249
|
+
explanation_start_line = block.end_line + 1
|
|
250
|
+
while explanation_start_line < len(new_lines) and not new_lines[explanation_start_line].strip():
|
|
251
|
+
explanation_start_line += 1
|
|
252
|
+
|
|
253
|
+
# Build the new section
|
|
254
|
+
new_section = (
|
|
255
|
+
new_lines[:content_start] +
|
|
256
|
+
converted_content +
|
|
257
|
+
[new_lines[content_end]] + # Keep closing delimiter
|
|
258
|
+
new_lines[content_end + 1:explanation_start_line] + # Preserve content between code and explanations
|
|
259
|
+
output_list +
|
|
260
|
+
new_lines[explanation_end + 1:]
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
new_lines = new_section
|
|
264
|
+
conversions += 1
|
|
265
|
+
self.changes_made += 1
|
|
266
|
+
|
|
267
|
+
# Write output
|
|
268
|
+
if conversions > 0 and not self.dry_run:
|
|
269
|
+
try:
|
|
270
|
+
with open(input_file, 'w', encoding='utf-8') as f:
|
|
271
|
+
f.write('\n'.join(new_lines) + '\n')
|
|
272
|
+
self.log(f"Wrote {input_file}")
|
|
273
|
+
except Exception as e:
|
|
274
|
+
print_colored(f"Error writing {input_file}: {e}", Colors.RED)
|
|
275
|
+
return 0, False
|
|
276
|
+
|
|
277
|
+
return conversions, conversions > 0
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def find_adoc_files(path: Path, exclude_dirs: List[str] = None, exclude_files: List[str] = None) -> List[Path]:
|
|
281
|
+
"""
|
|
282
|
+
Find all .adoc files in the given path.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
path: Path to search (file or directory)
|
|
286
|
+
exclude_dirs: List of directory patterns to exclude
|
|
287
|
+
exclude_files: List of file patterns to exclude
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
List of Path objects for .adoc files
|
|
291
|
+
"""
|
|
292
|
+
adoc_files = []
|
|
293
|
+
exclude_dirs = exclude_dirs or []
|
|
294
|
+
exclude_files = exclude_files or []
|
|
295
|
+
|
|
296
|
+
# Always exclude .vale directory by default (Vale linter fixtures)
|
|
297
|
+
if '.vale' not in exclude_dirs:
|
|
298
|
+
exclude_dirs.append('.vale')
|
|
299
|
+
|
|
300
|
+
if path.is_file():
|
|
301
|
+
if path.suffix == '.adoc':
|
|
302
|
+
# Check if file should be excluded
|
|
303
|
+
if not any(excl in str(path) for excl in exclude_files):
|
|
304
|
+
adoc_files.append(path)
|
|
305
|
+
elif path.is_dir():
|
|
306
|
+
# Recursively find all .adoc files
|
|
307
|
+
for adoc_file in path.rglob('*.adoc'):
|
|
308
|
+
# Check if in excluded directory
|
|
309
|
+
if any(excl in str(adoc_file) for excl in exclude_dirs):
|
|
310
|
+
continue
|
|
311
|
+
# Check if file should be excluded
|
|
312
|
+
if any(excl in str(adoc_file) for excl in exclude_files):
|
|
313
|
+
continue
|
|
314
|
+
adoc_files.append(adoc_file)
|
|
315
|
+
|
|
316
|
+
return sorted(adoc_files)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def load_exclusion_list(exclusion_file: Path) -> Tuple[List[str], List[str]]:
|
|
320
|
+
"""
|
|
321
|
+
Load exclusion list from file.
|
|
322
|
+
Returns tuple of (excluded_dirs, excluded_files).
|
|
323
|
+
"""
|
|
324
|
+
excluded_dirs = []
|
|
325
|
+
excluded_files = []
|
|
326
|
+
|
|
327
|
+
try:
|
|
328
|
+
with open(exclusion_file, 'r') as f:
|
|
329
|
+
for line in f:
|
|
330
|
+
line = line.strip()
|
|
331
|
+
# Skip comments and empty lines
|
|
332
|
+
if not line or line.startswith('#'):
|
|
333
|
+
continue
|
|
334
|
+
|
|
335
|
+
# If it ends with /, it's a directory
|
|
336
|
+
if line.endswith('/'):
|
|
337
|
+
excluded_dirs.append(line.rstrip('/'))
|
|
338
|
+
else:
|
|
339
|
+
# Could be file or directory - check if it has extension
|
|
340
|
+
if '.' in Path(line).name:
|
|
341
|
+
excluded_files.append(line)
|
|
342
|
+
else:
|
|
343
|
+
excluded_dirs.append(line)
|
|
344
|
+
except Exception as e:
|
|
345
|
+
print_colored(f"Warning: Could not read exclusion file {exclusion_file}: {e}", Colors.YELLOW)
|
|
346
|
+
|
|
347
|
+
return excluded_dirs, excluded_files
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def main():
|
|
351
|
+
"""Main entry point"""
|
|
352
|
+
parser = argparse.ArgumentParser(
|
|
353
|
+
description='Convert AsciiDoc callouts to various formats',
|
|
354
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
355
|
+
epilog="""
|
|
356
|
+
Convert AsciiDoc callout-style documentation to various formats.
|
|
357
|
+
|
|
358
|
+
This script identifies code blocks with callout numbers (<1>, <2>, etc.) and their
|
|
359
|
+
corresponding explanation lines, then converts them to your chosen format.
|
|
360
|
+
|
|
361
|
+
Formats:
|
|
362
|
+
deflist - Definition list with "where:" prefix (default)
|
|
363
|
+
bullets - Bulleted list format
|
|
364
|
+
comments - Inline comments within code (removes separate explanations)
|
|
365
|
+
|
|
366
|
+
Examples:
|
|
367
|
+
%(prog)s # Process all .adoc files in current directory
|
|
368
|
+
%(prog)s modules/ # Process all .adoc files in modules/
|
|
369
|
+
%(prog)s assemblies/my-guide.adoc # Process single file
|
|
370
|
+
%(prog)s --dry-run modules/ # Preview changes without modifying
|
|
371
|
+
%(prog)s --format bullets modules/ # Convert to bulleted list format
|
|
372
|
+
%(prog)s --format comments src/ # Convert to inline comments
|
|
373
|
+
%(prog)s --exclude-dir .vale modules/ # Exclude .vale directory
|
|
374
|
+
|
|
375
|
+
Example transformation (deflist format):
|
|
376
|
+
FROM:
|
|
377
|
+
[source,yaml]
|
|
378
|
+
----
|
|
379
|
+
name: <my-secret> <1>
|
|
380
|
+
key: <my-key> <2>
|
|
381
|
+
----
|
|
382
|
+
<1> Secret name
|
|
383
|
+
<2> Key value
|
|
384
|
+
|
|
385
|
+
TO:
|
|
386
|
+
[source,yaml]
|
|
387
|
+
----
|
|
388
|
+
name: <my-secret>
|
|
389
|
+
key: <my-key>
|
|
390
|
+
----
|
|
391
|
+
|
|
392
|
+
where:
|
|
393
|
+
|
|
394
|
+
`<my-secret>`::
|
|
395
|
+
Secret name
|
|
396
|
+
|
|
397
|
+
`<my-key>`::
|
|
398
|
+
Key value
|
|
399
|
+
"""
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
parser.add_argument(
|
|
403
|
+
'--version',
|
|
404
|
+
action='version',
|
|
405
|
+
version=f'%(prog)s {__version__}'
|
|
406
|
+
)
|
|
407
|
+
parser.add_argument(
|
|
408
|
+
'path',
|
|
409
|
+
nargs='?',
|
|
410
|
+
default='.',
|
|
411
|
+
help='File or directory to process (default: current directory)'
|
|
412
|
+
)
|
|
413
|
+
parser.add_argument(
|
|
414
|
+
'-n', '--dry-run',
|
|
415
|
+
action='store_true',
|
|
416
|
+
help='Show what would be changed without modifying files'
|
|
417
|
+
)
|
|
418
|
+
parser.add_argument(
|
|
419
|
+
'-v', '--verbose',
|
|
420
|
+
action='store_true',
|
|
421
|
+
help='Enable verbose output'
|
|
422
|
+
)
|
|
423
|
+
parser.add_argument(
|
|
424
|
+
'-f', '--format',
|
|
425
|
+
choices=['deflist', 'bullets', 'comments'],
|
|
426
|
+
default='deflist',
|
|
427
|
+
help='Output format: "deflist" for definition list (default), "bullets" for bulleted list, "comments" for inline comments'
|
|
428
|
+
)
|
|
429
|
+
parser.add_argument(
|
|
430
|
+
'--max-comment-length',
|
|
431
|
+
type=int,
|
|
432
|
+
default=120,
|
|
433
|
+
help='Maximum length for inline comments before falling back to definition list (default: 120 characters)'
|
|
434
|
+
)
|
|
435
|
+
parser.add_argument(
|
|
436
|
+
'--exclude-dir',
|
|
437
|
+
action='append',
|
|
438
|
+
dest='exclude_dirs',
|
|
439
|
+
default=[],
|
|
440
|
+
help='Directory to exclude (can be used multiple times)'
|
|
441
|
+
)
|
|
442
|
+
parser.add_argument(
|
|
443
|
+
'--exclude-file',
|
|
444
|
+
action='append',
|
|
445
|
+
dest='exclude_files',
|
|
446
|
+
default=[],
|
|
447
|
+
help='File to exclude (can be used multiple times)'
|
|
448
|
+
)
|
|
449
|
+
parser.add_argument(
|
|
450
|
+
'--exclude-list',
|
|
451
|
+
type=Path,
|
|
452
|
+
help='Path to file containing directories/files to exclude, one per line'
|
|
453
|
+
)
|
|
454
|
+
parser.add_argument(
|
|
455
|
+
'--warnings-report',
|
|
456
|
+
dest='warnings_report',
|
|
457
|
+
action='store_true',
|
|
458
|
+
default=True,
|
|
459
|
+
help='Generate warnings report file (default: enabled)'
|
|
460
|
+
)
|
|
461
|
+
parser.add_argument(
|
|
462
|
+
'--no-warnings-report',
|
|
463
|
+
dest='warnings_report',
|
|
464
|
+
action='store_false',
|
|
465
|
+
help='Disable warnings report file generation'
|
|
466
|
+
)
|
|
467
|
+
parser.add_argument(
|
|
468
|
+
'--warnings-file',
|
|
469
|
+
type=Path,
|
|
470
|
+
default=Path('callout-warnings-report.adoc'),
|
|
471
|
+
help='Path for warnings report file (default: callout-warnings-report.adoc)'
|
|
472
|
+
)
|
|
473
|
+
parser.add_argument(
|
|
474
|
+
'--force',
|
|
475
|
+
action='store_true',
|
|
476
|
+
help='Force strip callouts from code blocks even with warnings (USE WITH CAUTION: only after reviewing and fixing callout issues)'
|
|
477
|
+
)
|
|
478
|
+
parser.add_argument(
|
|
479
|
+
'-s', '--specifies',
|
|
480
|
+
action='store_true',
|
|
481
|
+
help='Add "Specifies " prefix before each definition (only applies to deflist format)'
|
|
482
|
+
)
|
|
483
|
+
parser.add_argument(
|
|
484
|
+
'--prefix',
|
|
485
|
+
type=str,
|
|
486
|
+
default='',
|
|
487
|
+
help='Custom prefix to add before each definition (only applies to deflist format, e.g., "Indicates ")'
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
args = parser.parse_args()
|
|
491
|
+
|
|
492
|
+
# Load exclusion list if provided
|
|
493
|
+
if args.exclude_list:
|
|
494
|
+
if args.exclude_list.exists():
|
|
495
|
+
excluded_dirs, excluded_files = load_exclusion_list(args.exclude_list)
|
|
496
|
+
args.exclude_dirs.extend(excluded_dirs)
|
|
497
|
+
args.exclude_files.extend(excluded_files)
|
|
498
|
+
else:
|
|
499
|
+
print_colored(f"Warning: Exclusion list file not found: {args.exclude_list}", Colors.YELLOW)
|
|
500
|
+
|
|
501
|
+
# Convert path to Path object
|
|
502
|
+
target_path = Path(args.path)
|
|
503
|
+
|
|
504
|
+
# Check if path exists
|
|
505
|
+
if not target_path.exists():
|
|
506
|
+
print_colored(f"Error: Path does not exist: {target_path}", Colors.RED)
|
|
507
|
+
sys.exit(1)
|
|
508
|
+
|
|
509
|
+
# Display dry-run mode message
|
|
510
|
+
if args.dry_run:
|
|
511
|
+
print_colored("DRY RUN MODE - No files will be modified", Colors.YELLOW)
|
|
512
|
+
|
|
513
|
+
# Find all AsciiDoc files
|
|
514
|
+
adoc_files = find_adoc_files(target_path, args.exclude_dirs, args.exclude_files)
|
|
515
|
+
|
|
516
|
+
if not adoc_files:
|
|
517
|
+
if target_path.is_file():
|
|
518
|
+
print_colored(f"Warning: {target_path} is not an AsciiDoc file (.adoc)", Colors.YELLOW)
|
|
519
|
+
else:
|
|
520
|
+
print(f"No AsciiDoc files found in {target_path}")
|
|
521
|
+
print("Processed 0 AsciiDoc file(s)")
|
|
522
|
+
return
|
|
523
|
+
|
|
524
|
+
print(f"Found {len(adoc_files)} AsciiDoc file(s) to process")
|
|
525
|
+
|
|
526
|
+
# If force mode is enabled, show warning and ask for confirmation
|
|
527
|
+
if args.force and not args.dry_run:
|
|
528
|
+
print_colored("\n⚠️ FORCE MODE ENABLED ⚠️", Colors.YELLOW)
|
|
529
|
+
print_colored("This will strip callouts from code blocks even when warnings are present.", Colors.YELLOW)
|
|
530
|
+
print_colored("You should only use this option AFTER:", Colors.YELLOW)
|
|
531
|
+
print_colored(" 1. Reviewing all warnings in the warnings report", Colors.YELLOW)
|
|
532
|
+
print_colored(" 2. Manually fixing callout issues where appropriate", Colors.YELLOW)
|
|
533
|
+
print_colored(" 3. Confirming that remaining warnings are acceptable", Colors.YELLOW)
|
|
534
|
+
print()
|
|
535
|
+
response = input("Are you sure you want to proceed with force mode? (yes/no): ").strip().lower()
|
|
536
|
+
if response not in ['yes', 'y']:
|
|
537
|
+
print_colored("Operation cancelled.", Colors.YELLOW)
|
|
538
|
+
sys.exit(0)
|
|
539
|
+
print()
|
|
540
|
+
|
|
541
|
+
# Determine definition prefix
|
|
542
|
+
definition_prefix = ""
|
|
543
|
+
if args.specifies:
|
|
544
|
+
definition_prefix = "Specifies "
|
|
545
|
+
elif args.prefix:
|
|
546
|
+
definition_prefix = args.prefix
|
|
547
|
+
# Add trailing space if user didn't include one
|
|
548
|
+
if definition_prefix and not definition_prefix.endswith(' '):
|
|
549
|
+
definition_prefix += ' '
|
|
550
|
+
|
|
551
|
+
# Create converter
|
|
552
|
+
converter = CalloutConverter(dry_run=args.dry_run, verbose=args.verbose, output_format=args.format,
|
|
553
|
+
max_comment_length=args.max_comment_length, force=args.force,
|
|
554
|
+
definition_prefix=definition_prefix)
|
|
555
|
+
|
|
556
|
+
# Process each file
|
|
557
|
+
files_processed = 0
|
|
558
|
+
files_modified = 0
|
|
559
|
+
total_conversions = 0
|
|
560
|
+
|
|
561
|
+
for file_path in adoc_files:
|
|
562
|
+
try:
|
|
563
|
+
conversions, modified = converter.convert_file(file_path)
|
|
564
|
+
|
|
565
|
+
if modified:
|
|
566
|
+
files_modified += 1
|
|
567
|
+
total_conversions += conversions
|
|
568
|
+
if args.dry_run:
|
|
569
|
+
print_colored(f"Would modify: {file_path} ({conversions} code block(s))", Colors.YELLOW)
|
|
570
|
+
else:
|
|
571
|
+
print_colored(f"Modified: {file_path} ({conversions} code block(s))", Colors.GREEN)
|
|
572
|
+
elif args.verbose:
|
|
573
|
+
print(f" No callouts found in: {file_path}")
|
|
574
|
+
|
|
575
|
+
files_processed += 1
|
|
576
|
+
|
|
577
|
+
except KeyboardInterrupt:
|
|
578
|
+
print_colored("\nOperation cancelled by user", Colors.YELLOW)
|
|
579
|
+
sys.exit(1)
|
|
580
|
+
except Exception as e:
|
|
581
|
+
print_colored(f"Unexpected error processing {file_path}: {e}", Colors.RED)
|
|
582
|
+
if args.verbose:
|
|
583
|
+
import traceback
|
|
584
|
+
traceback.print_exc()
|
|
585
|
+
|
|
586
|
+
# Summary
|
|
587
|
+
print(f"\nProcessed {files_processed} AsciiDoc file(s)")
|
|
588
|
+
if args.dry_run and files_modified > 0:
|
|
589
|
+
print(f"Would modify {files_modified} file(s) with {total_conversions} code block conversion(s)")
|
|
590
|
+
elif files_modified > 0:
|
|
591
|
+
print_colored(f"Modified {files_modified} file(s) with {total_conversions} code block conversion(s)", Colors.GREEN)
|
|
592
|
+
else:
|
|
593
|
+
print("No files with callouts to convert")
|
|
594
|
+
|
|
595
|
+
# Display warning summary if any warnings were collected
|
|
596
|
+
if converter.warnings:
|
|
597
|
+
# Generate warnings report if enabled
|
|
598
|
+
if args.warnings_report:
|
|
599
|
+
try:
|
|
600
|
+
generate_warnings_report(converter.warnings, args.warnings_file)
|
|
601
|
+
print_colored(f"\n⚠️ {len(converter.warnings)} Warning(s) - See {args.warnings_file} for details", Colors.YELLOW)
|
|
602
|
+
print()
|
|
603
|
+
print_colored(f"Suggestion: Review and fix the callout issues listed in {args.warnings_file}, then rerun this command.", Colors.YELLOW)
|
|
604
|
+
except Exception as e:
|
|
605
|
+
print_colored(f"\n⚠️ {len(converter.warnings)} Warning(s):", Colors.YELLOW)
|
|
606
|
+
print_colored(f"Error generating warnings report: {e}", Colors.RED)
|
|
607
|
+
# Fall back to displaying warnings in console
|
|
608
|
+
for warning in converter.warnings:
|
|
609
|
+
print_colored(f" {warning}", Colors.YELLOW)
|
|
610
|
+
print()
|
|
611
|
+
print_colored("Suggestion: Fix the callout issues listed above and rerun this command.", Colors.YELLOW)
|
|
612
|
+
else:
|
|
613
|
+
# Console-only output (legacy behavior)
|
|
614
|
+
print_colored(f"\n⚠️ {len(converter.warnings)} Warning(s):", Colors.YELLOW)
|
|
615
|
+
for warning in converter.warnings:
|
|
616
|
+
print_colored(f" {warning}", Colors.YELLOW)
|
|
617
|
+
print()
|
|
618
|
+
print_colored("Suggestion: Fix the callout issues listed above and rerun this command.", Colors.YELLOW)
|
|
619
|
+
print()
|
|
620
|
+
|
|
621
|
+
if args.dry_run and files_modified > 0:
|
|
622
|
+
print_colored("DRY RUN - No files were modified", Colors.YELLOW)
|
|
623
|
+
|
|
624
|
+
return 0 if files_processed >= 0 else 1
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
if __name__ == '__main__':
|
|
628
|
+
sys.exit(main())
|