icu_provider_fs/fs_data_provider.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
use crate::manifest::Manifest;
use icu_provider::prelude::*;
use icu_provider::DynamicDryDataProvider;
use std::fmt::Debug;
use std::fmt::Write;
use std::fs;
use std::path::PathBuf;
/// A data provider that reads ICU4X data from a filesystem directory.
///
/// [`FsDataProvider`] implements [`BufferProvider`], so it can be used in
/// `*_with_buffer_provider` constructors across ICU4X.
///
/// # Examples
///
/// ```
/// use icu_locale_core::locale;
/// use icu_provider::hello_world::HelloWorldFormatter;
/// use icu_provider_fs::FsDataProvider;
/// use writeable::assert_writeable_eq;
///
/// // Create a DataProvider from data files stored in a filesystem directory:
/// let provider = FsDataProvider::try_new("tests/data/json".into())
/// .expect("Directory exists");
///
/// // Check that it works:
/// let formatter = HelloWorldFormatter::try_new_with_buffer_provider(
/// &provider,
/// locale!("la").into(),
/// )
/// .expect("locale exists");
///
/// assert_writeable_eq!(formatter.format(), "Ave, munde");
/// ```
#[derive(Debug, PartialEq, Clone)]
pub struct FsDataProvider {
root: PathBuf,
manifest: Manifest,
}
impl FsDataProvider {
/// Create a new [`FsDataProvider`] given a filesystem directory.
///
/// # Examples
///
/// ```
/// use icu_provider_fs::FsDataProvider;
///
/// let provider = FsDataProvider::try_new("/path/to/data/directory".into())
/// .expect_err("Specify a real directory in the line above");
/// ```
pub fn try_new(root: PathBuf) -> Result<Self, DataError> {
Ok(Self {
manifest: Manifest::parse(&root)?,
root,
})
}
fn dry_load_internal(
&self,
marker: DataMarkerInfo,
req: DataRequest,
) -> Result<(DataResponseMetadata, PathBuf), DataError> {
if marker.is_singleton && !req.id.locale.is_default() {
return Err(DataErrorKind::InvalidRequest.with_req(marker, req));
}
let mut path = self.root.join(marker.path.as_str());
if !path.exists() {
return Err(DataErrorKind::MarkerNotFound.with_req(marker, req));
}
if !req.id.marker_attributes.is_empty() {
if req.metadata.attributes_prefix_match {
path.push(
std::fs::read_dir(&path)?
.filter_map(|e| e.ok()?.file_name().into_string().ok())
.filter(|c| c.starts_with(req.id.marker_attributes.as_str()))
.min()
.ok_or(DataErrorKind::IdentifierNotFound.with_req(marker, req))?,
);
} else {
path.push(req.id.marker_attributes.as_str());
}
}
if !marker.is_singleton {
let mut string_path = path.into_os_string();
write!(&mut string_path, "/{}", req.id.locale).expect("infallible");
path = PathBuf::from(string_path);
}
path.set_extension(self.manifest.file_extension);
if !path.exists() {
return Err(DataErrorKind::IdentifierNotFound.with_req(marker, req));
}
let mut metadata = DataResponseMetadata::default();
metadata.buffer_format = Some(self.manifest.buffer_format);
Ok((metadata, path))
}
}
impl DynamicDataProvider<BufferMarker> for FsDataProvider {
fn load_data(
&self,
marker: DataMarkerInfo,
req: DataRequest,
) -> Result<DataResponse<BufferMarker>, DataError> {
let (metadata, path) = self.dry_load_internal(marker, req)?;
let buffer = fs::read(&path).map_err(|e| DataError::from(e).with_path_context(&path))?;
Ok(DataResponse {
metadata,
payload: DataPayload::from_owned_buffer(buffer.into_boxed_slice()),
})
}
}
impl DynamicDryDataProvider<BufferMarker> for FsDataProvider {
fn dry_load_data(
&self,
marker: DataMarkerInfo,
req: DataRequest,
) -> Result<DataResponseMetadata, DataError> {
Ok(self.dry_load_internal(marker, req)?.0)
}
}