setver/
lib.rs

1//! SetVer comprehension for Rust.
2
3#![deny(missing_docs, clippy::all)]
4
5use std::collections::BTreeSet;
6use std::fmt::Display;
7use std::rc::Rc;
8use std::str::FromStr;
9
10/// A SetVer version specification.
11/// # Implementation details
12/// This struct is implemented using HashSet from the standard library.
13/// Therefore, it is not usable in no-std environments right now.
14#[derive(Eq, PartialEq, Clone, Debug, Ord, PartialOrd, Default)]
15pub struct SetVersion {
16	/// Making this an ordered set guarantees that all iterations are performed in order, which gives some nice guarantees for faster implementations.
17	versions: BTreeSet<Rc<SetVersion>>,
18}
19
20impl SetVersion {
21	/// Implements the SetVer comparison function, as per [the specification](https://github.com/RocketRace/setver#version-comparison-comparison).
22	///
23	/// The only returned values are 0, 1, Infinity and NaN.
24	pub fn setver_compare(&self, other: &SetVersion) -> f32 {
25		if self.is_subset(other) {
26			0.0
27		} else if self == other {
28			1.0
29		} else if other.is_subset(self) {
30			f32::INFINITY
31		} else {
32			f32::NAN
33		}
34	}
35
36	/// Returns whether this SetVer version is a subset of the other version, according to standard set laws.
37	/// ```rust
38	/// use setver::SetVersion;
39	/// let first_version: SetVersion = "{}".parse().unwrap();
40	/// let second_version: SetVersion = "{{}}".parse().unwrap();
41	/// assert!(first_version.is_subset(&second_version));
42	/// ```
43	pub fn is_subset(&self, other: &SetVersion) -> bool {
44		self.versions.is_subset(&other.versions)
45	}
46
47	/// Returns whether this SetVer version is a strict subset of the other version, according to standard set laws.
48	pub fn is_strict_subset(&self, other: &SetVersion) -> bool {
49		!other.is_superset(self)
50	}
51	/// Returns whether this SetVer version is a superset of the other version, according to standard set laws.
52	pub fn is_superset(&self, other: &SetVersion) -> bool {
53		self.versions.is_superset(&other.versions)
54	}
55
56	/// Returns whether this SetVer version is a strict superset of the other version, according to standard set laws.
57	pub fn is_strict_superset(&self, other: &SetVersion) -> bool {
58		!other.is_subset(self)
59	}
60
61	/// Adds the given version as a child version. This is useful when constructing a parent version for one or many previous child versions.
62	pub fn add_child_version(&mut self, child: Rc<SetVersion>) -> &mut Self {
63		self.versions.insert(child);
64		self
65	}
66
67	/// Implements the [Integralternative](https://github.com/RocketRace/setver#the-integralternative). This is the same as converting a `SetVersion` into an `u128`.
68	/// # Panics
69	/// For obvious reasons (if you know how the integralternative works), SetVersions with more than 128 braces in their text representation cannot be stored in a u128.
70	/// In this case, this function will panic. Use `to_integralternative_bytes` instead.
71	pub fn to_integralternative(&self) -> u128 {
72		self.into()
73	}
74
75	/// Implements the [Integralternative](https://github.com/RocketRace/setver#the-integralternative) and operates on a string.
76	/// This is convenient if the canonicalized form of the given setver spec is different from the string.
77	/// # Panics
78	/// For obvious reasons (if you know how the integralternative works), SetVersions with more than 128 braces in their text representation cannot be stored in a u128.
79	/// In this case, this function will panic. Use `to_integralternative_bytes` instead.
80	pub fn string_to_integralternative(setver: &str) -> u128 {
81		let bytes = Self::string_to_integralternative_bytes(setver);
82		Self::u128_from_vec(bytes)
83	}
84
85	/// Implements the [Integralternative](https://github.com/RocketRace/setver#the-integralternative).
86	/// The return value is not as practical as the one of `to_integralternative`, but it can always represent the integralternative and will never panic.
87	///
88	/// The returned bytes are in LSB-first order (little-endian).
89	pub fn to_integralternative_bytes(&self) -> Vec<u8> {
90		// Could be done more efficiently, but this saves us a bunch of code.
91		let stringified = String::from(self);
92		Self::string_to_integralternative_bytes(&stringified)
93	}
94
95	/// Implements the [Integralternative](https://github.com/RocketRace/setver#the-integralternative) but operates directly on a string.
96	/// This is convenient if the canonicalized form of the given setver spec is different from the string.
97	/// The return value is not as practical as the one of `to_integralternative`, but it can always represent the integralternative and will never panic.
98	///
99	/// The returned bytes are in LSB-first order (little-endian).
100	pub fn string_to_integralternative_bytes(setver: &str) -> Vec<u8> {
101		let mut current_byte = 0;
102		let mut bytes = Vec::new();
103		let mut bit_count = 0;
104		for c in setver.chars().rev() {
105			current_byte = (current_byte >> 1)
106				| match c {
107					'{' => 0,
108					'}' => 1 << 7,
109					_ => unreachable!(),
110				};
111			bit_count += 1;
112			if bit_count > 7 {
113				bit_count = 0;
114				bytes.push(current_byte);
115				current_byte = 0;
116			}
117		}
118		if bit_count != 0 {
119			// The shift-down is incomplete.
120			current_byte >>= 8 - bit_count;
121			bytes.push(current_byte);
122		}
123		bytes.reverse();
124		bytes
125	}
126
127	/// Does the "byte packing" required for the simple integralternative functions.
128	fn u128_from_vec(vec: Vec<u8>) -> u128 {
129		if vec.len() > 128 / 8 {
130			panic!("Input {:?} is too large to be represented in u128", vec);
131		}
132		let mut result = 0u128;
133		for byte in vec {
134			result = (result << 8) | (byte as u128);
135		}
136		result
137	}
138}
139
140impl Display for SetVersion {
141	/// The stringified version is always in canonical form, meaning that small sets are printed first.
142	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143		write!(f, "{{")?;
144		for version in &self.versions {
145			version.fmt(f)?;
146		}
147		write!(f, "}}")
148	}
149}
150
151impl From<&SetVersion> for String {
152	fn from(this: &SetVersion) -> Self {
153		format!("{}", this)
154	}
155}
156
157impl From<&SetVersion> for u128 {
158	fn from(this: &SetVersion) -> Self {
159		let bytes = this.to_integralternative_bytes();
160		SetVersion::u128_from_vec(bytes)
161	}
162}
163
164impl PartialEq<u128> for SetVersion {
165	/// Checks whether the integer is the canonical integralternative of this setver.
166	fn eq(&self, other: &u128) -> bool {
167		self.to_integralternative() == *other
168	}
169}
170
171impl PartialEq<&str> for SetVersion {
172	/// Checks whether the string parses to the same setver.
173	/// If the string is not a setver version, they are not equal.
174	fn eq(&self, other: &&str) -> bool {
175		match other.parse::<SetVersion>() {
176			Ok(other_setver) => self == &other_setver,
177			Err(_) => false,
178		}
179	}
180}
181
182/// The errors that can happen when parsing a SetVer.
183#[derive(Debug, Copy, Clone, Eq, PartialEq)]
184pub enum SetVerParseError {
185	/// An illegal character is in the parsed string. Stores the illegal character.
186	IllegalCharacter(char),
187	/// A set contains non-unique elements (sets).
188	NonUniqueElements,
189	/// A curly brace is unclosed.
190	UnclosedBrace,
191	/// The string is empty.
192	Empty,
193	/// There's more than one set here.
194	TooManySets,
195}
196
197impl Display for SetVerParseError {
198	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199		write!(
200			f,
201			"{}",
202			match &self {
203				Self::IllegalCharacter(c) => format!("Illegal character '{}'", c),
204				Self::NonUniqueElements => "Set contains non-unique subsets".to_string(),
205				Self::UnclosedBrace => "Unclosed set brace".to_string(),
206				Self::Empty => "Empty string".to_string(),
207				Self::TooManySets => "Too many sets (more than one)".to_string(),
208			}
209		)
210	}
211}
212
213impl FromStr for SetVersion {
214	type Err = SetVerParseError;
215	fn from_str(value: &str) -> Result<Self, Self::Err> {
216		// The smallest allowed setver specification is "{}" at length 2.
217		if value.len() < 2 {
218			return Err(SetVerParseError::Empty);
219		}
220		let mut chars = value.chars();
221		let open_curly = chars.next().unwrap();
222		if open_curly != '{' {
223			return Err(SetVerParseError::IllegalCharacter(open_curly));
224		}
225
226		// Find the matching brace.
227		let mut brace_level = 1;
228		let mut inner_sets = vec!["".to_owned()];
229		for next_char in &mut chars {
230			match next_char {
231				'{' => brace_level += 1,
232				'}' => brace_level -= 1,
233				_ => return Err(SetVerParseError::IllegalCharacter(next_char)),
234			}
235			if brace_level == 0 {
236				break;
237			}
238			inner_sets.last_mut().unwrap().push(next_char);
239			if brace_level == 1 {
240				inner_sets.push("".to_owned());
241			}
242		}
243		if brace_level != 0 {
244			return Err(SetVerParseError::UnclosedBrace);
245		}
246		if chars.next() != None {
247			return Err(SetVerParseError::TooManySets);
248		}
249
250		// The last set is a still-empty character collector if we got braces to match correctly.
251		inner_sets.remove(inner_sets.len() - 1);
252		if inner_sets.is_empty() {
253			return Ok(Self::default());
254		}
255
256		let versions = inner_sets
257			.iter()
258			.map(|string_set| string_set.parse::<SetVersion>().map(Rc::new))
259			.collect::<Result<BTreeSet<Rc<SetVersion>>, SetVerParseError>>()?;
260		if versions.len() < inner_sets.len() {
261			return Err(SetVerParseError::NonUniqueElements);
262		}
263		Ok(Self { versions })
264	}
265}
266
267#[cfg(test)]
268mod tests {
269	use super::*;
270
271	#[test]
272	fn parse_correct_setver() -> Result<(), SetVerParseError> {
273		// Note that there are other valid forms of these SetVer versions, but we need to use the canonical form where the smallest sets come first so that we can ensure they re-serialize correctly.
274		for test_string in
275			["{}", "{{}}", "{{}{{}}}", "{{}{{}}{{}{{}}}}", "{{{{{{{}}}}}}}", "{{}{{}}{{{}}}}", "{{}{{{}}{{}{{}}}}}"]
276		{
277			assert_eq!(test_string.parse::<SetVersion>()?.to_string(), test_string);
278		}
279
280		Ok(())
281	}
282
283	#[test]
284	fn parse_incorrect_setver() {
285		assert_eq!("".parse::<SetVersion>().unwrap_err(), SetVerParseError::Empty);
286		assert_eq!("asd".parse::<SetVersion>().unwrap_err(), SetVerParseError::IllegalCharacter('a'));
287		assert_eq!("{{b}}".parse::<SetVersion>().unwrap_err(), SetVerParseError::IllegalCharacter('b'));
288		"{{}{}".parse::<SetVersion>().unwrap_err();
289		"}{}".parse::<SetVersion>().unwrap_err();
290		assert_eq!("{}{}".parse::<SetVersion>().unwrap_err(), SetVerParseError::TooManySets);
291		assert_eq!("{{}{}}".parse::<SetVersion>().unwrap_err(), SetVerParseError::NonUniqueElements);
292		assert_eq!("{{{}{}}{}}".parse::<SetVersion>().unwrap_err(), SetVerParseError::NonUniqueElements);
293		assert_eq!("{{}{{}{{}}}{{}{{}}}}".parse::<SetVersion>().unwrap_err(), SetVerParseError::NonUniqueElements);
294	}
295
296	#[test]
297	fn equality() {
298		assert_eq!("{}".parse::<SetVersion>().unwrap(), "{}".parse::<SetVersion>().unwrap());
299		assert_ne!("{{{}}}".parse::<SetVersion>().unwrap(), "{{}}".parse::<SetVersion>().unwrap());
300		assert_eq!("{{}{{}}}".parse::<SetVersion>().unwrap(), "{{{}}{}}".parse::<SetVersion>().unwrap());
301		assert_eq!(
302			"{{{{}{{}}}{{}}}{}}".parse::<SetVersion>().unwrap(),
303			"{{}{{{}}{{}{{}}}}}".parse::<SetVersion>().unwrap()
304		);
305		assert_eq!("{{{{}{{}}}{{}}}{}}".parse::<SetVersion>().unwrap(), "{{}{{{}}{{}{{}}}}}");
306	}
307
308	#[test]
309	fn integralternative() {
310		assert_eq!("{{}{{{}}{{}{{}}}}}".parse::<SetVersion>().unwrap().to_integralternative(), 35999);
311		assert_eq!("{{}{{{}}{{}{{}}}}}".parse::<SetVersion>().unwrap(), 35999);
312		assert_eq!(SetVersion::string_to_integralternative("{{{{}}{}}{{}}}"), 871);
313		assert_eq!(SetVersion::string_to_integralternative("{{{}}{{{}}{}}}"), 1591);
314	}
315}