rolfedh-doc-utils 0.1.38__py3-none-any.whl → 0.1.40__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.
- convert_freemarker_to_asciidoc.py +288 -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/inventory_conditionals.py +164 -0
- doc_utils/unused_attributes.py +48 -0
- doc_utils/version.py +1 -1
- find_duplicate_content.py +209 -0
- find_duplicate_includes.py +198 -0
- find_unused_attributes.py +16 -1
- inventory_conditionals.py +53 -0
- {rolfedh_doc_utils-0.1.38.dist-info → rolfedh_doc_utils-0.1.40.dist-info}/METADATA +2 -1
- {rolfedh_doc_utils-0.1.38.dist-info → rolfedh_doc_utils-0.1.40.dist-info}/RECORD +17 -9
- {rolfedh_doc_utils-0.1.38.dist-info → rolfedh_doc_utils-0.1.40.dist-info}/WHEEL +1 -1
- {rolfedh_doc_utils-0.1.38.dist-info → rolfedh_doc_utils-0.1.40.dist-info}/entry_points.txt +4 -0
- {rolfedh_doc_utils-0.1.38.dist-info → rolfedh_doc_utils-0.1.40.dist-info}/top_level.txt +4 -0
- {rolfedh_doc_utils-0.1.38.dist-info → rolfedh_doc_utils-0.1.40.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
convert-freemarker-to-asciidoc - Convert FreeMarker-templated AsciiDoc to standard AsciiDoc.
|
|
4
|
+
|
|
5
|
+
Converts Keycloak-style FreeMarker template markup in AsciiDoc files to standard
|
|
6
|
+
AsciiDoc format. This tool:
|
|
7
|
+
|
|
8
|
+
- Removes FreeMarker import statements (<#import ...>)
|
|
9
|
+
- Converts <@tmpl.guide> blocks to AsciiDoc title and short description
|
|
10
|
+
- Removes closing </@tmpl.guide> tags
|
|
11
|
+
- Converts <@links.*> macros to AsciiDoc xref cross-references
|
|
12
|
+
- Converts <@kc.*> command macros to code blocks
|
|
13
|
+
- Handles <@profile.*> conditional blocks (community vs product)
|
|
14
|
+
- Removes <@opts.*> option macros
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
convert-freemarker-to-asciidoc # Process current directory
|
|
18
|
+
convert-freemarker-to-asciidoc docs/guides/ # Process specific directory
|
|
19
|
+
convert-freemarker-to-asciidoc --dry-run # Preview changes
|
|
20
|
+
convert-freemarker-to-asciidoc --structure-only # Only convert imports and guide blocks
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
# Preview changes without modifying files
|
|
24
|
+
convert-freemarker-to-asciidoc --dry-run docs/guides/
|
|
25
|
+
|
|
26
|
+
# Convert all .adoc files in current directory
|
|
27
|
+
convert-freemarker-to-asciidoc
|
|
28
|
+
|
|
29
|
+
# Only convert structure (imports, guide blocks) - leave inline macros
|
|
30
|
+
convert-freemarker-to-asciidoc --structure-only docs/guides/server/
|
|
31
|
+
|
|
32
|
+
# Keep product content instead of community content
|
|
33
|
+
convert-freemarker-to-asciidoc --product docs/guides/
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import argparse
|
|
37
|
+
import sys
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
|
|
40
|
+
from doc_utils.convert_freemarker_to_asciidoc import (
|
|
41
|
+
process_file,
|
|
42
|
+
find_adoc_files,
|
|
43
|
+
has_freemarker_content,
|
|
44
|
+
ConversionStats
|
|
45
|
+
)
|
|
46
|
+
from doc_utils.version_check import check_version_on_startup
|
|
47
|
+
from doc_utils.version import __version__
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Colors:
|
|
51
|
+
"""ANSI color codes for terminal output."""
|
|
52
|
+
RED = '\033[0;31m'
|
|
53
|
+
GREEN = '\033[0;32m'
|
|
54
|
+
YELLOW = '\033[1;33m'
|
|
55
|
+
CYAN = '\033[0;36m'
|
|
56
|
+
NC = '\033[0m' # No Color
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def print_colored(message: str, color: str = Colors.NC) -> None:
|
|
60
|
+
"""Print message with color."""
|
|
61
|
+
print(f"{color}{message}{Colors.NC}")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def aggregate_stats(stats_list: list) -> ConversionStats:
|
|
65
|
+
"""Aggregate statistics from multiple conversion results."""
|
|
66
|
+
total = ConversionStats()
|
|
67
|
+
for stats in stats_list:
|
|
68
|
+
total.imports_removed += stats.imports_removed
|
|
69
|
+
total.guide_blocks_converted += stats.guide_blocks_converted
|
|
70
|
+
total.closing_tags_removed += stats.closing_tags_removed
|
|
71
|
+
total.link_macros_converted += stats.link_macros_converted
|
|
72
|
+
total.kc_macros_converted += stats.kc_macros_converted
|
|
73
|
+
total.profile_blocks_handled += stats.profile_blocks_handled
|
|
74
|
+
total.noparse_blocks_handled += stats.noparse_blocks_handled
|
|
75
|
+
total.opts_macros_removed += stats.opts_macros_removed
|
|
76
|
+
total.features_macros_removed += stats.features_macros_removed
|
|
77
|
+
total.other_macros_removed += stats.other_macros_removed
|
|
78
|
+
total.directives_marked += stats.directives_marked
|
|
79
|
+
return total
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def format_stats_summary(stats: ConversionStats) -> str:
|
|
83
|
+
"""Format statistics as a summary string."""
|
|
84
|
+
parts = []
|
|
85
|
+
if stats.imports_removed > 0:
|
|
86
|
+
parts.append(f"{stats.imports_removed} import(s)")
|
|
87
|
+
if stats.guide_blocks_converted > 0:
|
|
88
|
+
parts.append(f"{stats.guide_blocks_converted} guide block(s)")
|
|
89
|
+
if stats.link_macros_converted > 0:
|
|
90
|
+
parts.append(f"{stats.link_macros_converted} link(s) -> xref")
|
|
91
|
+
if stats.kc_macros_converted > 0:
|
|
92
|
+
parts.append(f"{stats.kc_macros_converted} command(s) -> code")
|
|
93
|
+
if stats.profile_blocks_handled > 0:
|
|
94
|
+
parts.append(f"{stats.profile_blocks_handled} profile block(s)")
|
|
95
|
+
if stats.noparse_blocks_handled > 0:
|
|
96
|
+
parts.append(f"{stats.noparse_blocks_handled} noparse block(s)")
|
|
97
|
+
if stats.opts_macros_removed > 0:
|
|
98
|
+
parts.append(f"{stats.opts_macros_removed} opts macro(s)")
|
|
99
|
+
if stats.features_macros_removed > 0:
|
|
100
|
+
parts.append(f"{stats.features_macros_removed} features macro(s)")
|
|
101
|
+
if stats.directives_marked > 0:
|
|
102
|
+
parts.append(f"{stats.directives_marked} directive(s) marked")
|
|
103
|
+
if stats.other_macros_removed > 0:
|
|
104
|
+
parts.append(f"{stats.other_macros_removed} other macro(s)")
|
|
105
|
+
return ', '.join(parts) if parts else 'no changes'
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def main():
|
|
109
|
+
"""Main entry point."""
|
|
110
|
+
# Check for updates (non-blocking)
|
|
111
|
+
check_version_on_startup()
|
|
112
|
+
|
|
113
|
+
parser = argparse.ArgumentParser(
|
|
114
|
+
description="Convert FreeMarker-templated AsciiDoc to standard AsciiDoc",
|
|
115
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
116
|
+
epilog="""
|
|
117
|
+
Convert FreeMarker template markup to standard AsciiDoc:
|
|
118
|
+
|
|
119
|
+
STRUCTURE (always converted):
|
|
120
|
+
- Removes <#import ...> statements
|
|
121
|
+
- Converts <@tmpl.guide title="..." summary="..."> to = Title and summary
|
|
122
|
+
- Removes </@tmpl.guide> closing tags
|
|
123
|
+
|
|
124
|
+
INLINE MACROS (converted by default, skip with --structure-only):
|
|
125
|
+
- <@links.server id="hostname"/> -> xref:server/hostname.adoc[]
|
|
126
|
+
- <@kc.start parameters="--hostname x"/> -> code block with bin/kc.sh command
|
|
127
|
+
- <@profile.ifCommunity> blocks -> kept (or removed with --product)
|
|
128
|
+
- <@opts.*> macros -> removed (build-time generated)
|
|
129
|
+
|
|
130
|
+
This tool is designed for converting Keycloak documentation from FreeMarker
|
|
131
|
+
template format to standard AsciiDoc that can be used with other toolchains.
|
|
132
|
+
|
|
133
|
+
Examples:
|
|
134
|
+
%(prog)s # Process all .adoc files
|
|
135
|
+
%(prog)s docs/guides/ # Process specific directory
|
|
136
|
+
%(prog)s docs/guides/server/hostname.adoc # Process single file
|
|
137
|
+
%(prog)s --dry-run docs/guides/ # Preview changes
|
|
138
|
+
%(prog)s --structure-only docs/guides/ # Only convert structure
|
|
139
|
+
%(prog)s --product docs/guides/ # Keep product content
|
|
140
|
+
"""
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
parser.add_argument(
|
|
144
|
+
'path',
|
|
145
|
+
nargs='?',
|
|
146
|
+
default='.',
|
|
147
|
+
help='File or directory to process (default: current directory)'
|
|
148
|
+
)
|
|
149
|
+
parser.add_argument(
|
|
150
|
+
'-n', '--dry-run',
|
|
151
|
+
action='store_true',
|
|
152
|
+
help='Show what would be changed without modifying files'
|
|
153
|
+
)
|
|
154
|
+
parser.add_argument(
|
|
155
|
+
'-v', '--verbose',
|
|
156
|
+
action='store_true',
|
|
157
|
+
help='Show detailed output for each file'
|
|
158
|
+
)
|
|
159
|
+
parser.add_argument(
|
|
160
|
+
'--structure-only',
|
|
161
|
+
action='store_true',
|
|
162
|
+
help='Only convert structure (imports, guide blocks); leave inline macros'
|
|
163
|
+
)
|
|
164
|
+
parser.add_argument(
|
|
165
|
+
'--product',
|
|
166
|
+
action='store_true',
|
|
167
|
+
help='Keep product content in profile blocks (default: keep community)'
|
|
168
|
+
)
|
|
169
|
+
parser.add_argument(
|
|
170
|
+
'--base-path',
|
|
171
|
+
default='',
|
|
172
|
+
help='Base path prefix for xref links (e.g., "guides")'
|
|
173
|
+
)
|
|
174
|
+
parser.add_argument(
|
|
175
|
+
'--only-freemarker',
|
|
176
|
+
action='store_true',
|
|
177
|
+
help='Only process files that contain FreeMarker markup (faster for mixed repos)'
|
|
178
|
+
)
|
|
179
|
+
parser.add_argument(
|
|
180
|
+
'--version',
|
|
181
|
+
action='version',
|
|
182
|
+
version=f'%(prog)s {__version__}'
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
args = parser.parse_args()
|
|
186
|
+
|
|
187
|
+
# Convert path to Path object
|
|
188
|
+
target_path = Path(args.path)
|
|
189
|
+
|
|
190
|
+
# Check if path exists
|
|
191
|
+
if not target_path.exists():
|
|
192
|
+
print_colored(f"Error: Path does not exist: {target_path}", Colors.RED)
|
|
193
|
+
sys.exit(1)
|
|
194
|
+
|
|
195
|
+
# Display mode messages
|
|
196
|
+
if args.dry_run:
|
|
197
|
+
print_colored("DRY RUN MODE - No files will be modified", Colors.YELLOW)
|
|
198
|
+
print()
|
|
199
|
+
|
|
200
|
+
if args.structure_only:
|
|
201
|
+
print("Converting structure only (imports, guide blocks)")
|
|
202
|
+
print()
|
|
203
|
+
|
|
204
|
+
# Find all AsciiDoc files
|
|
205
|
+
adoc_files = find_adoc_files(target_path)
|
|
206
|
+
|
|
207
|
+
if not adoc_files:
|
|
208
|
+
if target_path.is_file():
|
|
209
|
+
print_colored(
|
|
210
|
+
f"Warning: {target_path} is not an AsciiDoc file (.adoc)",
|
|
211
|
+
Colors.YELLOW
|
|
212
|
+
)
|
|
213
|
+
print("No AsciiDoc files found.")
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
# Filter to only files with FreeMarker content if requested
|
|
217
|
+
if args.only_freemarker:
|
|
218
|
+
adoc_files = [f for f in adoc_files if has_freemarker_content(f)]
|
|
219
|
+
if not adoc_files:
|
|
220
|
+
print("No files with FreeMarker markup found.")
|
|
221
|
+
return
|
|
222
|
+
if args.verbose:
|
|
223
|
+
print(f"Found {len(adoc_files)} file(s) with FreeMarker markup")
|
|
224
|
+
print()
|
|
225
|
+
|
|
226
|
+
# Process each file
|
|
227
|
+
files_processed = 0
|
|
228
|
+
files_modified = 0
|
|
229
|
+
all_stats = []
|
|
230
|
+
|
|
231
|
+
for file_path in adoc_files:
|
|
232
|
+
try:
|
|
233
|
+
result = process_file(
|
|
234
|
+
file_path,
|
|
235
|
+
dry_run=args.dry_run,
|
|
236
|
+
verbose=args.verbose,
|
|
237
|
+
convert_all=not args.structure_only,
|
|
238
|
+
keep_community=not args.product,
|
|
239
|
+
base_path=args.base_path
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Print verbose messages
|
|
243
|
+
if args.verbose:
|
|
244
|
+
for msg in result.messages:
|
|
245
|
+
print(msg)
|
|
246
|
+
|
|
247
|
+
if result.changes_made:
|
|
248
|
+
files_modified += 1
|
|
249
|
+
all_stats.append(result.stats)
|
|
250
|
+
|
|
251
|
+
if args.dry_run:
|
|
252
|
+
print_colored(f"Would modify: {file_path}", Colors.YELLOW)
|
|
253
|
+
else:
|
|
254
|
+
print_colored(f"Modified: {file_path}", Colors.GREEN)
|
|
255
|
+
|
|
256
|
+
files_processed += 1
|
|
257
|
+
|
|
258
|
+
except KeyboardInterrupt:
|
|
259
|
+
print_colored("\nOperation cancelled by user", Colors.YELLOW)
|
|
260
|
+
sys.exit(1)
|
|
261
|
+
except IOError as e:
|
|
262
|
+
print_colored(f"{e}", Colors.RED)
|
|
263
|
+
except Exception as e:
|
|
264
|
+
print_colored(f"Unexpected error processing {file_path}: {e}", Colors.RED)
|
|
265
|
+
|
|
266
|
+
# Print summary
|
|
267
|
+
print()
|
|
268
|
+
print(f"Processed {files_processed} AsciiDoc file(s)")
|
|
269
|
+
|
|
270
|
+
if files_modified > 0:
|
|
271
|
+
if args.dry_run:
|
|
272
|
+
print(f"Would modify {files_modified} file(s)")
|
|
273
|
+
else:
|
|
274
|
+
print(f"Modified {files_modified} file(s)")
|
|
275
|
+
|
|
276
|
+
# Detailed stats
|
|
277
|
+
total_stats = aggregate_stats(all_stats)
|
|
278
|
+
summary = format_stats_summary(total_stats)
|
|
279
|
+
print(f" ({summary})")
|
|
280
|
+
else:
|
|
281
|
+
print("No files needed conversion.")
|
|
282
|
+
|
|
283
|
+
print()
|
|
284
|
+
print_colored("FreeMarker to AsciiDoc conversion complete!", Colors.CYAN)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
if __name__ == "__main__":
|
|
288
|
+
main()
|