11"""Text Utilities."""
2-
32# flake8: noqa
43
54
65from __future__ import annotations
76
8- import re
97from difflib import SequenceMatcher
108from typing import Iterable , Iterator
119
@@ -16,15 +14,12 @@ def escape_regex(p, white=''):
1614 # type: (str, str) -> str
1715 """Escape string for use within a regular expression."""
1816 # what's up with re.escape? that code must be neglected or something
19- return '' .join (
20- c if c .isalnum () or c in white else ('\\ 000' if c == '\000 ' else '\\ ' + c )
21- for c in p
22- )
17+ return '' .join (c if c .isalnum () or c in white
18+ else ('\\ 000' if c == '\000 ' else '\\ ' + c )
19+ for c in p )
2320
2421
25- def fmatch_iter (
26- needle : str , haystack : Iterable [str ], min_ratio : float = 0.6
27- ) -> Iterator [tuple [float , str ]]:
22+ def fmatch_iter (needle : str , haystack : Iterable [str ], min_ratio : float = 0.6 ) -> Iterator [tuple [float , str ]]:
2823 """Fuzzy match: iteratively.
2924
3025 Yields
@@ -37,76 +32,39 @@ def fmatch_iter(
3732 yield ratio , key
3833
3934
40- def fmatch_best (
41- needle : str , haystack : Iterable [str ], min_ratio : float = 0.6
42- ) -> str | None :
35+ def fmatch_best (needle : str , haystack : Iterable [str ], min_ratio : float = 0.6 ) -> str | None :
4336 """Fuzzy match - Find best match (scalar)."""
4437 try :
4538 return sorted (
46- fmatch_iter (needle , haystack , min_ratio ),
47- reverse = True ,
48- )[
49- 0
50- ][1 ]
39+ fmatch_iter (needle , haystack , min_ratio ), reverse = True ,
40+ )[0 ][1 ]
5141 except IndexError :
5242 return None
5343
5444
55- def version_string_as_tuple (version : str ) -> version_info_t :
56- """Parse a version string into its components and return a version_info_t tuple.
57-
58- The version string is expected to follow the pattern:
59- 'major.minor.micro[releaselevel][serial]'. Each component of the version
60- is extracted and returned as a tuple in the format (major, minor, micro,
61- releaselevel, serial).
62-
63- Args
64- ----
65- version (str): The version string to parse.
66-
67- Returns
68- -------
69- version_info_t: A tuple containing the parsed version components.
70-
71- Raises
72- ------
73- ValueError: If the version string is invalid and does not match the expected pattern.
74- """
75- pattern = r'^(\d+)' # catching the major version (mandatory)
76- pattern += r'(?:\.(\d+))?' # optionally catching the minor version
77- pattern += r'(?:\.(\d+))?' # optionally catching the micro version
78- pattern += r'(?:\.*([a-zA-Z+-][\da-zA-Z+-]*))?' # optionally catching the release level (starting with a letter, + or -) after a dot
79- pattern += r'(?:\.(.*))?' # optionally catching the serial number after a dot
80-
81- # applying the regex pattern to the input version string
82- match = re .match (pattern , version )
83-
84- if not match :
85- raise ValueError (f"Invalid version string: { version } " )
86-
87- # extracting the matched groups
88- major = int (match .group (1 ))
89- minor = int (match .group (2 )) if match .group (2 ) else 0
90- micro = int (match .group (3 )) if match .group (3 ) else 0
91- releaselevel = match .group (4 ) if match .group (4 ) else ''
92- serial = match .group (5 ) if match .group (5 ) else ''
93-
94- return _unpack_version (major , minor , micro , releaselevel , serial )
45+ def version_string_as_tuple (s : str ) -> version_info_t :
46+ """Convert version string to version info tuple."""
47+ v = _unpack_version (* s .split ('.' ))
48+ # X.Y.3a1 -> (X, Y, 3, 'a1')
49+ if isinstance (v .micro , str ):
50+ v = version_info_t (v .major , v .minor , * _splitmicro (* v [2 :]))
51+ # X.Y.3a1-40 -> (X, Y, 3, 'a1', '40')
52+ if not v .serial and v .releaselevel and '-' in v .releaselevel :
53+ v = version_info_t (* list (v [0 :3 ]) + v .releaselevel .split ('-' ))
54+ return v
9555
9656
9757def _unpack_version (
98- major : str | int = 0 ,
58+ major : str ,
9959 minor : str | int = 0 ,
10060 micro : str | int = 0 ,
10161 releaselevel : str = '' ,
102- serial : str = '' ,
62+ serial : str = ''
10363) -> version_info_t :
104- return version_info_t (int (major ), int (minor ), int ( micro ) , releaselevel , serial )
64+ return version_info_t (int (major ), int (minor ), micro , releaselevel , serial )
10565
10666
107- def _splitmicro (
108- micro : str , releaselevel : str = '' , serial : str = ''
109- ) -> tuple [int , str , str ]:
67+ def _splitmicro (micro : str , releaselevel : str = '' , serial : str = '' ) -> tuple [int , str , str ]:
11068 for index , char in enumerate (micro ):
11169 if not char .isdigit ():
11270 break
0 commit comments