mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Add capability to get current compiler version (#2016)
This commit is contained in:
parent
34ebbaeb10
commit
cf9bde3245
@ -11,7 +11,6 @@ mod watch;
|
||||
mod world;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::env;
|
||||
use std::io::{self, IsTerminal, Write};
|
||||
use std::process::ExitCode;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use typst::eval::{
|
||||
Datetime, Duration, EvalMode, Module, Never, NoneValue, Plugin, Regex,
|
||||
Datetime, Duration, EvalMode, Module, Never, NoneValue, Plugin, Regex, Version,
|
||||
};
|
||||
|
||||
use crate::prelude::*;
|
||||
@ -22,6 +22,7 @@ pub(super) fn define(global: &mut Scope) {
|
||||
global.define_type::<Regex>();
|
||||
global.define_type::<Datetime>();
|
||||
global.define_type::<Duration>();
|
||||
global.define_type::<Version>();
|
||||
global.define_type::<Plugin>();
|
||||
global.define_func::<repr>();
|
||||
global.define_func::<panic>();
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! Computational functions.
|
||||
|
||||
pub mod calc;
|
||||
pub mod sys;
|
||||
|
||||
mod data;
|
||||
mod foundations;
|
||||
@ -15,4 +16,5 @@ pub(super) fn define(global: &mut Scope) {
|
||||
self::foundations::define(global);
|
||||
self::data::define(global);
|
||||
self::calc::define(global);
|
||||
self::sys::define(global);
|
||||
}
|
||||
|
24
crates/typst-library/src/compute/sys.rs
Normal file
24
crates/typst-library/src/compute/sys.rs
Normal file
@ -0,0 +1,24 @@
|
||||
//! System-related things.
|
||||
|
||||
use typst::eval::{Module, Scope, Version};
|
||||
|
||||
/// Hook up all calculation definitions.
|
||||
pub(super) fn define(global: &mut Scope) {
|
||||
global.category("sys");
|
||||
global.define_module(module());
|
||||
}
|
||||
|
||||
/// A module with system-related things.
|
||||
fn module() -> Module {
|
||||
let mut scope = Scope::deduplicating();
|
||||
scope.category("sys");
|
||||
scope.define(
|
||||
"version",
|
||||
Version::from_iter([
|
||||
env!("CARGO_PKG_VERSION_MAJOR").parse::<u32>().unwrap(),
|
||||
env!("CARGO_PKG_VERSION_MINOR").parse::<u32>().unwrap(),
|
||||
env!("CARGO_PKG_VERSION_PATCH").parse::<u32>().unwrap(),
|
||||
]),
|
||||
);
|
||||
Module::new("sys", scope)
|
||||
}
|
@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
cast, func, ops, scope, ty, Args, Bytes, CastInfo, FromValue, Func, IntoValue,
|
||||
Reflect, Value, Vm,
|
||||
Reflect, Value, Version, Vm,
|
||||
};
|
||||
use crate::diag::{At, SourceResult, StrResult};
|
||||
use crate::eval::ops::{add, mul};
|
||||
@ -804,6 +804,7 @@ cast! {
|
||||
ToArray,
|
||||
v: Bytes => Self(v.iter().map(|&b| Value::Int(b.into())).collect()),
|
||||
v: Array => Self(v),
|
||||
v: Version => Self(v.values().iter().map(|&v| Value::Int(v as i64)).collect())
|
||||
}
|
||||
|
||||
impl Debug for Array {
|
||||
|
@ -1,6 +1,7 @@
|
||||
use ecow::{eco_format, EcoString};
|
||||
|
||||
use crate::diag::StrResult;
|
||||
use crate::eval::Version;
|
||||
use crate::geom::{Align, Length, Rel, Stroke};
|
||||
|
||||
use super::{IntoValue, Type, Value};
|
||||
@ -16,6 +17,10 @@ pub(crate) fn field(value: &Value, field: &str) -> StrResult<Value> {
|
||||
|
||||
// Special cases, such as module and dict, are handled by Value itself
|
||||
let result = match value {
|
||||
Value::Version(version) => match version.component(field) {
|
||||
Ok(i) => i.into_value(),
|
||||
Err(_) => return missing(),
|
||||
},
|
||||
Value::Length(length) => match field {
|
||||
"em" => length.em.get().into_value(),
|
||||
"abs" => length.abs.into_value(),
|
||||
@ -69,7 +74,9 @@ fn missing_field(ty: Type, field: &str) -> EcoString {
|
||||
|
||||
/// List the available fields for a type.
|
||||
pub fn fields_on(ty: Type) -> &'static [&'static str] {
|
||||
if ty == Type::of::<Length>() {
|
||||
if ty == Type::of::<Version>() {
|
||||
&Version::COMPONENTS
|
||||
} else if ty == Type::of::<Length>() {
|
||||
&["em", "abs"]
|
||||
} else if ty == Type::of::<Rel>() {
|
||||
&["ratio", "length"]
|
||||
|
@ -31,6 +31,7 @@ mod scope;
|
||||
mod symbol;
|
||||
mod tracer;
|
||||
mod ty;
|
||||
mod version;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use {
|
||||
@ -65,6 +66,7 @@ pub use self::symbol::{symbols, Symbol};
|
||||
pub use self::tracer::Tracer;
|
||||
pub use self::ty::{scope, ty, NativeType, NativeTypeData, Type};
|
||||
pub use self::value::{Dynamic, Value};
|
||||
pub use self::version::Version;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::mem;
|
||||
|
@ -366,6 +366,7 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool {
|
||||
(Fraction(a), Fraction(b)) => a == b,
|
||||
(Color(a), Color(b)) => a == b,
|
||||
(Symbol(a), Symbol(b)) => a == b,
|
||||
(Version(a), Version(b)) => a == b,
|
||||
(Str(a), Str(b)) => a == b,
|
||||
(Bytes(a), Bytes(b)) => a == b,
|
||||
(Label(a), Label(b)) => a == b,
|
||||
@ -408,6 +409,7 @@ pub fn compare(lhs: &Value, rhs: &Value) -> StrResult<Ordering> {
|
||||
(Ratio(a), Ratio(b)) => a.cmp(b),
|
||||
(Relative(a), Relative(b)) => try_cmp_values(a, b)?,
|
||||
(Fraction(a), Fraction(b)) => a.cmp(b),
|
||||
(Version(a), Version(b)) => a.cmp(b),
|
||||
(Str(a), Str(b)) => a.cmp(b),
|
||||
|
||||
// Some technically different things should be comparable.
|
||||
|
@ -9,7 +9,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use super::{
|
||||
cast, dict, func, scope, ty, Args, Array, Bytes, Dict, Func, IntoValue, Type, Value,
|
||||
Vm,
|
||||
Version, Vm,
|
||||
};
|
||||
use crate::diag::{bail, At, SourceResult, StrResult};
|
||||
use crate::geom::Align;
|
||||
@ -605,6 +605,7 @@ cast! {
|
||||
ToStr,
|
||||
v: i64 => Self::Int(v),
|
||||
v: f64 => Self::Str(format_str!("{}", v)),
|
||||
v: Version => Self::Str(format_str!("{}", v)),
|
||||
v: Bytes => Self::Str(
|
||||
std::str::from_utf8(&v)
|
||||
.map_err(|_| "bytes are not valid utf-8")?
|
||||
|
@ -14,7 +14,7 @@ use typst::eval::Duration;
|
||||
use super::{
|
||||
fields, format_str, ops, Args, Array, AutoValue, Bytes, CastInfo, Content, Dict,
|
||||
FromValue, Func, IntoValue, Module, NativeType, NoneValue, Plugin, Reflect, Scope,
|
||||
Str, Symbol, Type,
|
||||
Str, Symbol, Type, Version,
|
||||
};
|
||||
use crate::diag::StrResult;
|
||||
use crate::eval::Datetime;
|
||||
@ -50,6 +50,8 @@ pub enum Value {
|
||||
Color(Color),
|
||||
/// A symbol: `arrow.l`.
|
||||
Symbol(Symbol),
|
||||
/// A version.
|
||||
Version(Version),
|
||||
/// A string: `"string"`.
|
||||
Str(Str),
|
||||
/// Raw bytes.
|
||||
@ -122,6 +124,7 @@ impl Value {
|
||||
Self::Fraction(_) => Type::of::<Fr>(),
|
||||
Self::Color(_) => Type::of::<Color>(),
|
||||
Self::Symbol(_) => Type::of::<Symbol>(),
|
||||
Self::Version(_) => Type::of::<Version>(),
|
||||
Self::Str(_) => Type::of::<Str>(),
|
||||
Self::Bytes(_) => Type::of::<Bytes>(),
|
||||
Self::Label(_) => Type::of::<Label>(),
|
||||
@ -149,6 +152,7 @@ impl Value {
|
||||
pub fn field(&self, field: &str) -> StrResult<Value> {
|
||||
match self {
|
||||
Self::Symbol(symbol) => symbol.clone().modified(field).map(Self::Symbol),
|
||||
Self::Version(version) => version.component(field).map(Self::Int),
|
||||
Self::Dict(dict) => dict.get(field).cloned(),
|
||||
Self::Content(content) => content.get(field),
|
||||
Self::Type(ty) => ty.field(field).cloned(),
|
||||
@ -199,6 +203,7 @@ impl Value {
|
||||
Self::Int(v) => item!(text)(eco_format!("{v}")),
|
||||
Self::Float(v) => item!(text)(eco_format!("{v}")),
|
||||
Self::Str(v) => item!(text)(v.into()),
|
||||
Self::Version(v) => item!(text)(eco_format!("{v}")),
|
||||
Self::Symbol(v) => item!(text)(v.get().into()),
|
||||
Self::Content(v) => v,
|
||||
Self::Module(module) => module.content(),
|
||||
@ -231,6 +236,7 @@ impl Debug for Value {
|
||||
Self::Fraction(v) => Debug::fmt(v, f),
|
||||
Self::Color(v) => Debug::fmt(v, f),
|
||||
Self::Symbol(v) => Debug::fmt(v, f),
|
||||
Self::Version(v) => Debug::fmt(v, f),
|
||||
Self::Str(v) => Debug::fmt(v, f),
|
||||
Self::Bytes(v) => Debug::fmt(v, f),
|
||||
Self::Label(v) => Debug::fmt(v, f),
|
||||
@ -278,6 +284,7 @@ impl Hash for Value {
|
||||
Self::Fraction(v) => v.hash(state),
|
||||
Self::Color(v) => v.hash(state),
|
||||
Self::Symbol(v) => v.hash(state),
|
||||
Self::Version(v) => v.hash(state),
|
||||
Self::Str(v) => v.hash(state),
|
||||
Self::Bytes(v) => v.hash(state),
|
||||
Self::Label(v) => v.hash(state),
|
||||
@ -582,6 +589,7 @@ primitive! { Rel<Length>: "relative length",
|
||||
primitive! { Fr: "fraction", Fraction }
|
||||
primitive! { Color: "color", Color }
|
||||
primitive! { Symbol: "symbol", Symbol }
|
||||
primitive! { Version: "version", Version }
|
||||
primitive! {
|
||||
Str: "string",
|
||||
Str,
|
||||
|
199
crates/typst/src/eval/version.rs
Normal file
199
crates/typst/src/eval/version.rs
Normal file
@ -0,0 +1,199 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||
use std::hash::Hash;
|
||||
use std::iter::repeat;
|
||||
|
||||
use ecow::{eco_format, EcoVec};
|
||||
|
||||
use super::{cast, func, scope, ty};
|
||||
use crate::diag::{bail, error, StrResult};
|
||||
use crate::util::pretty_array_like;
|
||||
|
||||
/// A version, with any number of components.
|
||||
///
|
||||
/// 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 first three components have names: `major`, `minor`, `patch`. All
|
||||
/// components after that do not have names.
|
||||
#[ty(scope)]
|
||||
#[derive(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(|| error!("unknown version component"))
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// Get a component of a version.
|
||||
///
|
||||
/// 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) -> std::fmt::Result {
|
||||
let mut first = true;
|
||||
for &v in &self.0 {
|
||||
if !first {
|
||||
f.write_char('.')?;
|
||||
}
|
||||
write!(f, "{v}")?;
|
||||
first = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Version {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("version")?;
|
||||
let parts: Vec<_> = self.0.iter().map(|v| eco_format!("{v}")).collect();
|
||||
f.write_str(&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)
|
||||
}
|
@ -28,5 +28,5 @@
|
||||
#bytes((a: 1))
|
||||
|
||||
---
|
||||
// Error: 8-15 expected bytes or array, found string
|
||||
// Error: 8-15 expected bytes, array, or version, found string
|
||||
#array("hello")
|
||||
|
@ -168,7 +168,7 @@
|
||||
#test(str(10 / 3).len() > 10, true)
|
||||
|
||||
---
|
||||
// Error: 6-8 expected integer, float, bytes, label, type, or string, found content
|
||||
// Error: 6-8 expected integer, float, version, bytes, label, type, or string, found content
|
||||
#str([])
|
||||
|
||||
---
|
||||
|
47
tests/typ/compute/version.typ
Normal file
47
tests/typ/compute/version.typ
Normal file
@ -0,0 +1,47 @@
|
||||
// Test versions.
|
||||
// Ref: false
|
||||
|
||||
---
|
||||
// Test version constructor.
|
||||
|
||||
// Empty.
|
||||
#version()
|
||||
|
||||
// Plain.
|
||||
#version(1, 2)
|
||||
|
||||
// Single Array argument.
|
||||
#version((1, 2))
|
||||
|
||||
// Mixed arguments.
|
||||
#version(1, (2, 3), 4, (5, 6), 7)
|
||||
|
||||
---
|
||||
// Test equality of different-length versions
|
||||
#test(version(), version(0))
|
||||
#test(version(0), version(0, 0))
|
||||
#test(version(1, 2), version(1, 2, 0, 0, 0, 0))
|
||||
---
|
||||
// Test `version.at`.
|
||||
|
||||
// Non-negative index in bounds
|
||||
#test(version(1, 2).at(1), 2)
|
||||
|
||||
// Non-negative index out of bounds
|
||||
#test(version(1, 2).at(4), 0)
|
||||
|
||||
// Negative index in bounds
|
||||
#test(version(1, 2).at(-2), 1)
|
||||
|
||||
// Error: 2-22 component index out of bounds (index: -3, len: 2)
|
||||
#version(1, 2).at(-3)
|
||||
|
||||
---
|
||||
// Test version fields.
|
||||
#test(version(1, 2, 3).major, 1)
|
||||
#test(version(1, 2, 3).minor, 2)
|
||||
#test(version(1, 2, 3).patch, 3)
|
||||
|
||||
---
|
||||
// Test the type of `sys.version`
|
||||
#test(type(sys.version), version)
|
Loading…
x
Reference in New Issue
Block a user