nu_command/semver/
parse.rs1pub fn strip_loose_version_prefix(s: &str) -> (&str, &str) {
22 let after_v = match s.strip_prefix('v').or_else(|| s.strip_prefix('V')) {
23 Some(rest) => rest,
24 None => return ("", s),
25 };
26
27 if let Some(after_sep) = after_v
29 .strip_prefix('.')
30 .or_else(|| after_v.strip_prefix(':'))
31 .or_else(|| after_v.strip_prefix('-'))
32 .or_else(|| after_v.strip_prefix('_'))
33 && after_sep.starts_with(|c: char| c.is_ascii_digit())
34 {
35 let prefix_len = s.len() - after_sep.len();
36 return (&s[..prefix_len], after_sep);
37 }
38
39 if after_v.starts_with(|c: char| c.is_ascii_digit()) {
41 let prefix_len = s.len() - after_v.len();
42 return (&s[..prefix_len], after_v);
43 }
44
45 ("", s)
46}
47
48pub fn parse_version(s: &str, loose: bool) -> Result<(semver::Version, String), semver::Error> {
52 match semver::Version::parse(s) {
53 Ok(version) => Ok((version, String::new())),
54 Err(strict_err) => {
55 if !loose {
56 return Err(strict_err);
57 }
58 let (prefix, rest) = strip_loose_version_prefix(s);
59 if prefix.is_empty() {
60 return Err(strict_err);
61 }
62 let version = semver::Version::parse(rest)?;
63 Ok((version, prefix.to_string()))
64 }
65 }
66}
67
68pub fn normalize_loose_range(s: &str) -> String {
80 let mut out = String::with_capacity(s.len());
81 let mut rest = s;
82
83 while !rest.is_empty() {
84 let at_boundary = out.is_empty()
85 || matches!(
86 out.chars().next_back(),
87 Some(' ' | ',' | '<' | '>' | '=' | '^' | '~' | '!')
88 );
89
90 if at_boundary {
91 let (prefix, after) = strip_loose_version_prefix(rest);
92 if !prefix.is_empty() {
93 rest = after;
94 continue;
95 }
96 }
97
98 let mut chars = rest.chars();
99 let Some(ch) = chars.next() else {
100 break;
101 };
102 out.push(ch);
103 rest = chars.as_str();
104 }
105
106 out
107}
108
109pub fn parse_version_req(s: &str, loose: bool) -> Result<semver::VersionReq, semver::Error> {
111 match semver::VersionReq::parse(s) {
112 Ok(req) => Ok(req),
113 Err(strict_err) => {
114 if !loose {
115 return Err(strict_err);
116 }
117 let normalized = normalize_loose_range(s);
118 if normalized == s {
119 return Err(strict_err);
120 }
121 semver::VersionReq::parse(&normalized)
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn strip_prefix_variants() {
132 assert_eq!(strip_loose_version_prefix("v1.2.3"), ("v", "1.2.3"));
133 assert_eq!(strip_loose_version_prefix("V1.2.3"), ("V", "1.2.3"));
134 assert_eq!(strip_loose_version_prefix("v.1.2.3"), ("v.", "1.2.3"));
135 assert_eq!(strip_loose_version_prefix("v:1.2.3"), ("v:", "1.2.3"));
136 assert_eq!(strip_loose_version_prefix("V:1.2.3"), ("V:", "1.2.3"));
137 assert_eq!(strip_loose_version_prefix("v-1.2.3"), ("v-", "1.2.3"));
138 assert_eq!(strip_loose_version_prefix("v_1.2.3"), ("v_", "1.2.3"));
139 assert_eq!(strip_loose_version_prefix("V-1.2.3"), ("V-", "1.2.3"));
140 assert_eq!(strip_loose_version_prefix("V_1.2.3"), ("V_", "1.2.3"));
141 assert_eq!(strip_loose_version_prefix("1.2.3"), ("", "1.2.3"));
142 assert_eq!(strip_loose_version_prefix("version"), ("", "version"));
143 assert_eq!(
144 strip_loose_version_prefix("v1.2.3-alpha.1+build"),
145 ("v", "1.2.3-alpha.1+build")
146 );
147 assert_eq!(strip_loose_version_prefix("v-alpha"), ("", "v-alpha"));
149 }
150
151 #[test]
152 fn parse_version_loose() {
153 let (v, p) = parse_version("v1.2.3", true).unwrap();
154 assert_eq!(v.to_string(), "1.2.3");
155 assert_eq!(p, "v");
156
157 let (v, p) = parse_version("v.2.0.0", true).unwrap();
158 assert_eq!(v.to_string(), "2.0.0");
159 assert_eq!(p, "v.");
160
161 let (v, p) = parse_version("v:3.1.4", true).unwrap();
162 assert_eq!(v.to_string(), "3.1.4");
163 assert_eq!(p, "v:");
164
165 let (v, p) = parse_version("v-4.5.6", true).unwrap();
166 assert_eq!(v.to_string(), "4.5.6");
167 assert_eq!(p, "v-");
168
169 let (v, p) = parse_version("v_7.8.9", true).unwrap();
170 assert_eq!(v.to_string(), "7.8.9");
171 assert_eq!(p, "v_");
172
173 assert!(parse_version("v1.2.3", false).is_err());
174 let (v, p) = parse_version("1.2.3", true).unwrap();
175 assert_eq!(v.to_string(), "1.2.3");
176 assert!(p.is_empty());
177 }
178
179 #[test]
180 fn normalize_and_parse_range_loose() {
181 assert_eq!(normalize_loose_range(">=v1.0.0"), ">=1.0.0");
182 assert_eq!(normalize_loose_range("^v1.2.3"), "^1.2.3");
183 assert_eq!(normalize_loose_range("v1.2.3"), "1.2.3");
184 assert_eq!(
185 normalize_loose_range(">=v:1.0.0, <v.2.0.0"),
186 ">=1.0.0, <2.0.0"
187 );
188 assert_eq!(
189 normalize_loose_range(">=v-1.0.0, <v_2.0.0"),
190 ">=1.0.0, <2.0.0"
191 );
192
193 let req = parse_version_req(">=v1.0.0", true).unwrap();
194 assert_eq!(req.to_string(), ">=1.0.0");
195 assert!(parse_version_req(">=v1.0.0", false).is_err());
196
197 assert_eq!(normalize_loose_range("≥v1.0.0"), "≥v1.0.0");
199 assert_eq!(normalize_loose_range("≥ v1.0.0"), "≥ 1.0.0");
200 }
201}