summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/foundations/auto.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-10-27 19:04:55 +0100
committerGitHub <noreply@github.com>2024-10-27 18:04:55 +0000
commitbe7cfc85d08c545abfac08098b7b33b4bd71f37e (patch)
treef4137fa2aaa57babae1f7603a9b2ed7e688f43d8 /crates/typst-library/src/foundations/auto.rs
parentb8034a343831e8609aec2ec81eb7eeda57aa5d81 (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.rs267
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,
+ }
+ }
+}