upd-cli 0.0.25__tar.gz → 0.0.27__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {upd_cli-0.0.25 → upd_cli-0.0.27}/CHANGELOG.md +15 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/Cargo.lock +1 -1
- {upd_cli-0.0.25 → upd_cli-0.0.27}/Cargo.toml +1 -1
- {upd_cli-0.0.25 → upd_cli-0.0.27}/PKG-INFO +1 -1
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/align.rs +2 -10
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/pypi.rs +129 -27
- upd_cli-0.0.27/src/version/mod.rs +105 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/version/pep440.rs +3 -0
- upd_cli-0.0.25/src/version/mod.rs +0 -55
- {upd_cli-0.0.25 → upd_cli-0.0.27}/.mise.toml +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/.pre-commit-config.yaml +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/.pre-commit-hooks.yaml +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/.rumdl.toml +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/LICENSE +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/Makefile +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/README.md +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/assets/logo-wide.svg +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/assets/logo.svg +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/pyproject.toml +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/python/upd_cli/__init__.py +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/python/upd_cli/__main__.py +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/python/upd_cli/py.typed +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/rust-toolchain.toml +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/audit.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/cache.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/cli.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/config.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/interactive.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/lib.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/lockfile.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/main.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/crates_io.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/github_releases.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/go_proxy.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/mock.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/mod.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/npm.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/nuget.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/rubygems.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/terraform.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/utils.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/cargo_toml.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/csproj.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/gemfile.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/github_actions.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/go_mod.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/mise.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/mod.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/package_json.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/pre_commit.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/pyproject.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/requirements.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/terraform.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/src/version/semver_util.rs +0 -0
- {upd_cli-0.0.25 → upd_cli-0.0.27}/vership.toml +0 -0
|
@@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## [0.0.27](https://github.com/rvben/upd/compare/v0.0.26...v0.0.27) - 2026-04-15
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **align**: use pep440_rs for Python stable-version check ([7f132b3](https://github.com/rvben/upd/commit/7f132b351cdd9225a31df96d2a421c8c42926987))
|
|
16
|
+
- **version**: use PEP 440 release segments for precision matching ([fff041d](https://github.com/rvben/upd/commit/fff041d2d2e9508f117012a6bfc857ee57e5cd20))
|
|
17
|
+
|
|
18
|
+
## [0.0.26](https://github.com/rvben/upd/compare/v0.0.25...v0.0.26) - 2026-04-15
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **pypi**: rewrite HTML Simple API parser to handle multi-line anchor tags ([f9c937b](https://github.com/rvben/upd/commit/f9c937be297112e0556cae205f8c0f3ce54997f4))
|
|
23
|
+
|
|
9
24
|
## [0.0.25](https://github.com/rvben/upd/compare/v0.0.24...v0.0.25) - 2026-04-15
|
|
10
25
|
|
|
11
26
|
### Fixed
|
|
@@ -8,6 +8,7 @@ use crate::updater::{
|
|
|
8
8
|
Lang, MiseUpdater, PackageJsonUpdater, ParsedDependency, PreCommitUpdater, PyProjectUpdater,
|
|
9
9
|
RequirementsUpdater, TerraformUpdater, Updater,
|
|
10
10
|
};
|
|
11
|
+
use crate::version::is_stable_pep440;
|
|
11
12
|
use anyhow::Result;
|
|
12
13
|
use std::collections::HashMap;
|
|
13
14
|
use std::path::{Path, PathBuf};
|
|
@@ -170,16 +171,7 @@ fn find_highest_version(occurrences: &[PackageOccurrence], lang: Lang) -> Option
|
|
|
170
171
|
/// Check if a version is stable (not a pre-release)
|
|
171
172
|
fn is_stable_version(version: &str, lang: Lang) -> bool {
|
|
172
173
|
match lang {
|
|
173
|
-
Lang::Python =>
|
|
174
|
-
// Python pre-release indicators: a, b, rc, alpha, beta, dev
|
|
175
|
-
let v = version.to_lowercase();
|
|
176
|
-
!v.contains("a")
|
|
177
|
-
&& !v.contains("b")
|
|
178
|
-
&& !v.contains("rc")
|
|
179
|
-
&& !v.contains("alpha")
|
|
180
|
-
&& !v.contains("beta")
|
|
181
|
-
&& !v.contains("dev")
|
|
182
|
-
}
|
|
174
|
+
Lang::Python => is_stable_pep440(version),
|
|
183
175
|
Lang::Node | Lang::Rust | Lang::Go | Lang::DotNet => {
|
|
184
176
|
// Semver pre-release indicator: hyphen followed by identifier
|
|
185
177
|
!version.contains('-')
|
|
@@ -542,38 +542,88 @@ impl PyPiRegistry {
|
|
|
542
542
|
let mut versions: Vec<(Version, String)> = Vec::new();
|
|
543
543
|
let normalized = package.to_lowercase().replace('_', "-");
|
|
544
544
|
|
|
545
|
-
//
|
|
546
|
-
//
|
|
547
|
-
//
|
|
545
|
+
// Use a state machine to collect multi-line <a> opening tags.
|
|
546
|
+
//
|
|
547
|
+
// Some registries (e.g. Nexus Repository Manager) spread attributes
|
|
548
|
+
// across multiple lines:
|
|
549
|
+
// <a href="...pkg-1.0.0.whl#sha256=..."
|
|
550
|
+
// rel="internal"
|
|
551
|
+
// data-requires-python=">=3.10"
|
|
552
|
+
// >
|
|
553
|
+
// pkg-1.0.0.whl
|
|
554
|
+
// </a>
|
|
555
|
+
//
|
|
556
|
+
// We extract the filename from the `href` URL (the last path segment
|
|
557
|
+
// before the `#sha256=...` fragment) rather than the link text, so
|
|
558
|
+
// both single-line and multi-line formats are handled uniformly.
|
|
559
|
+
let mut collecting = false;
|
|
560
|
+
let mut is_yanked = false;
|
|
561
|
+
let mut href_value: Option<String> = None;
|
|
562
|
+
|
|
548
563
|
for line in html.lines() {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
564
|
+
let trimmed = line.trim();
|
|
565
|
+
|
|
566
|
+
// Detect start of an anchor element
|
|
567
|
+
if trimmed.starts_with("<a ") || trimmed.starts_with("<a\t") {
|
|
568
|
+
collecting = true;
|
|
569
|
+
is_yanked = false;
|
|
570
|
+
href_value = None;
|
|
552
571
|
}
|
|
553
572
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
};
|
|
560
|
-
let filename = &line[start + 1..start + end];
|
|
561
|
-
let Some(version_str) = Self::extract_version_from_filename(filename, &normalized)
|
|
562
|
-
else {
|
|
563
|
-
continue;
|
|
564
|
-
};
|
|
573
|
+
if collecting {
|
|
574
|
+
// Check for yanked attribute (may be on any line of the element)
|
|
575
|
+
if trimmed.contains("data-yanked") {
|
|
576
|
+
is_yanked = true;
|
|
577
|
+
}
|
|
565
578
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
579
|
+
// Extract href value from whichever line carries it
|
|
580
|
+
if href_value.is_none()
|
|
581
|
+
&& let Some(href_start) = trimmed.find("href=\"")
|
|
582
|
+
{
|
|
583
|
+
let after = &trimmed[href_start + 6..];
|
|
584
|
+
if let Some(href_end) = after.find('"') {
|
|
585
|
+
href_value = Some(after[..href_end].to_string());
|
|
586
|
+
}
|
|
587
|
+
}
|
|
569
588
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
589
|
+
// The opening tag ends when we see a bare `>`.
|
|
590
|
+
// `>` in attribute values (e.g. data-requires-python=">=3.10")
|
|
591
|
+
// is the HTML entity and does NOT contain a literal `>` byte in the
|
|
592
|
+
// raw HTTP response, so it won't trigger this check.
|
|
593
|
+
if trimmed.contains('>') {
|
|
594
|
+
collecting = false;
|
|
573
595
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
596
|
+
if is_yanked {
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
let Some(href) = href_value.take() else {
|
|
601
|
+
continue;
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// Filename is the last URL path segment, before any `#fragment`
|
|
605
|
+
let url_path = href.split('#').next().unwrap_or(&href);
|
|
606
|
+
let filename = url_path.split('/').next_back().unwrap_or("");
|
|
607
|
+
|
|
608
|
+
let Some(version_str) =
|
|
609
|
+
Self::extract_version_from_filename(filename, &normalized)
|
|
610
|
+
else {
|
|
611
|
+
continue;
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
if !include_prereleases && !Self::is_stable_version(&version_str) {
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
let Ok(version) = version_str.parse::<Version>() else {
|
|
619
|
+
continue;
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
// Avoid duplicates (e.g. both .whl and .tar.gz for the same version)
|
|
623
|
+
if !versions.iter().any(|(_, v)| v == &version_str) {
|
|
624
|
+
versions.push((version, version_str));
|
|
625
|
+
}
|
|
626
|
+
}
|
|
577
627
|
}
|
|
578
628
|
}
|
|
579
629
|
|
|
@@ -846,6 +896,9 @@ mod tests {
|
|
|
846
896
|
fn test_stable_version_detection() {
|
|
847
897
|
assert!(PyPiRegistry::is_stable_version("1.0.0"));
|
|
848
898
|
assert!(PyPiRegistry::is_stable_version("2.31.0"));
|
|
899
|
+
// Post-releases are stable releases that fix packaging issues
|
|
900
|
+
assert!(PyPiRegistry::is_stable_version("1.0.0.post1"));
|
|
901
|
+
assert!(PyPiRegistry::is_stable_version("2.1117.0.post1"));
|
|
849
902
|
assert!(!PyPiRegistry::is_stable_version("1.0.0a1"));
|
|
850
903
|
assert!(!PyPiRegistry::is_stable_version("1.0.0b2"));
|
|
851
904
|
assert!(!PyPiRegistry::is_stable_version("1.0.0rc1"));
|
|
@@ -952,6 +1005,56 @@ mod tests {
|
|
|
952
1005
|
assert_eq!(versions[1].1, "1.0.0");
|
|
953
1006
|
}
|
|
954
1007
|
|
|
1008
|
+
#[test]
|
|
1009
|
+
fn test_parse_simple_api_response_multiline_anchor_format() {
|
|
1010
|
+
// Some registries (e.g. Nexus Repository Manager) spread <a> attributes
|
|
1011
|
+
// across multiple lines, with the filename text on a separate line after
|
|
1012
|
+
// the closing '>'. Versions must be extracted from the href URL, not the
|
|
1013
|
+
// link text.
|
|
1014
|
+
let registry = PyPiRegistry::new();
|
|
1015
|
+
let html = r#"
|
|
1016
|
+
<!DOCTYPE html>
|
|
1017
|
+
<html lang="en">
|
|
1018
|
+
<head><title>Links for my-package</title>
|
|
1019
|
+
<meta name="api-version" value="2"/>
|
|
1020
|
+
</head>
|
|
1021
|
+
<body>
|
|
1022
|
+
<h1>Links for my-package</h1>
|
|
1023
|
+
<a href="../../packages/my-package/2.0.0/my_package-2.0.0.tar.gz#sha256=abc123"
|
|
1024
|
+
rel="internal"
|
|
1025
|
+
data-requires-python=">=3.10"
|
|
1026
|
+
|
|
1027
|
+
>
|
|
1028
|
+
my_package-2.0.0.tar.gz
|
|
1029
|
+
</a>
|
|
1030
|
+
<br/>
|
|
1031
|
+
<a href="../../packages/my-package/2.0.0/my_package-2.0.0-py3-none-any.whl#sha256=def456"
|
|
1032
|
+
rel="internal"
|
|
1033
|
+
data-requires-python=">=3.10"
|
|
1034
|
+
|
|
1035
|
+
>
|
|
1036
|
+
my_package-2.0.0-py3-none-any.whl
|
|
1037
|
+
</a>
|
|
1038
|
+
<br/>
|
|
1039
|
+
<a href="../../packages/my-package/1.9.0/my_package-1.9.0-py3-none-any.whl#sha256=ghi789"
|
|
1040
|
+
rel="internal"
|
|
1041
|
+
data-requires-python=">=3.10"
|
|
1042
|
+
data-yanked="superseded"
|
|
1043
|
+
>
|
|
1044
|
+
my_package-1.9.0-py3-none-any.whl
|
|
1045
|
+
</a>
|
|
1046
|
+
</body>
|
|
1047
|
+
</html>
|
|
1048
|
+
"#;
|
|
1049
|
+
let versions = registry
|
|
1050
|
+
.parse_simple_api_response(html, "my-package", false)
|
|
1051
|
+
.unwrap();
|
|
1052
|
+
// 2.0.0 appears twice (tar.gz + whl) but should be deduplicated
|
|
1053
|
+
// 1.9.0 is yanked and should be excluded
|
|
1054
|
+
assert_eq!(versions.len(), 1);
|
|
1055
|
+
assert_eq!(versions[0].1, "2.0.0");
|
|
1056
|
+
}
|
|
1057
|
+
|
|
955
1058
|
#[test]
|
|
956
1059
|
fn test_parse_simple_api_json_response() {
|
|
957
1060
|
let registry = PyPiRegistry::new();
|
|
@@ -1901,7 +2004,6 @@ mod tests {
|
|
|
1901
2004
|
let mock_server = MockServer::start().await;
|
|
1902
2005
|
|
|
1903
2006
|
// Server responds with HTML (no PEP 691 support)
|
|
1904
|
-
// Each <a> tag needs to be on its own line for the parser
|
|
1905
2007
|
let html = r#"<!DOCTYPE html>
|
|
1906
2008
|
<html>
|
|
1907
2009
|
<body>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
pub mod pep440;
|
|
2
|
+
pub mod semver_util;
|
|
3
|
+
|
|
4
|
+
pub use pep440::is_stable_pep440;
|
|
5
|
+
pub use semver_util::is_stable_semver;
|
|
6
|
+
|
|
7
|
+
/// Match the precision of a new version to the original version's precision.
|
|
8
|
+
///
|
|
9
|
+
/// For PEP 440 versions (Python), the release segment length is determined by
|
|
10
|
+
/// parsing with `pep440_rs`, so post-release (`.post1`), dev (`.dev0`), and
|
|
11
|
+
/// pre-release (`a1`, `b1`, `rc1`) suffixes are not counted as release segments
|
|
12
|
+
/// and are preserved when the release segment count matches.
|
|
13
|
+
///
|
|
14
|
+
/// For other version schemes (semver, Go, Ruby…) that `pep440_rs` cannot parse,
|
|
15
|
+
/// the function falls back to counting dot-separated segments.
|
|
16
|
+
///
|
|
17
|
+
/// Examples:
|
|
18
|
+
/// - ("2.0", "3.0.5") → "3.0"
|
|
19
|
+
/// - ("2.0.0", "3.0.5") → "3.0.5"
|
|
20
|
+
/// - ("2", "3.0.5") → "3"
|
|
21
|
+
/// - ("1.2.3.4", "5.6.7.8") → "5.6.7.8" (preserve all parts)
|
|
22
|
+
/// - ("2.1117.0", "2.1117.0.post1") → "2.1117.0.post1" (post-release preserved)
|
|
23
|
+
pub fn match_version_precision(original: &str, new_version: &str) -> String {
|
|
24
|
+
use pep440_rs::Version;
|
|
25
|
+
|
|
26
|
+
// Try PEP 440 parsing first; `.release()` returns only the numeric
|
|
27
|
+
// release tuple, correctly excluding pre/post/dev labels.
|
|
28
|
+
if let (Ok(orig), Ok(new)) = (original.parse::<Version>(), new_version.parse::<Version>()) {
|
|
29
|
+
let orig_len = orig.release().len();
|
|
30
|
+
let new_len = new.release().len();
|
|
31
|
+
|
|
32
|
+
return if orig_len >= new_len {
|
|
33
|
+
// Same or fewer release segments — keep the full new version string
|
|
34
|
+
// (including any post/pre/dev suffix) as written by the caller.
|
|
35
|
+
new_version.to_string()
|
|
36
|
+
} else {
|
|
37
|
+
// More release segments than original — truncate to original precision.
|
|
38
|
+
// Any post/pre/dev suffix on the new version is intentionally dropped
|
|
39
|
+
// since it belongs to a more specific release than we're tracking.
|
|
40
|
+
new.release()[..orig_len]
|
|
41
|
+
.iter()
|
|
42
|
+
.map(|n| n.to_string())
|
|
43
|
+
.collect::<Vec<_>>()
|
|
44
|
+
.join(".")
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Fallback for non-PEP-440 versions (semver, Go modules, etc.)
|
|
49
|
+
let original_parts: Vec<&str> = original.split('.').collect();
|
|
50
|
+
let new_parts: Vec<&str> = new_version.split('.').collect();
|
|
51
|
+
let precision = original_parts.len();
|
|
52
|
+
|
|
53
|
+
if precision >= new_parts.len() {
|
|
54
|
+
new_version.to_string()
|
|
55
|
+
} else {
|
|
56
|
+
new_parts[..precision].join(".")
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[cfg(test)]
|
|
61
|
+
mod tests {
|
|
62
|
+
use super::*;
|
|
63
|
+
|
|
64
|
+
#[test]
|
|
65
|
+
fn test_match_version_precision() {
|
|
66
|
+
// Major.minor only
|
|
67
|
+
assert_eq!(match_version_precision("2.0", "3.0.5"), "3.0");
|
|
68
|
+
assert_eq!(match_version_precision("2.8", "3.1.2"), "3.1");
|
|
69
|
+
|
|
70
|
+
// Full precision
|
|
71
|
+
assert_eq!(match_version_precision("2.0.0", "3.0.5"), "3.0.5");
|
|
72
|
+
assert_eq!(match_version_precision("1.2.3", "4.5.6"), "4.5.6");
|
|
73
|
+
|
|
74
|
+
// Major only
|
|
75
|
+
assert_eq!(match_version_precision("2", "3.0.5"), "3");
|
|
76
|
+
|
|
77
|
+
// New version has fewer parts than original (edge case)
|
|
78
|
+
assert_eq!(match_version_precision("2.0.0", "3.0"), "3.0");
|
|
79
|
+
assert_eq!(match_version_precision("2.0.0.0", "3.0.5"), "3.0.5");
|
|
80
|
+
|
|
81
|
+
// Same precision
|
|
82
|
+
assert_eq!(match_version_precision("1.0.0", "2.0.0"), "2.0.0");
|
|
83
|
+
|
|
84
|
+
// PEP 440 post-release suffix must be preserved when release precision matches
|
|
85
|
+
assert_eq!(
|
|
86
|
+
match_version_precision("2.1117.0", "2.1117.0.post1"),
|
|
87
|
+
"2.1117.0.post1"
|
|
88
|
+
);
|
|
89
|
+
assert_eq!(
|
|
90
|
+
match_version_precision("1.0.0", "1.0.0.post2"),
|
|
91
|
+
"1.0.0.post2"
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// PEP 440 pre-release labels (no dot separator) are also preserved
|
|
95
|
+
assert_eq!(match_version_precision("2.0.0", "2.0.1a1"), "2.0.1a1");
|
|
96
|
+
assert_eq!(match_version_precision("2.0.0", "2.0.1b1"), "2.0.1b1");
|
|
97
|
+
assert_eq!(match_version_precision("2.0.0", "2.0.1rc1"), "2.0.1rc1");
|
|
98
|
+
|
|
99
|
+
// PEP 440 dev releases are also preserved
|
|
100
|
+
assert_eq!(match_version_precision("2.0.0", "2.0.0.dev1"), "2.0.0.dev1");
|
|
101
|
+
|
|
102
|
+
// Truncation path: suffix is dropped when original has lower precision
|
|
103
|
+
assert_eq!(match_version_precision("2.0", "3.0.5.post1"), "3.0");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -28,6 +28,9 @@ mod tests {
|
|
|
28
28
|
assert!(is_stable_pep440("0.1.0"));
|
|
29
29
|
assert!(is_stable_pep440("1.0"));
|
|
30
30
|
assert!(is_stable_pep440("1"));
|
|
31
|
+
// Post-releases are stable releases that fix packaging issues
|
|
32
|
+
assert!(is_stable_pep440("1.0.0.post1"));
|
|
33
|
+
assert!(is_stable_pep440("2.1117.0.post1"));
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
#[test]
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
pub mod pep440;
|
|
2
|
-
pub mod semver_util;
|
|
3
|
-
|
|
4
|
-
pub use pep440::is_stable_pep440;
|
|
5
|
-
pub use semver_util::is_stable_semver;
|
|
6
|
-
|
|
7
|
-
/// Match the precision of a new version to the original version's precision.
|
|
8
|
-
///
|
|
9
|
-
/// Examples:
|
|
10
|
-
/// - ("2.0", "3.0.5") → "3.0"
|
|
11
|
-
/// - ("2.0.0", "3.0.5") → "3.0.5"
|
|
12
|
-
/// - ("2", "3.0.5") → "3"
|
|
13
|
-
/// - ("1.2.3.4", "5.6.7.8") → "5.6.7.8" (preserve all parts)
|
|
14
|
-
pub fn match_version_precision(original: &str, new_version: &str) -> String {
|
|
15
|
-
let original_parts: Vec<&str> = original.split('.').collect();
|
|
16
|
-
let new_parts: Vec<&str> = new_version.split('.').collect();
|
|
17
|
-
|
|
18
|
-
// Count how many parts the original has
|
|
19
|
-
let precision = original_parts.len();
|
|
20
|
-
|
|
21
|
-
// Take the same number of parts from the new version
|
|
22
|
-
if precision >= new_parts.len() {
|
|
23
|
-
// Original has same or more precision, use full new version
|
|
24
|
-
new_version.to_string()
|
|
25
|
-
} else {
|
|
26
|
-
// Original has less precision, truncate new version
|
|
27
|
-
new_parts[..precision].join(".")
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
#[cfg(test)]
|
|
32
|
-
mod tests {
|
|
33
|
-
use super::*;
|
|
34
|
-
|
|
35
|
-
#[test]
|
|
36
|
-
fn test_match_version_precision() {
|
|
37
|
-
// Major.minor only
|
|
38
|
-
assert_eq!(match_version_precision("2.0", "3.0.5"), "3.0");
|
|
39
|
-
assert_eq!(match_version_precision("2.8", "3.1.2"), "3.1");
|
|
40
|
-
|
|
41
|
-
// Full precision
|
|
42
|
-
assert_eq!(match_version_precision("2.0.0", "3.0.5"), "3.0.5");
|
|
43
|
-
assert_eq!(match_version_precision("1.2.3", "4.5.6"), "4.5.6");
|
|
44
|
-
|
|
45
|
-
// Major only
|
|
46
|
-
assert_eq!(match_version_precision("2", "3.0.5"), "3");
|
|
47
|
-
|
|
48
|
-
// New version has fewer parts than original (edge case)
|
|
49
|
-
assert_eq!(match_version_precision("2.0.0", "3.0"), "3.0");
|
|
50
|
-
assert_eq!(match_version_precision("2.0.0.0", "3.0.5"), "3.0.5");
|
|
51
|
-
|
|
52
|
-
// Same precision
|
|
53
|
-
assert_eq!(match_version_precision("1.0.0", "2.0.0"), "2.0.0");
|
|
54
|
-
}
|
|
55
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|