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 { match value { Value::Auto => Ok(Self), _ => Err(Self::error(&value)), } } } impl Reflect for AutoValue { fn input() -> CastInfo { CastInfo::Type(Type::of::()) } fn output() -> CastInfo { CastInfo::Type(Type::of::()) } 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 { /// The value should be determined smartly based on the circumstances. Auto, /// A specific value. Custom(T), } impl Smart { /// 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(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 { match self { Self::Auto => None, Self::Custom(x) => Some(x), } } /// Map the contained custom value with `f`. pub fn map(self, f: F) -> Smart 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(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) -> 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(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(self, f: F) -> Smart where F: FnOnce(T) -> Smart, { 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(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 Smart> { /// Removes a single level of nesting, returns `Auto` if the inner or outer value is `Auto`. pub fn flatten(self) -> Smart { match self { Smart::Custom(Smart::Auto) | Smart::Auto => Smart::Auto, Smart::Custom(Smart::Custom(v)) => Smart::Custom(v), } } } impl Default for Smart { fn default() -> Self { Self::Auto } } impl Reflect for Smart { 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 IntoValue for Smart { fn into_value(self) -> Value { match self { Smart::Custom(v) => v.into_value(), Smart::Auto => Value::Auto, } } } impl FromValue for Smart { fn from_value(value: Value) -> HintedStrResult { match value { Value::Auto => Ok(Self::Auto), v if T::castable(&v) => Ok(Self::Custom(T::from_value(v)?)), _ => Err(Self::error(&value)), } } } impl Resolve for Smart { type Output = Smart; fn resolve(self, styles: StyleChain) -> Self::Output { self.map(|v| v.resolve(styles)) } } impl Fold for Smart { 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, } } }