diff --git a/Cargo.lock b/Cargo.lock index a90e8cf414b..c166708adc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1897,6 +1897,7 @@ dependencies = [ "icu_provider_blob", "icu_provider_export", "icu_provider_fs", + "icu_provider_source", "itertools", "litemap", "lru", diff --git a/provider/adapters/Cargo.toml b/provider/adapters/Cargo.toml index 457e844680c..d744881f761 100644 --- a/provider/adapters/Cargo.toml +++ b/provider/adapters/Cargo.toml @@ -29,3 +29,7 @@ serde = { workspace = true, features = ["derive", "alloc"], optional = true } icu_provider = { path = "../../provider/core", features = ["macros", "deserialize_json"] } icu_locale = { path = "../../components/locale" } writeable = { path = "../../utils/writeable" } + +[features] +std = [] +export = ["icu_provider/export", "std"] diff --git a/provider/adapters/src/either.rs b/provider/adapters/src/either.rs index a52b2d78c79..94be6ef60ae 100644 --- a/provider/adapters/src/either.rs +++ b/provider/adapters/src/either.rs @@ -5,6 +5,8 @@ //! Helpers for switching between multiple providers. use alloc::collections::BTreeSet; +#[cfg(feature = "export")] +use icu_provider::export::ExportableProvider; use icu_provider::prelude::*; /// A provider that is one of two types determined at runtime. @@ -122,3 +124,18 @@ impl, P1: IterableDataProvider> } } } + +#[cfg(feature = "export")] +impl ExportableProvider for EitherProvider +where + P0: ExportableProvider, + P1: ExportableProvider, +{ + fn supported_markers(&self) -> std::collections::HashSet { + use EitherProvider::*; + match self { + A(p) => p.supported_markers(), + B(p) => p.supported_markers(), + } + } +} diff --git a/provider/adapters/src/empty.rs b/provider/adapters/src/empty.rs index a69a12a8b68..b3b6048fa95 100644 --- a/provider/adapters/src/empty.rs +++ b/provider/adapters/src/empty.rs @@ -7,6 +7,8 @@ //! Use [`EmptyDataProvider`] as a stand-in for a provider that always fails. use alloc::collections::BTreeSet; +#[cfg(feature = "export")] +use icu_provider::export::ExportableProvider; use icu_provider::prelude::*; /// A data provider that always returns an error. @@ -107,3 +109,10 @@ where Ok(Default::default()) } } + +#[cfg(feature = "export")] +impl ExportableProvider for EmptyDataProvider { + fn supported_markers(&self) -> std::collections::HashSet { + Default::default() + } +} diff --git a/provider/adapters/src/filter/mod.rs b/provider/adapters/src/filter/mod.rs index a8c2258d90f..700d3b79f37 100644 --- a/provider/adapters/src/filter/mod.rs +++ b/provider/adapters/src/filter/mod.rs @@ -23,6 +23,8 @@ mod impls; use alloc::collections::BTreeSet; +#[cfg(feature = "export")] +use icu_provider::export::ExportableProvider; use icu_provider::prelude::*; /// A data provider that selectively filters out data requests. @@ -165,3 +167,15 @@ where }) } } + +#[cfg(feature = "export")] +impl ExportableProvider for FilterDataProvider +where + P0: ExportableProvider, + F: Fn(DataIdentifierBorrowed) -> bool + Sync, +{ + fn supported_markers(&self) -> std::collections::HashSet { + // The predicate only takes DataIdentifier, not DataMarker, so we are not impacted + self.inner.supported_markers() + } +} diff --git a/provider/adapters/src/fork/by_error.rs b/provider/adapters/src/fork/by_error.rs index 1d5c85ebd9e..8f72865a64a 100644 --- a/provider/adapters/src/fork/by_error.rs +++ b/provider/adapters/src/fork/by_error.rs @@ -4,6 +4,8 @@ use super::ForkByErrorPredicate; use alloc::{collections::BTreeSet, vec::Vec}; +#[cfg(feature = "export")] +use icu_provider::export::ExportableProvider; use icu_provider::prelude::*; /// A provider that returns data from one of two child providers based on a predicate function. @@ -159,6 +161,20 @@ where } } +#[cfg(feature = "export")] +impl ExportableProvider for ForkByErrorProvider +where + P0: ExportableProvider, + P1: ExportableProvider, + F: ForkByErrorPredicate + Sync, +{ + fn supported_markers(&self) -> std::collections::HashSet { + let mut markers = self.0.supported_markers(); + markers.extend(self.1.supported_markers()); + markers + } +} + /// A provider that returns data from the first child provider passing a predicate function. /// /// This is an abstract forking provider that must be provided with a type implementing the @@ -334,3 +350,17 @@ where Err(last_error) } } + +#[cfg(feature = "export")] +impl ExportableProvider for MultiForkByErrorProvider +where + P: ExportableProvider, + F: ForkByErrorPredicate + Sync, +{ + fn supported_markers(&self) -> std::collections::HashSet { + self.providers + .iter() + .flat_map(|p| p.supported_markers()) + .collect() + } +} diff --git a/provider/adapters/src/lib.rs b/provider/adapters/src/lib.rs index bbbe379f611..876e0df17e2 100644 --- a/provider/adapters/src/lib.rs +++ b/provider/adapters/src/lib.rs @@ -10,7 +10,7 @@ //! - Use the [`fallback`] module to automatically resolve arbitrary locales for data loading. // https://github.com/unicode-org/icu4x/blob/main/documents/process/boilerplate.md#library-annotations -#![cfg_attr(not(test), no_std)] +#![cfg_attr(not(any(test, feature = "std")), no_std)] #![cfg_attr( not(test), deny( diff --git a/tools/md-tests/Cargo.toml b/tools/md-tests/Cargo.toml index d710c334e6a..3384f3def39 100644 --- a/tools/md-tests/Cargo.toml +++ b/tools/md-tests/Cargo.toml @@ -10,9 +10,10 @@ edition = "2021" [dev-dependencies] icu = { workspace = true, features = ["compiled_data", "serde"] } -icu_provider_export = { workspace = true } +icu_provider_export = { workspace = true, features = ["blob_exporter"] } +icu_provider_source = { workspace = true, features = ["networking"] } icu_provider = { workspace = true, features = ["deserialize_json"] } -icu_provider_adapters = { workspace = true, features = ["serde"] } +icu_provider_adapters = { workspace = true, features = ["serde", "export"] } icu_provider_blob = { workspace = true } icu_provider_fs = { workspace = true } diff --git a/tutorials/data_provider.md b/tutorials/data_provider.md index 99e687d57c6..97318365130 100644 --- a/tutorials/data_provider.md +++ b/tutorials/data_provider.md @@ -253,6 +253,83 @@ assert_eq!(formatter.format_to_string(&100007i64.into()), "100🐮007"); Forking providers can be implemented using `DataPayload::dynamic_cast`. For an example, see that function's documentation. +## Exporting Custom Data Markers + +To add custom data markers to your baked data or postcard file, create a forking exportable provider: + +```rust +use icu::locale::locale; +use icu::plurals::provider::CardinalV1Marker; +use icu_provider::prelude::*; +use icu_provider::DataMarker; +use icu_provider_adapters::fork::ForkByMarkerProvider; +use icu_provider_blob::BlobDataProvider; +use icu_provider_export::blob_exporter::BlobExporter; +use icu_provider_export::prelude::*; +use icu_provider_source::SourceDataProvider; +use std::borrow::Cow; +use std::collections::BTreeSet; + +#[icu_provider::data_struct(marker(CustomMarker, "x/custom@1"))] +#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize, databake::Bake)] +#[databake(path = crate)] +pub struct Custom<'data> { + message: Cow<'data, str>, +}; + +struct CustomProvider; +impl DataProvider for CustomProvider { + fn load(&self, req: DataRequest) -> Result, DataError> { + Ok(DataResponse { + metadata: Default::default(), + payload: DataPayload::from_owned(Custom { + message: format!("Custom data for locale {}!", req.id.locale).into(), + }), + }) + } +} + +impl IterableDataProvider for CustomProvider { + fn iter_ids(&self) -> Result, DataError> { + Ok([locale!("es"), locale!("ja")] + .into_iter() + .map(DataLocale::from) + .map(DataIdentifierCow::from_locale) + .collect()) + } +} + +icu_provider::export::make_exportable_provider!(CustomProvider, [CustomMarker,]); + +let icu4x_source_provider = SourceDataProvider::new_latest_tested(); +let custom_source_provider = CustomProvider; + +let mut buffer = Vec::::new(); + +ExportDriver::new( + [DataLocaleFamily::FULL], + DeduplicationStrategy::None.into(), + LocaleFallbacker::try_new_unstable(&icu4x_source_provider).unwrap(), +) +.with_markers([CardinalV1Marker::INFO, CustomMarker::INFO]) +.export( + &ForkByMarkerProvider::new(icu4x_source_provider, custom_source_provider), + BlobExporter::new_v2_with_sink(Box::new(&mut buffer)), +) +.unwrap(); + +let blob_provider = BlobDataProvider::try_new_from_blob(buffer.into()).unwrap(); + +let locale = DataLocale::from(&locale!("ja")); +let req = DataRequest { + id: DataIdentifierBorrowed::for_locale(&locale), + metadata: Default::default(), +}; + +assert!(blob_provider.load_data(CardinalV1Marker::INFO, req).is_ok()); +assert!(blob_provider.load_data(CustomMarker::INFO, req).is_ok()); +``` + ## Accessing the Resolved Locale ICU4X objects do not store their "resolved locale" because that is not a well-defined concept. Components can load data from many sources, and fallbacks to parent locales or root does not necessarily mean that a locale is not supported.