@@ -367,7 +367,7 @@ def _build_version_pattern(name: str, version: str) -> tuple[Pattern | None, lis
367
367
368
368
Returns
369
369
-------
370
- tuple[Pattern | None, list[str]]
370
+ tuple[Pattern | None, list[str], CommitFinderInfo ]
371
371
The tuple of the regex pattern that will match the version, the list of version parts that were extracted, and
372
372
the outcome to report. If an exception occurs from any regex operation, the pattern will be returned as None.
373
373
@@ -384,43 +384,19 @@ def _build_version_pattern(name: str, version: str) -> tuple[Pattern | None, lis
384
384
logger .debug ("Version contained no valid parts: %s" , version )
385
385
return None , [], CommitFinderInfo .INVALID_VERSION
386
386
387
- logger .debug ("Final version parts: %s" , parts )
387
+ logger .debug ("Version parts: %s" , parts )
388
+
389
+ # Determine optional suffixes.
390
+ optional_start_index = determine_optional_suffix_index (version , parts )
388
391
389
- this_version_pattern = ""
390
392
# Detect versions that end with a zero number (0, 00, 000, etc.), so that part can be made optional.
391
393
has_trailing_zero = len (parts ) > 2 and multiple_zero_pattern .match (parts [- 1 ])
392
394
393
- # Version parts that are alphanumeric, and do not come before parts that are purely numeric, can be treated
394
- # as optional suffixes.
395
- # E.g.
396
- # - 1.2.RELEASE -> 'RELEASE' becomes optional.
397
- # - 3.1.test.2.M5 -> 'M5' becomes optional.
398
- # Parts that come after a change in seperator are also flagged as optional.
399
- # - 2.2-3 -> '3' becomes optional.
400
- optional_start_index = None
401
- separators = _split_separators (version )
402
- last_separator = separators [0 ] if separators else None
403
- for index in range (1 , len (parts )):
404
- # Check if current part should be optional, or reset the index if not.
405
- optional_start_index = None if numeric_only_pattern .match (parts [index ]) else index
406
-
407
- if not last_separator :
408
- continue
409
-
410
- if index >= len (separators ):
411
- continue
412
-
413
- # Check if parts should be made optional based on a difference in separators.
414
- new_separator = separators [index ]
415
- if new_separator != last_separator :
416
- optional_start_index = index + 1
417
- break
418
- last_separator = new_separator
419
-
420
395
# Create the pattern.
396
+ this_version_pattern = ""
421
397
for count , part in enumerate (parts ):
422
- # This part will be made optional in the regex if within the optional suffix range, or the final part and it
423
- # is a trailing zero.
398
+ # This part will be made optional in the regex if it is within the optional suffix range, or is the final part
399
+ # and is a trailing zero.
424
400
optional = (optional_start_index and count >= optional_start_index ) or (
425
401
count == len (parts ) - 1 and has_trailing_zero
426
402
)
@@ -483,6 +459,54 @@ def _build_version_pattern(name: str, version: str) -> tuple[Pattern | None, lis
483
459
return None , [], CommitFinderInfo .REGEX_COMPILE_FAILURE
484
460
485
461
462
+ def determine_optional_suffix_index (version : str , parts : list [str ]) -> int | None :
463
+ """Determine optional suffix index of a given version string.
464
+
465
+ Version parts that are alphanumeric, and do not come before parts that are purely numeric, can be treated
466
+ as optional suffixes.
467
+ E.g.
468
+ - 1.2.RELEASE -> 'RELEASE' becomes optional.
469
+ - 3.1.test.2.M5 -> 'M5' becomes optional.
470
+ Parts that come after a change in seperator are also flagged as optional.
471
+ - 2.2-3 -> '3' becomes optional.
472
+
473
+ Parameters
474
+ ----------
475
+ version: str
476
+ The version string of the software component.
477
+ parts: list[str]
478
+ The non-separator parts of the version produced by a prior split operation.
479
+
480
+ Returns
481
+ -------
482
+ int | None
483
+ The index of the first optional part, or None if not found. This is a zero-based index to match the parts
484
+ parameter, with the caveat that a value of zero cannot be returned due to the behaviour of the algorithm.
485
+ In other words, there must always be at least one non-optional part.
486
+ """
487
+ optional_start_index = None
488
+ separators = _split_separators (version )
489
+ last_separator = separators [0 ] if separators else None
490
+ for index in range (1 , len (parts )):
491
+ # Check if current part should be optional, or reset the index if not.
492
+ optional_start_index = None if numeric_only_pattern .match (parts [index ]) else index
493
+
494
+ if not last_separator :
495
+ continue
496
+
497
+ if index >= len (separators ):
498
+ continue
499
+
500
+ # Check if parts should be made optional based on a difference in separators.
501
+ new_separator = separators [index ]
502
+ if new_separator != last_separator :
503
+ optional_start_index = index + 1
504
+ break
505
+ last_separator = new_separator
506
+
507
+ return optional_start_index
508
+
509
+
486
510
def match_tags (tag_list : list [str ], name : str , version : str ) -> tuple [list [str ], CommitFinderInfo ]:
487
511
"""Return items of the passed tag list that match the passed artifact name and version.
488
512
@@ -507,9 +531,8 @@ def match_tags(tag_list: list[str], name: str, version: str) -> tuple[list[str],
507
531
# Generally version identifiers do not contain the `v` prefix, while tags often do. If a version does contain such
508
532
# a prefix, it is expected to be in the tag also. If not, the `v` prefix is left as optional.
509
533
v_prefix = "(?:v)?" if not version .lower ().startswith ("v" ) else ""
510
- escaped_version = re .escape (version )
511
534
almost_exact_pattern = re .compile (
512
- f"^(?:[^/]+/)?(?P<prefix>{ re .escape (name )} -)?{ v_prefix } { escaped_version } $" , re .IGNORECASE
535
+ f"^(?:[^/]+/)?(?P<prefix>{ re .escape (name )} -)?{ v_prefix } { re . escape ( version ) } $" , re .IGNORECASE
513
536
)
514
537
515
538
# Compare tags to the almost exact pattern. Prefer tags that matched the name prefix as well.
0 commit comments