typst_library/foundations/
auto.rs

1use std::fmt::{self, Debug, Formatter};
2
3use ecow::EcoString;
4
5use crate::diag::HintedStrResult;
6use crate::foundations::{
7    CastInfo, Fold, FromValue, IntoValue, Reflect, Repr, Resolve, StyleChain, Type,
8    Value, ty,
9};
10
11/// A value that indicates a smart default.
12///
13/// The auto type has exactly one value: `{auto}`.
14///
15/// Parameters that support the `{auto}` value have some smart default or
16/// contextual behaviour. A good example is the [text direction]($text.dir)
17/// parameter. Setting it to `{auto}` lets Typst automatically determine the
18/// direction from the [text language]($text.lang).
19#[ty(cast, name = "auto")]
20#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
21pub struct AutoValue;
22
23impl IntoValue for AutoValue {
24    fn into_value(self) -> Value {
25        Value::Auto
26    }
27}
28
29impl FromValue for AutoValue {
30    fn from_value(value: Value) -> HintedStrResult<Self> {
31        match value {
32            Value::Auto => Ok(Self),
33            _ => Err(Self::error(&value)),
34        }
35    }
36}
37
38impl Reflect for AutoValue {
39    fn input() -> CastInfo {
40        CastInfo::Type(Type::of::<Self>())
41    }
42
43    fn output() -> CastInfo {
44        CastInfo::Type(Type::of::<Self>())
45    }
46
47    fn castable(value: &Value) -> bool {
48        matches!(value, Value::Auto)
49    }
50}
51
52impl Debug for AutoValue {
53    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
54        f.write_str("Auto")
55    }
56}
57
58impl Repr for AutoValue {
59    fn repr(&self) -> EcoString {
60        "auto".into()
61    }
62}
63
64/// A value that can be automatically determined.
65#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
66pub enum Smart<T> {
67    /// The value should be determined smartly based on the circumstances.
68    Auto,
69    /// A specific value.
70    Custom(T),
71}
72
73impl<T> Smart<T> {
74    /// Whether the value is `Auto`.
75    pub fn is_auto(&self) -> bool {
76        matches!(self, Self::Auto)
77    }
78
79    /// Whether this holds a custom value.
80    pub fn is_custom(&self) -> bool {
81        matches!(self, Self::Custom(_))
82    }
83
84    /// Whether this is a `Smart::Custom(x)` and `f(x)` is true.
85    pub fn is_custom_and<F>(self, f: F) -> bool
86    where
87        F: Fn(T) -> bool,
88    {
89        match self {
90            Self::Auto => false,
91            Self::Custom(x) => f(x),
92        }
93    }
94
95    /// Returns a `Smart<&T>` borrowing the inner `T`.
96    pub fn as_ref(&self) -> Smart<&T> {
97        match self {
98            Smart::Auto => Smart::Auto,
99            Smart::Custom(v) => Smart::Custom(v),
100        }
101    }
102
103    /// Returns the contained custom value.
104    ///
105    /// If the value is [`Smart::Auto`], returns `None`.
106    ///
107    /// Equivalently, this just converts `Smart` to `Option`.
108    pub fn custom(self) -> Option<T> {
109        match self {
110            Self::Auto => None,
111            Self::Custom(x) => Some(x),
112        }
113    }
114
115    /// Map the contained custom value with `f`.
116    pub fn map<F, U>(self, f: F) -> Smart<U>
117    where
118        F: FnOnce(T) -> U,
119    {
120        match self {
121            Self::Auto => Smart::Auto,
122            Self::Custom(x) => Smart::Custom(f(x)),
123        }
124    }
125
126    /// Map the contained custom value with `f` if it contains a custom value,
127    /// otherwise returns `default`.
128    pub fn map_or<F, U>(self, default: U, f: F) -> U
129    where
130        F: FnOnce(T) -> U,
131    {
132        match self {
133            Self::Auto => default,
134            Self::Custom(x) => f(x),
135        }
136    }
137
138    /// Keeps `self` if it contains a custom value, otherwise returns `other`.
139    pub fn or(self, other: Smart<T>) -> Self {
140        match self {
141            Self::Custom(x) => Self::Custom(x),
142            Self::Auto => other,
143        }
144    }
145
146    /// Keeps `self` if it contains a custom value, otherwise returns the
147    /// output of the given function.
148    pub fn or_else<F>(self, f: F) -> Self
149    where
150        F: FnOnce() -> Self,
151    {
152        match self {
153            Self::Custom(x) => Self::Custom(x),
154            Self::Auto => f(),
155        }
156    }
157
158    /// Returns `Auto` if `self` is `Auto`, otherwise calls the provided
159    /// function on the contained value and returns the result.
160    pub fn and_then<F, U>(self, f: F) -> Smart<U>
161    where
162        F: FnOnce(T) -> Smart<U>,
163    {
164        match self {
165            Smart::Auto => Smart::Auto,
166            Smart::Custom(x) => f(x),
167        }
168    }
169
170    /// Returns the contained custom value or a provided default value.
171    pub fn unwrap_or(self, default: T) -> T {
172        match self {
173            Self::Auto => default,
174            Self::Custom(x) => x,
175        }
176    }
177
178    /// Returns the contained custom value or computes a default value.
179    pub fn unwrap_or_else<F>(self, f: F) -> T
180    where
181        F: FnOnce() -> T,
182    {
183        match self {
184            Self::Auto => f(),
185            Self::Custom(x) => x,
186        }
187    }
188
189    /// Returns the contained custom value or the default value.
190    pub fn unwrap_or_default(self) -> T
191    where
192        T: Default,
193    {
194        // we want to do this; the Clippy lint is not type-aware
195        #[allow(clippy::unwrap_or_default)]
196        self.unwrap_or_else(T::default)
197    }
198}
199
200impl<T> Smart<Smart<T>> {
201    /// Removes a single level of nesting, returns `Auto` if the inner or outer value is `Auto`.
202    pub fn flatten(self) -> Smart<T> {
203        match self {
204            Smart::Custom(Smart::Auto) | Smart::Auto => Smart::Auto,
205            Smart::Custom(Smart::Custom(v)) => Smart::Custom(v),
206        }
207    }
208}
209
210impl<T> Default for Smart<T> {
211    fn default() -> Self {
212        Self::Auto
213    }
214}
215
216impl<T> From<Option<T>> for Smart<T> {
217    fn from(value: Option<T>) -> Self {
218        match value {
219            Some(v) => Smart::Custom(v),
220            None => Smart::Auto,
221        }
222    }
223}
224
225impl<T: Reflect> Reflect for Smart<T> {
226    fn input() -> CastInfo {
227        T::input() + AutoValue::input()
228    }
229
230    fn output() -> CastInfo {
231        T::output() + AutoValue::output()
232    }
233
234    fn castable(value: &Value) -> bool {
235        AutoValue::castable(value) || T::castable(value)
236    }
237}
238
239impl<T: IntoValue> IntoValue for Smart<T> {
240    fn into_value(self) -> Value {
241        match self {
242            Smart::Custom(v) => v.into_value(),
243            Smart::Auto => Value::Auto,
244        }
245    }
246}
247
248impl<T: FromValue> FromValue for Smart<T> {
249    fn from_value(value: Value) -> HintedStrResult<Self> {
250        match value {
251            Value::Auto => Ok(Self::Auto),
252            v if T::castable(&v) => Ok(Self::Custom(T::from_value(v)?)),
253            _ => Err(Self::error(&value)),
254        }
255    }
256}
257
258impl<T: Resolve> Resolve for Smart<T> {
259    type Output = Smart<T::Output>;
260
261    fn resolve(self, styles: StyleChain) -> Self::Output {
262        self.map(|v| v.resolve(styles))
263    }
264}
265
266impl<T: Fold> Fold for Smart<T> {
267    fn fold(self, outer: Self) -> Self {
268        use Smart::Custom;
269        match (self, outer) {
270            (Custom(inner), Custom(outer)) => Custom(inner.fold(outer)),
271            // An explicit `auto` should be respected, thus we don't do
272            // `inner.or(outer)`.
273            (inner, _) => inner,
274        }
275    }
276}