From 62143c4105d3ec4e1f02dee1b9bb31ef8d0c2dc4 Mon Sep 17 00:00:00 2001 From: deathaxe Date: Wed, 13 Sep 2023 17:40:47 +0200 Subject: [PATCH] Sync with Package Control --- .../clients/bitbucket_client.py | 2 +- .../package_control/clients/github_client.py | 2 +- .../package_control/clients/gitlab_client.py | 2 +- app/lib/package_control/deps/semver.py | 851 ------------------ .../downloaders/background_downloader.py | 63 -- .../downloaders/oscrypto_downloader.py | 11 +- .../http/validating_https_connection.py | 8 + app/lib/package_control/package_version.py | 106 +-- .../providers/schema_compat.py | 37 - .../providers/schema_version.py | 16 +- .../check/file/check_messages.py | 36 +- 11 files changed, 104 insertions(+), 1030 deletions(-) delete mode 100644 app/lib/package_control/deps/semver.py delete mode 100644 app/lib/package_control/downloaders/background_downloader.py delete mode 100644 app/lib/package_control/providers/schema_compat.py diff --git a/app/lib/package_control/clients/bitbucket_client.py b/app/lib/package_control/clients/bitbucket_client.py index 901fe55..fdc5732 100644 --- a/app/lib/package_control/clients/bitbucket_client.py +++ b/app/lib/package_control/clients/bitbucket_client.py @@ -205,7 +205,7 @@ def _get_releases(user_repo, tag_prefix, page_size=100): output.append(self._make_download_info(user_repo, tag, str(version), timestamp)) - num_releases += not version.prerelease + num_releases += version.is_final if max_releases > 0 and num_releases >= max_releases: break diff --git a/app/lib/package_control/clients/github_client.py b/app/lib/package_control/clients/github_client.py index a6f0f30..2bb46e2 100644 --- a/app/lib/package_control/clients/github_client.py +++ b/app/lib/package_control/clients/github_client.py @@ -194,7 +194,7 @@ def _get_releases(user_repo, tag_prefix=None, page_size=1000): output.append(self._make_download_info(user_repo, tag, str(version), timestamp)) - num_releases += not version.prerelease + num_releases += version.is_final if max_releases > 0 and num_releases >= max_releases: break diff --git a/app/lib/package_control/clients/gitlab_client.py b/app/lib/package_control/clients/gitlab_client.py index c83344d..80f0272 100644 --- a/app/lib/package_control/clients/gitlab_client.py +++ b/app/lib/package_control/clients/gitlab_client.py @@ -196,7 +196,7 @@ def _get_releases(user_repo, tag_prefix=None, page_size=1000): output.append(self._make_download_info(user_name, repo_name, tag, str(version), timestamp)) - num_releases += not version.prerelease + num_releases += version.is_final if max_releases > 0 and num_releases >= max_releases: break diff --git a/app/lib/package_control/deps/semver.py b/app/lib/package_control/deps/semver.py deleted file mode 100644 index 1ac82e2..0000000 --- a/app/lib/package_control/deps/semver.py +++ /dev/null @@ -1,851 +0,0 @@ -"""pysemver: Semantic Version comparing for Python. - -Provides comparing of semantic versions by using SemVer objects using rich comperations plus the -possibility to match a selector string against versions. Interesting for version dependencies. -Versions look like: "1.7.12+b.133" -Selectors look like: ">1.7.0 || 1.6.9+b.111 - 1.6.9+b.113" - -Example usages: - >>> SemVer(1, 2, 3, build=13) - SemVer("1.2.3+13") - >>> SemVer.valid("1.2.3.4") - False - >>> SemVer.clean("this is unimportant text 1.2.3-2 and will be stripped") - "1.2.3-2" - >>> SemVer("1.7.12+b.133").satisfies(">1.7.0 || 1.6.9+b.111 - 1.6.9+b.113") - True - >>> SemSel(">1.7.0 || 1.6.9+b.111 - 1.6.9+b.113").matches(SemVer("1.7.12+b.133"), - ... SemVer("1.6.9+b.112"), SemVer("1.6.10")) - [SemVer("1.7.12+b.133"), SemVer("1.6.9+b.112")] - >>> min(_) - SemVer("1.6.9+b.112") - >>> _.patch - 9 - -Exported classes: - * SemVer(collections.namedtuple()) - Parses semantic versions and defines methods for them. Supports rich comparisons. - * SemSel(tuple) - Parses semantic version selector strings and defines methods for them. - * SelParseError(Exception) - An error among others raised when parsing a semantic version selector failed. - -Other classes: - * SemComparator(object) - * SemSelAndChunk(list) - * SemSelOrChunk(list) - -Functions/Variables/Constants: - none - - -Copyright (c) 2013 Zachary King, FichteFoll - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: The above copyright notice and this -permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES -OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -""" - -import re -import sys -from collections import namedtuple # Python >=2.6 - - -__all__ = ('SemVer', 'SemSel', 'SelParseError') - - -if sys.version_info[0] == 3: - basestring = str - - def cmp(a, b): - return (a > b) - (a < b) - - -# @functools.total_ordering would be nice here but was added in 2.7, __cmp__ is not Py3 -class SemVer(namedtuple("_SemVer", 'major, minor, patch, prerelease, build')): - - """Semantic Version, consists of 3 to 5 components defining the version's adicity. - - See http://semver.org/ (2.0.0-rc.1) for the standard mainly used for this implementation, few - changes have been made. - - Information on this particular class and their instances: - - Immutable and hashable. - - Subclasses `collections.namedtuple`. - - Always `True` in boolean context. - - len() returns an int between 3 and 5; 4 when a pre-release is set and 5 when a build is - set. Note: Still returns 5 when build is set but not pre-release. - - Parts of the semantic version can be accessed by integer indexing, key (string) indexing, - slicing and getting an attribute. Returned slices are tuple. Leading '-' and '+' of - optional components are not stripped. Supported keys/attributes: - major, minor, patch, prerelease, build. - - Examples: - s = SemVer("1.2.3-4.5+6") - s[2] == 3 - s[:3] == (1, 2, 3) - s['build'] == '-4.5' - s.major == 1 - - Short information on semantic version structure: - - Semantic versions consist of: - * a major component (numeric) - * a minor component (numeric) - * a patch component (numeric) - * a pre-release component [optional] - * a build component [optional] - - The pre-release component is indicated by a hyphen '-' and followed by alphanumeric[1] sequences - separated by dots '.'. Sequences are compared numerically if applicable (both sequences of two - versions are numeric) or lexicographically. May also include hyphens. The existence of a - pre-release component lowers the actual version; the shorter pre-release component is considered - lower. An 'empty' pre-release component is considered to be the least version for this - major-minor-patch combination (e.g. "1.0.0-"). - - The build component may follow the optional pre-release component and is indicated by a plus '+' - followed by sequences, just as the pre-release component. Comparing works similarly. However the - existence of a build component raises the actual version and may also raise a pre-release. An - 'empty' build component is considered to be the highest version for this - major-minor-patch-prerelease combination (e.g. "1.2.3+"). - - - [1]: Regexp for a sequence: r'[0-9A-Za-z-]+'. - """ - - # Static class variables - _base_regex = r''' - (?P[0-9]+) - \.(?P[0-9]+) - \.(?P[0-9]+) - (?:\-(?P(?:[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?))? - (?:\+(?P(?:[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?))?''' - _search_regex = re.compile(r'(?x)%s' % _base_regex) - _match_regex = re.compile(r'(?x)^%s$' % _base_regex) # required because of $ anchor - - # "Constructor" - def __new__(cls, *args, **kwargs): - """There are two different constructor styles that are allowed: - - Option 1 allows specification of a semantic version as a string and the option to "clean" - the string before parsing it. - - Option 2 allows specification of each component separately as one parameter. - - Note that all the parameters specified in the following sections can be passed either as - positional or as named parameters while considering the usual Python rules for this. As - such, `SemVer(1, 2, minor=1)` will result in an exception and not in `SemVer("1.1.2")`. - - Option 1: - Constructor examples: - SemVer("1.0.1") - SemVer("this version 1.0.1-pre.1 here", True) - SemVer(ver="0.0.9-pre-alpha+34", clean=False) - - Parameters: - * ver (str) - The string containing the version. - * clean = `False` (bool; optional) - If this is true in boolean context, `SemVer.clean(ver)` is called before - parsing. - - Option 2: - Constructor examples: - SemVer(1, 0, 1) - SemVer(1, '0', prerelease='pre-alpha', patch=1, build=34) - SemVer(**dict(minor=2, major=1, patch=3)) - - Parameters: - * major (int, str, float ...) - * minor (...) - * patch (...) - Major to patch components must be an integer or convertable to an int (e.g. a - string or another number type). - - * prerelease = `None` (str, int, float ...; optional) - * build = `None` (...; optional) - Pre-release and build components should be a string (or number) type. - Will be passed to `str()` if not already a string but the final string must - match '^[0-9A-Za-z.-]*$' - - Raises: - * TypeError - Invalid parameter type(s) or combination (e.g. option 1 and 2). - * ValueError - Invalid semantic version or option 2 parameters unconvertable. - """ - ver, clean, comps = None, False, None - kw, length = kwargs.copy(), len(args) + len(kwargs) - - def inv(): - raise TypeError("Invalid parameter combination: args=%s; kwargs=%s" % (args, kwargs)) - - # Do validation and parse the parameters - if length == 0 or length > 5: - raise TypeError("SemVer accepts at least 1 and at most 5 arguments (%d given)" % length) - - elif length < 3: - if len(args) == 2: - ver, clean = args - else: - ver = args[0] if args else kw.pop('ver', None) - clean = kw.pop('clean', clean) - if kw: - inv() - - else: - comps = list(args) + [kw.pop(cls._fields[k], None) for k in range(len(args), 5)] - if kw or any(comps[i] is None for i in range(3)): - inv() - - typecheck = (int,) * 3 + (basestring,) * 2 - for i, (v, t) in enumerate(zip(comps, typecheck)): - if v is None: - continue - elif not isinstance(v, t): - try: - if i < 3: - v = typecheck[i](v) - else: # The real `basestring` can not be instatiated (Py2) - v = str(v) - except ValueError as e: - # Modify the exception message. I can't believe this actually works - e.args = ("Parameter #%d must be of type %s or convertable" - % (i, t.__name__),) - raise - else: - comps[i] = v - if t is basestring and not re.match(r"^[0-9A-Za-z.-]*$", v): - raise ValueError("Build and pre-release strings must match '^[0-9A-Za-z.-]*$'") - - # Final adjustments - if not comps: - if ver is None or clean is None: - inv() - ver = clean and cls.clean(ver) or ver - comps = cls._parse(ver) - - # Create the obj - return super(SemVer, cls).__new__(cls, *comps) - - # Magic methods - def __str__(self): - return ('.'.join(map(str, self[:3])) - + ('-' + self.prerelease if self.prerelease is not None else '') - + ('+' + self.build if self.build is not None else '')) - - def __repr__(self): - # Use the shortest representation - what would you prefer? - return 'SemVer("%s")' % str(self) - # return 'SemVer(%s)' % ', '.join('%s=%r' % (k, getattr(self, k)) for k in self._fields) - - def __hash__(self): - return hash(str(self)) - - def __len__(self): - return 3 + (self.build is not None and 2 or self.prerelease is not None) - - # Magic rich comparing methods - def __gt__(self, other): - return self._compare(other) == 1 if isinstance(other, SemVer) else NotImplemented - - def __eq__(self, other): - return self._compare(other) == 0 if isinstance(other, SemVer) else NotImplemented - - def __lt__(self, other): - return not (self > other or self == other) - - def __ge__(self, other): - return not (self < other) - - def __le__(self, other): - return not (self > other) - - def __ne__(self, other): - return not (self == other) - - # Utility (class-)methods - def satisfies(self, sel): - """Alias for `bool(sel.matches(self))` or `bool(SemSel(sel).matches(self))`. - - See `SemSel.__init__()` and `SemSel.matches(*vers)` for possible exceptions. - - Returns: - * bool: `True` if the version matches the passed selector, `False` otherwise. - """ - if not isinstance(sel, SemSel): - sel = SemSel(sel) # just "re-raise" exceptions - - return bool(sel.matches(self)) - - @classmethod - def valid(cls, ver): - """Check if `ver` is a valid semantic version. Classmethod. - - Parameters: - * ver (str) - The string that should be stripped. - - Raises: - * TypeError - Invalid parameter type. - - Returns: - * bool: `True` if it is valid, `False` otherwise. - """ - if not isinstance(ver, basestring): - raise TypeError("%r is not a string" % ver) - - if cls._match_regex.match(ver): - return True - else: - return False - - @classmethod - def clean(cls, vers): - """Remove everything before and after a valid version string. Classmethod. - - Parameters: - * vers (str) - The string that should be stripped. - - Raises: - * TypeError - Invalid parameter type. - - Returns: - * str: The stripped version string. Only the first version is matched. - * None: No version found in the string. - """ - if not isinstance(vers, basestring): - raise TypeError("%r is not a string" % vers) - m = cls._search_regex.search(vers) - if m: - return vers[m.start():m.end()] - else: - return None - - # Private (class-)methods - @classmethod - def _parse(cls, ver): - """Private. Do not touch. Classmethod. - """ - if not isinstance(ver, basestring): - raise TypeError("%r is not a string" % ver) - - match = cls._match_regex.match(ver) - - if match is None: - raise ValueError("'%s' is not a valid SemVer string" % ver) - - g = list(match.groups()) - for i in range(3): - g[i] = int(g[i]) - - return g # Will be passed as namedtuple(...)(*g) - - def _compare(self, other): - """Private. Do not touch. - self > other: 1 - self = other: 0 - self < other: -1 - """ - # Shorthand lambdas - def cp_len(t, i=0): - return cmp(len(t[i]), len(t[not i])) - - for i, (x1, x2) in enumerate(zip(self, other)): - if i > 2: - if x1 is None and x2 is None: - continue - - # self is greater when other has a prerelease but self doesn't - # self is less when other has a build but self doesn't - if x1 is None or x2 is None: - return int(2 * (i - 3.5)) * (1 - 2 * (x1 is None)) - - # self is less when other's build is empty - if i == 4 and (not x1 or not x2) and x1 != x2: - return 1 - 2 * bool(x1) - - # Split by '.' and use numeric comp or lexicographical order - t2 = [x1.split('.'), x2.split('.')] - for y1, y2 in zip(*t2): - if y1.isdigit() and y2.isdigit(): - y1 = int(y1) - y2 = int(y2) - if y1 > y2: - return 1 - elif y1 < y2: - return -1 - - # The "longer" sub-version is greater - d = cp_len(t2) - if d: - return d - else: - if x1 > x2: - return 1 - elif x1 < x2: - return -1 - - # The versions equal - return 0 - - -class SemComparator: - - """Holds a SemVer object and a comparing operator and can match these against a given version. - - Constructor: SemComparator('<=', SemVer("1.2.3")) - - Methods: - * matches(ver) - """ - # Private properties - _ops = { - '>=': '__ge__', - '<=': '__le__', - '>': '__gt__', - '<': '__lt__', - '=': '__eq__', - '!=': '__ne__' - } - _ops_satisfy = ('~', '!') - - # Constructor - def __init__(self, op, ver): - """Constructor examples: - SemComparator('<=', SemVer("1.2.3")) - SemComparator('!=', SemVer("2.3.4")) - - Parameters: - * op (str, False, None) - One of [>=, <=, >, <, =, !=, !, ~] or evaluates to `False` which defaults to '~'. - '~' means a "satisfy" operation where pre-releases and builds are ignored. - '!' is a negative "~". - * ver (SemVer) - Holds the version to compare with. - - Raises: - * ValueError - Invalid `op` parameter. - * TypeError - Invalid `ver` parameter. - """ - super(SemComparator, self).__init__() - - if op and op not in self._ops_satisfy and op not in self._ops: - raise ValueError("Invalid value for `op` parameter.") - if not isinstance(ver, SemVer): - raise TypeError("`ver` parameter is not instance of SemVer.") - - # Default to '~' for versions with no build or pre-release - op = op or '~' - # Fallback to '=' and '!=' if len > 3 - if len(ver) != 3: - if op == '~': - op = '=' - if op == '!': - op = '!=' - - self.op = op - self.ver = ver - - # Magic methods - def __str__(self): - return (self.op or "") + str(self.ver) - - # Utility methods - def matches(self, ver): - """Match the internal version (constructor) against `ver`. - - Parameters: - * ver (SemVer) - - Raises: - * TypeError - Could not compare `ver` against the version passed in the constructor with the - passed operator. - - Returns: - * bool - `True` if the version matched the specified operator and internal version, `False` - otherwise. - """ - if self.op in self._ops_satisfy: - # Compare only the first three parts (which are tuples) and directly - return bool((self.ver[:3] == ver[:3]) + (self.op == '!') * -1) - ret = getattr(ver, self._ops[self.op])(self.ver) - if ret == NotImplemented: - raise TypeError("Unable to compare %r with operator '%s'" % (ver, self.op)) - return ret - - -class SemSelAndChunk(list): - - """Extends list and defines a few methods used for matching versions. - - New elements should be added by calling `.add_child(op, ver)` which creates a SemComparator - instance and adds that to itself. - - Methods: - * matches(ver) - * add_child(op, ver) - """ - - # Magic methods - - def __str__(self): - return ' '.join(map(str, self)) - - # Utitlity methods - - def matches(self, ver): - """Match all of the added children against `ver`. - - Parameters: - * ver (SemVer) - - Raises: - * TypeError - Invalid `ver` parameter. - - Returns: - * bool: - `True` if *all* of the SemComparator children match `ver`, `False` otherwise. - """ - if not isinstance(ver, SemVer): - raise TypeError("`ver` parameter is not instance of SemVer.") - return all(cp.matches(ver) for cp in self) - - def add_child(self, op, ver): - """Create a SemComparator instance with the given parameters and appends that to self. - - Parameters: - * op (str) - * ver (SemVer) - Both parameters are forwarded to `SemComparator.__init__`, see there for a more detailed - description. - - Raises: - Exceptions raised by `SemComparator.__init__`. - """ - self.append(SemComparator(op, SemVer(ver))) - - -class SemSelOrChunk(list): - - """Extends list and defines a few methods used for matching versions. - - New elements should be added by calling `.new_child()` which returns a SemSelAndChunk - instance. - - Methods: - * matches(ver) - * new_child() - """ - - # Magic methods - - def __str__(self): - return ' || '.join(map(str, self)) - - # Utility methods - - def matches(self, ver): - """Match all of the added children against `ver`. - - Parameters: - * ver (SemVer) - - Raises: - * TypeError - Invalid `ver` parameter. - - Returns: - * bool - `True` if *any* of the SemSelAndChunk children matches `ver`. - `False` otherwise. - """ - if not isinstance(ver, SemVer): - raise TypeError("`ver` parameter is not instance of SemVer.") - return any(ch.matches(ver) for ch in self) - - def new_child(self): - """Creates a new SemSelAndChunk instance, appends it to self and returns it. - - Returns: - * SemSelAndChunk: An empty instance. - """ - ch = SemSelAndChunk() - self.append(ch) - return ch - - -class SelParseError(Exception): - - """An Exception raised when parsing a semantic selector failed. - """ - pass - - -# Subclass `tuple` because this is a somewhat simple method to make this immutable -class SemSel(tuple): - - """A Semantic Version Selector, holds a selector and can match it against semantic versions. - - Information on this particular class and their instances: - - Immutable but not hashable because the content within might have changed. - - Subclasses `tuple` but does not behave like one. - - Always `True` in boolean context. - - len() returns the number of containing *and chunks* (see below). - - Iterable, iterates over containing *and chunks*. - - When talking about "versions" it refers to a semantic version (SemVer). For information on how - versions compare to one another, see SemVer's doc string. - - List for **comparators**: - "1.0.0" matches the version 1.0.0 and all its pre-release and build variants - "!1.0.0" matches any version that is not 1.0.0 or any of its variants - "=1.0.0" matches only the version 1.0.0 - "!=1.0.0" matches any version that is not 1.0.0 - ">=1.0.0" matches versions greater than or equal 1.0.0 - "<1.0.0" matches versions smaller than 1.0.0 - "1.0.0 - 1.0.3" matches versions greater than or equal 1.0.0 thru 1.0.3 - "~1.0" matches versions greater than or equal 1.0.0 thru 1.0.9999 (and more) - "~1", "1.x", "1.*" match versions greater than or equal 1.0.0 thru 1.9999.9999 (and more) - "~1.1.2" matches versions greater than or equal 1.1.2 thru 1.1.9999 (and more) - "~1.1.2+any" matches versions greater than or equal 1.1.2+any thru 1.1.9999 (and more) - "*", "~", "~x" match any version - - Multiple comparators can be combined by using ' ' spaces and every comparator must match to make - the **and chunk** match a version. - Multiple and chunks can be combined to **or chunks** using ' || ' and match if any of the and - chunks split by these matches. - - A complete example would look like: - ~1 || 0.0.3 || <0.0.2 >0.0.1+b.1337 || 2.0.x || 2.1.0 - 2.1.0+b.12 !=2.1.0+b.9 - - Methods: - * matches(*vers) - """ - # Private properties - _fuzzy_regex = re.compile(r'''(?x)^ - (?P[<>]=?|~>?=?)? - (?:(?P\d+) - (?:\.(?P\d+) - (?:\.(?P\d+) - (?P[-+][a-zA-Z0-9-+.]*)? - )? - )? - )?$''') - _xrange_regex = re.compile(r'''(?x)^ - (?P[<>]=?|~>?=?)? - (?:(?P\d+|[xX*]) - (?:\.(?P\d+|[xX*]) - (?:\.(?P\d+|[xX*]))? - )? - ) - (?P.*)$''') - _split_op_regex = re.compile(r'^(?P=|[<>!]=?)?(?P.*)$') - - # "Constructor" - def __new__(cls, sel): - """Constructor examples: - SemSel(">1.0.0") - SemSel("~1.2.9 !=1.2.12") - - Parameters: - * sel (str) - A version selector string. - - Raises: - * TypeError - `sel` parameter is not a string. - * ValueError - A version in the selector could not be matched as a SemVer. - * SemParseError - The version selector's syntax is unparsable; invalid ranges (fuzzy, xrange or - explicit range) or invalid '||' - """ - chunk = cls._parse(sel) - return super(SemSel, cls).__new__(cls, (chunk,)) - - # Magic methods - def __str__(self): - return str(self._chunk) - - def __repr__(self): - return 'SemSel("%s")' % self._chunk - - def __len__(self): - # What would you expect? - return len(self._chunk) - - def __iter__(self): - return iter(self._chunk) - - # Read-only (private) attributes - @property - def _chunk(self): - return self[0] - - # Utility methods - def matches(self, *vers): - """Match the selector against a selection of versions. - - Parameters: - * *vers (str, SemVer) - Versions can be passed as strings and SemVer objects will be created with them. - May also be a mixed list. - - Raises: - * TypeError - A version is not an instance of str (basestring) or SemVer. - * ValueError - A string version could not be parsed as a SemVer. - - Returns: - * list - A list with all the versions that matched, may be empty. Use `max()` to determine - the highest matching version, or `min()` for the lowest. - """ - ret = [] - for v in vers: - if isinstance(v, str): - t = self._chunk.matches(SemVer(v)) - elif isinstance(v, SemVer): - t = self._chunk.matches(v) - else: - raise TypeError("Invalid parameter type '%s': %s" % (v, type(v))) - if t: - ret.append(v) - - return ret - - # Private methods - @classmethod - def _parse(cls, sel): - """Private. Do not touch. - - 1. split by whitespace into tokens - a. start new and_chunk on ' || ' - b. parse " - " ranges - c. replace "xX*" ranges with "~" equivalent - d. parse "~" ranges - e. parse unmatched token as comparator - ~. append to current and_chunk - 2. return SemSelOrChunk - - Raises TypeError, ValueError or SelParseError. - """ - if not isinstance(sel, basestring): - raise TypeError("Selector must be a string") - if not sel: - raise ValueError("String must not be empty") - - # Split selector by spaces and crawl the tokens - tokens = sel.split() - i = -1 - or_chunk = SemSelOrChunk() - and_chunk = or_chunk.new_child() - - while i + 1 < len(tokens): - i += 1 - t = tokens[i] - - # Replace x ranges with ~ selector - m = cls._xrange_regex.match(t) - m = m and m.groups('') - if m and any(not x.isdigit() for x in m[1:4]) and not m[0].startswith('>'): - # (do not match '>1.0' or '>*') - if m[4]: - raise SelParseError("XRanges do not allow pre-release or build components") - - # Only use digit parts and fail if digit found after non-digit - mm, xran = [], False - for x in m[1:4]: - if x.isdigit(): - if xran: - raise SelParseError("Invalid fuzzy range or XRange '%s'" % tokens[i]) - mm.append(x) - else: - xran = True - t = m[0] + '.'.join(mm) # x for x in m[1:4] if x.isdigit()) - # Append "~" if not already present - if not t.startswith('~'): - t = '~' + t - - # switch t: - if t == '||': - if i == 0 or tokens[i - 1] == '||' or i + 1 == len(tokens): - raise SelParseError("OR range must not be empty") - # Start a new and_chunk - and_chunk = or_chunk.new_child() - - elif t == '-': - # ' - ' range - i += 1 - invalid = False - try: - # If these result in exceptions, you know you're doing it wrong - t = tokens[i] - c = and_chunk[-1] - except (Exception): - raise SelParseError("Invalid ' - ' range position") - - # If there is an op in front of one of the bound versions - invalid = (c.op not in ('=', '~') - or cls._split_op_regex.match(t).group(1) not in (None, '=')) - if invalid: - raise SelParseError("Invalid ' - ' range '%s - %s'" - % (tokens[i - 2], tokens[i])) - - c.op = ">=" - and_chunk.add_child('<=', t) - - elif t == '': - # Multiple spaces - pass - - elif t.startswith('~'): - m = cls._fuzzy_regex.match(t) - if not m: - raise SelParseError("Invalid fuzzy range or XRange '%s'" % tokens[i]) - - mm, m = m.groups('')[1:4], m.groupdict('') # mm: major to patch - - # Minimum requirement - min_ver = ('.'.join(x or '0' for x in mm) + '-' - if not m['other'] - else cls._split_op_regex(t[1:]).group('ver')) - and_chunk.add_child('>=', min_ver) - - if m['major']: - # Increase version before none (or second to last if '~1.2.3') - e = [0, 0, 0] - for j, d in enumerate(mm): - if not d or j == len(mm) - 1: - e[j - 1] = e[j - 1] + 1 - break - e[j] = int(d) - - and_chunk.add_child('<', '.'.join(str(x) for x in e) + '-') - - # else: just plain '~' or '*', or '~>X' which are already handled - - else: - # A normal comparator - m = cls._split_op_regex.match(t).groupdict() # this regex can't fail - and_chunk.add_child(**m) - - # Finally return the or_chunk - return or_chunk diff --git a/app/lib/package_control/downloaders/background_downloader.py b/app/lib/package_control/downloaders/background_downloader.py deleted file mode 100644 index a689d8e..0000000 --- a/app/lib/package_control/downloaders/background_downloader.py +++ /dev/null @@ -1,63 +0,0 @@ -import threading - - -class BackgroundDownloader(threading.Thread): - - """ - Downloads information from one or more URLs in the background. - Normal usage is to use one BackgroundDownloader per domain name. - - :param settings: - A dict containing at least the following fields: - `cache_length`, - `debug`, - `timeout`, - `user_agent`, - `http_proxy`, - `https_proxy`, - `proxy_username`, - `proxy_password` - - :param providers: - An array of providers that can download the URLs - """ - - def __init__(self, settings, providers): - self.settings = settings - self.urls = [] - self.providers = providers - self.used_providers = {} - threading.Thread.__init__(self) - - def add_url(self, url): - """ - Adds a URL to the list to download - - :param url: - The URL to download info about - """ - - self.urls.append(url) - - def get_provider(self, url): - """ - Returns the provider for the URL specified - - :param url: - The URL to return the provider for - - :return: - The provider object for the URL - """ - - return self.used_providers.get(url) - - def run(self): - for url in self.urls: - for provider_class in self.providers: - if provider_class.match_url(url): - provider = provider_class(url, self.settings) - break - - provider.prefetch() - self.used_providers[url] = provider diff --git a/app/lib/package_control/downloaders/oscrypto_downloader.py b/app/lib/package_control/downloaders/oscrypto_downloader.py index ddae04c..7d6346c 100644 --- a/app/lib/package_control/downloaders/oscrypto_downloader.py +++ b/app/lib/package_control/downloaders/oscrypto_downloader.py @@ -36,10 +36,13 @@ 'sublime_text' in sys.executable or 'plugin_host' in sys.executable): install_dir = os.path.dirname(sys.executable) - use_openssl( - os.path.join(install_dir, 'libcrypto.so.1.1'), - os.path.join(install_dir, 'libssl.so.1.1') - ) + try: + use_openssl( + os.path.join(install_dir, 'libcrypto.so.1.1'), + os.path.join(install_dir, 'libssl.so.1.1') + ) + except RuntimeError: + pass # runtime error may be raised, when reloading modules. from ..deps.oscrypto import tls # noqa from ..deps.oscrypto import errors as oscrypto_errors # noqa diff --git a/app/lib/package_control/http/validating_https_connection.py b/app/lib/package_control/http/validating_https_connection.py index af9e1e2..8448792 100644 --- a/app/lib/package_control/http/validating_https_connection.py +++ b/app/lib/package_control/http/validating_https_connection.py @@ -85,6 +85,14 @@ def validate_cert_host(self, cert, hostname): return True return False + # Compatibility for python 3.3 vs 3.8 + # python 3.8 replaced _set_hostport() by _get_hostport() + if not hasattr(DebuggableHTTPConnection, '_set_hostport'): + + def _set_hostport(self, host, port): + (self.host, self.port) = self._get_hostport(host, port) + self._validate_host(self.host) + def _tunnel(self): """ This custom _tunnel method allows us to read and print the debug diff --git a/app/lib/package_control/package_version.py b/app/lib/package_control/package_version.py index 1a96f5f..dd508cc 100644 --- a/app/lib/package_control/package_version.py +++ b/app/lib/package_control/package_version.py @@ -1,55 +1,38 @@ import re -from .deps.semver import SemVer from .console_write import console_write +from .pep440 import PEP440Version, PEP440InvalidVersionError -class PackageVersion(SemVer): +class PackageVersion(PEP440Version): + __slots__ = ["_str"] - _date_pattern = re.compile(r'^(\d{4})\.(\d{2})\.(\d{2})\.(\d{2})\.(\d{2})\.(\d{2})$') - _pre_semver_pattern = re.compile(r'^(\d+)(?:\.(\d+)(?:\.(\d+)(?:[T\.](\d+(\.\d+)*))?)?)?$') + _date_time_regex = re.compile(r"^\d{4}\.\d{2}\.\d{2}(?:\.\d{2}\.\d{2}\.\d{2})?$") - @classmethod - def _parse(cls, ver): + def __init__(self, ver): """ - Converts a string version number into SemVer. If the version is based on - a date, converts to 0.0.1+yyyy.mm.dd.hh.mm.ss. + Initialize a ``PackageVersion`` instance. + + The initializer acts as compatibility layer to convert legacy version schemes + into a ``PEP440Version``. + + If the version is based on a date, converts to 0.0.1+yyyy.mm.dd.hh.mm.ss. :param ver: A string, dict with 'version' key, or a SemVer object :raises: - TypeError, if ver is not one of: str, dict with version, SemVer + TypeError, if ver is not a ``str``. ValueError, if ver is no valid version string - - :return: - A list of 5 items representing a valid semantic version number """ - # Allowing passing in a dict containing info about a package - if isinstance(ver, dict): - if 'version' not in ver: - raise TypeError("%s is not a package or library release" % ver) - ver = ver['version'] - - if isinstance(ver, SemVer): - return ver - if not isinstance(ver, str): - raise TypeError("%r is not a string" % ver) + raise TypeError("{!r} is not a string".format(ver)) - # Trim v off of the front - if ver.startswith('v'): - ver = ver[1:] - - # Match semver compatible strings - match = cls._match_regex.match(ver) - if match: - g = list(match.groups()) - for i in range(3): - g[i] = int(g[i]) - - return g + # Store original version string to maintain backward compatibility + # with regards to not normalize it. + # The one and only use case is to keep existing CI tests working without change. + self._str = ver # We prepend 0 to all date-based version numbers so that developers # may switch to explicit versioning from GitHub/GitLab/BitBucket @@ -61,23 +44,24 @@ def _parse(cls, ver): # # The result looks like: # 0.0.1+2020.07.15.10.50.38 - match = cls._date_pattern.match(ver) + match = self._date_time_regex.match(ver) if match: - return [0, 0, 1, None, '.'.join(match.groups())] + ver = "0.0.1+" + ver - # This handles versions that were valid pre-semver with 1 to 4+ dotted - # groups, such as 1, 1.6, or 1.6.9.0 - match = cls._pre_semver_pattern.match(ver) - if match: - return [ - int(match.group(1) or 0), - int(match.group(2) or 0), - int(match.group(3) or 0), - None, - match.group(4) - ] + try: + super().__init__(ver) + except PEP440InvalidVersionError: + # maybe semver with incompatible pre-release tag + # if, so treat it as dev build with local version + if "-" in ver: + ver, pre = ver.split("-", 1) + if ver and pre: + super().__init__(ver + "-dev+" + pre) + return + raise - raise ValueError("'%s' is not a valid SemVer string" % ver) + def __str__(self): + return self._str def version_match_prefix(version, filter_prefix): @@ -126,21 +110,25 @@ def version_sort(sortable, *fields, **kwargs): """ def _version_sort_key(item): - result = PackageVersion(item) - if fields: - values = [result] - for field in fields: - values.append(item[field]) - result = tuple(values) - return result + if isinstance(item, dict): + if "version" not in item: + raise TypeError("%s is not a package or library release" % item) + result = PackageVersion(item["version"]) + if fields: + result = (result,) + for field in fields: + result += (item[field],) + return result + + return PackageVersion(item) try: return sorted(sortable, key=_version_sort_key, **kwargs) - except (ValueError) as e: + except ValueError as e: console_write( - ''' + """ Error sorting versions - %s - ''', - e + """, + e, ) return [] diff --git a/app/lib/package_control/providers/schema_compat.py b/app/lib/package_control/providers/schema_compat.py deleted file mode 100644 index d25ebb1..0000000 --- a/app/lib/package_control/providers/schema_compat.py +++ /dev/null @@ -1,37 +0,0 @@ -from ..deps.semver import SemVer - - -class SchemaVersion(SemVer): - supported_versions = ('2.0', '3.0.0', '4.0.0') - - @classmethod - def _parse(cls, ver): - """ - Custom version string parsing to maintain backward compatibility. - - SemVer needs all of major, minor and patch parts being present in `ver`. - - :param ver: - An integer, float or string containing a version string. - - :returns: - List of (major, minor, patch) - """ - try: - if isinstance(ver, int): - ver = float(ver) - if isinstance(ver, float): - ver = str(ver) - except ValueError: - raise ValueError('the "schema_version" is not a valid number.') - - if ver not in cls.supported_versions: - raise ValueError( - 'the "schema_version" is not recognized. Must be one of: %s or %s.' - % (', '.join(cls.supported_versions[:-1]), cls.supported_versions[-1]) - ) - - if ver.count('.') == 1: - ver += '.0' - - return SemVer._parse(ver) diff --git a/app/lib/package_control/providers/schema_version.py b/app/lib/package_control/providers/schema_version.py index d25ebb1..3e5efbb 100644 --- a/app/lib/package_control/providers/schema_version.py +++ b/app/lib/package_control/providers/schema_version.py @@ -1,11 +1,10 @@ -from ..deps.semver import SemVer +from ..pep440 import PEP440Version -class SchemaVersion(SemVer): +class SchemaVersion(PEP440Version): supported_versions = ('2.0', '3.0.0', '4.0.0') - @classmethod - def _parse(cls, ver): + def __init__(self, ver): """ Custom version string parsing to maintain backward compatibility. @@ -25,13 +24,10 @@ def _parse(cls, ver): except ValueError: raise ValueError('the "schema_version" is not a valid number.') - if ver not in cls.supported_versions: + if ver not in self.supported_versions: raise ValueError( 'the "schema_version" is not recognized. Must be one of: %s or %s.' - % (', '.join(cls.supported_versions[:-1]), cls.supported_versions[-1]) + % (', '.join(self.supported_versions[:-1]), self.supported_versions[-1]) ) - if ver.count('.') == 1: - ver += '.0' - - return SemVer._parse(ver) + super().__init__(ver) diff --git a/app/lib/st_package_reviewer/check/file/check_messages.py b/app/lib/st_package_reviewer/check/file/check_messages.py index 45be76f..5ea9b31 100644 --- a/app/lib/st_package_reviewer/check/file/check_messages.py +++ b/app/lib/st_package_reviewer/check/file/check_messages.py @@ -1,10 +1,40 @@ import json import re -from ...lib.semver import SemVer - from . import FileChecker +_semver_regex = re.compile( + r""" + ^\s* + v? + (?P[0-9]+(?:\.[0-9]+){2}) # semver release segment + (?P
                                              # pre-release
+        [-_.]?
+        (?Palpha|a|beta|b|prerelease|preview|pre|c|rc)
+        [-_.]?
+        (?P[0-9]+)?
+    )?
+    (?P                                             # post release
+        (?:-(?P[0-9]+))
+        |
+        (?:
+            [-_.]?
+            (?Ppatch|post|rev|r)
+            [-_.]?
+            (?P[0-9]+)?
+        )
+    )?
+    (?P                                              # dev release
+        [-_.]?
+        (?Pdevelopment|develop|devel|dev)
+        [-_.]?
+        (?P[0-9]+)?
+    )?
+    \s*$
+    """,
+    re.VERBOSE,
+)
+
 
 class CheckMessages(FileChecker):
 
@@ -42,7 +72,7 @@ def check(self):
             for key, rel_path in data.items():
                 if key == "install":
                     pass
-                elif SemVer.valid(re.sub(prefix_regex, '', key)):
+                elif _semver_regex.match(re.sub(prefix_regex, '', key)):
                     pass
                 else:
                     self.fail("Key {!r} is not 'install' or a valid semantic version"