@@ -517,3 +517,128 @@ def normalize_pep440(version):
517517 "Parse error at '%s'" % parser .input [parser .context ()["start" ]:],
518518 )
519519 return parser .context ()["norm" ]
520+
521+ def _upper (* , epoch = 0 , release , pre = "" , post = "" , dev = "" , local = "" ):
522+ epoch = epoch
523+ _release = list (release [:- 1 ])
524+ pre = pre
525+ post = post
526+ dev = dev
527+ local = local
528+
529+ if pre or dev or local :
530+ return _new_version (
531+ epoch = epoch ,
532+ release = release ,
533+ )
534+
535+ _release [- 1 ] = _release [- 1 ] + 1
536+ release = "." .join ([str (d ) for d in _release ])
537+
538+ return _new_version (
539+ epoch = epoch ,
540+ release = release ,
541+ )
542+
543+ def _version_eq (left , right ):
544+ if left .epoch != right .epoch :
545+ return False
546+
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 )
549+
550+ return left .release [:check_len ] == right .release [:check_len ]
551+
552+ def _version_lt (left , right ):
553+ if left .epoch < right .epoch :
554+ return True
555+ elif left .epoch > right .epoch :
556+ return False
557+
558+ return left .release < right .release
559+
560+ def _version_gt (left , right ):
561+ if left .epoch > right .epoch :
562+ return True
563+ elif left .epoch < right .epoch :
564+ return False
565+
566+ return left .release > right .release
567+
568+ def _new_version (* , epoch = 0 , release , pre = "" , post = "" , dev = "" , local = "" ):
569+ epoch = epoch or 0
570+ _release = tuple ([int (d ) for d in release .split ("." )])
571+ pre = pre or ""
572+ post = post or ""
573+ dev = dev or ""
574+ local = local or ""
575+
576+ self = struct (
577+ epoch = epoch ,
578+ release = _release ,
579+ pre = pre ,
580+ post = post ,
581+ dev = dev ,
582+ local = local ,
583+ upper = lambda : _upper (
584+ epoch = epoch ,
585+ release = _release ,
586+ pre = pre ,
587+ post = post ,
588+ dev = dev ,
589+ local = local ,
590+ ),
591+ key = lambda : (
592+ epoch ,
593+ _release ,
594+ pre ,
595+ post ,
596+ dev ,
597+ local ,
598+ ),
599+ eq = lambda x : _version_eq (self , x ), # buildifier: disable=uninitialized
600+ ne = lambda x : not _version_eq (self , x ), # buildifier: disable=uninitialized
601+ lt = lambda x : _version_lt (self , x ), # buildifier: disable=uninitialized
602+ gt = lambda x : _version_gt (self , x ), # buildifier: disable=uninitialized
603+ le = lambda x : not _version_gt (self , x ), # buildifier: disable=uninitialized
604+ ge = lambda x : not _version_lt (self , x ), # buildifier: disable=uninitialized
605+ eqq = lambda x : _version_eqq (self , x ), # buildifier: disable=uninitialized
606+ )
607+
608+ return self
609+
610+ def parse_version (version ):
611+ """Escape the version component of a filename.
612+
613+ See https://packaging.python.org/en/latest/specifications/binary-distribution-format/#escaping-and-unicode
614+ and https://peps.python.org/pep-0440/
615+
616+ Args:
617+ version: version string to be normalized according to PEP 440.
618+
619+ Returns:
620+ string containing the normalized version.
621+ """
622+ parser = _new (version .strip ()) # PEP 440: Leading and Trailing Whitespace
623+ accept (parser , _is ("v" ), "" ) # PEP 440: Preceding v character
624+
625+ parts = {}
626+ fns = [
627+ ("epoch" , accept_epoch ),
628+ ("release" , accept_release ),
629+ ("pre" , accept_prerelease ),
630+ ("post" , accept_postrelease ),
631+ ("dev" , accept_devrelease ),
632+ ("local" , accept_local ),
633+ ]
634+
635+ for p , fn in fns :
636+ fn (parser )
637+ parts [p ] = parser .context ()["norm" ]
638+ parser .context ()["norm" ] = ""
639+
640+ if parser .input [parser .context ()["start" ]:]:
641+ # If we fail to parse the version return None
642+ return None
643+
644+ return _new_version (** parts )
0 commit comments