rolfedh-doc-utils 0.1.12__py3-none-any.whl → 0.1.14__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,14 @@ 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') -> 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
+
235
246
  Returns: List[(file_path, full_macro, url, link_text, line_number)]
236
247
  """
237
248
  if scan_dirs is None:
@@ -248,7 +259,7 @@ def collect_all_macros(scan_dirs: List[str] = None) -> List[Tuple[str, str, str,
248
259
  for file in files:
249
260
  if file.endswith('.adoc'):
250
261
  file_path = os.path.join(root, file)
251
- macros = find_link_macros(file_path)
262
+ macros = find_link_macros(file_path, macro_type)
252
263
  for full_macro, url, link_text, line_num in macros:
253
264
  all_macros.append((file_path, full_macro, url, link_text, line_num))
254
265
 
@@ -257,13 +268,14 @@ def collect_all_macros(scan_dirs: List[str] = None) -> List[Tuple[str, str, str,
257
268
 
258
269
  def create_attributes(url_groups: Dict[str, List[Tuple[str, str, str, int]]],
259
270
  existing_attrs: Dict[str, str],
260
- interactive: bool = True) -> Dict[str, str]:
271
+ interactive: bool = True) -> Tuple[Dict[str, str], Dict[str, str]]:
261
272
  """
262
- Create new attributes for each unique URL.
273
+ Create new attributes for each unique URL and track existing ones.
263
274
 
264
- Returns: Dict[attribute_name, attribute_value]
275
+ Returns: Tuple[new_attributes, existing_matching_attributes]
265
276
  """
266
277
  new_attributes = {}
278
+ existing_matching_attributes = {}
267
279
  existing_attr_names = set(existing_attrs.keys())
268
280
  counter = 1
269
281
 
@@ -273,6 +285,7 @@ def create_attributes(url_groups: Dict[str, List[Tuple[str, str, str, int]]],
273
285
  for attr_name, attr_value in existing_attrs.items():
274
286
  if url in attr_value:
275
287
  existing_attr = attr_name
288
+ existing_matching_attributes[attr_name] = attr_value
276
289
  break
277
290
 
278
291
  if existing_attr:
@@ -296,7 +309,7 @@ def create_attributes(url_groups: Dict[str, List[Tuple[str, str, str, int]]],
296
309
 
297
310
  print(f"Created attribute: :{attr_name}: {attr_value}")
298
311
 
299
- return new_attributes
312
+ return new_attributes, existing_matching_attributes
300
313
 
301
314
 
302
315
  def update_attribute_file(file_path: str, new_attributes: Dict[str, str], dry_run: bool = False):
@@ -450,10 +463,20 @@ def extract_link_attributes(attributes_file: str = None,
450
463
  interactive: bool = True,
451
464
  dry_run: bool = False,
452
465
  validate_links: bool = False,
453
- fail_on_broken: bool = False) -> bool:
466
+ fail_on_broken: bool = False,
467
+ macro_type: str = 'both') -> bool:
454
468
  """
455
469
  Main function to extract link attributes.
456
470
 
471
+ Args:
472
+ attributes_file: Path to attributes file
473
+ scan_dirs: Directories to scan
474
+ interactive: Enable interactive mode
475
+ dry_run: Preview changes without modifying files
476
+ validate_links: Validate URLs before extraction
477
+ fail_on_broken: Exit if broken links found
478
+ macro_type: Type of macros to process - 'link', 'xref', or 'both' (default: 'both')
479
+
457
480
  Returns: True if successful, False otherwise
458
481
  """
459
482
  # Find or confirm attributes file
@@ -488,16 +511,17 @@ def extract_link_attributes(attributes_file: str = None,
488
511
  spinner.stop(f"Loaded {len(existing_attrs)} existing attributes")
489
512
 
490
513
  # Collect all macros
491
- spinner = Spinner("Scanning for link and xref macros with attributes")
514
+ macro_desc = {'link': 'link', 'xref': 'xref', 'both': 'link and xref'}[macro_type]
515
+ spinner = Spinner(f"Scanning for {macro_desc} macros with attributes")
492
516
  spinner.start()
493
- all_macros = collect_all_macros(scan_dirs)
517
+ all_macros = collect_all_macros(scan_dirs, macro_type)
494
518
  spinner.stop()
495
519
 
496
520
  if not all_macros:
497
- print("No link or xref macros with attributes found.")
521
+ print(f"No {macro_desc} macros with attributes found.")
498
522
  return True
499
523
 
500
- print(f"Found {len(all_macros)} link/xref macros with attributes")
524
+ print(f"Found {len(all_macros)} {macro_desc} macros with attributes")
501
525
 
502
526
  # Group by URL
503
527
  spinner = Spinner("Grouping macros by URL")
@@ -505,15 +529,15 @@ def extract_link_attributes(attributes_file: str = None,
505
529
  url_groups = group_macros_by_url(all_macros)
506
530
  spinner.stop(f"Grouped into {len(url_groups)} unique URLs")
507
531
 
508
- # Create new attributes
509
- new_attributes = create_attributes(url_groups, existing_attrs, interactive)
532
+ # Create new attributes and track existing ones
533
+ new_attributes, existing_matching_attributes = create_attributes(url_groups, existing_attrs, interactive)
510
534
 
511
- if not new_attributes:
512
- print("No new attributes to create.")
535
+ if not new_attributes and not existing_matching_attributes:
536
+ print("No new attributes to create and no existing attributes match found URLs.")
513
537
  return True
514
538
 
515
539
  # Validate new attributes before writing if requested
516
- if validate_links and not dry_run:
540
+ if validate_links and not dry_run and new_attributes:
517
541
  print("\nValidating new link attributes...")
518
542
  spinner = Spinner("Validating new URLs")
519
543
  spinner.start()
@@ -543,10 +567,11 @@ def extract_link_attributes(attributes_file: str = None,
543
567
  print("\nStopping due to broken URLs in new attributes (--fail-on-broken)")
544
568
  return False
545
569
 
546
- # Update attribute file
547
- update_attribute_file(attributes_file, new_attributes, dry_run)
570
+ # Update attribute file (only if there are new attributes)
571
+ if new_attributes:
572
+ update_attribute_file(attributes_file, new_attributes, dry_run)
548
573
 
549
- # Prepare file updates
574
+ # Prepare file updates (include both new and existing matching attributes)
550
575
  all_attributes = {**existing_attrs, **new_attributes}
551
576
  file_updates = prepare_file_updates(url_groups, all_attributes)
552
577
 
@@ -560,6 +585,14 @@ def extract_link_attributes(attributes_file: str = None,
560
585
  if dry_run:
561
586
  print("\n[DRY RUN] No files were modified. Run without --dry-run to apply changes.")
562
587
  else:
563
- print(f"\nSuccessfully extracted {len(new_attributes)} link attributes")
588
+ total_processed = len(new_attributes) + len(existing_matching_attributes)
589
+ if new_attributes and existing_matching_attributes:
590
+ print(f"\nSuccessfully processed {total_processed} link attributes:")
591
+ print(f" - Created {len(new_attributes)} new attributes")
592
+ print(f" - Replaced macros using {len(existing_matching_attributes)} existing attributes")
593
+ elif new_attributes:
594
+ print(f"\nSuccessfully extracted {len(new_attributes)} link attributes")
595
+ elif existing_matching_attributes:
596
+ print(f"\nSuccessfully replaced macros using {len(existing_matching_attributes)} existing link attributes")
564
597
 
565
598
  return True
@@ -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
 
@@ -165,7 +171,7 @@ def main():
165
171
  spinner.start()
166
172
 
167
173
  for file_path in adoc_files:
168
- replacements = replace_link_attributes_in_file(file_path, attributes, args.dry_run)
174
+ replacements = replace_link_attributes_in_file(file_path, attributes, args.dry_run, args.macro_type)
169
175
  if replacements > 0:
170
176
  rel_path = file_path.relative_to(repo_root)
171
177
  total_replacements += replacements
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rolfedh-doc-utils
3
- Version: 0.1.12
3
+ Version: 0.1.14
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=XpgdJN56yI-udrZYEnPjw79gFLYTjJdGRk2MA1JoXnI,6830
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=onSe1AOlKHLH6t1nQ3T_DvDAhP9sbANhc6W05tAAgPg,19639
10
+ doc_utils/extract_link_attributes.py,sha256=TSXrPB5BLMtvuYQ0y8vwg2q9qDSugQC4BoHOpLRaYLA,21748
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.12.dist-info/licenses/LICENSE,sha256=vLxtwMVOJA_hEy8b77niTkdmQI9kNJskXHq0dBS36e0,1075
22
- rolfedh_doc_utils-0.1.12.dist-info/METADATA,sha256=Bzd64efcaXy4RZh9wkUxqlvr_Y1HmsuoocIbxWwaclE,7386
23
- rolfedh_doc_utils-0.1.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
- rolfedh_doc_utils-0.1.12.dist-info/entry_points.txt,sha256=2J4Ojc3kkuArpe2xcUOPc0LxSWCmnctvw8hy8zpnbO4,418
25
- rolfedh_doc_utils-0.1.12.dist-info/top_level.txt,sha256=1w0JWD7w7gnM5Sga2K4fJieNZ7CHPTAf0ozYk5iIlmo,182
26
- rolfedh_doc_utils-0.1.12.dist-info/RECORD,,
21
+ rolfedh_doc_utils-0.1.14.dist-info/licenses/LICENSE,sha256=vLxtwMVOJA_hEy8b77niTkdmQI9kNJskXHq0dBS36e0,1075
22
+ rolfedh_doc_utils-0.1.14.dist-info/METADATA,sha256=CK1mmV7kV3d1dtTw9iRdM2N_ztFE1IpN7pWi8BF_YoE,7386
23
+ rolfedh_doc_utils-0.1.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
+ rolfedh_doc_utils-0.1.14.dist-info/entry_points.txt,sha256=2J4Ojc3kkuArpe2xcUOPc0LxSWCmnctvw8hy8zpnbO4,418
25
+ rolfedh_doc_utils-0.1.14.dist-info/top_level.txt,sha256=1w0JWD7w7gnM5Sga2K4fJieNZ7CHPTAf0ozYk5iIlmo,182
26
+ rolfedh_doc_utils-0.1.14.dist-info/RECORD,,