Add at method to arguments (#4864)

This commit is contained in:
Malo 2024-09-02 18:22:43 +02:00 committed by GitHub
parent 1ccfaba88e
commit 9fd796e0e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 91 additions and 2 deletions

View File

@ -2,9 +2,9 @@ use std::fmt::{self, Debug, Formatter};
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
use crate::diag::{bail, error, At, SourceDiagnostic, SourceResult};
use crate::diag::{bail, error, At, SourceDiagnostic, SourceResult, StrResult};
use crate::foundations::{
func, repr, scope, ty, Array, Dict, FromValue, IntoValue, Repr, Str, Value,
cast, func, repr, scope, ty, Array, Dict, FromValue, IntoValue, Repr, Str, Value,
};
use crate::syntax::{Span, Spanned};
@ -256,6 +256,42 @@ impl Args {
}
}
/// A key that can be used to get an argument: either the index of a positional
/// argument, or the name of a named argument.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ArgumentKey {
Index(i64),
Name(Str),
}
cast! {
ArgumentKey,
v: i64 => Self::Index(v),
v: Str => Self::Name(v),
}
impl Args {
fn get(&self, key: &ArgumentKey) -> Option<&Value> {
let item = match key {
&ArgumentKey::Index(index) => {
let mut iter = self.items.iter().filter(|item| item.name.is_none());
if index < 0 {
let index = (-(index + 1)).try_into().ok()?;
iter.nth_back(index)
} else {
let index = index.try_into().ok()?;
iter.nth(index)
}
}
// Accept the last argument with the right name.
ArgumentKey::Name(name) => {
self.items.iter().rfind(|item| item.name.as_ref() == Some(name))
}
};
item.map(|item| &item.value.v)
}
}
#[scope]
impl Args {
/// Construct spreadable arguments in place.
@ -279,6 +315,28 @@ impl Args {
args.take()
}
/// Returns the positional argument at the specified index, or the named
/// argument with the specified name.
///
/// If the key is an [integer]($int), this is equivalent to first calling
/// [`pos`]($arguments.pos) and then [`array.at`]. If it is a [string]($str),
/// this is equivalent to first calling [`named`]($arguments.named) and then
/// [`dictionary.at`].
#[func]
pub fn at(
&self,
/// The index or name of the argument to get.
key: ArgumentKey,
/// A default value to return if the key is invalid.
#[named]
default: Option<Value>,
) -> StrResult<Value> {
self.get(&key)
.cloned()
.or(default)
.ok_or_else(|| missing_key_no_default(key))
}
/// Returns the captured positional arguments as an array.
#[func(name = "pos", title = "Positional")]
pub fn to_pos(&self) -> Array {
@ -380,3 +438,16 @@ where
Args::new(fallback, self)
}
}
/// The missing key access error message when no default was given.
#[cold]
fn missing_key_no_default(key: ArgumentKey) -> EcoString {
eco_format!(
"arguments do not contain key {} \
and no default value was specified",
match key {
ArgumentKey::Index(i) => i.repr(),
ArgumentKey::Name(name) => name.repr(),
}
)
}

View File

@ -0,0 +1,18 @@
// Test arguments.
--- arguments-at ---
#let args = arguments(0, 1, a: 2, 3)
#test(args.at(0), 0)
#test(args.at(1), 1)
#test(args.at(2), 3)
#test(args.at("a"), 2)
--- arguments-at-invalid-index ---
#let args = arguments(0, 1, a: 2, 3)
// Error: 2-12 arguments do not contain key 4 and no default value was specified
#args.at(4)
--- arguments-at-invalid-name ---
#let args = arguments(0, 1, a: 2, 3)
// Error: 2-14 arguments do not contain key "b" and no default value was specified
#args.at("b")