diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-10-27 19:04:55 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-27 18:04:55 +0000 |
| commit | be7cfc85d08c545abfac08098b7b33b4bd71f37e (patch) | |
| tree | f4137fa2aaa57babae1f7603a9b2ed7e688f43d8 /crates/typst-library/src/foundations/auto.rs | |
| parent | b8034a343831e8609aec2ec81eb7eeda57aa5d81 (diff) | |
Split out four new crates (#5302)
Diffstat (limited to 'crates/typst-library/src/foundations/auto.rs')
| -rw-r--r-- | crates/typst-library/src/foundations/auto.rs | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/crates/typst-library/src/foundations/auto.rs b/crates/typst-library/src/foundations/auto.rs new file mode 100644 index 00000000..8237b450 --- /dev/null +++ b/crates/typst-library/src/foundations/auto.rs @@ -0,0 +1,267 @@ +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, + } + } +} |
