nu_command/conversions/into/
semver_range.rs1use crate::semver::parse;
2use crate::semver::range::SemverRangeValue;
3use nu_engine::command_prelude::*;
4use nu_protocol::shell_error::generic::GenericError;
5
6#[derive(Clone)]
7pub struct IntoSemverRange;
8
9impl Command for IntoSemverRange {
10 fn name(&self) -> &str {
11 "into semver-range"
12 }
13
14 fn signature(&self) -> Signature {
15 Signature::build("into semver-range")
16 .input_output_types(vec![
17 (Type::String, Type::Custom("semver-range".into())),
18 (
19 Type::Custom("semver-range".into()),
20 Type::Custom("semver-range".into()),
21 ),
22 ])
23 .switch(
24 "loose",
25 "Allow common non-strict version prefixes such as v1.2.3, v.1.2.3, v:1.2.3, v-1.2.3, or v_1.2.3 inside the range",
26 Some('l'),
27 )
28 .category(Category::Conversions)
29 }
30
31 fn description(&self) -> &str {
32 "Convert a string to a semver range."
33 }
34
35 fn search_terms(&self) -> Vec<&str> {
36 vec!["version", "requirement", "semantic"]
37 }
38
39 fn run(
40 &self,
41 engine_state: &EngineState,
42 stack: &mut Stack,
43 call: &Call,
44 input: PipelineData,
45 ) -> Result<PipelineData, ShellError> {
46 let head = call.head;
47 let loose = call.has_flag(engine_state, stack, "loose")?;
48
49 input.map(
50 move |value| into_semver_range(&value, head, loose),
51 engine_state.signals(),
52 )
53 }
54
55 fn examples(&self) -> Vec<Example<'static>> {
56 vec![
57 Example {
58 description: "Convert a string to a semver range",
59 example: "'>=1.0.0' | into semver-range",
60 result: None,
61 },
62 Example {
63 description: "Convert a complex range",
64 example: "'^1.2.3' | into semver-range",
65 result: None,
66 },
67 Example {
68 description: "Parse a range with a leading v on the version",
69 example: "'>=v1.0.0' | into semver-range --loose",
70 result: None,
71 },
72 ]
73 }
74}
75
76fn into_semver_range(input: &Value, head: Span, loose: bool) -> Value {
77 match input {
78 Value::Custom { val, .. } if val.type_name() == "semver-range" => input.clone(),
79 Value::String { val, .. } => match parse::parse_version_req(val, loose) {
80 Ok(requirement) => Value::custom(Box::new(SemverRangeValue::new(requirement)), head),
81 Err(_) => {
82 let help = if loose {
83 "expected format: >=1.0.0, ^1.2.3, ~1.2, etc.; version numbers may use a v/V, v., v:, v-, or v_ prefix"
84 } else {
85 "expected format: >=1.0.0, ^1.2.3, ~1.2, etc.; use --loose for prefixes like v1.2.3"
86 };
87 Value::error(
88 ShellError::Generic(
89 GenericError::new(
90 format!("Cannot convert \"{val}\" to a semver range"),
91 "the given string is not a valid semver requirement",
92 head,
93 )
94 .with_help(help),
95 ),
96 head,
97 )
98 }
99 },
100 _ => Value::error(
101 ShellError::Generic(GenericError::new(
102 format!("Cannot convert {} to semver range", input.get_type()),
103 "expected a string or semver-range value",
104 head,
105 )),
106 head,
107 ),
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 fn get_custom_value(value: &Value) -> &SemverRangeValue {
116 match value {
117 Value::Custom { val, .. } => val.as_any().downcast_ref::<SemverRangeValue>().unwrap(),
118 _ => panic!("Expected Custom value"),
119 }
120 }
121
122 #[test]
123 fn test_into_semver_range_from_string() {
124 let value = Value::string(">=1.0.0", Span::test_data());
125 let result = into_semver_range(&value, Span::test_data(), false);
126
127 assert!(matches!(result, Value::Custom { .. }));
128 let range_val = get_custom_value(&result);
129 assert_eq!(range_val.requirement.to_string(), ">=1.0.0");
130 }
131
132 #[test]
133 fn test_into_semver_range_caret() {
134 let value = Value::string("^1.2.3", Span::test_data());
135 let result = into_semver_range(&value, Span::test_data(), false);
136
137 let range_val = get_custom_value(&result);
138 assert_eq!(range_val.requirement.to_string(), "^1.2.3");
139 }
140
141 #[test]
142 fn test_into_semver_range_tilde() {
143 let value = Value::string("~1.2", Span::test_data());
144 let result = into_semver_range(&value, Span::test_data(), false);
145
146 let range_val = get_custom_value(&result);
147 assert_eq!(range_val.requirement.to_string(), "~1.2");
148 }
149
150 #[test]
151 fn test_into_semver_range_complex() {
152 let value = Value::string(">=1.0.0, <2.0.0", Span::test_data());
153 let result = into_semver_range(&value, Span::test_data(), false);
154
155 let range_val = get_custom_value(&result);
156 assert_eq!(range_val.requirement.to_string(), ">=1.0.0, <2.0.0");
157 }
158
159 #[test]
160 fn test_into_semver_range_wildcard() {
161 let value = Value::string("*", Span::test_data());
162 let result = into_semver_range(&value, Span::test_data(), false);
163
164 let range_val = get_custom_value(&result);
165 assert_eq!(range_val.requirement.to_string(), "*");
166 }
167
168 #[test]
169 fn test_into_semver_range_from_semver_range() {
170 let original = SemverRangeValue::new(semver::VersionReq::parse(">=1.0.0").unwrap());
171 let value = Value::custom(Box::new(original), Span::test_data());
172 let result = into_semver_range(&value, Span::test_data(), false);
173
174 let range_val = get_custom_value(&result);
176 assert_eq!(range_val.requirement.to_string(), ">=1.0.0");
177 }
178
179 #[test]
180 fn test_into_semver_range_invalid() {
181 let value = Value::string("not-a-range", Span::test_data());
182 let result = into_semver_range(&value, Span::test_data(), false);
183
184 assert!(matches!(result, Value::Error { .. }));
185 }
186
187 #[test]
188 fn test_into_semver_range_unsupported_type() {
189 let value = Value::int(42, Span::test_data());
190 let result = into_semver_range(&value, Span::test_data(), false);
191
192 assert!(matches!(result, Value::Error { .. }));
193 }
194
195 #[test]
196 fn test_into_semver_range_wrong_custom_value() {
197 use crate::semver::value::SemverValue;
199 let semver = SemverValue::new(semver::Version::parse("1.2.3").unwrap());
200 let value = Value::custom(Box::new(semver), Span::test_data());
201 let result = into_semver_range(&value, Span::test_data(), false);
202
203 assert!(matches!(result, Value::Error { .. }));
204 }
205
206 #[test]
207 fn test_into_semver_range_loose() {
208 for (input, expected) in [
209 (">=v1.0.0", ">=1.0.0"),
210 ("^v1.2.3", "^1.2.3"),
211 ("~v1.2", "~1.2"),
212 ("v1.2.3", "^1.2.3"),
214 (">=v:1.0.0, <v.2.0.0", ">=1.0.0, <2.0.0"),
215 (">=v-1.0.0, <v_2.0.0", ">=1.0.0, <2.0.0"),
216 ] {
217 let value = Value::string(input, Span::test_data());
218 let result = into_semver_range(&value, Span::test_data(), true);
219 let range_val = get_custom_value(&result);
220 assert_eq!(range_val.requirement.to_string(), expected, "input={input}");
221 }
222
223 let value = Value::string(">=v1.0.0", Span::test_data());
224 let result = into_semver_range(&value, Span::test_data(), false);
225 assert!(matches!(result, Value::Error { .. }));
226 }
227}
228
229#[cfg(test)]
230mod test {
231 use super::*;
232
233 #[test]
234 fn test_examples() -> nu_test_support::Result {
235 nu_test_support::test().examples(IntoSemverRange)
236 }
237}