rolfedh-doc-utils 0.1.13__py3-none-any.whl → 0.1.15__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.
@@ -85,10 +85,14 @@ def load_existing_attributes(file_path: str) -> Dict[str, str]:
85
85
  return attributes
86
86
 
87
87
 
88
- def find_link_macros(file_path: str) -> List[Tuple[str, str, str, int]]:
88
+ def find_link_macros(file_path: str, macro_type: str = 'both') -> List[Tuple[str, str, str, int]]:
89
89
  """
90
90
  Find all link: and xref: macros containing attributes in their URLs.
91
91
 
92
+ Args:
93
+ file_path: Path to the file to scan
94
+ macro_type: Type of macros to find - 'link', 'xref', or 'both' (default: 'both')
95
+
92
96
  Returns list of tuples: (full_macro, url, link_text, line_number)
93
97
  """
94
98
  macros = []
@@ -97,10 +101,13 @@ def find_link_macros(file_path: str) -> List[Tuple[str, str, str, int]]:
97
101
  for line_num, line in enumerate(f, 1):
98
102
  # Pattern to match link: and xref: macros
99
103
  # Matches: (link|xref):url[text] where url contains {attribute}
100
- patterns = [
101
- r'(link:([^[\]]*\{[^}]+\}[^[\]]*)\[([^\]]*)\])',
102
- r'(xref:([^[\]]*\{[^}]+\}[^[\]]*)\[([^\]]*)\])'
103
- ]
104
+ patterns = []
105
+
106
+ if macro_type in ('link', 'both'):
107
+ patterns.append(r'(link:([^[\]]*\{[^}]+\}[^[\]]*)\[([^\]]*)\])')
108
+
109
+ if macro_type in ('xref', 'both'):
110
+ patterns.append(r'(xref:([^[\]]*\{[^}]+\}[^[\]]*)\[([^\]]*)\])')
104
111
 
105
112
  for pattern in patterns:
106
113
  for match in re.finditer(pattern, line, re.IGNORECASE):
@@ -228,10 +235,15 @@ def select_link_text(url: str, variations: List[Tuple[str, str, str, int]], inte
228
235
  return most_common[0]
229
236
 
230
237
 
231
- def collect_all_macros(scan_dirs: List[str] = None) -> List[Tuple[str, str, str, str, int]]:
238
+ def collect_all_macros(scan_dirs: List[str] = None, macro_type: str = 'both', exclude_files: List[str] = None) -> List[Tuple[str, str, str, str, int]]:
232
239
  """
233
240
  Collect all link/xref macros with attributes from all .adoc files.
234
241
 
242
+ Args:
243
+ scan_dirs: Directories to scan (default: current directory)
244
+ macro_type: Type of macros to find - 'link', 'xref', or 'both' (default: 'both')
245
+ exclude_files: List of file paths to exclude from scanning (typically all attributes files)
246
+
235
247
  Returns: List[(file_path, full_macro, url, link_text, line_number)]
236
248
  """
237
249
  if scan_dirs is None:
@@ -239,6 +251,13 @@ def collect_all_macros(scan_dirs: List[str] = None) -> List[Tuple[str, str, str,
239
251
 
240
252
  all_macros = []
241
253
 
254
+ # Normalize all exclude file paths
255
+ exclude_paths = set()
256
+ if exclude_files:
257
+ for file in exclude_files:
258
+ if file: # Check for None or empty string
259
+ exclude_paths.add(os.path.abspath(file))
260
+
242
261
  for scan_dir in scan_dirs:
243
262
  for root, _, files in os.walk(scan_dir):
244
263
  # Skip hidden directories and .archive
@@ -248,7 +267,10 @@ def collect_all_macros(scan_dirs: List[str] = None) -> List[Tuple[str, str, str,
248
267
  for file in files:
249
268
  if file.endswith('.adoc'):
250
269
  file_path = os.path.join(root, file)
251
- macros = find_link_macros(file_path)
270
+ # Skip any attributes files to prevent self-referencing
271
+ if exclude_paths and os.path.abspath(file_path) in exclude_paths:
272
+ continue
273
+ macros = find_link_macros(file_path, macro_type)
252
274
  for full_macro, url, link_text, line_num in macros:
253
275
  all_macros.append((file_path, full_macro, url, link_text, line_num))
254
276
 
@@ -452,10 +474,20 @@ def extract_link_attributes(attributes_file: str = None,
452
474
  interactive: bool = True,
453
475
  dry_run: bool = False,
454
476
  validate_links: bool = False,
455
- fail_on_broken: bool = False) -> bool:
477
+ fail_on_broken: bool = False,
478
+ macro_type: str = 'both') -> bool:
456
479
  """
457
480
  Main function to extract link attributes.
458
481
 
482
+ Args:
483
+ attributes_file: Path to attributes file
484
+ scan_dirs: Directories to scan
485
+ interactive: Enable interactive mode
486
+ dry_run: Preview changes without modifying files
487
+ validate_links: Validate URLs before extraction
488
+ fail_on_broken: Exit if broken links found
489
+ macro_type: Type of macros to process - 'link', 'xref', or 'both' (default: 'both')
490
+
459
491
  Returns: True if successful, False otherwise
460
492
  """
461
493
  # Find or confirm attributes file
@@ -489,17 +521,27 @@ def extract_link_attributes(attributes_file: str = None,
489
521
  existing_attrs = load_existing_attributes(attributes_file)
490
522
  spinner.stop(f"Loaded {len(existing_attrs)} existing attributes")
491
523
 
492
- # Collect all macros
493
- spinner = Spinner("Scanning for link and xref macros with attributes")
524
+ # Find all attributes files to exclude from processing
525
+ all_attribute_files = find_attribute_files()
526
+
527
+ # Notify user about excluded files if there are multiple
528
+ if len(all_attribute_files) > 1:
529
+ print(f"Excluding {len(all_attribute_files)} attributes files from processing:")
530
+ for f in all_attribute_files:
531
+ print(f" - {f}")
532
+
533
+ # Collect all macros, excluding ALL attributes files
534
+ macro_desc = {'link': 'link', 'xref': 'xref', 'both': 'link and xref'}[macro_type]
535
+ spinner = Spinner(f"Scanning for {macro_desc} macros with attributes")
494
536
  spinner.start()
495
- all_macros = collect_all_macros(scan_dirs)
537
+ all_macros = collect_all_macros(scan_dirs, macro_type, exclude_files=all_attribute_files)
496
538
  spinner.stop()
497
539
 
498
540
  if not all_macros:
499
- print("No link or xref macros with attributes found.")
541
+ print(f"No {macro_desc} macros with attributes found.")
500
542
  return True
501
543
 
502
- print(f"Found {len(all_macros)} link/xref macros with attributes")
544
+ print(f"Found {len(all_macros)} {macro_desc} macros with attributes")
503
545
 
504
546
  # Group by URL
505
547
  spinner = Spinner("Grouping macros by URL")
@@ -65,8 +65,18 @@ def resolve_nested_attributes(attributes: Dict[str, str], max_iterations: int =
65
65
  return attributes
66
66
 
67
67
 
68
- def replace_link_attributes_in_file(file_path: Path, attributes: Dict[str, str], dry_run: bool = False) -> int:
69
- """Replace attribute references within link macros in a single file."""
68
+ def replace_link_attributes_in_file(file_path: Path, attributes: Dict[str, str], dry_run: bool = False, macro_type: str = 'both') -> int:
69
+ """
70
+ Replace attribute references within link macros in a single file.
71
+
72
+ Args:
73
+ file_path: Path to the file to process
74
+ attributes: Dictionary of attribute definitions
75
+ dry_run: Preview changes without modifying files
76
+ macro_type: Type of macros to process - 'link', 'xref', or 'both' (default: 'both')
77
+
78
+ Returns: Number of replacements made
79
+ """
70
80
  with open(file_path, 'r', encoding='utf-8') as f:
71
81
  content = f.read()
72
82
 
@@ -75,14 +85,23 @@ def replace_link_attributes_in_file(file_path: Path, attributes: Dict[str, str],
75
85
 
76
86
  # Find all link macros containing attributes in the URL portion only
77
87
  # Match link: and xref: macros, capturing URL and text separately
78
- link_patterns = [
88
+ link_patterns = []
89
+
90
+ if macro_type in ('link', 'both'):
79
91
  # link:url[text] - replace only in URL portion
80
- (r'link:([^[\]]*)\[([^\]]*)\]', 'link'),
92
+ link_patterns.append((r'link:([^[\]]*)\[([^\]]*)\]', 'link'))
93
+
94
+ if macro_type in ('xref', 'both'):
81
95
  # xref:target[text] - replace only in target portion
82
- (r'xref:([^[\]]*)\[([^\]]*)\]', 'xref'),
83
- # link:url[] or xref:target[] - replace in URL/target portion
84
- (r'(link|xref):([^[\]]*)\[\]', 'empty_text')
85
- ]
96
+ link_patterns.append((r'xref:([^[\]]*)\[([^\]]*)\]', 'xref'))
97
+
98
+ # Handle empty text cases based on macro type
99
+ if macro_type == 'both':
100
+ link_patterns.append((r'(link|xref):([^[\]]*)\[\]', 'empty_text'))
101
+ elif macro_type == 'link':
102
+ link_patterns.append((r'(link):([^[\]]*)\[\]', 'empty_text'))
103
+ elif macro_type == 'xref':
104
+ link_patterns.append((r'(xref):([^[\]]*)\[\]', 'empty_text'))
86
105
 
87
106
  for pattern, link_type in link_patterns:
88
107
  matches = list(re.finditer(pattern, content))
@@ -77,6 +77,13 @@ Examples:
77
77
  help='Exit extraction if broken links are found in attributes (requires --validate-links)'
78
78
  )
79
79
 
80
+ parser.add_argument(
81
+ '--macro-type',
82
+ choices=['link', 'xref', 'both'],
83
+ default='both',
84
+ help='Type of macros to process: link, xref, or both (default: both)'
85
+ )
86
+
80
87
  args = parser.parse_args()
81
88
 
82
89
  try:
@@ -86,7 +93,8 @@ Examples:
86
93
  interactive=not args.non_interactive,
87
94
  dry_run=args.dry_run,
88
95
  validate_links=args.validate_links,
89
- fail_on_broken=args.fail_on_broken
96
+ fail_on_broken=args.fail_on_broken,
97
+ macro_type=args.macro_type
90
98
  )
91
99
 
92
100
  if not success:
@@ -102,6 +102,12 @@ def main():
102
102
  type=str,
103
103
  help='Path to attributes.adoc file (skips interactive selection)'
104
104
  )
105
+ parser.add_argument(
106
+ '--macro-type',
107
+ choices=['link', 'xref', 'both'],
108
+ default='both',
109
+ help='Type of macros to process: link, xref, or both (default: both)'
110
+ )
105
111
 
106
112
  args = parser.parse_args()
107
113
 
@@ -149,8 +155,18 @@ def main():
149
155
  adoc_files = find_adoc_files(repo_root)
150
156
  spinner.stop()
151
157
 
152
- # Exclude the attributes file itself
153
- adoc_files = [f for f in adoc_files if f != attributes_file]
158
+ # Find ALL attributes files to exclude from processing
159
+ all_attribute_files = find_attributes_files(repo_root)
160
+ exclude_paths = {f.resolve() for f in all_attribute_files}
161
+
162
+ # Notify user about excluded files if there are multiple
163
+ if len(all_attribute_files) > 1:
164
+ print(f"Excluding {len(all_attribute_files)} attributes files from processing:")
165
+ for f in all_attribute_files:
166
+ print(f" - {f.relative_to(repo_root)}")
167
+
168
+ # Exclude ALL attributes files, not just the selected one
169
+ adoc_files = [f for f in adoc_files if f.resolve() not in exclude_paths]
154
170
 
155
171
  print(f"Found {len(adoc_files)} AsciiDoc files to process")
156
172
 
@@ -165,7 +181,7 @@ def main():
165
181
  spinner.start()
166
182
 
167
183
  for file_path in adoc_files:
168
- replacements = replace_link_attributes_in_file(file_path, attributes, args.dry_run)
184
+ replacements = replace_link_attributes_in_file(file_path, attributes, args.dry_run, args.macro_type)
169
185
  if replacements > 0:
170
186
  rel_path = file_path.relative_to(repo_root)
171
187
  total_replacements += replacements
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rolfedh-doc-utils
3
- Version: 0.1.13
3
+ Version: 0.1.15
4
4
  Summary: CLI tools for AsciiDoc documentation projects
5
5
  Author: Rolfe Dlugy-Hegwer
6
6
  License: MIT License
@@ -1,16 +1,16 @@
1
1
  archive_unused_files.py,sha256=h7CRwSPBVCOQs0hn_ASD4EXz8QJFcAO2x3KX9FVhXNM,1974
2
2
  archive_unused_images.py,sha256=4GSVPYkxqFoY-isy47P_1AhD1ziXgmajFiBGCtZ3olg,1564
3
3
  check_scannability.py,sha256=MvGLW4UGGcx-jZLsVRYXpXNAIEQyJZZnsN99zJzbtyc,5178
4
- extract_link_attributes.py,sha256=rp1yRYIWOEvU3l6lpN4b5rCBae5Q7bdBxEDQ9BNuFH8,2976
4
+ extract_link_attributes.py,sha256=grPvVwOF9kqIOZ_uxYtJTkO3C2DNtIpKNPp6LlGK3Xs,3216
5
5
  find_unused_attributes.py,sha256=IUJKJr_MzxBXqg9rafUs9Kwi8AbU0x-H0AVflc1dhCU,3288
6
6
  format_asciidoc_spacing.py,sha256=_XpHqxYWm1AnZLUK_cDpfAJtsDCDF0b66m3opfYnIuU,3912
7
- replace_link_attributes.py,sha256=ZkBqrrpIiYGccGMgRjDBrWQKgpfOzHIegURmcgTwaHg,6614
7
+ replace_link_attributes.py,sha256=lJHrzIRJOmdt5WzCD0Lf-v9lOJDXBV7b75bOFZLNDPQ,7324
8
8
  validate_links.py,sha256=409fTAyBGTUrp6iSWuJ9AXExcdz8dC_4QeA_RvCIhus,5845
9
9
  doc_utils/__init__.py,sha256=qqZR3lohzkP63soymrEZPBGzzk6-nFzi4_tSffjmu_0,74
10
- doc_utils/extract_link_attributes.py,sha256=IIEq2bQmACwDszmaCMeMnYnPKwxSOHWbu_spYOJezlE,20700
10
+ doc_utils/extract_link_attributes.py,sha256=U0EvPZReJQigNfbT-icBsVT6Li64hYki5W7MQz6qqbc,22743
11
11
  doc_utils/file_utils.py,sha256=fpTh3xx759sF8sNocdn_arsP3KAv8XA6cTQTAVIZiZg,4247
12
12
  doc_utils/format_asciidoc_spacing.py,sha256=XnVJekaj39aDzjV3xFKl58flM41AaJzejxNYJIIAMz0,10139
13
- doc_utils/replace_link_attributes.py,sha256=kBiePbxjQn3O2rzqmYY8Mqy_mJgZ6yw048vSZ5SSB5E,6587
13
+ doc_utils/replace_link_attributes.py,sha256=gmAs68_njBqEz-Qni-UGgeYEDTMxlTWk_IOm76FONNE,7279
14
14
  doc_utils/scannability.py,sha256=XwlmHqDs69p_V36X7DLjPTy0DUoLszSGqYjJ9wE-3hg,982
15
15
  doc_utils/spinner.py,sha256=lJg15qzODiKoR0G6uFIk2BdVNgn9jFexoTRUMrjiWvk,3554
16
16
  doc_utils/topic_map_parser.py,sha256=tKcIO1m9r2K6dvPRGue58zqMr0O2zKU1gnZMzEE3U6o,4571
@@ -18,9 +18,9 @@ doc_utils/unused_adoc.py,sha256=2cbqcYr1os2EhETUU928BlPRlsZVSdI00qaMhqjSIqQ,5263
18
18
  doc_utils/unused_attributes.py,sha256=EjTtWIKW_aXsR1JOgw5RSDVAqitJ_NfRMVOXVGaiWTY,5282
19
19
  doc_utils/unused_images.py,sha256=nqn36Bbrmon2KlGlcaruNjJJvTQ8_9H0WU9GvCW7rW8,1456
20
20
  doc_utils/validate_links.py,sha256=iBGXnwdeLlgIT3fo3v01ApT5k0X2FtctsvkrE6E3VMk,19610
21
- rolfedh_doc_utils-0.1.13.dist-info/licenses/LICENSE,sha256=vLxtwMVOJA_hEy8b77niTkdmQI9kNJskXHq0dBS36e0,1075
22
- rolfedh_doc_utils-0.1.13.dist-info/METADATA,sha256=BijPKBcklacOn-2U2VTfcMEJBkyOMoI84Ce5ItNK-vc,7386
23
- rolfedh_doc_utils-0.1.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
- rolfedh_doc_utils-0.1.13.dist-info/entry_points.txt,sha256=2J4Ojc3kkuArpe2xcUOPc0LxSWCmnctvw8hy8zpnbO4,418
25
- rolfedh_doc_utils-0.1.13.dist-info/top_level.txt,sha256=1w0JWD7w7gnM5Sga2K4fJieNZ7CHPTAf0ozYk5iIlmo,182
26
- rolfedh_doc_utils-0.1.13.dist-info/RECORD,,
21
+ rolfedh_doc_utils-0.1.15.dist-info/licenses/LICENSE,sha256=vLxtwMVOJA_hEy8b77niTkdmQI9kNJskXHq0dBS36e0,1075
22
+ rolfedh_doc_utils-0.1.15.dist-info/METADATA,sha256=wUhG5aOP3eUggSenYO_D3bS8R4wCI7abvg1F7gWgR3E,7386
23
+ rolfedh_doc_utils-0.1.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
+ rolfedh_doc_utils-0.1.15.dist-info/entry_points.txt,sha256=2J4Ojc3kkuArpe2xcUOPc0LxSWCmnctvw8hy8zpnbO4,418
25
+ rolfedh_doc_utils-0.1.15.dist-info/top_level.txt,sha256=1w0JWD7w7gnM5Sga2K4fJieNZ7CHPTAf0ozYk5iIlmo,182
26
+ rolfedh_doc_utils-0.1.15.dist-info/RECORD,,