#![allow(clippy::needless_doctest_main)]
#![cfg_attr(
not(test),
deny(
// This is a tool, and as such we don't care about panics too much
// clippy::indexing_slicing,
// clippy::unwrap_used,
// clippy::expect_used,
// clippy::panic,
clippy::exhaustive_structs,
clippy::exhaustive_enums,
missing_debug_implementations,
)
)]
#![warn(missing_docs)]
mod driver;
#[cfg(feature = "provider")]
mod provider;
mod registry;
pub use driver::DatagenDriver;
pub use driver::DeduplicationStrategy;
pub use driver::FallbackOptions;
pub use driver::LocaleFamily;
pub use driver::NoFallbackOptions;
pub use driver::RuntimeFallbackLocation;
#[cfg(feature = "provider")]
pub use provider::CollationHanDatabase;
#[cfg(feature = "provider")]
pub use provider::CoverageLevel;
#[cfg(feature = "provider")]
pub use provider::DatagenProvider;
#[cfg(feature = "baked_exporter")]
pub mod baked_exporter;
#[cfg(feature = "blob_exporter")]
pub use icu_provider_blob::export as blob_exporter;
#[cfg(feature = "fs_exporter")]
pub use icu_provider_fs::export as fs_exporter;
pub mod prelude {
#[doc(no_inline)]
#[cfg(feature = "provider")]
pub use crate::provider::{CollationHanDatabase, CoverageLevel, DatagenProvider};
#[doc(no_inline)]
pub use crate::{
DatagenDriver, DeduplicationStrategy, FallbackMode, FallbackOptions, LocaleFamily,
NoFallbackOptions, RuntimeFallbackLocation,
};
#[doc(no_inline)]
pub use icu_locid::{langid, LanguageIdentifier};
#[doc(no_inline)]
pub use icu_provider::{datagen::DataExporter, DataKey, KeyedDataMarker};
#[cfg(feature = "legacy_api")]
#[allow(deprecated)]
#[doc(hidden)]
pub use crate::{provider::CldrLocaleSubset, syntax, BakedOptions, Out, SourceData};
}
use icu_provider::prelude::*;
use std::path::Path;
#[cfg(feature = "rayon")]
pub(crate) use rayon::prelude as rayon_prelude;
#[cfg(not(feature = "rayon"))]
pub(crate) mod rayon_prelude {
pub trait IntoParallelIterator: IntoIterator + Sized {
fn into_par_iter(self) -> <Self as IntoIterator>::IntoIter {
self.into_iter()
}
}
impl<T: IntoIterator> IntoParallelIterator for T {}
}
macro_rules! cb {
($($marker:path = $path:literal,)+ #[experimental] $($emarker:path = $epath:literal,)+) => {
pub fn all_keys() -> Vec<DataKey> {
#[cfg(features = "experimental_components")]
log::warn!("The icu_datagen crates has been built with the `experimental_components` feature, so `all_keys` returns experimental keys");
vec![
$(
<$marker>::KEY,
)+
$(
#[cfg(feature = "experimental_components")]
<$emarker>::KEY,
)+
]
}
#[test]
fn no_key_collisions() {
let mut map = std::collections::BTreeMap::new();
let mut failed = false;
for key in all_keys() {
if let Some(colliding_key) = map.insert(key.hashed(), key) {
println!(
"{:?} and {:?} collide at {:?}",
key.path(),
colliding_key.path(),
key.hashed()
);
failed = true;
}
}
if failed {
panic!();
}
}
pub fn key<S: AsRef<str>>(string: S) -> Option<DataKey> {
use once_cell::sync::OnceCell;
static LOOKUP: OnceCell<std::collections::HashMap<&'static str, Result<DataKey, &'static str>>> = OnceCell::new();
let lookup = LOOKUP.get_or_init(|| {
[
("core/helloworld@1", Ok(icu_provider::hello_world::HelloWorldV1Marker::KEY)),
$(
($path, Ok(<$marker>::KEY)),
)+
$(
#[cfg(feature = "experimental_components")]
($epath, Ok(<$emarker>::KEY)),
#[cfg(not(feature = "experimental_components"))]
($epath, Err(stringify!(feature = "experimental_components"))),
)+
]
.into_iter()
.collect()
});
let path = string.as_ref();
match lookup.get(path).copied() {
None => {
log::warn!("Unknown key {path:?}");
None
},
Some(Err(feature)) => {
log::warn!("Key {path:?} requires {feature}");
None
},
Some(Ok(key)) => Some(key)
}
}
#[test]
fn test_paths_correct() {
$(
assert_eq!(<$marker>::KEY.path().get(), $path);
)+
$(
assert_eq!(<$emarker>::KEY.path().get(), $epath);
)+
}
#[macro_export]
#[doc(hidden)]
macro_rules! make_exportable_provider {
($ty:ty) => {
icu_provider::make_exportable_provider!(
$ty,
[
icu_provider::hello_world::HelloWorldV1Marker,
$(
$marker,
)+
$(
#[cfg(feature = "experimental_components")]
$emarker,
)+
]
);
}
}
}
}
crate::registry!(cb);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum FallbackMode {
#[default]
PreferredForExporter,
Runtime,
RuntimeManual,
Preresolved,
Hybrid,
}
pub fn keys<S: AsRef<str>>(strings: &[S]) -> Vec<DataKey> {
strings.iter().filter_map(crate::key).collect()
}
#[deprecated(since = "1.3.0", note = "use Rust code")]
#[cfg(feature = "legacy_api")]
pub fn keys_from_file<P: AsRef<Path>>(path: P) -> std::io::Result<Vec<DataKey>> {
let file = std::fs::File::open(path.as_ref())?;
keys_from_file_inner(&file)
}
#[cfg(feature = "legacy_api")]
fn keys_from_file_inner<R: std::io::Read>(source: R) -> std::io::Result<Vec<DataKey>> {
use std::io::{BufRead, BufReader};
BufReader::new(source)
.lines()
.filter_map(|k| k.map(crate::key).transpose())
.collect()
}
pub fn keys_from_bin<P: AsRef<Path>>(path: P) -> std::io::Result<Vec<DataKey>> {
let file = std::fs::read(path.as_ref())?;
let file = file.as_slice();
Ok(keys_from_bin_inner(file))
}
fn keys_from_bin_inner(bytes: &[u8]) -> Vec<DataKey> {
use memchr::memmem::*;
const LEADING_TAG: &[u8] = icu_provider::leading_tag!().as_bytes();
const TRAILING_TAG: &[u8] = icu_provider::trailing_tag!().as_bytes();
let trailing_tag = Finder::new(TRAILING_TAG);
let mut result: Vec<DataKey> = find_iter(bytes, LEADING_TAG)
.map(|tag_position| tag_position + LEADING_TAG.len())
.map(|key_start| &bytes[key_start..])
.filter_map(move |key_fragment| {
trailing_tag
.find(key_fragment)
.map(|end| &key_fragment[..end])
})
.map(std::str::from_utf8)
.filter_map(Result::ok)
.filter_map(crate::key)
.collect();
result.sort();
result.dedup();
result
}
#[deprecated(since = "1.3.0", note = "use `DatagenDriver`")]
#[allow(deprecated)]
#[cfg(feature = "legacy_api")]
pub use provider::SourceData;
#[deprecated(since = "1.3.0", note = "use `DatagenDriver`")]
#[non_exhaustive]
#[cfg(feature = "legacy_api")]
pub enum Out {
Fs {
output_path: std::path::PathBuf,
serializer: Box<dyn icu_provider_fs::export::serializers::AbstractSerializer + Sync>,
overwrite: bool,
fingerprint: bool,
},
Blob(Box<dyn std::io::Write + Sync>),
Baked {
mod_directory: std::path::PathBuf,
options: BakedOptions,
},
#[doc(hidden)]
#[deprecated(since = "1.1.2", note = "please use `Out::Baked` instead")]
Module {
mod_directory: std::path::PathBuf,
pretty: bool,
use_separate_crates: bool,
insert_feature_gates: bool,
},
}
#[allow(deprecated)]
#[cfg(feature = "legacy_api")]
impl core::fmt::Debug for Out {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Fs {
output_path,
serializer,
overwrite,
fingerprint,
} => f
.debug_struct("Fs")
.field("output_path", output_path)
.field("serializer", serializer)
.field("overwrite", overwrite)
.field("fingerprint", fingerprint)
.finish(),
Self::Blob(_) => f.debug_tuple("Blob").field(&"[...]").finish(),
Self::Baked {
mod_directory,
options,
} => f
.debug_struct("Baked")
.field("mod_directory", mod_directory)
.field("options", options)
.finish(),
#[allow(deprecated)]
Self::Module {
mod_directory,
pretty,
use_separate_crates,
insert_feature_gates,
} => f
.debug_struct("Module")
.field("mod_directory", mod_directory)
.field("pretty", pretty)
.field("insert_feature_gates", insert_feature_gates)
.field("use_separate_crates", use_separate_crates)
.finish(),
}
}
}
#[deprecated(since = "1.3.0", note = "use `DatagenDriver`")]
#[cfg(feature = "legacy_api")]
#[allow(deprecated)]
pub fn datagen(
locales: Option<&[icu_locid::LanguageIdentifier]>,
keys: &[DataKey],
source: &SourceData,
outs: Vec<Out>,
) -> Result<(), DataError> {
let exporter = DatagenDriver::new()
.with_keys(keys.iter().cloned())
.with_fallback_mode(FallbackMode::Hybrid)
.with_additional_collations(source.collations.clone());
match locales {
Some(locales) => exporter
.with_locales(
locales
.iter()
.cloned()
.chain([icu_locid::LanguageIdentifier::UND]),
)
.with_segmenter_models({
let mut models = vec![];
for locale in locales {
let locale = locale.into();
if let Some(model) = crate::lstm_data_locale_to_model_name(&locale) {
models.push(model.into());
}
if let Some(model) = crate::dictionary_data_locale_to_model_name(&locale) {
models.push(model.into());
}
}
models
}),
_ => exporter.with_all_locales(),
}
.export(
&DatagenProvider {
source: source.clone(),
},
icu_provider::datagen::MultiExporter::new(
outs.into_iter()
.map(
|out| -> Result<Box<dyn icu_provider::datagen::DataExporter>, DataError> {
Ok(match out {
Out::Fs {
output_path,
serializer,
overwrite,
fingerprint,
} => {
let mut options = fs_exporter::Options::default();
options.root = output_path;
if overwrite {
options.overwrite =
fs_exporter::OverwriteOption::RemoveAndReplace
}
options.fingerprint = fingerprint;
Box::new(fs_exporter::FilesystemExporter::try_new(
serializer, options,
)?)
}
Out::Blob(write) => {
Box::new(blob_exporter::BlobExporter::new_with_sink(write))
}
Out::Baked {
mod_directory,
options,
} => Box::new(baked_exporter::BakedExporter::new(
mod_directory,
options,
)?),
#[allow(deprecated)]
Out::Module {
mod_directory,
pretty,
insert_feature_gates,
use_separate_crates,
} => Box::new(baked_exporter::BakedExporter::new(
mod_directory,
baked_exporter::Options {
pretty,
insert_feature_gates,
use_separate_crates,
overwrite: false,
},
)?),
})
},
)
.collect::<Result<_, _>>()?,
),
)
}
use icu_locid::langid;
#[cfg(feature = "provider")]
fn lstm_model_name_to_data_locale(name: &str) -> Option<DataLocale> {
match name {
"Burmese_codepoints_exclusive_model4_heavy" => Some(langid!("my").into()),
"Khmer_codepoints_exclusive_model4_heavy" => Some(langid!("km").into()),
"Lao_codepoints_exclusive_model4_heavy" => Some(langid!("lo").into()),
"Thai_codepoints_exclusive_model4_heavy" => Some(langid!("th").into()),
_ => None,
}
}
fn lstm_data_locale_to_model_name(locale: &DataLocale) -> Option<&'static str> {
match locale.get_langid() {
id if id == langid!("my") => Some("Burmese_codepoints_exclusive_model4_heavy"),
id if id == langid!("km") => Some("Khmer_codepoints_exclusive_model4_heavy"),
id if id == langid!("lo") => Some("Lao_codepoints_exclusive_model4_heavy"),
id if id == langid!("th") => Some("Thai_codepoints_exclusive_model4_heavy"),
_ => None,
}
}
#[cfg(feature = "provider")]
fn dictionary_model_name_to_data_locale(name: &str) -> Option<DataLocale> {
match name {
"khmerdict" => Some(langid!("km").into()),
"cjdict" => Some(langid!("ja").into()),
"laodict" => Some(langid!("lo").into()),
"burmesedict" => Some(langid!("my").into()),
"thaidict" => Some(langid!("th").into()),
_ => None,
}
}
fn dictionary_data_locale_to_model_name(locale: &DataLocale) -> Option<&'static str> {
match locale.get_langid() {
id if id == langid!("km") => Some("khmerdict"),
id if id == langid!("ja") => Some("cjdict"),
id if id == langid!("lo") => Some("laodict"),
id if id == langid!("my") => Some("burmesedict"),
id if id == langid!("th") => Some("thaidict"),
_ => None,
}
}
#[test]
fn test_keys() {
assert_eq!(
keys(&[
"list/and@1",
"datetime/gregory/datelengths@1",
"decimal/symbols@1",
"trash",
]),
vec![
icu_list::provider::AndListV1Marker::KEY,
icu_datetime::provider::calendar::GregorianDateLengthsV1Marker::KEY,
icu_decimal::provider::DecimalSymbolsV1Marker::KEY,
]
);
}
#[test]
#[cfg(feature = "legacy_api")]
fn test_keys_from_file() {
#![allow(deprecated)]
const BYTES: &[u8] = include_bytes!("../tests/data/tutorial_buffer+keys.txt");
assert_eq!(
keys_from_file_inner(BYTES).unwrap(),
vec![
icu_datetime::provider::calendar::GregorianDateLengthsV1Marker::KEY,
icu_datetime::provider::calendar::GregorianDateSymbolsV1Marker::KEY,
icu_datetime::provider::calendar::TimeSymbolsV1Marker::KEY,
icu_calendar::provider::WeekDataV1Marker::KEY,
icu_decimal::provider::DecimalSymbolsV1Marker::KEY,
icu_plurals::provider::OrdinalV1Marker::KEY,
]
);
}
#[test]
fn test_keys_from_bin() {
assert_eq!(
keys_from_bin_inner(include_bytes!("../tests/data/tutorial_buffer.wasm")),
vec![
icu_datetime::provider::calendar::GregorianDateLengthsV1Marker::KEY,
icu_datetime::provider::calendar::GregorianDateSymbolsV1Marker::KEY,
icu_datetime::provider::calendar::TimeLengthsV1Marker::KEY,
icu_datetime::provider::calendar::TimeSymbolsV1Marker::KEY,
icu_calendar::provider::WeekDataV1Marker::KEY,
icu_decimal::provider::DecimalSymbolsV1Marker::KEY,
icu_plurals::provider::OrdinalV1Marker::KEY,
]
);
}
#[deprecated(
since = "1.3.0",
note = "use `all_keys` with the required cargo features"
)]
#[cfg(feature = "legacy_api")]
pub fn all_keys_with_experimental() -> Vec<DataKey> {
all_keys()
}
#[cfg(feature = "legacy_api")]
#[deprecated(since = "1.3.0", note = "use methods on `DatagenProvider`")]
pub fn is_missing_cldr_error(e: DataError) -> bool {
DatagenProvider::is_missing_cldr_error(e)
}
#[cfg(feature = "legacy_api")]
#[deprecated(since = "1.3.0", note = "use methods on `DatagenProvider`")]
pub fn is_missing_icuexport_error(e: DataError) -> bool {
DatagenProvider::is_missing_icuexport_error(e)
}
#[cfg(feature = "legacy_api")]
#[deprecated(since = "1.3.0", note = "use `fs_exporter::serializers`")]
pub mod syntax {
#[doc(no_inline)]
pub use crate::fs_exporter::serializers::Bincode;
#[doc(no_inline)]
pub use crate::fs_exporter::serializers::Json;
#[doc(no_inline)]
pub use crate::fs_exporter::serializers::Postcard;
}
#[cfg(feature = "legacy_api")]
#[doc(hidden)]
pub use baked_exporter::Options as BakedOptions;
#[cfg(feature = "legacy_api")]
#[doc(hidden)]
pub use provider::CldrLocaleSubset;