typst_library/foundations/
version.rs1use std::cmp::Ordering;
2use std::fmt::{self, Display, Formatter, Write};
3use std::hash::Hash;
4use std::iter::repeat;
5
6use ecow::{EcoString, EcoVec, eco_format};
7
8use crate::diag::{StrResult, bail};
9use crate::foundations::{Repr, cast, func, repr, scope, ty};
10
11#[ty(scope, cast)]
26#[derive(Debug, Default, Clone, Hash)]
27pub struct Version(EcoVec<u32>);
28
29impl Version {
30 pub const COMPONENTS: [&'static str; 3] = ["major", "minor", "patch"];
32
33 pub fn new() -> Self {
35 Self::default()
36 }
37
38 pub fn component(&self, name: &str) -> StrResult<i64> {
43 self.0
44 .iter()
45 .zip(Self::COMPONENTS)
46 .find_map(|(&i, s)| (s == name).then_some(i as i64))
47 .ok_or_else(|| "unknown version component".into())
48 }
49
50 pub fn push(&mut self, component: u32) {
52 self.0.push(component);
53 }
54
55 pub fn values(&self) -> &[u32] {
57 &self.0
58 }
59}
60
61#[scope]
62impl Version {
63 #[func(constructor)]
90 pub fn construct(
91 #[variadic]
93 components: Vec<VersionComponents>,
94 ) -> Version {
95 let mut version = Version::new();
96 for c in components {
97 match c {
98 VersionComponents::Single(v) => version.push(v),
99 VersionComponents::Multiple(values) => {
100 for v in values {
101 version.push(v);
102 }
103 }
104 }
105 }
106 version
107 }
108
109 #[func]
114 pub fn at(
115 &self,
116 index: i64,
119 ) -> StrResult<i64> {
120 let mut index = index;
121 if index < 0 {
122 match (self.0.len() as i64).checked_add(index) {
123 Some(pos_index) if pos_index >= 0 => index = pos_index,
124 _ => bail!(
125 "component index out of bounds (index: {index}, len: {})",
126 self.0.len(),
127 ),
128 }
129 }
130 Ok(usize::try_from(index)
131 .ok()
132 .and_then(|i| self.0.get(i).copied())
133 .unwrap_or_default() as i64)
134 }
135}
136
137impl FromIterator<u32> for Version {
138 fn from_iter<T: IntoIterator<Item = u32>>(iter: T) -> Self {
139 Self(EcoVec::from_iter(iter))
140 }
141}
142
143impl IntoIterator for Version {
144 type Item = u32;
145 type IntoIter = ecow::vec::IntoIter<u32>;
146
147 fn into_iter(self) -> Self::IntoIter {
148 self.0.into_iter()
149 }
150}
151
152impl Ord for Version {
153 fn cmp(&self, other: &Self) -> Ordering {
154 let max_len = self.0.len().max(other.0.len());
155 let tail = repeat(&0);
156
157 let self_iter = self.0.iter().chain(tail.clone());
158 let other_iter = other.0.iter().chain(tail);
159
160 for (l, r) in self_iter.zip(other_iter).take(max_len) {
161 match l.cmp(r) {
162 Ordering::Equal => (),
163 ord => return ord,
164 }
165 }
166
167 Ordering::Equal
168 }
169}
170
171impl PartialOrd for Version {
172 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
173 Some(self.cmp(other))
174 }
175}
176
177impl Eq for Version {}
178
179impl PartialEq for Version {
180 fn eq(&self, other: &Self) -> bool {
181 matches!(self.cmp(other), Ordering::Equal)
182 }
183}
184
185impl Display for Version {
186 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
187 let mut first = true;
188 for &v in &self.0 {
189 if !first {
190 f.write_char('.')?;
191 }
192 write!(f, "{v}")?;
193 first = false;
194 }
195 Ok(())
196 }
197}
198
199impl Repr for Version {
200 fn repr(&self) -> EcoString {
201 let parts: Vec<_> = self.0.iter().map(|v| eco_format!("{v}")).collect();
202 eco_format!("version{}", &repr::pretty_array_like(&parts, false))
203 }
204}
205
206pub enum VersionComponents {
208 Single(u32),
209 Multiple(Vec<u32>),
210}
211
212cast! {
213 VersionComponents,
214 v: u32 => Self::Single(v),
215 v: Vec<u32> => Self::Multiple(v)
216}