Skip to content

Commit

Permalink
Change icu_pattern PlaceholderValueProvider and parts behavior (unico…
Browse files Browse the repository at this point in the history
…de-org#5908)

See discussion in unicode-org#5897
  • Loading branch information
sffc authored Dec 17, 2024
1 parent 7e5dcec commit a2a0060
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 121 deletions.
141 changes: 111 additions & 30 deletions components/pattern/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use crate::Error;
use writeable::{Part, TryWriteable};
use writeable::{TryWriteable, Writeable};

#[cfg(feature = "alloc")]
use alloc::{borrow::Cow, boxed::Box};
Expand Down Expand Up @@ -110,56 +110,137 @@ pub trait PatternBackend: crate::private::Sealed + 'static + core::fmt::Debug {
fn empty() -> &'static Self::Store;
}

/// Default annotation for the literal portion of a pattern.
/// Trait implemented on collections that can produce [`TryWriteable`]s for interpolation.
///
/// For more information, see [`PlaceholderValueProvider`]. For an example, see [`Pattern`].
/// This trait can add [`Part`]s for individual literals or placeholders. The implementations
/// of this trait on standard types do not add any [`Part`]s.
///
/// [`Pattern`]: crate::Pattern
pub const PATTERN_LITERAL_PART: Part = Part {
category: "pattern",
value: "literal",
};

/// Default annotation for the placeholder portion of a pattern.
/// # Examples
///
/// For more information, see [`PlaceholderValueProvider`]. For an example, see [`Pattern`].
/// A custom implementation that adds parts:
///
/// [`Pattern`]: crate::Pattern
pub const PATTERN_PLACEHOLDER_PART: Part = Part {
category: "pattern",
value: "placeholder",
};

/// Trait implemented on collections that can produce [`TryWriteable`]s for interpolation.
/// ```
/// use core::str::FromStr;
/// use icu_pattern::Pattern;
/// use icu_pattern::DoublePlaceholder;
/// use icu_pattern::DoublePlaceholderKey;
/// use icu_pattern::PlaceholderValueProvider;
/// use writeable::adapters::WithPart;
/// use writeable::adapters::WriteableAsTryWriteableInfallible;
/// use writeable::assert_writeable_parts_eq;
/// use writeable::Part;
/// use writeable::Writeable;
///
/// let pattern = Pattern::<DoublePlaceholder>::try_from_str(
/// "Hello, {0} and {1}!",
/// Default::default(),
/// )
/// .unwrap();
///
/// struct ValuesWithParts<'a>(&'a str, &'a str);
///
/// const PART_PLACEHOLDER_0: Part = Part {
/// category: "custom",
/// value: "placeholder0",
/// };
/// const PART_PLACEHOLDER_1: Part = Part {
/// category: "custom",
/// value: "placeholder1",
/// };
/// const PART_LITERAL: Part = Part {
/// category: "custom",
/// value: "literal",
/// };
///
/// impl PlaceholderValueProvider<DoublePlaceholderKey> for ValuesWithParts<'_> {
/// type Error = core::convert::Infallible;
///
/// type W<'a> = WriteableAsTryWriteableInfallible<WithPart<&'a str>>
/// where
/// Self: 'a;
///
/// type L<'a, 'l> = WithPart<&'l str>
/// where
/// Self: 'a;
///
/// #[inline]
/// fn value_for(&self, key: DoublePlaceholderKey) -> Self::W<'_> {
/// let writeable = match key {
/// DoublePlaceholderKey::Place0 => WithPart {
/// writeable: self.0,
/// part: PART_PLACEHOLDER_0,
/// },
/// DoublePlaceholderKey::Place1 => WithPart {
/// writeable: self.1,
/// part: PART_PLACEHOLDER_1,
/// },
/// };
/// WriteableAsTryWriteableInfallible(writeable)
/// }
///
/// #[inline]
/// fn map_literal<'a, 'l>(&'a self, literal: &'l str) -> Self::L<'a, 'l> {
/// WithPart {
/// writeable: literal,
/// part: PART_LITERAL,
/// }
/// }
/// }
///
/// This trait determines the [`Part`]s produced by the writeable. In this crate, implementations
/// of this trait default to using [`PATTERN_LITERAL_PART`] and [`PATTERN_PLACEHOLDER_PART`].
/// assert_writeable_parts_eq!(
/// pattern.interpolate(ValuesWithParts("Alice", "Bob")),
/// "Hello, Alice and Bob!",
/// [
/// (0, 7, PART_LITERAL),
/// (7, 12, PART_PLACEHOLDER_0),
/// (12, 17, PART_LITERAL),
/// (17, 20, PART_PLACEHOLDER_1),
/// (20, 21, PART_LITERAL),
/// ]
/// );
/// ```
///
/// [`Part`]: writeable::Part
pub trait PlaceholderValueProvider<K> {
type Error;

type W<'a>: TryWriteable<Error = Self::Error>
where
Self: 'a;

const LITERAL_PART: Part;
type L<'a, 'l>: Writeable
where
Self: 'a;

/// Returns the [`TryWriteable`] to substitute for the given placeholder
/// and the [`Part`] representing it.
fn value_for(&self, key: K) -> (Self::W<'_>, Part);
/// Returns the [`TryWriteable`] to substitute for the given placeholder.
///
/// See [`PatternItem::Placeholder`]
fn value_for(&self, key: K) -> Self::W<'_>;

/// Maps a literal string to a [`Writeable`] that could contain parts.
///
/// See [`PatternItem::Literal`]
fn map_literal<'a, 'l>(&'a self, literal: &'l str) -> Self::L<'a, 'l>;
}

impl<'b, K, T> PlaceholderValueProvider<K> for &'b T
where
T: PlaceholderValueProvider<K> + ?Sized,
{
type Error = T::Error;
type W<'a>
= T::W<'a>

type W<'a> = T::W<'a>
where
T: 'a,
'b: 'a;
const LITERAL_PART: Part = T::LITERAL_PART;
fn value_for(&self, key: K) -> (Self::W<'_>, Part) {
Self: 'a;

type L<'a, 'l> = T::L<'a, 'l>
where
Self: 'a;

fn value_for(&self, key: K) -> Self::W<'_> {
(*self).value_for(key)
}
fn map_literal<'a, 'l>(&'a self, literal: &'l str) -> Self::L<'a, 'l> {
(*self).map_literal(literal)
}
}
47 changes: 28 additions & 19 deletions components/pattern/src/double.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,22 +75,26 @@ where
W1: Writeable,
{
type Error = Infallible;
type W<'a>
= WriteableAsTryWriteableInfallible<Either<&'a W0, &'a W1>>

type W<'a> = WriteableAsTryWriteableInfallible<Either<&'a W0, &'a W1>>
where
Self: 'a;

type L<'a, 'l> = &'l str
where
W0: 'a,
W1: 'a;
const LITERAL_PART: writeable::Part = crate::PATTERN_LITERAL_PART;
Self: 'a;

#[inline]
fn value_for(&self, key: DoublePlaceholderKey) -> (Self::W<'_>, writeable::Part) {
fn value_for(&self, key: DoublePlaceholderKey) -> Self::W<'_> {
let writeable = match key {
DoublePlaceholderKey::Place0 => Either::Left(&self.0),
DoublePlaceholderKey::Place1 => Either::Right(&self.1),
};
(
WriteableAsTryWriteableInfallible(writeable),
crate::PATTERN_PLACEHOLDER_PART,
)
WriteableAsTryWriteableInfallible(writeable)
}
#[inline]
fn map_literal<'a, 'l>(&'a self, literal: &'l str) -> Self::L<'a, 'l> {
literal
}
}

Expand All @@ -99,22 +103,27 @@ where
W: Writeable,
{
type Error = Infallible;
type W<'a>
= WriteableAsTryWriteableInfallible<&'a W>

type W<'a> = WriteableAsTryWriteableInfallible<&'a W>
where
Self: 'a;

type L<'a, 'l> = &'l str
where
W: 'a;
const LITERAL_PART: writeable::Part = crate::PATTERN_LITERAL_PART;
Self: 'a;

#[inline]
fn value_for(&self, key: DoublePlaceholderKey) -> (Self::W<'_>, writeable::Part) {
fn value_for(&self, key: DoublePlaceholderKey) -> Self::W<'_> {
let [item0, item1] = self;
let writeable = match key {
DoublePlaceholderKey::Place0 => item0,
DoublePlaceholderKey::Place1 => item1,
};
(
WriteableAsTryWriteableInfallible(writeable),
crate::PATTERN_PLACEHOLDER_PART,
)
WriteableAsTryWriteableInfallible(writeable)
}
#[inline]
fn map_literal<'a, 'l>(&'a self, literal: &'l str) -> Self::L<'a, 'l> {
literal
}
}

Expand Down
42 changes: 14 additions & 28 deletions components/pattern/src/frontend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ use crate::Parser;
use crate::ParserOptions;
#[cfg(feature = "alloc")]
use alloc::{borrow::ToOwned, boxed::Box, str::FromStr, string::String};
use core::{
convert::Infallible,
fmt::{self, Write},
marker::PhantomData,
};
use core::{convert::Infallible, fmt, marker::PhantomData};
use writeable::{adapters::TryWriteableInfallibleAsWriteable, PartsWrite, TryWriteable, Writeable};

/// A string pattern with placeholders.
Expand All @@ -38,42 +34,35 @@ use writeable::{adapters::TryWriteableInfallibleAsWriteable, PartsWrite, TryWrit
///
/// # Format to Parts
///
/// [`Pattern`] supports interpolating with [writeable::Part]s, annotations for whether the
/// substring was a placeholder or a literal.
///
/// By default, the substrings are annotated with [`PATTERN_LITERAL_PART`] and
/// [`PATTERN_PLACEHOLDER_PART`]. This can be customized with [`PlaceholderValueProvider`].
/// [`Pattern`] propagates [`Part`]s from inner writeables. In addition, it supports annotating
/// [`Part`]s for individual literals or placeholders via the [`PlaceholderValueProvider`] trait.
///
/// # Examples
///
/// Interpolating a [`SinglePlaceholder`] pattern with parts:
/// Interpolating a [`SinglePlaceholder`] pattern:
///
/// ```
/// use core::str::FromStr;
/// use icu_pattern::Pattern;
/// use icu_pattern::SinglePlaceholder;
/// use writeable::assert_writeable_parts_eq;
/// use writeable::assert_writeable_eq;
///
/// let pattern = Pattern::<SinglePlaceholder>::try_from_str(
/// "Hello, {0}!",
/// Default::default(),
/// )
/// .unwrap();
///
/// assert_writeable_parts_eq!(
/// assert_writeable_eq!(
/// pattern.interpolate(["Alice"]),
/// "Hello, Alice!",
/// [
/// (0, 7, icu_pattern::PATTERN_LITERAL_PART),
/// (7, 12, icu_pattern::PATTERN_PLACEHOLDER_PART),
/// (12, 13, icu_pattern::PATTERN_LITERAL_PART),
/// ]
/// "Hello, Alice!"
/// );
/// ```
///
/// [`SinglePlaceholder`]: crate::SinglePlaceholder
/// [`DoublePlaceholder`]: crate::DoublePlaceholder
/// [`MultiNamedPlaceholder`]: crate::MultiNamedPlaceholder
/// [`Part`]: writeable::Part
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
#[repr(transparent)]
pub struct Pattern<B: PatternBackend> {
Expand Down Expand Up @@ -344,17 +333,14 @@ where
for item in it {
match item {
PatternItem::Literal(s) => {
sink.with_part(P::LITERAL_PART, |sink| sink.write_str(s))?;
self.value_provider.map_literal(s).write_to_parts(sink)?;
}
PatternItem::Placeholder(key) => {
let (element_writeable, part) = self.value_provider.value_for(key);
sink.with_part(part, |sink| {
if let Err(e) = element_writeable.try_write_to_parts(sink)? {
// Keep the first error if there was one
error.get_or_insert(e);
}
Ok(())
})?;
let element_writeable = self.value_provider.value_for(key);
if let Err(e) = element_writeable.try_write_to_parts(sink)? {
// Keep the first error if there was one
error.get_or_insert(e);
}
}
}
#[cfg(debug_assertions)]
Expand Down
2 changes: 0 additions & 2 deletions components/pattern/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ pub use common::PatternItem;
#[cfg(feature = "alloc")]
pub use common::PatternItemCow;
pub use common::PlaceholderValueProvider;
pub use common::PATTERN_LITERAL_PART;
pub use common::PATTERN_PLACEHOLDER_PART;
pub use double::DoublePlaceholder;
pub use double::DoublePlaceholderKey;
pub use error::PatternError;
Expand Down
Loading

0 comments on commit a2a0060

Please sign in to comment.