use rustc_hash::FxHashMap;
use std::borrow::Borrow;
use std::borrow::Cow;
use std::collections::hash_map::Entry as HashEntry;
use std::default::Default;
use std::fmt;
use fluent_syntax::ast;
use intl_memoizer::IntlLangMemoizer;
use unic_langid::LanguageIdentifier;
use crate::args::FluentArgs;
use crate::entry::Entry;
use crate::entry::GetEntry;
use crate::errors::{EntryKind, FluentError};
use crate::memoizer::MemoizerKind;
use crate::message::FluentMessage;
use crate::resolver::{ResolveValue, Scope, WriteValue};
use crate::resource::FluentResource;
use crate::types::FluentValue;
pub struct FluentBundle<R, M> {
pub locales: Vec<LanguageIdentifier>,
pub(crate) resources: Vec<R>,
pub(crate) entries: FxHashMap<String, Entry>,
pub(crate) intls: M,
pub(crate) use_isolating: bool,
pub(crate) transform: Option<fn(&str) -> Cow<str>>,
pub(crate) formatter: Option<fn(&FluentValue, &M) -> Option<String>>,
}
impl<R, M> FluentBundle<R, M> {
pub fn add_resource(&mut self, r: R) -> Result<(), Vec<FluentError>>
where
R: Borrow<FluentResource>,
{
let mut errors = vec![];
let res = r.borrow();
let res_pos = self.resources.len();
for (entry_pos, entry) in res.entries().enumerate() {
let (id, entry) = match entry {
ast::Entry::Message(ast::Message { ref id, .. }) => {
(id.name, Entry::Message((res_pos, entry_pos)))
}
ast::Entry::Term(ast::Term { ref id, .. }) => {
(id.name, Entry::Term((res_pos, entry_pos)))
}
_ => continue,
};
match self.entries.entry(id.to_string()) {
HashEntry::Vacant(empty) => {
empty.insert(entry);
}
HashEntry::Occupied(_) => {
let kind = match entry {
Entry::Message(..) => EntryKind::Message,
Entry::Term(..) => EntryKind::Term,
_ => unreachable!(),
};
errors.push(FluentError::Overriding {
kind,
id: id.to_string(),
});
}
}
}
self.resources.push(r);
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
pub fn add_resource_overriding(&mut self, r: R)
where
R: Borrow<FluentResource>,
{
let res = r.borrow();
let res_pos = self.resources.len();
for (entry_pos, entry) in res.entries().enumerate() {
let (id, entry) = match entry {
ast::Entry::Message(ast::Message { ref id, .. }) => {
(id.name, Entry::Message((res_pos, entry_pos)))
}
ast::Entry::Term(ast::Term { ref id, .. }) => {
(id.name, Entry::Term((res_pos, entry_pos)))
}
_ => continue,
};
self.entries.insert(id.to_string(), entry);
}
self.resources.push(r);
}
pub fn set_use_isolating(&mut self, value: bool) {
self.use_isolating = value;
}
pub fn set_transform(&mut self, func: Option<fn(&str) -> Cow<str>>) {
self.transform = func;
}
pub fn set_formatter(&mut self, func: Option<fn(&FluentValue, &M) -> Option<String>>) {
self.formatter = func;
}
pub fn has_message(&self, id: &str) -> bool
where
R: Borrow<FluentResource>,
{
self.get_entry_message(id).is_some()
}
pub fn get_message<'l>(&'l self, id: &str) -> Option<FluentMessage<'l>>
where
R: Borrow<FluentResource>,
{
self.get_entry_message(id).map(Into::into)
}
pub fn write_pattern<'bundle, W>(
&'bundle self,
w: &mut W,
pattern: &'bundle ast::Pattern<&str>,
args: Option<&'bundle FluentArgs>,
errors: &mut Vec<FluentError>,
) -> fmt::Result
where
R: Borrow<FluentResource>,
W: fmt::Write,
M: MemoizerKind,
{
let mut scope = Scope::new(self, args, Some(errors));
pattern.write(w, &mut scope)
}
pub fn format_pattern<'bundle>(
&'bundle self,
pattern: &'bundle ast::Pattern<&'bundle str>,
args: Option<&FluentArgs>,
errors: &mut Vec<FluentError>,
) -> Cow<'bundle, str>
where
R: Borrow<FluentResource>,
M: MemoizerKind,
{
let mut scope = Scope::new(self, args, Some(errors));
let value = pattern.resolve(&mut scope);
value.into_string(&scope)
}
pub fn add_function<F>(&mut self, id: &str, func: F) -> Result<(), FluentError>
where
F: for<'a> Fn(&[FluentValue<'a>], &FluentArgs) -> FluentValue<'a> + Sync + Send + 'static,
{
match self.entries.entry(id.to_owned()) {
HashEntry::Vacant(entry) => {
entry.insert(Entry::Function(Box::new(func)));
Ok(())
}
HashEntry::Occupied(_) => Err(FluentError::Overriding {
kind: EntryKind::Function,
id: id.to_owned(),
}),
}
}
pub fn add_builtins(&mut self) -> Result<(), FluentError> {
self.add_function("NUMBER", crate::builtins::NUMBER)?;
Ok(())
}
}
impl<R> Default for FluentBundle<R, IntlLangMemoizer> {
fn default() -> Self {
Self::new(vec![LanguageIdentifier::default()])
}
}
impl<R> FluentBundle<R, IntlLangMemoizer> {
pub fn new(locales: Vec<LanguageIdentifier>) -> Self {
let first_locale = locales.first().cloned().unwrap_or_default();
Self {
locales,
resources: vec![],
entries: FxHashMap::default(),
intls: IntlLangMemoizer::new(first_locale),
use_isolating: true,
transform: None,
formatter: None,
}
}
}
impl crate::memoizer::MemoizerKind for IntlLangMemoizer {
fn new(lang: LanguageIdentifier) -> Self
where
Self: Sized,
{
Self::new(lang)
}
fn with_try_get_threadsafe<I, R, U>(&self, args: I::Args, cb: U) -> Result<R, I::Error>
where
Self: Sized,
I: intl_memoizer::Memoizable + Send + Sync + 'static,
I::Args: Send + Sync + 'static,
U: FnOnce(&I) -> R,
{
self.with_try_get(args, cb)
}
fn stringify_value(
&self,
value: &dyn crate::types::FluentType,
) -> std::borrow::Cow<'static, str> {
value.as_string(self)
}
}