Skip to main content

typst_library/foundations/
auto.rs

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