typst_library/foundations/version.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
use std::cmp::Ordering;
use std::fmt::{self, Display, Formatter, Write};
use std::hash::Hash;
use std::iter::repeat;
use ecow::{eco_format, EcoString, EcoVec};
use crate::diag::{bail, StrResult};
use crate::foundations::{cast, func, repr, scope, ty, Repr};
/// A version with an arbitrary number of components.
///
/// The first three components have names that can be used as fields: `major`,
/// `minor`, `patch`. All following components do not have names.
///
/// The list of components is semantically extended by an infinite list of
/// zeros. This means that, for example, `0.8` is the same as `0.8.0`. As a
/// special case, the empty version (that has no components at all) is the same
/// as `0`, `0.0`, `0.0.0`, and so on.
///
/// The current version of the Typst compiler is available as `sys.version`.
///
/// You can convert a version to an array of explicitly given components using
/// the [`array`] constructor.
#[ty(scope, cast)]
#[derive(Debug, Default, Clone, Hash)]
#[allow(clippy::derived_hash_with_manual_eq)]
pub struct Version(EcoVec<u32>);
impl Version {
/// The names for the first components of a version.
pub const COMPONENTS: [&'static str; 3] = ["major", "minor", "patch"];
/// Create a new (empty) version.
pub fn new() -> Self {
Self::default()
}
/// Get a named component of a version.
///
/// Always non-negative. Returns `0` if the version isn't specified to the
/// necessary length.
pub fn component(&self, name: &str) -> StrResult<i64> {
self.0
.iter()
.zip(Self::COMPONENTS)
.find_map(|(&i, s)| (s == name).then_some(i as i64))
.ok_or_else(|| "unknown version component".into())
}
/// Push a component to the end of this version.
pub fn push(&mut self, component: u32) {
self.0.push(component);
}
/// The values of the version
pub fn values(&self) -> &[u32] {
&self.0
}
}
#[scope]
impl Version {
/// Creates a new version.
///
/// It can have any number of components (even zero).
///
/// ```example
/// #version() \
/// #version(1) \
/// #version(1, 2, 3, 4) \
/// #version((1, 2, 3, 4)) \
/// #version((1, 2), 3)
/// ```
#[func(constructor)]
pub fn construct(
/// The components of the version (array arguments are flattened)
#[variadic]
components: Vec<VersionComponents>,
) -> Version {
let mut version = Version::new();
for c in components {
match c {
VersionComponents::Single(v) => version.push(v),
VersionComponents::Multiple(values) => {
for v in values {
version.push(v);
}
}
}
}
version
}
/// Retrieves a component of a version.
///
/// The returned integer is always non-negative. Returns `0` if the version
/// isn't specified to the necessary length.
#[func]
pub fn at(
&self,
/// The index at which to retrieve the component. If negative, indexes
/// from the back of the explicitly given components.
index: i64,
) -> StrResult<i64> {
let mut index = index;
if index < 0 {
match (self.0.len() as i64).checked_add(index) {
Some(pos_index) if pos_index >= 0 => index = pos_index,
_ => bail!(
"component index out of bounds (index: {index}, len: {})",
self.0.len()
),
}
}
Ok(usize::try_from(index)
.ok()
.and_then(|i| self.0.get(i).copied())
.unwrap_or_default() as i64)
}
}
impl FromIterator<u32> for Version {
fn from_iter<T: IntoIterator<Item = u32>>(iter: T) -> Self {
Self(EcoVec::from_iter(iter))
}
}
impl IntoIterator for Version {
type Item = u32;
type IntoIter = ecow::vec::IntoIter<u32>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
let max_len = self.0.len().max(other.0.len());
let tail = repeat(&0);
let self_iter = self.0.iter().chain(tail.clone());
let other_iter = other.0.iter().chain(tail);
for (l, r) in self_iter.zip(other_iter).take(max_len) {
match l.cmp(r) {
Ordering::Equal => (),
ord => return ord,
}
}
Ordering::Equal
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Eq for Version {}
impl PartialEq for Version {
fn eq(&self, other: &Self) -> bool {
matches!(self.cmp(other), Ordering::Equal)
}
}
impl Display for Version {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut first = true;
for &v in &self.0 {
if !first {
f.write_char('.')?;
}
write!(f, "{v}")?;
first = false;
}
Ok(())
}
}
impl Repr for Version {
fn repr(&self) -> EcoString {
let parts: Vec<_> = self.0.iter().map(|v| eco_format!("{v}")).collect();
eco_format!("version{}", &repr::pretty_array_like(&parts, false))
}
}
/// One or multiple version components.
pub enum VersionComponents {
Single(u32),
Multiple(Vec<u32>),
}
cast! {
VersionComponents,
v: u32 => Self::Single(v),
v: Vec<u32> => Self::Multiple(v)
}