Skip to content

Commit

Permalink
Adding crate icu-cldr-json-data-provider.
Browse files Browse the repository at this point in the history
  • Loading branch information
sffc committed Aug 7, 2020
1 parent 2155838 commit 9cfe573
Show file tree
Hide file tree
Showing 12 changed files with 1,431 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]

members = [
"components/cldr-json-data-provider",
"components/data-provider",
"components/icu",
"components/icu4x",
Expand Down
23 changes: 23 additions & 0 deletions components/cldr-json-data-provider/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "icu-cldr-json-data-provider"
description = "Data provider that reads from a CLDR JSON data source"
version = "0.0.1"
authors = ["The ICU4X Project Developers"]
edition = "2018"
readme = "README.md"
repository = "https://github.com/unicode-org/icu4x"
license-file = "../../LICENSE"
categories = ["internationalization"]
include = [
"src/**/*",
"Cargo.toml",
"README.md"
]

[dependencies]
icu-data-provider = { path = "../data-provider" }
icu-locale = { path = "../locale" }
json = "0.12"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde-tuple-vec-map = "1.0"
Empty file.
60 changes: 60 additions & 0 deletions components/cldr-json-data-provider/src/cldr_langid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use icu_locale::LanguageIdentifier;
use serde::{Deserialize, Deserializer};

// TODO: Make this non-pub
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct CldrLanguage(pub LanguageIdentifier);

impl<'de> Deserialize<'de> for CldrLanguage {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct CldrLanguageVisitor;

impl<'de> serde::de::Visitor<'de> for CldrLanguageVisitor {
type Value = CldrLanguage;

fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "a valid Unicode Language Identifier or 'root'")
}

fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if s == "root" {
Ok(CldrLanguage("und".parse().unwrap()))
} else {
s.parse::<LanguageIdentifier>()
.map(CldrLanguage)
.map_err(serde::de::Error::custom)
}
}
}

deserializer.deserialize_string(CldrLanguageVisitor)
}
}

#[test]
fn deserialize() -> Result<(), Box<dyn std::error::Error>> {
let fr = serde_json::from_str::<CldrLanguage>(r#""fr""#)?;
let en = serde_json::from_str::<CldrLanguage>(r#""en-US""#)?;
let root = serde_json::from_str::<CldrLanguage>(r#""root""#)?;

assert_eq!(fr, CldrLanguage("fr".parse()?));
assert_eq!(en, CldrLanguage("en-US".parse()?));
assert_eq!(root, CldrLanguage("und".parse()?));

let failed = serde_json::from_str::<CldrLanguage>(r#""2Xs""#);
assert!(failed.is_err());
let err = failed.unwrap_err();
assert!(err.is_data());
assert_eq!(
err.to_string(),
"The given language subtag is invalid at line 1 column 5".to_string()
);

Ok(())
}
21 changes: 21 additions & 0 deletions components/cldr-json-data-provider/src/cldr_paths.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use crate::error::MissingSourceError;
use std::default::Default;
use std::path::PathBuf;

/// Struct containing filesystem paths to the CLDR JSON resource directories.
/// The fields should be Ok if present. They default to Err when not present.
#[non_exhaustive]
#[derive(Debug, PartialEq)]
pub struct CldrPaths {
/// Path to checkout of cldr-core:
/// https://github.com/unicode-cldr/cldr-core
pub cldr_core: Result<PathBuf, MissingSourceError>,
}

impl Default for CldrPaths {
fn default() -> CldrPaths {
CldrPaths {
cldr_core: Err(MissingSourceError { src: "cldr-core" }),
}
}
}
53 changes: 53 additions & 0 deletions components/cldr-json-data-provider/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use std::error;
use std::fmt;

#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
JsonError(serde_json::error::Error),
IoError(std::io::Error, std::path::PathBuf),
MissingSource(MissingSourceError),
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub struct MissingSourceError {
pub src: &'static str,
}

impl fmt::Display for MissingSourceError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Missing CLDR data source: {}", self.src)
}
}

impl From<serde_json::error::Error> for Error {
fn from(err: serde_json::error::Error) -> Self {
Self::JsonError(err)
}
}

impl From<MissingSourceError> for Error {
fn from(err: MissingSourceError) -> Self {
Self::MissingSource(err)
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::JsonError(err) => write!(f, "{}", err),
Error::IoError(err, path) => write!(f, "{}: {}", err, path.to_string_lossy()),
Error::MissingSource(err) => err.fmt(f),
}
}
}

impl error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::JsonError(err) => Some(err),
Error::IoError(err, _) => Some(err),
_ => None,
}
}
}
13 changes: 13 additions & 0 deletions components/cldr-json-data-provider/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// #![feature(type_alias_impl_trait)]

mod cldr_langid;
mod cldr_paths;
mod error;
mod reader;
mod support;

pub mod transform;

pub use cldr_paths::CldrPaths;
pub use error::Error as CldrError;
pub use transform::CldrJsonDataProvider;
12 changes: 12 additions & 0 deletions components/cldr-json-data-provider/src/reader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::error::Error;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;

pub fn open_reader(path: PathBuf) -> Result<BufReader<File>, Error> {
let file = match File::open(&path) {
Ok(file) => file,
Err(err) => return Err(Error::IoError(err, path)),
};
Ok(BufReader::new(file))
}
71 changes: 71 additions & 0 deletions components/cldr-json-data-provider/src/support.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use crate::CldrPaths;
use icu_data_provider::iter::DataEntryCollection;
use icu_data_provider::prelude::*;
use std::convert::TryFrom;
use std::sync::RwLock;

pub(crate) trait DataKeySupport {
fn supports_key(data_key: &DataKey) -> Result<(), DataError>;
}

pub(crate) struct LazyCldrProvider<T> {
data_provider: RwLock<Option<T>>,
}

impl<'b, 'd, T> LazyCldrProvider<T>
where
T: DataProvider<'d> + DataKeySupport + DataEntryCollection + TryFrom<&'b CldrPaths>,
<T as TryFrom<&'b CldrPaths>>::Error: 'static + std::error::Error,
{
pub fn new() -> Self {
Self {
data_provider: RwLock::new(None),
}
}

pub fn try_load(
&self,
req: &DataRequest,
cldr_paths: &'b CldrPaths,
) -> Result<Option<DataResponse<'d>>, DataError> {
if T::supports_key(&req.data_key).is_err() {
return Ok(None);
}
if self.data_provider.read().unwrap().is_none() {
self.data_provider.write().unwrap().replace(
T::try_from(cldr_paths).map_err(|e| DataError::ResourceError(Box::new(e)))?,
);
};
self
.data_provider
.read()
.unwrap()
.as_ref()
.unwrap()
.load(req)
.map(Some)
}

pub fn try_iter(
&self,
data_key: &DataKey,
cldr_paths: &'b CldrPaths,
) -> Result<Option<Box<dyn Iterator<Item = DataEntry>>>, DataError> {
if T::supports_key(data_key).is_err() {
return Ok(None);
}
if self.data_provider.read().unwrap().is_none() {
self.data_provider.write().unwrap().replace(
T::try_from(cldr_paths).map_err(|e| DataError::ResourceError(Box::new(e)))?,
);
};
self
.data_provider
.read()
.unwrap()
.as_ref()
.unwrap()
.iter_for_key(data_key)
.map(Some)
}
}
43 changes: 43 additions & 0 deletions components/cldr-json-data-provider/src/transform/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
mod plurals;

pub use plurals::PluralsProvider;

use crate::support::LazyCldrProvider;
use crate::CldrPaths;
use icu_data_provider::iter::DataEntryCollection;
use icu_data_provider::prelude::*;

pub struct CldrJsonDataProvider<'a, 'd> {
pub cldr_paths: &'a CldrPaths,
plurals: LazyCldrProvider<PluralsProvider<'d>>,
}

impl<'a, 'd> CldrJsonDataProvider<'a, 'd> {
pub fn new(cldr_paths: &'a CldrPaths) -> Self {
CldrJsonDataProvider {
cldr_paths,
plurals: LazyCldrProvider::new(),
}
}
}

impl<'a, 'd> DataProvider<'d> for CldrJsonDataProvider<'a, 'd> {
fn load(&self, req: &DataRequest) -> Result<DataResponse<'d>, DataError> {
if let Some(resp) = self.plurals.try_load(req, &self.cldr_paths)? {
return Ok(resp);
}
Err(DataError::UnsupportedDataKey(req.data_key))
}
}

impl<'a, 'd> DataEntryCollection for CldrJsonDataProvider<'a, 'd> {
fn iter_for_key(
&self,
data_key: &DataKey,
) -> Result<Box<dyn Iterator<Item = DataEntry>>, DataError> {
if let Some(resp) = self.plurals.try_iter(data_key, &self.cldr_paths)? {
return Ok(resp);
}
Err(DataError::UnsupportedDataKey(*data_key))
}
}
Loading

0 comments on commit 9cfe573

Please sign in to comment.