-
-
Notifications
You must be signed in to change notification settings - Fork 29
Support Normalization of VersionRange #108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
keshav-space
merged 31 commits into
aboutcode-org:main
from
keshav-space:range_normalization
Jul 24, 2024
Merged
Changes from 12 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
218c61a
Add support for NormalizedVersionRanges
keshav-space 1a69d07
Test NormalizedVersionRanges
keshav-space 3b3570c
Add ABOUT and LICENSE file for spans.py
keshav-space d88b80c
Fallback to builtin set when intbitset is not installed
keshav-space 5ab9b3a
Added docs server script, dark mode & copybutton for docs
OmkarPh af7e542
Merge pull request #83 from OmkarPh/enhance/docs
AyanSinhaMahapatra 0a9d983
Update CSS to widen page and handle mobile #84
johnmhoran 4e36fc6
Delete theme_overrides_SUPERSEDED.css as no longer needed #84
johnmhoran 7d74b8a
Fix top padding for rst content
AyanSinhaMahapatra 0071028
Merge pull request #85 from nexB/84-widen-rtd-page
AyanSinhaMahapatra 008d521
Update CI runners and python version
AyanSinhaMahapatra acf94b3
Merge pull request #87 from nexB/update-macos-runners
AyanSinhaMahapatra 124da3d
Replace deprecated macos CI runners
AyanSinhaMahapatra be4e14d
Update minimum required python version to 3.8
AyanSinhaMahapatra 5c3e935
Merge pull request #90 from nexB/update-ci-runners
keshav-space f0bac8c
Support both PURL and GitLab schema in from_gitlab_native
keshav-space 629d03a
Use native impl for Maven and NuGet in from_gitlab_native
keshav-space 1c70ea5
Use proper splitter for composer in from_gitlab_native
keshav-space d109a1b
Support splitting bracket notation ranges
keshav-space 169a6a1
Add support for version range from snyk advisory
keshav-space e7d7c55
Add support for version range from discrete versions
keshav-space f8a6d70
Refactor NormalizedVersionRange
keshav-space d2904b0
Add multi vers test for normalized version range
keshav-space 5a32eda
Fix the edge case resulting in incorrect `contains` resolution
keshav-space c833b97
Refactor VersionRange normalization without Span
keshav-space 8f0d727
Add function to parse bracket notation constraints
keshav-space 3d0de11
Use from_versions for getting vers from discrete versions
keshav-space fa7009a
Merge remote-tracking branch 'origin/main' into range_normalization
keshav-space fe35a34
Merge remote-tracking branch 'skeleton/main' into range_normalization
keshav-space 594baf5
Set `shell` param to False while running code style tests
keshav-space b12572d
Use only macOS-14 image for macOS 14 CI
keshav-space File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| # | ||
| # Copyright (c) nexB Inc. and others. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Visit https://aboutcode.org and https://github.com/nexB/univers for support and download. | ||
|
|
||
|
|
||
| from typing import List | ||
|
|
||
| import attr | ||
|
|
||
| from univers.span import Span | ||
| from univers.version_constraint import VersionConstraint | ||
| from univers.version_range import RANGE_CLASS_BY_SCHEMES | ||
| from univers.version_range import VersionRange | ||
|
|
||
|
|
||
| @attr.s(frozen=True, order=False, eq=True, hash=True) | ||
| class NormalizedVersionRange: | ||
| """ | ||
| A normalized_range is a summation of the largest contiguous version ranges. | ||
|
|
||
| For example, for an npm package with the version range "vers:npm/<=2.0.0|>=3.0.0|<3.1.0|4.0.0" | ||
| and available package versions ["1.0.0", "2.0.0", "3.0.0", "3.1.0", "4.0.0"], the normalized range | ||
| would be "vers:npm/>=1.0.0|<=3.0.0|4.0.0". | ||
| """ | ||
|
|
||
| normalized_range = attr.ib(type=VersionRange, default=None) | ||
|
|
||
| def __str__(self): | ||
| return str(self.normalized_range) | ||
|
|
||
| @classmethod | ||
| def from_vers(cls, vers_range: VersionRange, all_versions: List): | ||
| """ | ||
| Return NormalizedVersionRange computed from vers_range and all the available version of package. | ||
| """ | ||
| version_class = vers_range.version_class | ||
| versions = sorted([version_class(i.lstrip("vV")) for i in all_versions]) | ||
|
keshav-space marked this conversation as resolved.
Outdated
|
||
|
|
||
| bounded_span = None | ||
| total_span = None | ||
| for constraint in vers_range.constraints: | ||
| local_span = get_region(constraint=constraint, versions=versions) | ||
|
|
||
| if bounded_span and constraint.comparator in ("<", "<="): | ||
| local_span = bounded_span.intersection(local_span) | ||
| elif constraint.comparator in (">", ">="): | ||
| bounded_span = local_span | ||
| continue | ||
|
|
||
| total_span = local_span if not total_span else total_span.union(local_span) | ||
| bounded_span = None | ||
|
|
||
| # If '<' or '<=' is the last constraint. | ||
| if bounded_span: | ||
| total_span = bounded_span if not total_span else total_span.union(bounded_span) | ||
|
|
||
| normalized_version_range = get_version_range_from_span( | ||
| span=total_span, purl_type=vers_range.scheme, versions=versions | ||
| ) | ||
| return cls(normalized_range=normalized_version_range) | ||
|
|
||
|
|
||
| def get_version_range_from_span(span: Span, purl_type: str, versions: List): | ||
| """ | ||
| Return VersionRange computed from the span and all the versions available for package. | ||
|
|
||
| For example:: | ||
| >>> from univers.versions import SemverVersion as SV | ||
| >>> versions = [SV("1.0"), SV("1.1"), SV("1.2"), SV("1.3")] | ||
| >>> span = Span(0,1).union(Span(3)) | ||
| >>> vr = get_version_range_from_span(span, "npm", versions) | ||
| >>> assert str(vr) == "vers:npm/>=1.0.0|<=1.1.0|1.3.0" | ||
| """ | ||
|
|
||
| version_range_class = RANGE_CLASS_BY_SCHEMES[purl_type] | ||
| version_constraints = [] | ||
| spans = span.subspans() | ||
| for subspan in spans: | ||
| lower_bound = versions[subspan.start] | ||
| upper_bound = versions[subspan.end] | ||
| if lower_bound == upper_bound: | ||
| version_constraints.append( | ||
| VersionConstraint( | ||
| version=lower_bound, | ||
| ) | ||
| ) | ||
| continue | ||
| version_constraints.append(VersionConstraint(comparator=">=", version=lower_bound)) | ||
| version_constraints.append( | ||
| VersionConstraint( | ||
| comparator="<=", | ||
| version=upper_bound, | ||
| ) | ||
| ) | ||
|
|
||
| return version_range_class(constraints=version_constraints) | ||
|
|
||
|
|
||
| def get_region(constraint: VersionConstraint, versions: List): | ||
| """ | ||
| Return a Span representing the region covered by the constraint on | ||
| the given universe of versions. | ||
|
|
||
| For example:: | ||
| >>> from univers.versions import SemverVersion as SV | ||
| >>> versions = [SV("1.0"), SV("1.1"), SV("1.2"), SV("1.3")] | ||
| >>> constraint = VersionConstraint(comparator="<", version=SV("1.2")) | ||
| >>> get_region(constraint, versions) | ||
| Span(0, 1) | ||
| """ | ||
|
|
||
| try: | ||
| index = 0 | ||
| if str(constraint.version) != "0": | ||
| index = versions.index(constraint.version) | ||
| except ValueError as err: | ||
| err.args = (f"'{constraint.version}' doesn't exist in the versions list.",) | ||
| raise | ||
|
|
||
| last_index = len(versions) - 1 | ||
| comparator = constraint.comparator | ||
|
|
||
| if comparator == "<": | ||
| return Span(0, index - 1) | ||
| if comparator == ">": | ||
| return Span(index + 1, last_index) | ||
| if comparator == ">=": | ||
| return Span(index, last_index) | ||
| if comparator == "<=": | ||
| return Span(0, index) | ||
| if comparator == "=": | ||
| return Span(index) | ||
| if comparator == "!=": | ||
| return Span(0, last_index).difference(Span(index)) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.