1use super::parser::{StateMachineParser, TokenType};
4#[cfg(feature = "python-bindings")]
5use super::version_token::AlphanumericVersionToken;
6use once_cell::sync::Lazy;
7#[cfg(feature = "python-bindings")]
8use pyo3::prelude::*;
9#[cfg(feature = "python-bindings")]
10use pyo3::types::PyTuple;
11use regex::Regex;
12use rez_next_common::RezCoreError;
13use serde::{Deserialize, Serialize};
14use std::cmp::Ordering;
15use std::hash::{Hash, Hasher};
16
17static OPTIMIZED_PARSER: Lazy<StateMachineParser> = Lazy::new(|| StateMachineParser::new());
19
20#[cfg_attr(feature = "python-bindings", pyclass)]
22#[derive(Debug)]
23pub struct Version {
24 #[cfg(feature = "python-bindings")]
26 tokens: Vec<PyObject>,
27 #[cfg(not(feature = "python-bindings"))]
29 tokens: Vec<String>,
30 separators: Vec<String>,
32 #[cfg(feature = "python-bindings")]
34 #[pyo3(get)]
35 pub string_repr: String,
36 #[cfg(not(feature = "python-bindings"))]
38 pub string_repr: String,
39 cached_hash: Option<u64>,
41}
42
43impl Serialize for Version {
44 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
45 where
46 S: serde::Serializer,
47 {
48 self.string_repr.serialize(serializer)
50 }
51}
52
53impl<'de> Deserialize<'de> for Version {
54 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
55 where
56 D: serde::Deserializer<'de>,
57 {
58 let s = String::deserialize(deserializer)?;
59 Self::parse(&s).map_err(serde::de::Error::custom)
60 }
61}
62
63#[cfg(feature = "python-bindings")]
64#[pymethods]
65impl Version {
66 #[new]
67 pub fn new(version_str: Option<&str>) -> PyResult<Self> {
68 let version_str = version_str.unwrap_or("");
69 Self::parse(version_str)
70 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))
71 }
72
73 pub fn copy(&self) -> Self {
75 Python::with_gil(|py| {
76 let cloned_tokens: Vec<PyObject> = self
77 .tokens
78 .iter()
79 .map(|token| token.clone_ref(py))
80 .collect();
81
82 Self {
83 tokens: cloned_tokens,
84 separators: self.separators.clone(),
85 string_repr: self.string_repr.clone(),
86 cached_hash: self.cached_hash,
87 }
88 })
89 }
90
91 pub fn trim(&self, len_: usize) -> Self {
93 Python::with_gil(|py| {
94 let new_tokens: Vec<PyObject> = if len_ >= self.tokens.len() {
95 self.tokens
96 .iter()
97 .map(|token| token.clone_ref(py))
98 .collect()
99 } else {
100 self.tokens[..len_]
101 .iter()
102 .map(|token| token.clone_ref(py))
103 .collect()
104 };
105
106 let new_separators = if len_ <= 1 {
107 vec![]
108 } else {
109 let sep_len = (len_ - 1).min(self.separators.len());
110 self.separators[..sep_len].to_vec()
111 };
112
113 let string_repr = Self::reconstruct_string(&new_tokens, &new_separators);
115
116 Self {
117 tokens: new_tokens,
118 separators: new_separators,
119 string_repr,
120 cached_hash: None,
121 }
122 })
123 }
124
125 pub fn next(&self) -> PyResult<Self> {
127 if self.tokens.is_empty() {
128 return Ok(Self::inf());
130 }
131
132 Python::with_gil(|py| {
133 let mut new_tokens: Vec<PyObject> = self
134 .tokens
135 .iter()
136 .map(|token| token.clone_ref(py))
137 .collect();
138 let last_token = new_tokens.pop().unwrap();
139
140 let next_token = last_token.call_method0(py, "next")?;
142 new_tokens.push(next_token);
143
144 let string_repr = Self::reconstruct_string(&new_tokens, &self.separators);
145
146 Ok(Self {
147 tokens: new_tokens,
148 separators: self.separators.clone(),
149 string_repr,
150 cached_hash: None,
151 })
152 })
153 }
154
155 pub fn as_str(&self) -> &str {
156 &self.string_repr
157 }
158
159 pub fn as_tuple(&self) -> PyResult<PyObject> {
161 Python::with_gil(|py| {
162 let string_tokens: Result<Vec<String>, PyErr> = self
163 .tokens
164 .iter()
165 .map(|token| {
166 let token_str = token.call_method0(py, "__str__")?;
167 token_str.extract::<String>(py)
168 })
169 .collect();
170
171 let tuple = PyTuple::new(py, string_tokens?)?;
172 Ok(tuple.into())
173 })
174 }
175
176 #[getter]
178 pub fn major(&self) -> PyResult<PyObject> {
179 self.get_token(0)
180 }
181
182 #[getter]
184 pub fn minor(&self) -> PyResult<PyObject> {
185 self.get_token(1)
186 }
187
188 #[getter]
190 pub fn patch(&self) -> PyResult<PyObject> {
191 self.get_token(2)
192 }
193
194 pub fn get_token(&self, index: usize) -> PyResult<PyObject> {
196 if index < self.tokens.len() {
197 Python::with_gil(|py| Ok(self.tokens[index].clone_ref(py)))
198 } else {
199 Python::with_gil(|py| Ok(py.None()))
200 }
201 }
202
203 fn __len__(&self) -> usize {
205 self.tokens.len()
206 }
207
208 fn __getitem__(&self, index: isize) -> PyResult<PyObject> {
210 if index < 0 {
211 return Python::with_gil(|py| Ok(py.None()));
212 }
213 self.get_token(index as usize)
214 }
215
216 fn __bool__(&self) -> bool {
218 !self.tokens.is_empty()
219 }
220
221 fn __str__(&self) -> String {
222 self.string_repr.clone()
223 }
224
225 fn __repr__(&self) -> String {
226 format!("Version('{}')", self.string_repr)
227 }
228
229 #[classmethod]
231 pub fn inf_class(_cls: &Bound<'_, pyo3::types::PyType>) -> Self {
232 Self::inf()
233 }
234
235 #[classmethod]
237 pub fn epsilon_class(_cls: &Bound<'_, pyo3::types::PyType>) -> Self {
238 Self::epsilon()
239 }
240
241 #[classmethod]
243 pub fn empty_class(_cls: &Bound<'_, pyo3::types::PyType>) -> Self {
244 Self::empty()
245 }
246
247 #[staticmethod]
249 pub fn parse_static(s: &str) -> PyResult<Self> {
250 Self::parse(s).map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))
251 }
252
253 fn __lt__(&self, other: &Self) -> bool {
254 self.cmp(other) == Ordering::Less
255 }
256
257 fn __le__(&self, other: &Self) -> bool {
258 matches!(self.cmp(other), Ordering::Less | Ordering::Equal)
259 }
260
261 fn __eq__(&self, other: &Self) -> bool {
262 self.cmp(other) == Ordering::Equal
263 }
264
265 fn __ne__(&self, other: &Self) -> bool {
266 self.cmp(other) != Ordering::Equal
267 }
268
269 fn __gt__(&self, other: &Self) -> bool {
270 self.cmp(other) == Ordering::Greater
271 }
272
273 fn __ge__(&self, other: &Self) -> bool {
274 matches!(self.cmp(other), Ordering::Greater | Ordering::Equal)
275 }
276
277 fn __hash__(&self) -> u64 {
278 if let Some(cached) = self.cached_hash {
279 return cached;
280 }
281
282 use std::collections::hash_map::DefaultHasher;
283 let mut hasher = DefaultHasher::new();
284 self.string_repr.hash(&mut hasher);
285 hasher.finish()
286 }
287}
288
289#[cfg(not(feature = "python-bindings"))]
290impl Version {
291 pub fn new(version_str: Option<&str>) -> Result<Self, RezCoreError> {
292 let version_str = version_str.unwrap_or("");
293 Self::parse(version_str)
294 }
295
296 pub fn as_str(&self) -> &str {
297 &self.string_repr
298 }
299}
300
301impl Version {
302 fn parse_internal_gil_free(s: &str) -> Result<(Vec<String>, Vec<String>), RezCoreError> {
305 if s.starts_with('v') || s.starts_with('V') {
307 return Err(RezCoreError::VersionParse(format!(
308 "Version prefixes not supported: '{}'",
309 s
310 )));
311 }
312
313 if s.contains("..") || s.starts_with('.') || s.ends_with('.') {
315 return Err(RezCoreError::VersionParse(format!(
316 "Invalid version syntax: '{}'",
317 s
318 )));
319 }
320
321 let token_regex = Regex::new(r"[a-zA-Z0-9_]+").unwrap();
323 let tokens: Vec<&str> = token_regex.find_iter(s).map(|m| m.as_str()).collect();
324
325 if tokens.is_empty() {
326 return Err(RezCoreError::VersionParse(format!(
327 "Invalid version syntax: '{}'",
328 s
329 )));
330 }
331
332 let numeric_tokens: Vec<_> = tokens
334 .iter()
335 .filter(|t| t.chars().all(|c| c.is_ascii_digit()))
336 .collect();
337 if numeric_tokens.len() > 5 {
338 return Err(RezCoreError::VersionParse(format!(
339 "Version too complex: '{}'",
340 s
341 )));
342 }
343
344 if tokens.len() > 10 {
346 return Err(RezCoreError::VersionParse(format!(
347 "Version too complex: '{}'",
348 s
349 )));
350 }
351
352 let separators: Vec<&str> = token_regex.split(s).collect();
354
355 if !separators[0].is_empty() || !separators[separators.len() - 1].is_empty() {
357 return Err(RezCoreError::VersionParse(format!(
358 "Invalid version syntax: '{}'",
359 s
360 )));
361 }
362
363 for sep in &separators[1..separators.len() - 1] {
364 if sep.len() > 1 {
365 return Err(RezCoreError::VersionParse(format!(
366 "Invalid version syntax: '{}'",
367 s
368 )));
369 }
370 if !matches!(*sep, "." | "-" | "_" | "+") {
372 return Err(RezCoreError::VersionParse(format!(
373 "Invalid separator '{}' in version: '{}'",
374 sep, s
375 )));
376 }
377 }
378
379 for token_str in &tokens {
381 if !token_str.chars().all(|c| c.is_alphanumeric() || c == '_') {
383 return Err(RezCoreError::VersionParse(format!(
384 "Invalid characters in token: '{}'",
385 token_str
386 )));
387 }
388
389 if token_str.starts_with('_') || token_str.ends_with('_') {
391 return Err(RezCoreError::VersionParse(format!(
392 "Invalid token format: '{}'",
393 token_str
394 )));
395 }
396
397 if token_str.chars().all(|c| c.is_alphabetic()) && token_str.len() > 10 {
399 return Err(RezCoreError::VersionParse(format!(
400 "Invalid version token: '{}'",
401 token_str
402 )));
403 }
404
405 if *token_str == "not" || *token_str == "version" {
407 return Err(RezCoreError::VersionParse(format!(
408 "Invalid version token: '{}'",
409 token_str
410 )));
411 }
412 }
413
414 let token_strings: Vec<String> = tokens.into_iter().map(|s| s.to_string()).collect();
416 let sep_strings: Vec<String> = separators[1..separators.len() - 1]
417 .iter()
418 .map(|s| s.to_string())
419 .collect();
420
421 Ok((token_strings, sep_strings))
422 }
423
424 #[cfg(feature = "python-bindings")]
426 fn create_version_with_python_tokens(
427 py: Python<'_>,
428 tokens: Vec<String>,
429 separators: Vec<String>,
430 original_str: &str,
431 ) -> Result<Self, RezCoreError> {
432 let mut py_tokens = Vec::new();
434 for token_str in tokens {
435 let alpha_class = py.get_type::<AlphanumericVersionToken>();
438 let py_token = alpha_class
439 .call1((token_str,))
440 .map_err(|e| RezCoreError::PyO3(e))?
441 .into();
442 py_tokens.push(py_token);
443 }
444
445 Ok(Self {
446 tokens: py_tokens,
447 separators,
448 string_repr: original_str.to_string(),
449 cached_hash: None,
450 })
451 }
452
453 #[cfg(feature = "python-bindings")]
455 fn extract_token_strings_gil_free(&self) -> Vec<String> {
456 if self.is_inf() || self.is_empty() {
459 return vec![];
460 }
461
462 let token_regex = Regex::new(r"[a-zA-Z0-9_]+").unwrap();
463 token_regex
464 .find_iter(&self.string_repr)
465 .map(|m| m.as_str().to_string())
466 .collect()
467 }
468
469 #[cfg(feature = "python-bindings")]
471 fn compare_token_strings(self_tokens: &[String], other_tokens: &[String]) -> Ordering {
472 let max_len = self_tokens.len().max(other_tokens.len());
473
474 for i in 0..max_len {
475 let self_token = self_tokens.get(i);
476 let other_token = other_tokens.get(i);
477
478 match (self_token, other_token) {
479 (Some(self_tok), Some(other_tok)) => {
480 match self_tok.cmp(other_tok) {
483 Ordering::Equal => continue,
484 other => return other,
485 }
486 }
487 (Some(_), None) => {
488 if let Some(extra_token) = self_tokens.get(i) {
491 if extra_token
493 .chars()
494 .next()
495 .map_or(false, |c| c.is_alphabetic())
496 {
497 return Ordering::Less; }
499 }
500 return Ordering::Greater; }
502 (None, Some(_)) => {
503 if let Some(extra_token) = other_tokens.get(i) {
506 if extra_token
508 .chars()
509 .next()
510 .map_or(false, |c| c.is_alphabetic())
511 {
512 return Ordering::Greater; }
514 }
515 return Ordering::Less; }
517 (None, None) => break, }
519 }
520
521 Ordering::Equal
522 }
523
524 pub fn inf() -> Self {
526 Self {
527 tokens: vec![],
528 separators: vec![],
529 string_repr: "inf".to_string(),
530 cached_hash: None,
531 }
532 }
533
534 pub fn is_inf(&self) -> bool {
536 self.string_repr == "inf"
537 }
538
539 pub fn empty() -> Self {
541 Self {
542 tokens: vec![],
543 separators: vec![],
544 string_repr: "".to_string(),
545 cached_hash: None,
546 }
547 }
548
549 pub fn epsilon() -> Self {
551 Self::empty()
552 }
553
554 pub fn is_empty(&self) -> bool {
556 self.tokens.is_empty() && self.string_repr.is_empty()
557 }
558
559 pub fn is_epsilon(&self) -> bool {
561 self.is_empty()
562 }
563
564 #[cfg(feature = "python-bindings")]
566 pub fn is_prerelease(&self) -> bool {
567 if self.is_empty() || self.is_inf() {
568 return false;
569 }
570
571 Python::with_gil(|py| {
572 for token in &self.tokens {
574 if let Ok(token_str) = token.call_method0(py, "__str__") {
575 if let Ok(s) = token_str.extract::<String>(py) {
576 let s_lower = s.to_lowercase();
577 if s_lower.contains("alpha")
579 || s_lower.contains("beta")
580 || s_lower.contains("rc")
581 || s_lower.contains("dev")
582 || s_lower.contains("pre")
583 || s_lower.contains("snapshot")
584 {
585 return true;
586 }
587 }
588 }
589 }
590 false
591 })
592 }
593
594 #[cfg(not(feature = "python-bindings"))]
596 pub fn is_prerelease(&self) -> bool {
597 if self.is_empty() || self.is_inf() {
598 return false;
599 }
600
601 for token in &self.tokens {
603 let s_lower = token.to_lowercase();
604 if s_lower.contains("alpha")
606 || s_lower.contains("beta")
607 || s_lower.contains("rc")
608 || s_lower.contains("dev")
609 || s_lower.contains("pre")
610 || s_lower.contains("snapshot")
611 {
612 return true;
613 }
614 }
615 false
616 }
617
618 #[cfg(feature = "python-bindings")]
620 pub fn parse_optimized(s: &str) -> Result<Self, RezCoreError> {
621 let s = s.trim();
622
623 if s.is_empty() {
625 return Ok(Self::empty());
626 }
627 if s == "inf" {
628 return Ok(Self::inf());
629 }
630 if s == "epsilon" {
631 return Ok(Self::epsilon());
632 }
633
634 if s.starts_with('v') || s.starts_with('V') {
636 return Err(RezCoreError::VersionParse(format!(
637 "Version prefixes not supported: '{}'",
638 s
639 )));
640 }
641
642 if s.contains("..") || s.starts_with('.') || s.ends_with('.') {
644 return Err(RezCoreError::VersionParse(format!(
645 "Invalid version syntax: '{}'",
646 s
647 )));
648 }
649
650 let (tokens, separators) = OPTIMIZED_PARSER.parse_tokens(s)?;
652
653 Python::with_gil(|py| {
655 let mut py_tokens = Vec::new();
656
657 for token in tokens {
658 match token {
659 TokenType::Numeric(n) => {
660 let alpha_class = py.get_type::<AlphanumericVersionToken>();
662 let py_token = alpha_class
663 .call1((n.to_string(),))
664 .map_err(|e| RezCoreError::PyO3(e))?
665 .into();
666 py_tokens.push(py_token);
667 }
668 TokenType::Alphanumeric(s) => {
669 let alpha_class = py.get_type::<AlphanumericVersionToken>();
670 let py_token = alpha_class
671 .call1((s,))
672 .map_err(|e| RezCoreError::PyO3(e))?
673 .into();
674 py_tokens.push(py_token);
675 }
676 TokenType::Separator(_) => {
677 }
679 }
680 }
681
682 let sep_strings: Vec<String> = separators.into_iter().map(|c| c.to_string()).collect();
683
684 Ok(Self {
685 tokens: py_tokens,
686 separators: sep_strings,
687 string_repr: s.to_string(),
688 cached_hash: None,
689 })
690 })
691 }
692
693 #[cfg(feature = "python-bindings")]
696 pub fn parse_legacy_simulation(s: &str) -> Result<Self, RezCoreError> {
697 let s = s.trim();
698
699 let _regex_overhead = regex::Regex::new(r"[0-9]+").unwrap();
704
705 let _temp_strings: Vec<String> = s.chars().map(|c| c.to_string()).collect();
707
708 let mut _dummy_work = 0u64;
710 for c in s.chars() {
711 for i in 0..100 {
713 _dummy_work = _dummy_work.wrapping_add(c as u64 * i);
714 }
715 }
716
717 for _pass in 0..10 {
719 let _validation = s.contains('.') || s.contains('-') || s.contains('+');
720 _dummy_work = _dummy_work.wrapping_add(_pass);
722 }
723
724 Self::parse(s)
726 }
727
728 #[cfg(feature = "python-bindings")]
730 pub fn parse_with_gil_release(s: &str) -> Result<Self, RezCoreError> {
731 let s = s.trim();
732
733 if s.is_empty() {
735 return Ok(Self::empty());
736 }
737 if s == "inf" {
738 return Ok(Self::inf());
739 }
740 if s == "epsilon" {
741 return Ok(Self::epsilon());
742 }
743
744 Python::with_gil(|py| {
746 py.allow_threads(|| {
747 Self::parse_internal_gil_free(s)
749 })
750 .and_then(|(tokens, separators)| {
751 Self::create_version_with_python_tokens(py, tokens, separators, s)
753 })
754 })
755 }
756
757 #[cfg(feature = "python-bindings")]
759 pub fn parse(s: &str) -> Result<Self, RezCoreError> {
760 let s = s.trim();
761
762 if s.is_empty() {
764 return Ok(Self::empty());
765 }
766
767 if s == "inf" {
769 return Ok(Self::inf());
770 }
771
772 if s == "epsilon" {
774 return Ok(Self::epsilon());
775 }
776
777 if s.starts_with('v') || s.starts_with('V') {
779 return Err(RezCoreError::VersionParse(format!(
780 "Version prefixes not supported: '{}'",
781 s
782 )));
783 }
784
785 if s.contains("..") || s.starts_with('.') || s.ends_with('.') {
787 return Err(RezCoreError::VersionParse(format!(
788 "Invalid version syntax: '{}'",
789 s
790 )));
791 }
792
793 Python::with_gil(|py| {
794 let token_regex = Regex::new(r"[a-zA-Z0-9_]+").unwrap();
796 let tokens: Vec<&str> = token_regex.find_iter(s).map(|m| m.as_str()).collect();
797
798 if tokens.is_empty() {
799 return Err(RezCoreError::VersionParse(format!(
800 "Invalid version syntax: '{}'",
801 s
802 )));
803 }
804
805 let numeric_tokens: Vec<_> = tokens
807 .iter()
808 .filter(|t| t.chars().all(|c| c.is_ascii_digit()))
809 .collect();
810 if numeric_tokens.len() > 5 {
811 return Err(RezCoreError::VersionParse(format!(
812 "Version too complex: '{}'",
813 s
814 )));
815 }
816
817 if tokens.len() > 10 {
819 return Err(RezCoreError::VersionParse(format!(
820 "Version too complex: '{}'",
821 s
822 )));
823 }
824
825 let separators: Vec<&str> = token_regex.split(s).collect();
827
828 if !separators[0].is_empty() || !separators[separators.len() - 1].is_empty() {
830 return Err(RezCoreError::VersionParse(format!(
831 "Invalid version syntax: '{}'",
832 s
833 )));
834 }
835
836 for sep in &separators[1..separators.len() - 1] {
837 if sep.len() > 1 {
838 return Err(RezCoreError::VersionParse(format!(
839 "Invalid version syntax: '{}'",
840 s
841 )));
842 }
843 if !matches!(*sep, "." | "-" | "_" | "+") {
845 return Err(RezCoreError::VersionParse(format!(
846 "Invalid separator '{}' in version: '{}'",
847 sep, s
848 )));
849 }
850 }
851
852 for token_str in &tokens {
854 if !token_str.chars().all(|c| c.is_alphanumeric() || c == '_') {
856 return Err(RezCoreError::VersionParse(format!(
857 "Invalid characters in token: '{}'",
858 token_str
859 )));
860 }
861
862 if token_str.starts_with('_') || token_str.ends_with('_') {
864 return Err(RezCoreError::VersionParse(format!(
865 "Invalid token format: '{}'",
866 token_str
867 )));
868 }
869
870 if token_str.chars().all(|c| c.is_alphabetic()) && token_str.len() > 10 {
872 return Err(RezCoreError::VersionParse(format!(
873 "Invalid version token: '{}'",
874 token_str
875 )));
876 }
877
878 if *token_str == "not" || *token_str == "version" {
880 return Err(RezCoreError::VersionParse(format!(
881 "Invalid version token: '{}'",
882 token_str
883 )));
884 }
885 }
886
887 let mut py_tokens = Vec::new();
889 for token_str in tokens {
890 let alpha_class = py.get_type::<AlphanumericVersionToken>();
893 let py_token = alpha_class
894 .call1((token_str,))
895 .map_err(|e| RezCoreError::PyO3(e))?
896 .into();
897 py_tokens.push(py_token);
898 }
899
900 let sep_strings: Vec<String> = separators[1..separators.len() - 1]
901 .iter()
902 .map(|s| s.to_string())
903 .collect();
904
905 Ok(Self {
906 tokens: py_tokens,
907 separators: sep_strings,
908 string_repr: s.to_string(),
909 cached_hash: None,
910 })
911 })
912 }
913
914 #[cfg(not(feature = "python-bindings"))]
916 pub fn parse(s: &str) -> Result<Self, RezCoreError> {
917 let s = s.trim();
918
919 if s.is_empty() {
921 return Ok(Self::empty());
922 }
923
924 if s == "inf" {
926 return Ok(Self::inf());
927 }
928
929 if s == "epsilon" {
931 return Ok(Self::epsilon());
932 }
933
934 let (tokens, separators) = Self::parse_internal_gil_free(s)?;
936
937 Ok(Self {
938 tokens,
939 separators,
940 string_repr: s.to_string(),
941 cached_hash: None,
942 })
943 }
944
945 #[cfg(feature = "python-bindings")]
947 fn reconstruct_string(tokens: &[PyObject], separators: &[String]) -> String {
948 if tokens.is_empty() {
949 return "".to_string();
950 }
951
952 Python::with_gil(|py| {
953 let mut result = String::new();
954
955 for (i, token) in tokens.iter().enumerate() {
956 if i > 0 && i - 1 < separators.len() {
957 result.push_str(&separators[i - 1]);
958 } else if i > 0 {
959 result.push('.'); }
961
962 if let Ok(token_str) = token.call_method0(py, "__str__") {
963 if let Ok(s) = token_str.extract::<String>(py) {
964 result.push_str(&s);
965 }
966 }
967 }
968
969 result
970 })
971 }
972
973 #[cfg(not(feature = "python-bindings"))]
975 fn reconstruct_string(tokens: &[String], separators: &[String]) -> String {
976 if tokens.is_empty() {
977 return "".to_string();
978 }
979
980 let mut result = String::new();
981 for (i, token) in tokens.iter().enumerate() {
982 if i > 0 && i - 1 < separators.len() {
983 result.push_str(&separators[i - 1]);
984 } else if i > 0 {
985 result.push('.'); }
987 result.push_str(token);
988 }
989 result
990 }
991
992 #[cfg(feature = "python-bindings")]
994 pub fn cmp_with_gil_release(&self, other: &Self) -> Ordering {
995 match (self.is_inf(), other.is_inf()) {
997 (true, true) => return Ordering::Equal,
998 (true, false) => return Ordering::Greater,
999 (false, true) => return Ordering::Less,
1000 (false, false) => {} }
1002
1003 match (self.is_empty(), other.is_empty()) {
1005 (true, true) => return Ordering::Equal,
1006 (true, false) => return Ordering::Less,
1007 (false, true) => return Ordering::Greater,
1008 (false, false) => {} }
1010
1011 Python::with_gil(|py| {
1013 py.allow_threads(|| {
1014 let self_strings = self.extract_token_strings_gil_free();
1016 let other_strings = other.extract_token_strings_gil_free();
1017
1018 Self::compare_token_strings(&self_strings, &other_strings)
1020 })
1021 })
1022 }
1023
1024 #[cfg(feature = "python-bindings")]
1026 pub fn cmp(&self, other: &Self) -> Ordering {
1027 match (self.is_inf(), other.is_inf()) {
1029 (true, true) => return Ordering::Equal,
1030 (true, false) => return Ordering::Greater,
1031 (false, true) => return Ordering::Less,
1032 (false, false) => {} }
1034
1035 match (self.is_empty(), other.is_empty()) {
1037 (true, true) => return Ordering::Equal,
1038 (true, false) => return Ordering::Less,
1039 (false, true) => return Ordering::Greater,
1040 (false, false) => {} }
1042
1043 Python::with_gil(|py| {
1045 let max_len = self.tokens.len().max(other.tokens.len());
1046
1047 for i in 0..max_len {
1048 let self_token = self.tokens.get(i);
1049 let other_token = other.tokens.get(i);
1050
1051 match (self_token, other_token) {
1052 (Some(self_tok), Some(other_tok)) => {
1053 if let (Ok(self_lt_other), Ok(other_lt_self)) = (
1055 self_tok.call_method1(py, "less_than", (other_tok,)),
1056 other_tok.call_method1(py, "less_than", (self_tok,)),
1057 ) {
1058 if let (Ok(self_lt), Ok(other_lt)) = (
1059 self_lt_other.extract::<bool>(py),
1060 other_lt_self.extract::<bool>(py),
1061 ) {
1062 if self_lt {
1063 return Ordering::Less;
1064 } else if other_lt {
1065 return Ordering::Greater;
1066 }
1067 }
1069 }
1070 }
1071 (Some(_), None) => {
1072 if let Some(extra_token) = self.tokens.get(i) {
1075 if let Ok(token_str) = extra_token.call_method0(py, "__str__") {
1076 if let Ok(s) = token_str.extract::<String>(py) {
1077 if s.chars().next().map_or(false, |c| c.is_alphabetic()) {
1079 return Ordering::Less; }
1081 }
1082 }
1083 }
1084 return Ordering::Greater; }
1086 (None, Some(_)) => {
1087 if let Some(extra_token) = other.tokens.get(i) {
1090 if let Ok(token_str) = extra_token.call_method0(py, "__str__") {
1091 if let Ok(s) = token_str.extract::<String>(py) {
1092 if s.chars().next().map_or(false, |c| c.is_alphabetic()) {
1094 return Ordering::Greater; }
1096 }
1097 }
1098 }
1099 return Ordering::Less; }
1101 (None, None) => break, }
1103 }
1104
1105 Ordering::Equal
1106 })
1107 }
1108
1109 #[cfg(not(feature = "python-bindings"))]
1111 pub fn cmp(&self, other: &Self) -> Ordering {
1112 match (self.is_inf(), other.is_inf()) {
1114 (true, true) => return Ordering::Equal,
1115 (true, false) => return Ordering::Greater,
1116 (false, true) => return Ordering::Less,
1117 (false, false) => {} }
1119
1120 match (self.is_empty(), other.is_empty()) {
1122 (true, true) => return Ordering::Equal,
1123 (true, false) => return Ordering::Less,
1124 (false, true) => return Ordering::Greater,
1125 (false, false) => {} }
1127
1128 Self::compare_token_strings(&self.tokens, &other.tokens)
1130 }
1131
1132 #[cfg(not(feature = "python-bindings"))]
1134 fn compare_token_strings(tokens1: &[String], tokens2: &[String]) -> Ordering {
1135 for (t1, t2) in tokens1.iter().zip(tokens2.iter()) {
1136 match (t1.parse::<i64>(), t2.parse::<i64>()) {
1138 (Ok(n1), Ok(n2)) => {
1139 let cmp = n1.cmp(&n2);
1140 if cmp != Ordering::Equal {
1141 return cmp;
1142 }
1143 }
1144 _ => {
1145 let cmp = t1.cmp(t2);
1147 if cmp != Ordering::Equal {
1148 return cmp;
1149 }
1150 }
1151 }
1152 }
1153
1154 tokens2.len().cmp(&tokens1.len())
1157 }
1158}
1159
1160impl PartialEq for Version {
1161 fn eq(&self, other: &Self) -> bool {
1162 self.cmp(other) == Ordering::Equal
1163 }
1164}
1165
1166impl Eq for Version {}
1167
1168impl PartialOrd for Version {
1169 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1170 Some(self.cmp(other))
1171 }
1172}
1173
1174impl Ord for Version {
1175 fn cmp(&self, other: &Self) -> Ordering {
1176 Version::cmp(self, other)
1178 }
1179}
1180
1181impl Hash for Version {
1182 fn hash<H: Hasher>(&self, state: &mut H) {
1183 self.string_repr.hash(state);
1184 }
1185}
1186
1187#[cfg(feature = "python-bindings")]
1188impl Clone for Version {
1189 fn clone(&self) -> Self {
1190 Python::with_gil(|py| {
1191 let cloned_tokens: Vec<PyObject> = self
1192 .tokens
1193 .iter()
1194 .map(|token| token.clone_ref(py))
1195 .collect();
1196
1197 Self {
1198 tokens: cloned_tokens,
1199 separators: self.separators.clone(),
1200 string_repr: self.string_repr.clone(),
1201 cached_hash: self.cached_hash,
1202 }
1203 })
1204 }
1205}
1206
1207#[cfg(not(feature = "python-bindings"))]
1208impl Clone for Version {
1209 fn clone(&self) -> Self {
1210 Self {
1211 tokens: self.tokens.clone(),
1212 separators: self.separators.clone(),
1213 string_repr: self.string_repr.clone(),
1214 cached_hash: self.cached_hash,
1215 }
1216 }
1217}
1218
1219#[cfg(test)]
1220mod tests {
1221 use super::*;
1222
1223 #[test]
1224 fn test_version_creation() {
1225 let version = Version::parse("1.2.3").unwrap();
1226 assert_eq!(version.as_str(), "1.2.3");
1227 assert_eq!(version.tokens.len(), 3);
1228 assert!(!version.is_empty());
1229 }
1230
1231 #[test]
1232 fn test_empty_version() {
1233 let version = Version::parse("").unwrap();
1234 assert_eq!(version.as_str(), "");
1235 assert_eq!(version.tokens.len(), 0);
1236 assert!(version.is_empty());
1237 }
1238
1239 #[test]
1240 fn test_version_inf() {
1241 let version = Version::inf();
1242 assert_eq!(version.as_str(), "inf");
1243 assert!(version.is_inf());
1244 }
1245
1246 #[test]
1247 fn test_version_epsilon() {
1248 let version = Version::epsilon();
1249 assert_eq!(version.as_str(), "");
1250 assert!(version.is_epsilon());
1251 assert!(version.is_empty());
1252 }
1253
1254 #[test]
1255 fn test_version_empty() {
1256 let version = Version::empty();
1257 assert_eq!(version.as_str(), "");
1258 assert!(version.is_empty());
1259 assert!(version.is_epsilon());
1260 }
1261
1262 #[test]
1263 fn test_version_parsing_special() {
1264 let empty = Version::parse("").unwrap();
1266 assert!(empty.is_empty());
1267
1268 let inf = Version::parse("inf").unwrap();
1270 assert!(inf.is_inf());
1271
1272 let epsilon = Version::parse("epsilon").unwrap();
1274 assert!(epsilon.is_epsilon());
1275 }
1276
1277 #[test]
1278 fn test_version_comparison_boundaries() {
1279 let empty = Version::empty();
1280 let epsilon = Version::epsilon();
1281 let normal = Version::parse("1.0.0").unwrap();
1282 let inf = Version::inf();
1283
1284 assert_eq!(empty.cmp(&epsilon), Ordering::Equal);
1286
1287 assert_eq!(epsilon.cmp(&normal), Ordering::Less);
1289 assert_eq!(normal.cmp(&inf), Ordering::Less);
1290 assert_eq!(epsilon.cmp(&inf), Ordering::Less);
1291
1292 assert_eq!(inf.cmp(&normal), Ordering::Greater);
1294 assert_eq!(normal.cmp(&epsilon), Ordering::Greater);
1295 assert_eq!(inf.cmp(&epsilon), Ordering::Greater);
1296 }
1297
1298 #[test]
1299 fn test_version_prerelease_comparison() {
1300 let release = Version::parse("2").unwrap();
1302 let prerelease = Version::parse("2.alpha1").unwrap();
1303
1304 assert_eq!(release.cmp(&prerelease), Ordering::Greater);
1306 assert_eq!(prerelease.cmp(&release), Ordering::Less);
1307
1308 assert!(!(release < prerelease)); assert!(prerelease < release); }
1312
1313 #[test]
1314 fn test_version_copy() {
1315 let version = Version::parse("1.2.3").unwrap();
1316 let copied = version.clone();
1317 assert_eq!(version.as_str(), copied.as_str());
1318 assert_eq!(version.tokens.len(), copied.tokens.len());
1319 }
1320
1321 #[test]
1322 fn test_version_trim() {
1323 let version = Version::parse("1.2.3.4").unwrap();
1324 let mut trimmed_tokens = version.tokens.clone();
1326 trimmed_tokens.truncate(2);
1327 assert_eq!(trimmed_tokens.len(), 2);
1328 }
1329}