@@ -540,23 +540,75 @@ def _upper(*, epoch = 0, release, pre = "", post = "", dev = "", local = ""):
540540 release = release ,
541541 )
542542
543+ def _pad_zeros (release , n ):
544+ if len (release ) >= n :
545+ return release
546+
547+ release = list (release )
548+ release += [0 ] * len (release )
549+ return tuple (release )
550+
551+ # TODO @aignas 2025-05-04: add tests for the comparison
543552def _version_eq (left , right ):
544553 if left .epoch != right .epoch :
545554 return False
546555
547- # Check at most 3 terms and check the same number of terms
548- check_len = min (min (len (left .release ), len (right .release )), 3 )
556+ if left .is_prefix :
557+ right_release = right .release [:len (left .release )]
558+ else :
559+ right_release = _pad_zeros (right .release , len (left .release ))
549560
550- return left .release [:check_len ] == right .release [:check_len ]
561+ if right .is_prefix :
562+ left_release = left .release [:len (right .release )]
563+ else :
564+ left_release = _pad_zeros (left .release , len (right .release ))
551565
566+ if left_release != right_release :
567+ return False
568+
569+ if left .is_prefix or right .is_prefix :
570+ return True
571+
572+ return (
573+ left .pre == right .pre and
574+ left .post == right .post and
575+ left .dev == right .dev and
576+ left .local == right .local
577+ )
578+
579+ # TODO @aignas 2025-05-04: add tests for the comparison
552580def _version_lt (left , right ):
581+ if left .epoch > right .epoch :
582+ return False
553583 if left .epoch < right .epoch :
554584 return True
555- elif left .epoch > right .epoch :
585+
586+ if left .is_prefix :
587+ right_release = right .release [:len (left .release )]
588+ else :
589+ right_release = _pad_zeros (right .release , len (left .release ))
590+
591+ if right .is_prefix :
592+ left_release = left .release [:len (right .release )]
593+ else :
594+ left_release = _pad_zeros (left .release , len (right .release ))
595+
596+ if left_release > right_release :
556597 return False
598+ elif left_release < right_release :
599+ return True
600+
601+ if left .is_prefix or right .is_prefix :
602+ return True
557603
558- return left .release < right .release
604+ return (
605+ left .pre < right .pre and
606+ left .post < right .post and
607+ left .dev < right .dev and
608+ left .local < right .local
609+ )
559610
611+ # TODO @aignas 2025-05-04: add tests for the comparison
560612def _version_gt (left , right ):
561613 if left .epoch > right .epoch :
562614 return True
@@ -565,7 +617,7 @@ def _version_gt(left, right):
565617
566618 return left .release > right .release
567619
568- def _new_version (* , epoch = 0 , release , pre = "" , post = "" , dev = "" , local = "" ):
620+ def _new_version (* , epoch = 0 , release , pre = "" , post = "" , dev = "" , local = "" , is_prefix = False ):
569621 epoch = epoch or 0
570622 _release = tuple ([int (d ) for d in release .split ("." )])
571623 pre = pre or ""
@@ -580,6 +632,7 @@ def _new_version(*, epoch = 0, release, pre = "", post = "", dev = "", local = "
580632 post = post ,
581633 dev = dev ,
582634 local = local ,
635+ is_prefix = is_prefix ,
583636 upper = lambda : _upper (
584637 epoch = epoch ,
585638 release = _release ,
@@ -588,14 +641,7 @@ def _new_version(*, epoch = 0, release, pre = "", post = "", dev = "", local = "
588641 dev = dev ,
589642 local = local ,
590643 ),
591- key = lambda : (
592- epoch ,
593- _release ,
594- pre ,
595- post ,
596- dev ,
597- local ,
598- ),
644+ # TODO @aignas 2025-05-04: add tests for the comparison
599645 eq = lambda x : _version_eq (self , x ), # buildifier: disable=uninitialized
600646 ne = lambda x : not _version_eq (self , x ), # buildifier: disable=uninitialized
601647 lt = lambda x : _version_lt (self , x ), # buildifier: disable=uninitialized
@@ -607,7 +653,9 @@ def _new_version(*, epoch = 0, release, pre = "", post = "", dev = "", local = "
607653 return self
608654
609655def parse_version (version ):
610- """Escape the version component of a filename.
656+ """Parse a PEP4408 compliant version
657+
658+ TODO: finish
611659
612660 See https://packaging.python.org/en/latest/specifications/binary-distribution-format/#escaping-and-unicode
613661 and https://peps.python.org/pep-0440/
@@ -619,9 +667,7 @@ def parse_version(version):
619667 string containing the normalized version.
620668 """
621669
622- # TODO @aignas 2025-05-04: this is discarding '.*', but per spec this should be only done
623- # for public version segments
624- parser = _new (version .strip (" .*" )) # PEP 440: Leading and Trailing Whitespace
670+ parser = _new (version .strip (" .*" )) # PEP 440: Leading and Trailing Whitespace and .*
625671 accept (parser , _is ("v" ), "" ) # PEP 440: Preceding v character
626672
627673 parts = {}
@@ -637,7 +683,15 @@ def parse_version(version):
637683 for p , fn in fns :
638684 fn (parser )
639685 parts [p ] = parser .context ()["norm" ]
640- parser .context ()["norm" ] = ""
686+ parser .context ()["norm" ] = "" # Clear out the buffer so that it is easy to separate the fields
687+
688+ is_prefix = version .endswith (".*" )
689+ parts ["is_prefix" ] = is_prefix
690+ if is_prefix and (parts ["local" ] or parts ["post" ] or parts ["dev" ] or parts ["pre" ]):
691+ # local version part has been obtained, but only public segments can have prefix
692+ # matches. Just return None.
693+ # https://peps.python.org/pep-0440/#public-version-identifiers
694+ return None
641695
642696 if parser .input [parser .context ()["start" ]:]:
643697 # If we fail to parse the version return None
0 commit comments