mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
268 lines
6.8 KiB
Rust
268 lines
6.8 KiB
Rust
use std::fmt::{self, Debug, Formatter};
|
|
|
|
use ecow::EcoString;
|
|
|
|
use crate::diag::HintedStrResult;
|
|
use crate::foundations::{
|
|
ty, CastInfo, Fold, FromValue, IntoValue, Reflect, Repr, Resolve, StyleChain, Type,
|
|
Value,
|
|
};
|
|
|
|
/// A value that indicates a smart default.
|
|
///
|
|
/// The auto type has exactly one value: `{auto}`.
|
|
///
|
|
/// Parameters that support the `{auto}` value have some smart default or
|
|
/// contextual behaviour. A good example is the [text direction]($text.dir)
|
|
/// parameter. Setting it to `{auto}` lets Typst automatically determine the
|
|
/// direction from the [text language]($text.lang).
|
|
#[ty(cast, name = "auto")]
|
|
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
|
pub struct AutoValue;
|
|
|
|
impl IntoValue for AutoValue {
|
|
fn into_value(self) -> Value {
|
|
Value::Auto
|
|
}
|
|
}
|
|
|
|
impl FromValue for AutoValue {
|
|
fn from_value(value: Value) -> HintedStrResult<Self> {
|
|
match value {
|
|
Value::Auto => Ok(Self),
|
|
_ => Err(Self::error(&value)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Reflect for AutoValue {
|
|
fn input() -> CastInfo {
|
|
CastInfo::Type(Type::of::<Self>())
|
|
}
|
|
|
|
fn output() -> CastInfo {
|
|
CastInfo::Type(Type::of::<Self>())
|
|
}
|
|
|
|
fn castable(value: &Value) -> bool {
|
|
matches!(value, Value::Auto)
|
|
}
|
|
}
|
|
|
|
impl Debug for AutoValue {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
f.write_str("Auto")
|
|
}
|
|
}
|
|
|
|
impl Repr for AutoValue {
|
|
fn repr(&self) -> EcoString {
|
|
"auto".into()
|
|
}
|
|
}
|
|
|
|
/// A value that can be automatically determined.
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
|
pub enum Smart<T> {
|
|
/// The value should be determined smartly based on the circumstances.
|
|
Auto,
|
|
/// A specific value.
|
|
Custom(T),
|
|
}
|
|
|
|
impl<T> Smart<T> {
|
|
/// Whether the value is `Auto`.
|
|
pub fn is_auto(&self) -> bool {
|
|
matches!(self, Self::Auto)
|
|
}
|
|
|
|
/// Whether this holds a custom value.
|
|
pub fn is_custom(&self) -> bool {
|
|
matches!(self, Self::Custom(_))
|
|
}
|
|
|
|
/// Whether this is a `Smart::Custom(x)` and `f(x)` is true.
|
|
pub fn is_custom_and<F>(self, f: F) -> bool
|
|
where
|
|
F: Fn(T) -> bool,
|
|
{
|
|
match self {
|
|
Self::Auto => false,
|
|
Self::Custom(x) => f(x),
|
|
}
|
|
}
|
|
|
|
/// Returns a `Smart<&T>` borrowing the inner `T`.
|
|
pub fn as_ref(&self) -> Smart<&T> {
|
|
match self {
|
|
Smart::Auto => Smart::Auto,
|
|
Smart::Custom(v) => Smart::Custom(v),
|
|
}
|
|
}
|
|
|
|
/// Returns the contained custom value.
|
|
///
|
|
/// If the value is [`Smart::Auto`], returns `None`.
|
|
///
|
|
/// Equivalently, this just converts `Smart` to `Option`.
|
|
pub fn custom(self) -> Option<T> {
|
|
match self {
|
|
Self::Auto => None,
|
|
Self::Custom(x) => Some(x),
|
|
}
|
|
}
|
|
|
|
/// Map the contained custom value with `f`.
|
|
pub fn map<F, U>(self, f: F) -> Smart<U>
|
|
where
|
|
F: FnOnce(T) -> U,
|
|
{
|
|
match self {
|
|
Self::Auto => Smart::Auto,
|
|
Self::Custom(x) => Smart::Custom(f(x)),
|
|
}
|
|
}
|
|
|
|
/// Map the contained custom value with `f` if it contains a custom value,
|
|
/// otherwise returns `default`.
|
|
pub fn map_or<F, U>(self, default: U, f: F) -> U
|
|
where
|
|
F: FnOnce(T) -> U,
|
|
{
|
|
match self {
|
|
Self::Auto => default,
|
|
Self::Custom(x) => f(x),
|
|
}
|
|
}
|
|
|
|
/// Keeps `self` if it contains a custom value, otherwise returns `other`.
|
|
pub fn or(self, other: Smart<T>) -> Self {
|
|
match self {
|
|
Self::Custom(x) => Self::Custom(x),
|
|
Self::Auto => other,
|
|
}
|
|
}
|
|
|
|
/// Keeps `self` if it contains a custom value, otherwise returns the
|
|
/// output of the given function.
|
|
pub fn or_else<F>(self, f: F) -> Self
|
|
where
|
|
F: FnOnce() -> Self,
|
|
{
|
|
match self {
|
|
Self::Custom(x) => Self::Custom(x),
|
|
Self::Auto => f(),
|
|
}
|
|
}
|
|
|
|
/// Returns `Auto` if `self` is `Auto`, otherwise calls the provided
|
|
/// function on the contained value and returns the result.
|
|
pub fn and_then<F, U>(self, f: F) -> Smart<U>
|
|
where
|
|
F: FnOnce(T) -> Smart<U>,
|
|
{
|
|
match self {
|
|
Smart::Auto => Smart::Auto,
|
|
Smart::Custom(x) => f(x),
|
|
}
|
|
}
|
|
|
|
/// Returns the contained custom value or a provided default value.
|
|
pub fn unwrap_or(self, default: T) -> T {
|
|
match self {
|
|
Self::Auto => default,
|
|
Self::Custom(x) => x,
|
|
}
|
|
}
|
|
|
|
/// Returns the contained custom value or computes a default value.
|
|
pub fn unwrap_or_else<F>(self, f: F) -> T
|
|
where
|
|
F: FnOnce() -> T,
|
|
{
|
|
match self {
|
|
Self::Auto => f(),
|
|
Self::Custom(x) => x,
|
|
}
|
|
}
|
|
|
|
/// Returns the contained custom value or the default value.
|
|
pub fn unwrap_or_default(self) -> T
|
|
where
|
|
T: Default,
|
|
{
|
|
// we want to do this; the Clippy lint is not type-aware
|
|
#[allow(clippy::unwrap_or_default)]
|
|
self.unwrap_or_else(T::default)
|
|
}
|
|
}
|
|
|
|
impl<T> Smart<Smart<T>> {
|
|
/// Removes a single level of nesting, returns `Auto` if the inner or outer value is `Auto`.
|
|
pub fn flatten(self) -> Smart<T> {
|
|
match self {
|
|
Smart::Custom(Smart::Auto) | Smart::Auto => Smart::Auto,
|
|
Smart::Custom(Smart::Custom(v)) => Smart::Custom(v),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> Default for Smart<T> {
|
|
fn default() -> Self {
|
|
Self::Auto
|
|
}
|
|
}
|
|
|
|
impl<T: Reflect> Reflect for Smart<T> {
|
|
fn input() -> CastInfo {
|
|
T::input() + AutoValue::input()
|
|
}
|
|
|
|
fn output() -> CastInfo {
|
|
T::output() + AutoValue::output()
|
|
}
|
|
|
|
fn castable(value: &Value) -> bool {
|
|
AutoValue::castable(value) || T::castable(value)
|
|
}
|
|
}
|
|
|
|
impl<T: IntoValue> IntoValue for Smart<T> {
|
|
fn into_value(self) -> Value {
|
|
match self {
|
|
Smart::Custom(v) => v.into_value(),
|
|
Smart::Auto => Value::Auto,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: FromValue> FromValue for Smart<T> {
|
|
fn from_value(value: Value) -> HintedStrResult<Self> {
|
|
match value {
|
|
Value::Auto => Ok(Self::Auto),
|
|
v if T::castable(&v) => Ok(Self::Custom(T::from_value(v)?)),
|
|
_ => Err(Self::error(&value)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Resolve> Resolve for Smart<T> {
|
|
type Output = Smart<T::Output>;
|
|
|
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
self.map(|v| v.resolve(styles))
|
|
}
|
|
}
|
|
|
|
impl<T: Fold> Fold for Smart<T> {
|
|
fn fold(self, outer: Self) -> Self {
|
|
use Smart::Custom;
|
|
match (self, outer) {
|
|
(Custom(inner), Custom(outer)) => Custom(inner.fold(outer)),
|
|
// An explicit `auto` should be respected, thus we don't do
|
|
// `inner.or(outer)`.
|
|
(inner, _) => inner,
|
|
}
|
|
}
|
|
}
|