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