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,134 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
format-asciidoc-spacing - Format AsciiDoc spacing.
|
|
4
|
+
|
|
5
|
+
Ensures blank lines after headings and around include directives.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from doc_utils.format_asciidoc_spacing import process_file, find_adoc_files
|
|
13
|
+
from doc_utils.version_check import check_version_on_startup
|
|
14
|
+
from doc_utils.version import __version__
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
from doc_utils.spinner import Spinner
|
|
18
|
+
# Colors for output
|
|
19
|
+
class Colors:
|
|
20
|
+
RED = '\033[0;31m'
|
|
21
|
+
GREEN = '\033[0;32m'
|
|
22
|
+
YELLOW = '\033[1;33m'
|
|
23
|
+
NC = '\033[0m' # No Color
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def print_colored(message: str, color: str = Colors.NC) -> None:
|
|
27
|
+
"""Print message with color"""
|
|
28
|
+
print(f"{color}{message}{Colors.NC}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main():
|
|
32
|
+
# Check for updates (non-blocking, won't interfere with tool operation)
|
|
33
|
+
check_version_on_startup()
|
|
34
|
+
"""Main entry point"""
|
|
35
|
+
parser = argparse.ArgumentParser(
|
|
36
|
+
description="Format AsciiDoc files to ensure proper spacing",
|
|
37
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
38
|
+
epilog="""
|
|
39
|
+
Format AsciiDoc files to ensure proper spacing:
|
|
40
|
+
- Blank line after headings (=, ==, ===, etc.)
|
|
41
|
+
- Blank lines around include:: directives
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
%(prog)s # Process all .adoc files in current directory
|
|
45
|
+
%(prog)s modules/ # Process all .adoc files in modules/
|
|
46
|
+
%(prog)s assemblies/my-guide.adoc # Process single file
|
|
47
|
+
%(prog)s --dry-run modules/ # Preview changes without modifying
|
|
48
|
+
"""
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
'path',
|
|
53
|
+
nargs='?',
|
|
54
|
+
default='.',
|
|
55
|
+
help='File or directory to process (default: current directory)'
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
'-n', '--dry-run',
|
|
59
|
+
action='store_true',
|
|
60
|
+
help='Show what would be changed without modifying files'
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
'-v', '--verbose',
|
|
64
|
+
action='store_true',
|
|
65
|
+
help='Show detailed output'
|
|
66
|
+
)
|
|
67
|
+
parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
|
|
68
|
+
|
|
69
|
+
args = parser.parse_args()
|
|
70
|
+
|
|
71
|
+
# Convert path to Path object
|
|
72
|
+
target_path = Path(args.path)
|
|
73
|
+
|
|
74
|
+
# Check if path exists
|
|
75
|
+
if not target_path.exists():
|
|
76
|
+
print_colored(f"Error: Path does not exist: {target_path}", Colors.RED)
|
|
77
|
+
sys.exit(1)
|
|
78
|
+
|
|
79
|
+
# Display dry-run mode message
|
|
80
|
+
if args.dry_run:
|
|
81
|
+
print_colored("DRY RUN MODE - No files will be modified", Colors.YELLOW)
|
|
82
|
+
|
|
83
|
+
# Find all AsciiDoc files
|
|
84
|
+
adoc_files = find_adoc_files(target_path)
|
|
85
|
+
|
|
86
|
+
if not adoc_files:
|
|
87
|
+
if target_path.is_file():
|
|
88
|
+
print_colored(f"Warning: {target_path} is not an AsciiDoc file (.adoc)", Colors.YELLOW)
|
|
89
|
+
print(f"Processed 0 AsciiDoc file(s)")
|
|
90
|
+
print("AsciiDoc spacing formatting complete!")
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
# Process each file
|
|
94
|
+
files_processed = 0
|
|
95
|
+
files_modified = 0
|
|
96
|
+
|
|
97
|
+
for file_path in adoc_files:
|
|
98
|
+
try:
|
|
99
|
+
changes_made, messages = process_file(file_path, args.dry_run, args.verbose)
|
|
100
|
+
|
|
101
|
+
# Print verbose messages
|
|
102
|
+
if args.verbose:
|
|
103
|
+
for msg in messages:
|
|
104
|
+
print(msg)
|
|
105
|
+
|
|
106
|
+
if changes_made:
|
|
107
|
+
files_modified += 1
|
|
108
|
+
if args.dry_run:
|
|
109
|
+
print_colored(f"Would modify: {file_path}", Colors.YELLOW)
|
|
110
|
+
else:
|
|
111
|
+
print_colored(f"Modified: {file_path}", Colors.GREEN)
|
|
112
|
+
elif args.verbose:
|
|
113
|
+
print(f" No changes needed for: {file_path}")
|
|
114
|
+
|
|
115
|
+
files_processed += 1
|
|
116
|
+
|
|
117
|
+
except KeyboardInterrupt:
|
|
118
|
+
print_colored("\nOperation cancelled by user", Colors.YELLOW)
|
|
119
|
+
sys.exit(1)
|
|
120
|
+
except IOError as e:
|
|
121
|
+
print_colored(f"{e}", Colors.RED)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print_colored(f"Unexpected error processing {file_path}: {e}", Colors.RED)
|
|
124
|
+
|
|
125
|
+
print(f"Processed {files_processed} AsciiDoc file(s)")
|
|
126
|
+
if args.dry_run and files_modified > 0:
|
|
127
|
+
print(f"Would modify {files_modified} file(s)")
|
|
128
|
+
elif files_modified > 0:
|
|
129
|
+
print(f"Modified {files_modified} file(s)")
|
|
130
|
+
print("AsciiDoc spacing formatting complete!")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
main()
|
insert_abstract_role.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
insert-abstract-role - Insert [role="_abstract"] above the first paragraph after the title.
|
|
4
|
+
|
|
5
|
+
Ensures AsciiDoc files have the [role="_abstract"] attribute required for DITA short description
|
|
6
|
+
conversion, as enforced by the AsciiDocDITA.ShortDescription vale rule.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from doc_utils.insert_abstract_role import process_file, find_adoc_files
|
|
14
|
+
from doc_utils.version_check import check_version_on_startup
|
|
15
|
+
from doc_utils.version import __version__
|
|
16
|
+
from doc_utils.file_utils import parse_exclude_list_file
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Colors for output
|
|
20
|
+
class Colors:
|
|
21
|
+
RED = '\033[0;31m'
|
|
22
|
+
GREEN = '\033[0;32m'
|
|
23
|
+
YELLOW = '\033[1;33m'
|
|
24
|
+
NC = '\033[0m' # No Color
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def print_colored(message: str, color: str = Colors.NC) -> None:
|
|
28
|
+
"""Print message with color"""
|
|
29
|
+
print(f"{color}{message}{Colors.NC}")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main():
|
|
33
|
+
# Check for updates (non-blocking, won't interfere with tool operation)
|
|
34
|
+
check_version_on_startup()
|
|
35
|
+
|
|
36
|
+
parser = argparse.ArgumentParser(
|
|
37
|
+
description="Insert [role=\"_abstract\"] above the first paragraph after the document title",
|
|
38
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
39
|
+
epilog="""
|
|
40
|
+
Insert [role="_abstract"] above the first paragraph after the document title in AsciiDoc files.
|
|
41
|
+
This attribute is required for DITA short description conversion.
|
|
42
|
+
|
|
43
|
+
The tool identifies the first paragraph after a level 1 heading (= Title) and inserts
|
|
44
|
+
the [role="_abstract"] attribute on the line immediately before it.
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
%(prog)s # Process all .adoc files in current directory
|
|
48
|
+
%(prog)s modules/ # Process all .adoc files in modules/
|
|
49
|
+
%(prog)s modules/rn/my-release-note.adoc # Process single file
|
|
50
|
+
%(prog)s --dry-run modules/ # Preview changes without modifying
|
|
51
|
+
%(prog)s --exclude-dir .archive modules/ # Exclude .archive directories
|
|
52
|
+
"""
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
'path',
|
|
57
|
+
nargs='?',
|
|
58
|
+
default='.',
|
|
59
|
+
help='File or directory to process (default: current directory)'
|
|
60
|
+
)
|
|
61
|
+
parser.add_argument(
|
|
62
|
+
'-n', '--dry-run',
|
|
63
|
+
action='store_true',
|
|
64
|
+
help='Show what would be changed without modifying files'
|
|
65
|
+
)
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
'-v', '--verbose',
|
|
68
|
+
action='store_true',
|
|
69
|
+
help='Show detailed output'
|
|
70
|
+
)
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
'--exclude-dir',
|
|
73
|
+
action='append',
|
|
74
|
+
default=[],
|
|
75
|
+
help='Directory to exclude (can be specified multiple times)'
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument(
|
|
78
|
+
'--exclude-file',
|
|
79
|
+
action='append',
|
|
80
|
+
default=[],
|
|
81
|
+
help='File to exclude (can be specified multiple times)'
|
|
82
|
+
)
|
|
83
|
+
parser.add_argument(
|
|
84
|
+
'--exclude-list',
|
|
85
|
+
help='Path to file containing list of files/directories to exclude (one per line)'
|
|
86
|
+
)
|
|
87
|
+
parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
|
|
88
|
+
|
|
89
|
+
args = parser.parse_args()
|
|
90
|
+
|
|
91
|
+
# Convert path to Path object
|
|
92
|
+
target_path = Path(args.path)
|
|
93
|
+
|
|
94
|
+
# Check if path exists
|
|
95
|
+
if not target_path.exists():
|
|
96
|
+
print_colored(f"Error: Path does not exist: {target_path}", Colors.RED)
|
|
97
|
+
sys.exit(1)
|
|
98
|
+
|
|
99
|
+
# Parse exclusion list file if provided
|
|
100
|
+
exclude_dirs = list(args.exclude_dir)
|
|
101
|
+
exclude_files = list(args.exclude_file)
|
|
102
|
+
|
|
103
|
+
if args.exclude_list:
|
|
104
|
+
list_dirs, list_files = parse_exclude_list_file(args.exclude_list)
|
|
105
|
+
exclude_dirs.extend(list_dirs)
|
|
106
|
+
exclude_files.extend(list_files)
|
|
107
|
+
|
|
108
|
+
# Display dry-run mode message
|
|
109
|
+
if args.dry_run:
|
|
110
|
+
print_colored("DRY RUN MODE - No files will be modified", Colors.YELLOW)
|
|
111
|
+
|
|
112
|
+
# Find all AsciiDoc files
|
|
113
|
+
adoc_files = find_adoc_files(target_path, exclude_dirs, exclude_files)
|
|
114
|
+
|
|
115
|
+
if not adoc_files:
|
|
116
|
+
if target_path.is_file():
|
|
117
|
+
print_colored(f"Warning: {target_path} is not an AsciiDoc file (.adoc)", Colors.YELLOW)
|
|
118
|
+
print(f"Processed 0 AsciiDoc file(s)")
|
|
119
|
+
print("Insert abstract role complete!")
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
# Process each file
|
|
123
|
+
files_processed = 0
|
|
124
|
+
files_modified = 0
|
|
125
|
+
|
|
126
|
+
for file_path in adoc_files:
|
|
127
|
+
try:
|
|
128
|
+
changes_made, messages = process_file(file_path, args.dry_run, args.verbose)
|
|
129
|
+
|
|
130
|
+
# Print verbose messages
|
|
131
|
+
if args.verbose:
|
|
132
|
+
for msg in messages:
|
|
133
|
+
print(msg)
|
|
134
|
+
|
|
135
|
+
if changes_made:
|
|
136
|
+
files_modified += 1
|
|
137
|
+
if args.dry_run:
|
|
138
|
+
print_colored(f"Would modify: {file_path}", Colors.YELLOW)
|
|
139
|
+
else:
|
|
140
|
+
print_colored(f"Modified: {file_path}", Colors.GREEN)
|
|
141
|
+
elif args.verbose:
|
|
142
|
+
print(f" No changes needed for: {file_path}")
|
|
143
|
+
|
|
144
|
+
files_processed += 1
|
|
145
|
+
|
|
146
|
+
except KeyboardInterrupt:
|
|
147
|
+
print_colored("\nOperation cancelled by user", Colors.YELLOW)
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
except IOError as e:
|
|
150
|
+
print_colored(f"{e}", Colors.RED)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
print_colored(f"Unexpected error processing {file_path}: {e}", Colors.RED)
|
|
153
|
+
|
|
154
|
+
print(f"Processed {files_processed} AsciiDoc file(s)")
|
|
155
|
+
if args.dry_run and files_modified > 0:
|
|
156
|
+
print(f"Would modify {files_modified} file(s)")
|
|
157
|
+
elif files_modified > 0:
|
|
158
|
+
print(f"Modified {files_modified} file(s)")
|
|
159
|
+
print("Insert abstract role complete!")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
main()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
CLI tool to create an inventory of AsciiDoc conditional directives.
|
|
4
|
+
|
|
5
|
+
Scans .adoc files for ifdef, ifndef, endif, and ifeval directives
|
|
6
|
+
and creates a timestamped inventory file.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
inventory-conditionals [directory] [-o OUTPUT_DIR]
|
|
10
|
+
|
|
11
|
+
If no directory is specified, the current working directory is used.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from doc_utils.inventory_conditionals import create_inventory
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main():
|
|
21
|
+
parser = argparse.ArgumentParser(
|
|
22
|
+
description='Create an inventory of AsciiDoc conditional directives.'
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
'directory',
|
|
26
|
+
nargs='?',
|
|
27
|
+
default='.',
|
|
28
|
+
help='Directory to scan for .adoc files (default: current directory)'
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
'-o', '--output-dir',
|
|
32
|
+
default=None,
|
|
33
|
+
help='Directory to write the inventory file (default: current directory)'
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
args = parser.parse_args()
|
|
37
|
+
|
|
38
|
+
directory = Path(args.directory).resolve()
|
|
39
|
+
if not directory.is_dir():
|
|
40
|
+
print(f"Error: {directory} is not a valid directory")
|
|
41
|
+
return 1
|
|
42
|
+
|
|
43
|
+
output_dir = Path(args.output_dir).resolve() if args.output_dir else Path.cwd()
|
|
44
|
+
|
|
45
|
+
print(f"Scanning for .adoc files in: {directory}")
|
|
46
|
+
output_file = create_inventory(directory, output_dir)
|
|
47
|
+
print(f"Inventory written to: {output_file}")
|
|
48
|
+
|
|
49
|
+
return 0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == '__main__':
|
|
53
|
+
exit(main())
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
replace-link-attributes - Replace AsciiDoc attributes within link URLs with their actual values.
|
|
4
|
+
|
|
5
|
+
This script finds and replaces attribute references (like {attribute-name}) that appear
|
|
6
|
+
in the URL portion of AsciiDoc link macros (link: and xref:) with their resolved values
|
|
7
|
+
from attributes.adoc. Link text is preserved unchanged.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from doc_utils.replace_link_attributes import (
|
|
16
|
+
find_attributes_files,
|
|
17
|
+
load_attributes,
|
|
18
|
+
resolve_nested_attributes,
|
|
19
|
+
replace_link_attributes_in_file,
|
|
20
|
+
find_adoc_files
|
|
21
|
+
)
|
|
22
|
+
from doc_utils.version_check import check_version_on_startup
|
|
23
|
+
from doc_utils.version import __version__
|
|
24
|
+
from doc_utils.spinner import Spinner
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def prompt_for_attributes_file(attributes_files: list[Path]) -> Optional[Path]:
|
|
28
|
+
"""Prompt user to select or specify attributes file."""
|
|
29
|
+
if not attributes_files:
|
|
30
|
+
print("No attributes.adoc files found in the repository.")
|
|
31
|
+
response = input("Enter the path to your attributes.adoc file (or 'q' to quit): ").strip()
|
|
32
|
+
if response.lower() == 'q':
|
|
33
|
+
return None
|
|
34
|
+
path = Path(response)
|
|
35
|
+
if path.exists() and path.is_file():
|
|
36
|
+
return path
|
|
37
|
+
else:
|
|
38
|
+
print(f"Error: File not found: {response}")
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
if len(attributes_files) == 1:
|
|
42
|
+
file_path = attributes_files[0]
|
|
43
|
+
response = input(f"Found attributes file: {file_path}\nUse this file? (y/n/q): ").strip().lower()
|
|
44
|
+
if response == 'y':
|
|
45
|
+
return file_path
|
|
46
|
+
elif response == 'q':
|
|
47
|
+
return None
|
|
48
|
+
else:
|
|
49
|
+
response = input("Enter the path to your attributes.adoc file (or 'q' to quit): ").strip()
|
|
50
|
+
if response.lower() == 'q':
|
|
51
|
+
return None
|
|
52
|
+
path = Path(response)
|
|
53
|
+
if path.exists() and path.is_file():
|
|
54
|
+
return path
|
|
55
|
+
else:
|
|
56
|
+
print(f"Error: File not found: {response}")
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
# Multiple files found
|
|
60
|
+
print("\nFound multiple attributes.adoc files:")
|
|
61
|
+
for i, file_path in enumerate(attributes_files, 1):
|
|
62
|
+
print(f" {i}. {file_path}")
|
|
63
|
+
print(f" {len(attributes_files) + 1}. Enter custom path")
|
|
64
|
+
|
|
65
|
+
while True:
|
|
66
|
+
response = input(f"\nSelect option (1-{len(attributes_files) + 1}) or 'q' to quit: ").strip()
|
|
67
|
+
if response.lower() == 'q':
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
choice = int(response)
|
|
72
|
+
if 1 <= choice <= len(attributes_files):
|
|
73
|
+
return attributes_files[choice - 1]
|
|
74
|
+
elif choice == len(attributes_files) + 1:
|
|
75
|
+
response = input("Enter the path to your attributes.adoc file: ").strip()
|
|
76
|
+
path = Path(response)
|
|
77
|
+
if path.exists() and path.is_file():
|
|
78
|
+
return path
|
|
79
|
+
else:
|
|
80
|
+
print(f"Error: File not found: {response}")
|
|
81
|
+
else:
|
|
82
|
+
print(f"Invalid choice. Please enter a number between 1 and {len(attributes_files) + 1}")
|
|
83
|
+
except ValueError:
|
|
84
|
+
print("Invalid input. Please enter a number.")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def main():
|
|
88
|
+
# Check for updates (non-blocking, won't interfere with tool operation)
|
|
89
|
+
check_version_on_startup()
|
|
90
|
+
parser = argparse.ArgumentParser(
|
|
91
|
+
description='Replace AsciiDoc attributes within link macros with their actual values.'
|
|
92
|
+
)
|
|
93
|
+
parser.add_argument(
|
|
94
|
+
'--dry-run', '-n',
|
|
95
|
+
action='store_true',
|
|
96
|
+
help='Show what would be changed without making actual modifications'
|
|
97
|
+
)
|
|
98
|
+
parser.add_argument(
|
|
99
|
+
'--path', '-p',
|
|
100
|
+
type=str,
|
|
101
|
+
default='.',
|
|
102
|
+
help='Repository path to search (default: current directory)'
|
|
103
|
+
)
|
|
104
|
+
parser.add_argument(
|
|
105
|
+
'--attributes-file', '-a',
|
|
106
|
+
type=str,
|
|
107
|
+
help='Path to attributes.adoc file (skips interactive selection)'
|
|
108
|
+
)
|
|
109
|
+
parser.add_argument(
|
|
110
|
+
'--macro-type',
|
|
111
|
+
choices=['link', 'xref', 'both'],
|
|
112
|
+
default='both',
|
|
113
|
+
help='Type of macros to process: link, xref, or both (default: both)'
|
|
114
|
+
)
|
|
115
|
+
parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
|
|
116
|
+
|
|
117
|
+
args = parser.parse_args()
|
|
118
|
+
|
|
119
|
+
# Determine repository root
|
|
120
|
+
repo_root = Path(args.path).resolve()
|
|
121
|
+
|
|
122
|
+
if not repo_root.exists() or not repo_root.is_dir():
|
|
123
|
+
print(f"Error: Directory not found: {repo_root}")
|
|
124
|
+
sys.exit(1)
|
|
125
|
+
|
|
126
|
+
print(f"{'DRY RUN MODE - ' if args.dry_run else ''}Searching in: {repo_root}")
|
|
127
|
+
|
|
128
|
+
# Find or get attributes file
|
|
129
|
+
if args.attributes_file:
|
|
130
|
+
attributes_file = Path(args.attributes_file)
|
|
131
|
+
if not attributes_file.exists():
|
|
132
|
+
print(f"Error: Specified attributes file not found: {attributes_file}")
|
|
133
|
+
sys.exit(1)
|
|
134
|
+
else:
|
|
135
|
+
spinner = Spinner("Searching for attributes.adoc files")
|
|
136
|
+
spinner.start()
|
|
137
|
+
attributes_files = find_attributes_files(repo_root)
|
|
138
|
+
spinner.stop()
|
|
139
|
+
attributes_file = prompt_for_attributes_file(attributes_files)
|
|
140
|
+
|
|
141
|
+
if not attributes_file:
|
|
142
|
+
print("Operation cancelled.")
|
|
143
|
+
sys.exit(0)
|
|
144
|
+
|
|
145
|
+
spinner = Spinner(f"Loading attributes from {attributes_file.name}")
|
|
146
|
+
spinner.start()
|
|
147
|
+
attributes = load_attributes(attributes_file)
|
|
148
|
+
|
|
149
|
+
if not attributes:
|
|
150
|
+
spinner.stop("No attributes found in the file", success=False)
|
|
151
|
+
sys.exit(1)
|
|
152
|
+
|
|
153
|
+
# Resolve nested references
|
|
154
|
+
attributes = resolve_nested_attributes(attributes)
|
|
155
|
+
spinner.stop(f"Loaded and resolved {len(attributes)} attributes")
|
|
156
|
+
|
|
157
|
+
# Find all AsciiDoc files
|
|
158
|
+
spinner = Spinner(f"Searching for .adoc files in {repo_root}")
|
|
159
|
+
spinner.start()
|
|
160
|
+
adoc_files = find_adoc_files(repo_root)
|
|
161
|
+
spinner.stop()
|
|
162
|
+
|
|
163
|
+
# Find ALL attributes files to exclude from processing
|
|
164
|
+
all_attribute_files = find_attributes_files(repo_root)
|
|
165
|
+
exclude_paths = {f.resolve() for f in all_attribute_files}
|
|
166
|
+
|
|
167
|
+
# Notify user about excluded files if there are multiple
|
|
168
|
+
if len(all_attribute_files) > 1:
|
|
169
|
+
print(f"Excluding {len(all_attribute_files)} attributes files from processing:")
|
|
170
|
+
for f in all_attribute_files:
|
|
171
|
+
print(f" - {f.relative_to(repo_root)}")
|
|
172
|
+
|
|
173
|
+
# Exclude ALL attributes files, not just the selected one
|
|
174
|
+
adoc_files = [f for f in adoc_files if f.resolve() not in exclude_paths]
|
|
175
|
+
|
|
176
|
+
print(f"Found {len(adoc_files)} AsciiDoc files to process")
|
|
177
|
+
|
|
178
|
+
if args.dry_run:
|
|
179
|
+
print("\n*** DRY RUN MODE - No files will be modified ***\n")
|
|
180
|
+
|
|
181
|
+
# Process each file
|
|
182
|
+
total_replacements = 0
|
|
183
|
+
files_modified = 0
|
|
184
|
+
|
|
185
|
+
spinner = Spinner(f"Processing {len(adoc_files)} files")
|
|
186
|
+
spinner.start()
|
|
187
|
+
|
|
188
|
+
for file_path in adoc_files:
|
|
189
|
+
replacements = replace_link_attributes_in_file(file_path, attributes, args.dry_run, args.macro_type)
|
|
190
|
+
if replacements > 0:
|
|
191
|
+
rel_path = file_path.relative_to(repo_root)
|
|
192
|
+
total_replacements += replacements
|
|
193
|
+
files_modified += 1
|
|
194
|
+
|
|
195
|
+
spinner.stop(f"Processed {len(adoc_files)} files")
|
|
196
|
+
|
|
197
|
+
# Summary
|
|
198
|
+
print(f"\nSummary:")
|
|
199
|
+
if args.dry_run:
|
|
200
|
+
print(f" Would modify {files_modified} files")
|
|
201
|
+
print(f" Would make {total_replacements} replacements")
|
|
202
|
+
print("\nRun without --dry-run to apply changes.")
|
|
203
|
+
else:
|
|
204
|
+
print(f" Total files modified: {files_modified}")
|
|
205
|
+
print(f" Total replacements: {total_replacements}")
|
|
206
|
+
|
|
207
|
+
if total_replacements == 0:
|
|
208
|
+
print("\nNo attribute references found within link macros.")
|
|
209
|
+
else:
|
|
210
|
+
print("\nReplacement complete!")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
if __name__ == '__main__':
|
|
214
|
+
main()
|