mirror of
https://github.com/typst/typst
synced 2025-05-15 01:25:28 +08:00
Fix .at(default: ...)
for strings and content (#1339)
This commit is contained in:
parent
31dfe32242
commit
e4557f6639
@ -31,7 +31,11 @@ pub fn call(
|
|||||||
"len" => Value::Int(string.len()),
|
"len" => Value::Int(string.len()),
|
||||||
"first" => Value::Str(string.first().at(span)?),
|
"first" => Value::Str(string.first().at(span)?),
|
||||||
"last" => Value::Str(string.last().at(span)?),
|
"last" => Value::Str(string.last().at(span)?),
|
||||||
"at" => Value::Str(string.at(args.expect("index")?, None).at(span)?),
|
"at" => {
|
||||||
|
let index = args.expect("index")?;
|
||||||
|
let default = args.named::<EcoString>("default")?;
|
||||||
|
Value::Str(string.at(index, default.as_deref()).at(span)?)
|
||||||
|
}
|
||||||
"slice" => {
|
"slice" => {
|
||||||
let start = args.expect("start")?;
|
let start = args.expect("start")?;
|
||||||
let mut end = args.eat()?;
|
let mut end = args.eat()?;
|
||||||
@ -74,7 +78,9 @@ pub fn call(
|
|||||||
Value::Content(content) => match method {
|
Value::Content(content) => match method {
|
||||||
"func" => content.func().into(),
|
"func" => content.func().into(),
|
||||||
"has" => Value::Bool(content.has(&args.expect::<EcoString>("field")?)),
|
"has" => Value::Bool(content.has(&args.expect::<EcoString>("field")?)),
|
||||||
"at" => content.at(&args.expect::<EcoString>("field")?, None).at(span)?,
|
"at" => content
|
||||||
|
.at(&args.expect::<EcoString>("field")?, args.named("default")?)
|
||||||
|
.at(span)?,
|
||||||
"location" => content
|
"location" => content
|
||||||
.location()
|
.location()
|
||||||
.ok_or("this method can only be called on content returned by query(..)")
|
.ok_or("this method can only be called on content returned by query(..)")
|
||||||
|
@ -71,9 +71,9 @@ impl Str {
|
|||||||
/// Extract the grapheme cluster at the given index.
|
/// Extract the grapheme cluster at the given index.
|
||||||
pub fn at<'a>(&'a self, index: i64, default: Option<&'a str>) -> StrResult<Self> {
|
pub fn at<'a>(&'a self, index: i64, default: Option<&'a str>) -> StrResult<Self> {
|
||||||
let len = self.len();
|
let len = self.len();
|
||||||
let grapheme = self.0[self.locate(index)?..]
|
let grapheme = self
|
||||||
.graphemes(true)
|
.locate_opt(index)?
|
||||||
.next()
|
.and_then(|i| self.0[i..].graphemes(true).next())
|
||||||
.or(default)
|
.or(default)
|
||||||
.ok_or_else(|| no_default_and_out_of_bounds(index, len))?;
|
.ok_or_else(|| no_default_and_out_of_bounds(index, len))?;
|
||||||
Ok(grapheme.into())
|
Ok(grapheme.into())
|
||||||
@ -325,22 +325,28 @@ impl Str {
|
|||||||
Ok(Self(self.0.repeat(n)))
|
Ok(Self(self.0.repeat(n)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve an index.
|
/// Resolve an index, if it is within bounds.
|
||||||
fn locate(&self, index: i64) -> StrResult<usize> {
|
/// Errors on invalid char boundaries.
|
||||||
|
fn locate_opt(&self, index: i64) -> StrResult<Option<usize>> {
|
||||||
let wrapped =
|
let wrapped =
|
||||||
if index >= 0 { Some(index) } else { self.len().checked_add(index) };
|
if index >= 0 { Some(index) } else { self.len().checked_add(index) };
|
||||||
|
|
||||||
let resolved = wrapped
|
let resolved = wrapped
|
||||||
.and_then(|v| usize::try_from(v).ok())
|
.and_then(|v| usize::try_from(v).ok())
|
||||||
.filter(|&v| v <= self.0.len())
|
.filter(|&v| v <= self.0.len());
|
||||||
.ok_or_else(|| out_of_bounds(index, self.len()))?;
|
|
||||||
|
|
||||||
if !self.0.is_char_boundary(resolved) {
|
if resolved.map_or(false, |i| !self.0.is_char_boundary(i)) {
|
||||||
return Err(not_a_char_boundary(index));
|
return Err(not_a_char_boundary(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(resolved)
|
Ok(resolved)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve an index or throw an out of bounds error.
|
||||||
|
fn locate(&self, index: i64) -> StrResult<usize> {
|
||||||
|
self.locate_opt(index)?
|
||||||
|
.ok_or_else(|| out_of_bounds(index, self.len()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The out of bounds access error message.
|
/// The out of bounds access error message.
|
||||||
|
@ -26,6 +26,10 @@
|
|||||||
test(rewritten, "Hello!\n This is a sentence!\n And one more!")
|
test(rewritten, "Hello!\n This is a sentence!\n And one more!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test .at() default values for content.
|
||||||
|
#test(auto, [a].at("doesn't exist", default: auto))
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 2:2-2:15 type array has no method `fun`
|
// Error: 2:2-2:15 type array has no method `fun`
|
||||||
#let numbers = ()
|
#let numbers = ()
|
||||||
|
@ -28,6 +28,10 @@
|
|||||||
#test("Hello".at(-2), "l")
|
#test("Hello".at(-2), "l")
|
||||||
#test("Hey: 🏳️🌈 there!".at(5), "🏳️🌈")
|
#test("Hey: 🏳️🌈 there!".at(5), "🏳️🌈")
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test `at`'s 'default' parameter.
|
||||||
|
#test("z", "Hello".at(5, default: "z"))
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 2-14 string index 2 is not a character boundary
|
// Error: 2-14 string index 2 is not a character boundary
|
||||||
#"🏳️🌈".at(2)
|
#"🏳️🌈".at(2)
|
||||||
@ -36,6 +40,10 @@
|
|||||||
// Error: 2-15 no default value was specified and string index out of bounds (index: 5, len: 5)
|
// Error: 2-15 no default value was specified and string index out of bounds (index: 5, len: 5)
|
||||||
#"Hello".at(5)
|
#"Hello".at(5)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 25-32 expected string, found dictionary
|
||||||
|
#"Hello".at(5, default: (a: 10))
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test the `slice` method.
|
// Test the `slice` method.
|
||||||
#test("abc".slice(1, 2), "b")
|
#test("abc".slice(1, 2), "b")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user