44import enum
55import itertools
66import logging
7- import re
87import typing as t
98
109import packaging .version
1110import semver
1211
12+ from . import patterns
13+ from .parser import parse_release_str , parse_pre_release_str , parse_local_str
14+
1315_LOG = logging .getLogger (__name__ )
1416
1517PY_PRE_RELEASE_INDICATORS = {'a' , 'b' , 'c' , 'rc' }
1820@enum .unique
1921class VersionComponent (enum .IntEnum ):
2022 """Enumeration of standard version components."""
23+
2124 # pylint: disable = invalid-name
2225
2326 Major = 1 << 1
@@ -39,87 +42,10 @@ class Version(collections.abc.Hashable): # pylint: disable = too-many-public-me
3942 Definitions of acceptable version formats are provided in readme.
4043 """
4144
42- _re_number = r'(?:0|[123456789][0123456789]*)'
43- # _re_sha = r'[0123456789abcdef]+'
44- _re_letters = r'(?:[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]+)'
45- _pattern_letters = re .compile (_re_letters )
46- _re_alphanumeric = r'(?:[0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]+)'
47- _pattern_alphanumeric = re .compile (_re_alphanumeric )
48- _re_sep = r'(?:[\.-])'
49-
50- _re_release_parts = \
51- r'(?P<major>{n})(?:\.(?P<minor>{n}))?(?:\.(?P<patch>{n}))?' .format (n = _re_number )
52- _pattern_release = re .compile (_re_release_parts )
53-
54- @classmethod
55- def _parse_release_str (cls , release : str ) -> tuple :
56- match = cls ._pattern_release .fullmatch (release )
57- assert match is not None
58- major_match = match .group ('major' )
59- assert major_match is not None
60- major = int (major_match )
61- minor_match = match .group ('minor' )
62- if minor_match is not None :
63- minor = int (minor_match )
64- else :
65- minor = None
66- patch_match = match .group ('patch' )
67- if patch_match is not None :
68- patch = int (patch_match )
69- else :
70- patch = None
71- return major , minor , patch
72-
73- _re_pre_separator = rf'(?P<preseparator>{ _re_sep } )'
74- _re_pre_type = rf'(?P<pretype>{ _re_letters } )'
75- _re_pre_patch = rf'(?P<prepatch>{ _re_number } )'
76- _re_pre_release_part = rf'{ _re_pre_separator } ?{ _re_pre_type } ?{ _re_pre_patch } ?'
77- _pattern_pre_release_part = re .compile (_re_pre_release_part )
78- _re_pre_release_parts = r'(?:{0}{2})|(?:{0}?{1}{2}?)' .format (_re_sep , _re_letters , _re_number )
79- _pattern_pre_release = re .compile (_re_pre_release_parts )
80- _pattern_pre_release_check = re .compile (rf'(?:{ _re_pre_release_parts } )+' )
81-
82- @classmethod
83- def _parse_pre_release_str (cls , pre_release : str ) -> t .Sequence [
84- t .Tuple [t .Optional [str ], t .Optional [str ], t .Optional [int ]]]:
85- parts = cls ._pattern_pre_release .findall (pre_release )
86- _LOG .debug ('parsed pre-release string %s into %s' ,
87- repr (pre_release ), parts )
88- tuples = []
89- for part in parts :
90- match = cls ._pattern_pre_release_part .fullmatch (part )
91- assert match is not None
92- pre_patch_match = match .group ('prepatch' )
93- if pre_patch_match is not None :
94- pre_patch = int (pre_patch_match )
95- else :
96- pre_patch = None
97- tuples .append ((match .group ('preseparator' ), match .group ('pretype' ), pre_patch ))
98- return tuples
99-
100- _re_local_separator = rf'({ _re_sep } )'
101- _re_local_part = rf'({ _re_alphanumeric } )'
102- _re_local_parts = rf'\+{ _re_local_part } (?:{ _re_local_separator } { _re_local_part } )*'
103- _pattern_local = re .compile (_re_local_parts )
104-
105- @classmethod
106- def _parse_local_str (cls , local : str ) -> tuple :
107- match = cls ._pattern_local .fullmatch (local )
108- assert match is not None
109- return tuple ([_ for _ in match .groups () if _ is not None ])
110-
111- _re_release = r'(?P<release>{n}(?:\.{n})?(?:\.{n})?)' .format (n = _re_number )
112- _re_pre_release = r'(?P<prerelease>(?:(?:{0}{2})|(?:{0}?{1}{2}?))+)' .format (
113- _re_sep , _re_letters , _re_number )
114- _re_local = r'(?P<local>\+{0}([\.-]{0})*)' .format (_re_alphanumeric )
115- # _re_named_parts_count = 3 + 3
116- _re_version = rf'{ _re_release } { _re_pre_release } ?{ _re_local } ?'
117- _pattern_version = re .compile (_re_version )
118-
11945 @classmethod
12046 def from_str (cls , version_str : str ):
12147 """Create version from string."""
122- match = cls . _pattern_version .fullmatch (version_str ) # type: t.Optional[t.Match[str]]
48+ match = patterns . VERSION .fullmatch (version_str ) # type: t.Optional[t.Match[str]]
12349 if match is None :
12450 raise ValueError (f'version string { repr (version_str )} is invalid' )
12551 _LOG .debug ('version_query parsed version string %s into %s: %s %s' ,
@@ -129,9 +55,9 @@ def from_str(cls, version_str: str):
12955 _pre_release = match .group ('prerelease' )
13056 _local = match .group ('local' )
13157
132- major , minor , patch = cls . _parse_release_str (_release )
133- pre_release = None if _pre_release is None else cls . _parse_pre_release_str (_pre_release )
134- local = None if _local is None else cls . _parse_local_str (_local )
58+ major , minor , patch = parse_release_str (_release )
59+ pre_release = None if _pre_release is None else parse_pre_release_str (_pre_release )
60+ local = None if _local is None else parse_local_str (_local )
13561
13662 return cls (major = major , minor = minor , patch = patch , pre_release = pre_release , local = local )
13763
@@ -196,7 +122,7 @@ def from_sem_version(cls, sem_version: t.Union[dict, semver.VersionInfo]):
196122 if pre_release is not None :
197123 raise NotImplementedError (sem_version )
198124 if local is not None :
199- local = cls . _parse_local_str (f'+{ local } ' )
125+ local = parse_local_str (f'+{ local } ' )
200126 return cls (major , minor , patch , pre_release = pre_release , local = local )
201127
202128 @classmethod
@@ -357,7 +283,7 @@ def _check_pre_release_parts(self, pre_separator, pre_type, pre_patch):
357283 if pre_type is not None and not isinstance (pre_type , str ):
358284 raise TypeError (
359285 f'pre_type={ repr (pre_type )} is of wrong type { type (pre_type )} in { repr (self )} ' )
360- if pre_type is not None and type ( self ). _pattern_letters .fullmatch (pre_type ) is None :
286+ if pre_type is not None and patterns . LETTERS .fullmatch (pre_type ) is None :
361287 raise ValueError (f'pre_type={ repr (pre_type )} has wrong value in { repr (self )} ' )
362288 if pre_patch is not None and not isinstance (pre_patch , int ):
363289 raise TypeError (
@@ -396,7 +322,7 @@ def local(self, local: t.Optional[t.Sequence[str]]):
396322 raise TypeError (f'local_part or local_separator { repr (part )} is of wrong type'
397323 f' { type (part )} in { repr (self )} ' )
398324 if i % 2 == 0 :
399- if type ( self ). _pattern_alphanumeric .fullmatch (part ) is None :
325+ if patterns . ALPHANUMERIC .fullmatch (part ) is None :
400326 raise ValueError (f'local_part={ repr (part )} has wrong value in { repr (self )} ' )
401327 elif part not in ('-' , '.' ):
402328 raise ValueError (f'local_separator={ repr (part )} has wrong value in { repr (self )} ' )
0 commit comments