summaryrefslogtreecommitdiffstats
path: root/intl/l10n/rust/l10nregistry-rs/src/registry
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /intl/l10n/rust/l10nregistry-rs/src/registry
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'intl/l10n/rust/l10nregistry-rs/src/registry')
-rw-r--r--intl/l10n/rust/l10nregistry-rs/src/registry/asynchronous.rs294
-rw-r--r--intl/l10n/rust/l10nregistry-rs/src/registry/mod.rs363
-rw-r--r--intl/l10n/rust/l10nregistry-rs/src/registry/synchronous.rs307
3 files changed, 964 insertions, 0 deletions
diff --git a/intl/l10n/rust/l10nregistry-rs/src/registry/asynchronous.rs b/intl/l10n/rust/l10nregistry-rs/src/registry/asynchronous.rs
new file mode 100644
index 0000000000..bfcff941b5
--- /dev/null
+++ b/intl/l10n/rust/l10nregistry-rs/src/registry/asynchronous.rs
@@ -0,0 +1,294 @@
+use std::{
+ pin::Pin,
+ task::{Context, Poll},
+};
+
+use crate::{
+ env::ErrorReporter,
+ errors::{L10nRegistryError, L10nRegistrySetupError},
+ fluent::{FluentBundle, FluentError},
+ registry::{BundleAdapter, L10nRegistry, MetaSources},
+ solver::{AsyncTester, ParallelProblemSolver},
+ source::{ResourceOption, ResourceStatus},
+};
+
+use fluent_fallback::{generator::BundleStream, types::ResourceId};
+use futures::{
+ stream::{Collect, FuturesOrdered},
+ Stream, StreamExt,
+};
+use std::future::Future;
+use unic_langid::LanguageIdentifier;
+
+impl<P, B> L10nRegistry<P, B>
+where
+ P: Clone,
+ B: Clone,
+{
+ /// This method is useful for testing various configurations.
+ #[cfg(feature = "test-fluent")]
+ pub fn generate_bundles_for_lang(
+ &self,
+ langid: LanguageIdentifier,
+ resource_ids: Vec<ResourceId>,
+ ) -> Result<GenerateBundles<P, B>, L10nRegistrySetupError> {
+ let lang_ids = vec![langid];
+
+ Ok(GenerateBundles::new(
+ self.clone(),
+ lang_ids.into_iter(),
+ resource_ids,
+ // Cheaply create an immutable shallow copy of the [MetaSources].
+ self.try_borrow_metasources()?.clone(),
+ ))
+ }
+
+ // Asynchronously generate the bundles.
+ pub fn generate_bundles(
+ &self,
+ locales: std::vec::IntoIter<LanguageIdentifier>,
+ resource_ids: Vec<ResourceId>,
+ ) -> Result<GenerateBundles<P, B>, L10nRegistrySetupError> {
+ Ok(GenerateBundles::new(
+ self.clone(),
+ locales,
+ resource_ids,
+ // Cheaply create an immutable shallow copy of the [MetaSources].
+ self.try_borrow_metasources()?.clone(),
+ ))
+ }
+}
+
+/// This enum contains the various states the [GenerateBundles] can be in during the
+/// asynchronous generation step.
+enum State<P, B> {
+ Empty,
+ Locale(LanguageIdentifier),
+ Solver {
+ locale: LanguageIdentifier,
+ solver: ParallelProblemSolver<GenerateBundles<P, B>>,
+ },
+}
+
+impl<P, B> Default for State<P, B> {
+ fn default() -> Self {
+ Self::Empty
+ }
+}
+
+impl<P, B> State<P, B> {
+ fn get_locale(&self) -> &LanguageIdentifier {
+ match self {
+ Self::Locale(locale) => locale,
+ Self::Solver { locale, .. } => locale,
+ Self::Empty => unreachable!("Attempting to get a locale for an empty state."),
+ }
+ }
+
+ fn take_solver(&mut self) -> ParallelProblemSolver<GenerateBundles<P, B>> {
+ replace_with::replace_with_or_default_and_return(self, |self_| match self_ {
+ Self::Solver { locale, solver } => (solver, Self::Locale(locale)),
+ _ => unreachable!("Attempting to take a solver in an invalid state."),
+ })
+ }
+
+ fn put_back_solver(&mut self, solver: ParallelProblemSolver<GenerateBundles<P, B>>) {
+ replace_with::replace_with_or_default(self, |self_| match self_ {
+ Self::Locale(locale) => Self::Solver { locale, solver },
+ _ => unreachable!("Attempting to put back a solver in an invalid state."),
+ })
+ }
+}
+
+pub struct GenerateBundles<P, B> {
+ /// Do not access the metasources in the registry, as they may be mutated between
+ /// async iterations.
+ reg: L10nRegistry<P, B>,
+ /// This is an immutable shallow copy of the MetaSources that should not be mutated
+ /// during the iteration process. This ensures that the iterator will still be
+ /// valid if the L10nRegistry is mutated while iterating through the sources.
+ metasources: MetaSources,
+ locales: std::vec::IntoIter<LanguageIdentifier>,
+ current_metasource: usize,
+ resource_ids: Vec<ResourceId>,
+ state: State<P, B>,
+}
+
+impl<P, B> GenerateBundles<P, B> {
+ fn new(
+ reg: L10nRegistry<P, B>,
+ locales: std::vec::IntoIter<LanguageIdentifier>,
+ resource_ids: Vec<ResourceId>,
+ metasources: MetaSources,
+ ) -> Self {
+ Self {
+ reg,
+ metasources,
+ locales,
+ current_metasource: 0,
+ resource_ids,
+ state: State::Empty,
+ }
+ }
+}
+
+pub type ResourceSetStream = Collect<FuturesOrdered<ResourceStatus>, Vec<ResourceOption>>;
+pub struct TestResult(ResourceSetStream);
+impl std::marker::Unpin for TestResult {}
+
+impl Future for TestResult {
+ type Output = Vec<bool>;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ let pinned = Pin::new(&mut self.0);
+ pinned
+ .poll(cx)
+ .map(|set| set.iter().map(|c| !c.is_required_and_missing()).collect())
+ }
+}
+
+impl<'l, P, B> AsyncTester for GenerateBundles<P, B> {
+ type Result = TestResult;
+
+ fn test_async(&self, query: Vec<(usize, usize)>) -> Self::Result {
+ let locale = self.state.get_locale();
+
+ let stream = query
+ .iter()
+ .map(|(res_idx, source_idx)| {
+ let resource_id = &self.resource_ids[*res_idx];
+ self.metasources
+ .filesource(self.current_metasource, *source_idx)
+ .fetch_file(locale, resource_id)
+ })
+ .collect::<FuturesOrdered<_>>();
+ TestResult(stream.collect::<_>())
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl<P, B> BundleStream for GenerateBundles<P, B> {
+ async fn prefetch_async(&mut self) {
+ todo!();
+ }
+}
+
+/// Generate [FluentBundles](FluentBundle) asynchronously.
+impl<P, B> Stream for GenerateBundles<P, B>
+where
+ P: ErrorReporter,
+ B: BundleAdapter,
+{
+ type Item = Result<FluentBundle, (FluentBundle, Vec<FluentError>)>;
+
+ /// Asynchronously try and get a solver, and then with the solver generate a bundle.
+ /// If the solver is not ready yet, then this function will return as `Pending`, and
+ /// the Future runner will need to re-enter at a later point to try again.
+ fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
+ if self.metasources.is_empty() {
+ // There are no metasources available, so no bundles can be generated.
+ return None.into();
+ }
+ loop {
+ if let State::Solver { .. } = self.state {
+ // A solver has already been set up, continue iterating through the
+ // resources and generating a bundle.
+
+ // Pin the solver so that the async try_poll_next can be called.
+ let mut solver = self.state.take_solver();
+ let pinned_solver = Pin::new(&mut solver);
+
+ if let std::task::Poll::Ready(solver_result) =
+ pinned_solver.try_poll_next(cx, &self, false)
+ {
+ // The solver is ready, but may not have generated an ordering.
+
+ if let Ok(Some(order)) = solver_result {
+ // The solver resolved an ordering, and a bundle may be able
+ // to be generated.
+
+ let bundle = self.metasources.bundle_from_order(
+ self.current_metasource,
+ self.state.get_locale().clone(),
+ &order,
+ &self.resource_ids,
+ &self.reg.shared.provider,
+ self.reg.shared.bundle_adapter.as_ref(),
+ );
+
+ self.state.put_back_solver(solver);
+
+ if bundle.is_some() {
+ // The bundle was successfully generated.
+ return bundle.into();
+ }
+
+ // No bundle was generated, continue on.
+ continue;
+ }
+
+ // There is no bundle ordering available.
+
+ if self.current_metasource > 0 {
+ // There are more metasources, create a new solver and try the
+ // next metasource. If there is an error in the solver_result
+ // ignore it for now, since there are more metasources.
+ self.current_metasource -= 1;
+ let solver = ParallelProblemSolver::new(
+ self.resource_ids.len(),
+ self.metasources.get(self.current_metasource).len(),
+ );
+ self.state = State::Solver {
+ locale: self.state.get_locale().clone(),
+ solver,
+ };
+ continue;
+ }
+
+ if let Err(idx) = solver_result {
+ // Since there are no more metasources, and there is an error,
+ // report it instead of ignoring it.
+ self.reg.shared.provider.report_errors(vec![
+ L10nRegistryError::MissingResource {
+ locale: self.state.get_locale().clone(),
+ resource_id: self.resource_ids[idx].clone(),
+ },
+ ]);
+ }
+
+ // There are no more metasources.
+ self.state = State::Empty;
+ continue;
+ }
+
+ // The solver is not ready yet, so exit out of this async task
+ // and mark it as pending. It can be tried again later.
+ self.state.put_back_solver(solver);
+ return std::task::Poll::Pending;
+ }
+
+ // There are no more metasources to search.
+
+ // Try the next locale.
+ if let Some(locale) = self.locales.next() {
+ // Restart at the end of the metasources for this locale, and iterate
+ // backwards.
+ let last_metasource_idx = self.metasources.len() - 1;
+ self.current_metasource = last_metasource_idx;
+
+ let solver = ParallelProblemSolver::new(
+ self.resource_ids.len(),
+ self.metasources.get(self.current_metasource).len(),
+ );
+ self.state = State::Solver { locale, solver };
+
+ // Continue iterating on the next solver.
+ continue;
+ }
+
+ // There are no more locales or metasources to search. This iterator
+ // is done.
+ return None.into();
+ }
+ }
+}
diff --git a/intl/l10n/rust/l10nregistry-rs/src/registry/mod.rs b/intl/l10n/rust/l10nregistry-rs/src/registry/mod.rs
new file mode 100644
index 0000000000..c342aa55aa
--- /dev/null
+++ b/intl/l10n/rust/l10nregistry-rs/src/registry/mod.rs
@@ -0,0 +1,363 @@
+mod asynchronous;
+mod synchronous;
+
+use crate::{
+ env::ErrorReporter,
+ errors::L10nRegistrySetupError,
+ fluent::FluentBundle,
+ source::{FileSource, ResourceId},
+};
+use fluent_bundle::FluentResource;
+use fluent_fallback::generator::BundleGenerator;
+use rustc_hash::FxHashSet;
+use std::{
+ cell::{Ref, RefCell, RefMut},
+ collections::HashSet,
+ rc::Rc,
+};
+use unic_langid::LanguageIdentifier;
+
+pub use asynchronous::GenerateBundles;
+pub use synchronous::GenerateBundlesSync;
+
+pub type FluentResourceSet = Vec<Rc<FluentResource>>;
+
+/// The shared information that makes up the configuration the L10nRegistry. It is
+/// broken out into a separate struct so that it can be shared via an Rc pointer.
+#[derive(Default)]
+struct Shared<P, B> {
+ metasources: RefCell<MetaSources>,
+ provider: P,
+ bundle_adapter: Option<B>,
+}
+
+/// [FileSources](FileSource) represent a single directory location to look for .ftl
+/// files. These are Stored in a [Vec]. For instance, in a built version of Firefox with
+/// the en-US locale, each [FileSource] may represent a different folder with many
+/// different files.
+///
+/// Firefox supports other *meta sources* for localization files in the form of language
+/// packs which can be downloaded from the addon store. These language packs then would
+/// be a separate metasource than the app' language. This [MetaSources] adds another [Vec]
+/// over the [Vec] of [FileSources](FileSource) in order to provide a unified way to
+/// iterate over all possible [FileSource] locations to finally obtain the final bundle.
+///
+/// This structure uses an [Rc] to point to the [FileSource] so that a shallow copy
+/// of these [FileSources](FileSource) can be obtained for iteration. This makes
+/// it quick to copy the list of [MetaSources] for iteration, and guards against
+/// invalidating that async nature of iteration when the underlying data mutates.
+///
+/// Note that the async iteration of bundles is still only happening in one thread,
+/// and is not multi-threaded. The processing is just split over time.
+///
+/// The [MetaSources] are ultimately owned by the [Shared] in a [RefCell] so that the
+/// source of truth can be mutated, and shallow copies of the [MetaSources] used for
+/// iteration will be unaffected.
+///
+/// Deriving [Clone] here is a relatively cheap operation, since the [Rc] will be cloned,
+/// and point to the original [FileSource].
+#[derive(Default, Clone)]
+pub struct MetaSources(Vec<Vec<Rc<FileSource>>>);
+
+impl MetaSources {
+ /// Iterate over all FileSources in all MetaSources.
+ pub fn filesources(&self) -> impl Iterator<Item = &Rc<FileSource>> {
+ self.0.iter().flatten()
+ }
+
+ /// Iterate over all FileSources in all MetaSources.
+ pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Vec<Rc<FileSource>>> {
+ self.0.iter_mut()
+ }
+
+ /// The number of metasources.
+ pub fn len(&self) -> usize {
+ self.0.len()
+ }
+
+ /// If there are no metasources.
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+
+ /// Clears out all metasources.
+ pub fn clear(&mut self) {
+ self.0.clear();
+ }
+
+ /// Clears out only empty metasources.
+ pub fn clear_empty_metasources(&mut self) {
+ self.0.retain(|metasource| !metasource.is_empty());
+ }
+
+ /// Adds a [FileSource] to its appropriate metasource.
+ pub fn add_filesource(&mut self, new_source: FileSource) {
+ if let Some(metasource) = self
+ .0
+ .iter_mut()
+ .find(|source| source[0].metasource == new_source.metasource)
+ {
+ // A metasource was found, add to the existing one.
+ metasource.push(Rc::new(new_source));
+ } else {
+ // Create a new metasource.
+ self.0.push(vec![Rc::new(new_source)]);
+ }
+ }
+
+ /// Adds a [FileSource] to its appropriate metasource.
+ pub fn update_filesource(&mut self, new_source: &FileSource) -> bool {
+ if let Some(metasource) = self
+ .0
+ .iter_mut()
+ .find(|source| source[0].metasource == new_source.metasource)
+ {
+ if let Some(idx) = metasource.iter().position(|source| **source == *new_source) {
+ *metasource.get_mut(idx).unwrap() = Rc::new(new_source.clone());
+ return true;
+ }
+ }
+ false
+ }
+
+ /// Get a metasource by index, but provide a nice error message if the index
+ /// is out of bounds.
+ pub fn get(&self, metasource_idx: usize) -> &Vec<Rc<FileSource>> {
+ if let Some(metasource) = self.0.get(metasource_idx) {
+ return &metasource;
+ }
+ panic!(
+ "Metasource index of {} is out of range of the list of {} meta sources.",
+ metasource_idx,
+ self.0.len()
+ );
+ }
+
+ /// Get a [FileSource] from a metasource, but provide a nice error message if the
+ /// index is out of bounds.
+ pub fn filesource(&self, metasource_idx: usize, filesource_idx: usize) -> &FileSource {
+ let metasource = self.get(metasource_idx);
+ let reversed_idx = metasource.len() - 1 - filesource_idx;
+ if let Some(file_source) = metasource.get(reversed_idx) {
+ return file_source;
+ }
+ panic!(
+ "File source index of {} is out of range of the list of {} file sources.",
+ filesource_idx,
+ metasource.len()
+ );
+ }
+
+ /// Get a [FileSource] by name from a metasource. This is useful for testing.
+ #[cfg(feature = "test-fluent")]
+ pub fn file_source_by_name(&self, metasource_idx: usize, name: &str) -> Option<&FileSource> {
+ use std::borrow::Borrow;
+ self.get(metasource_idx)
+ .iter()
+ .find(|&source| source.name == name)
+ .map(|source| source.borrow())
+ }
+
+ /// Get an iterator for the [FileSources](FileSource) that match the [LanguageIdentifier]
+ /// and [ResourceId].
+ #[cfg(feature = "test-fluent")]
+ pub fn get_sources_for_resource<'l>(
+ &'l self,
+ metasource_idx: usize,
+ langid: &'l LanguageIdentifier,
+ resource_id: &'l ResourceId,
+ ) -> impl Iterator<Item = &FileSource> {
+ use std::borrow::Borrow;
+ self.get(metasource_idx)
+ .iter()
+ .filter(move |source| source.has_file(langid, resource_id) != Some(false))
+ .map(|source| source.borrow())
+ }
+}
+
+/// The [BundleAdapter] can adapt the bundle to the environment with such actions as
+/// setting the platform, and hooking up functions such as Fluent's DATETIME and
+/// NUMBER formatting functions.
+pub trait BundleAdapter {
+ fn adapt_bundle(&self, bundle: &mut FluentBundle);
+}
+
+/// The L10nRegistry is the main struct for owning the registry information.
+///
+/// `P` - A provider
+/// `B` - A bundle adapter
+#[derive(Clone)]
+pub struct L10nRegistry<P, B> {
+ shared: Rc<Shared<P, B>>,
+}
+
+impl<P, B> L10nRegistry<P, B> {
+ /// Create a new [L10nRegistry] from a provider.
+ pub fn with_provider(provider: P) -> Self {
+ Self {
+ shared: Rc::new(Shared {
+ metasources: Default::default(),
+ provider,
+ bundle_adapter: None,
+ }),
+ }
+ }
+
+ /// Set the bundle adapter. See [BundleAdapter] for more information.
+ pub fn set_bundle_adapter(&mut self, bundle_adapter: B) -> Result<(), L10nRegistrySetupError>
+ where
+ B: BundleAdapter,
+ {
+ let shared = Rc::get_mut(&mut self.shared).ok_or(L10nRegistrySetupError::RegistryLocked)?;
+ shared.bundle_adapter = Some(bundle_adapter);
+ Ok(())
+ }
+
+ pub fn try_borrow_metasources(&self) -> Result<Ref<MetaSources>, L10nRegistrySetupError> {
+ self.shared
+ .metasources
+ .try_borrow()
+ .map_err(|_| L10nRegistrySetupError::RegistryLocked)
+ }
+
+ pub fn try_borrow_metasources_mut(
+ &self,
+ ) -> Result<RefMut<MetaSources>, L10nRegistrySetupError> {
+ self.shared
+ .metasources
+ .try_borrow_mut()
+ .map_err(|_| L10nRegistrySetupError::RegistryLocked)
+ }
+
+ /// Adds a new [FileSource] to the registry and to its appropriate metasource. If the
+ /// metasource for this [FileSource] does not exist, then it is created.
+ pub fn register_sources(
+ &self,
+ new_sources: Vec<FileSource>,
+ ) -> Result<(), L10nRegistrySetupError> {
+ for new_source in new_sources {
+ self.try_borrow_metasources_mut()?
+ .add_filesource(new_source);
+ }
+ Ok(())
+ }
+
+ /// Update the information about sources already stored in the registry. Each
+ /// [FileSource] provided must exist, or else a [L10nRegistrySetupError] will
+ /// be returned.
+ pub fn update_sources(
+ &self,
+ new_sources: Vec<FileSource>,
+ ) -> Result<(), L10nRegistrySetupError> {
+ for new_source in new_sources {
+ if !self
+ .try_borrow_metasources_mut()?
+ .update_filesource(&new_source)
+ {
+ return Err(L10nRegistrySetupError::MissingSource {
+ name: new_source.name,
+ });
+ }
+ }
+ Ok(())
+ }
+
+ /// Remove the provided sources. If a metasource becomes empty after this operation,
+ /// the metasource is also removed.
+ pub fn remove_sources<S>(&self, del_sources: Vec<S>) -> Result<(), L10nRegistrySetupError>
+ where
+ S: ToString,
+ {
+ let del_sources: Vec<String> = del_sources.into_iter().map(|s| s.to_string()).collect();
+
+ for metasource in self.try_borrow_metasources_mut()?.iter_mut() {
+ metasource.retain(|source| !del_sources.contains(&source.name));
+ }
+
+ self.try_borrow_metasources_mut()?.clear_empty_metasources();
+
+ Ok(())
+ }
+
+ /// Clears out all metasources and sources.
+ pub fn clear_sources(&self) -> Result<(), L10nRegistrySetupError> {
+ self.try_borrow_metasources_mut()?.clear();
+ Ok(())
+ }
+
+ /// Flattens out all metasources and returns the complete list of source names.
+ pub fn get_source_names(&self) -> Result<Vec<String>, L10nRegistrySetupError> {
+ Ok(self
+ .try_borrow_metasources()?
+ .filesources()
+ .map(|s| s.name.clone())
+ .collect())
+ }
+
+ /// Checks if any metasources has a source, by the name.
+ pub fn has_source(&self, name: &str) -> Result<bool, L10nRegistrySetupError> {
+ Ok(self
+ .try_borrow_metasources()?
+ .filesources()
+ .any(|source| source.name == name))
+ }
+
+ /// Get a [FileSource] by name by searching through all meta sources.
+ pub fn file_source_by_name(
+ &self,
+ name: &str,
+ ) -> Result<Option<FileSource>, L10nRegistrySetupError> {
+ Ok(self
+ .try_borrow_metasources()?
+ .filesources()
+ .find(|source| source.name == name)
+ .map(|source| (**source).clone()))
+ }
+
+ /// Returns a unique list of locale names from all sources.
+ pub fn get_available_locales(&self) -> Result<Vec<LanguageIdentifier>, L10nRegistrySetupError> {
+ let mut result = HashSet::new();
+ let metasources = self.try_borrow_metasources()?;
+ for source in metasources.filesources() {
+ for locale in source.locales() {
+ result.insert(locale);
+ }
+ }
+ Ok(result.into_iter().map(|l| l.to_owned()).collect())
+ }
+}
+
+/// Defines how to generate bundles synchronously and asynchronously.
+impl<P, B> BundleGenerator for L10nRegistry<P, B>
+where
+ P: ErrorReporter + Clone,
+ B: BundleAdapter + Clone,
+{
+ type Resource = Rc<FluentResource>;
+ type Iter = GenerateBundlesSync<P, B>;
+ type Stream = GenerateBundles<P, B>;
+ type LocalesIter = std::vec::IntoIter<LanguageIdentifier>;
+
+ /// The synchronous version of the bundle generator. This is hooked into Gecko
+ /// code via the `l10nregistry_generate_bundles_sync` function.
+ fn bundles_iter(
+ &self,
+ locales: Self::LocalesIter,
+ resource_ids: FxHashSet<ResourceId>,
+ ) -> Self::Iter {
+ let resource_ids = resource_ids.into_iter().collect();
+ self.generate_bundles_sync(locales, resource_ids)
+ }
+
+ /// The asynchronous version of the bundle generator. This is hooked into Gecko
+ /// code via the `l10nregistry_generate_bundles` function.
+ fn bundles_stream(
+ &self,
+ locales: Self::LocalesIter,
+ resource_ids: FxHashSet<ResourceId>,
+ ) -> Self::Stream {
+ let resource_ids = resource_ids.into_iter().collect();
+ self.generate_bundles(locales, resource_ids)
+ .expect("Unable to get the MetaSources.")
+ }
+}
diff --git a/intl/l10n/rust/l10nregistry-rs/src/registry/synchronous.rs b/intl/l10n/rust/l10nregistry-rs/src/registry/synchronous.rs
new file mode 100644
index 0000000000..097ca68eee
--- /dev/null
+++ b/intl/l10n/rust/l10nregistry-rs/src/registry/synchronous.rs
@@ -0,0 +1,307 @@
+use super::{BundleAdapter, L10nRegistry, MetaSources};
+use crate::env::ErrorReporter;
+use crate::errors::L10nRegistryError;
+use crate::fluent::{FluentBundle, FluentError};
+use crate::solver::{SerialProblemSolver, SyncTester};
+use crate::source::ResourceOption;
+use fluent_fallback::{generator::BundleIterator, types::ResourceId};
+use unic_langid::LanguageIdentifier;
+
+impl MetaSources {
+ pub(crate) fn bundle_from_order<P, B>(
+ &self,
+ metasource: usize,
+ locale: LanguageIdentifier,
+ source_order: &[usize],
+ resource_ids: &[ResourceId],
+ error_reporter: &P,
+ bundle_adapter: Option<&B>,
+ ) -> Option<Result<FluentBundle, (FluentBundle, Vec<FluentError>)>>
+ where
+ P: ErrorReporter,
+ B: BundleAdapter,
+ {
+ let mut bundle = FluentBundle::new(vec![locale.clone()]);
+
+ if let Some(bundle_adapter) = bundle_adapter {
+ bundle_adapter.adapt_bundle(&mut bundle);
+ }
+
+ let mut errors = vec![];
+
+ for (&source_idx, resource_id) in source_order.iter().zip(resource_ids.iter()) {
+ let source = self.filesource(metasource, source_idx);
+ if let ResourceOption::Some(res) =
+ source.fetch_file_sync(&locale, resource_id, /* overload */ true)
+ {
+ if source.options.allow_override {
+ bundle.add_resource_overriding(res);
+ } else if let Err(err) = bundle.add_resource(res) {
+ errors.extend(err.into_iter().map(|error| L10nRegistryError::FluentError {
+ resource_id: resource_id.clone(),
+ loc: None,
+ error,
+ }));
+ }
+ } else if resource_id.is_required() {
+ return None;
+ }
+ }
+
+ if !errors.is_empty() {
+ error_reporter.report_errors(errors);
+ }
+ Some(Ok(bundle))
+ }
+}
+
+impl<P, B> L10nRegistry<P, B>
+where
+ P: Clone,
+ B: Clone,
+{
+ /// A test-only function for easily generating bundles for a single langid.
+ #[cfg(feature = "test-fluent")]
+ pub fn generate_bundles_for_lang_sync(
+ &self,
+ langid: LanguageIdentifier,
+ resource_ids: Vec<ResourceId>,
+ ) -> GenerateBundlesSync<P, B> {
+ let lang_ids = vec![langid];
+
+ GenerateBundlesSync::new(self.clone(), lang_ids.into_iter(), resource_ids)
+ }
+
+ /// Wiring for hooking up the synchronous bundle generation to the
+ /// [BundleGenerator] trait.
+ pub fn generate_bundles_sync(
+ &self,
+ locales: std::vec::IntoIter<LanguageIdentifier>,
+ resource_ids: Vec<ResourceId>,
+ ) -> GenerateBundlesSync<P, B> {
+ GenerateBundlesSync::new(self.clone(), locales, resource_ids)
+ }
+}
+
+enum State {
+ Empty,
+ Locale(LanguageIdentifier),
+ Solver {
+ locale: LanguageIdentifier,
+ solver: SerialProblemSolver,
+ },
+}
+
+impl Default for State {
+ fn default() -> Self {
+ Self::Empty
+ }
+}
+
+impl State {
+ fn get_locale(&self) -> &LanguageIdentifier {
+ match self {
+ Self::Locale(locale) => locale,
+ Self::Solver { locale, .. } => locale,
+ Self::Empty => unreachable!("Attempting to get a locale for an empty state."),
+ }
+ }
+
+ fn take_solver(&mut self) -> SerialProblemSolver {
+ replace_with::replace_with_or_default_and_return(self, |self_| match self_ {
+ Self::Solver { locale, solver } => (solver, Self::Locale(locale)),
+ _ => unreachable!("Attempting to take a solver in an invalid state."),
+ })
+ }
+
+ fn put_back_solver(&mut self, solver: SerialProblemSolver) {
+ replace_with::replace_with_or_default(self, |self_| match self_ {
+ Self::Locale(locale) => Self::Solver { locale, solver },
+ _ => unreachable!("Attempting to put back a solver in an invalid state."),
+ })
+ }
+}
+
+pub struct GenerateBundlesSync<P, B> {
+ reg: L10nRegistry<P, B>,
+ locales: std::vec::IntoIter<LanguageIdentifier>,
+ current_metasource: usize,
+ resource_ids: Vec<ResourceId>,
+ state: State,
+}
+
+impl<P, B> GenerateBundlesSync<P, B> {
+ fn new(
+ reg: L10nRegistry<P, B>,
+ locales: std::vec::IntoIter<LanguageIdentifier>,
+ resource_ids: Vec<ResourceId>,
+ ) -> Self {
+ Self {
+ reg,
+ locales,
+ current_metasource: 0,
+ resource_ids,
+ state: State::Empty,
+ }
+ }
+}
+
+impl<P, B> SyncTester for GenerateBundlesSync<P, B> {
+ fn test_sync(&self, res_idx: usize, source_idx: usize) -> bool {
+ let locale = self.state.get_locale();
+ let resource_id = &self.resource_ids[res_idx];
+ !self
+ .reg
+ .try_borrow_metasources()
+ .expect("Unable to get the MetaSources.")
+ .filesource(self.current_metasource, source_idx)
+ .fetch_file_sync(locale, resource_id, /* overload */ true)
+ .is_required_and_missing()
+ }
+}
+
+impl<P, B> BundleIterator for GenerateBundlesSync<P, B>
+where
+ P: ErrorReporter,
+{
+ fn prefetch_sync(&mut self) {
+ if let State::Solver { .. } = self.state {
+ let mut solver = self.state.take_solver();
+ if let Err(idx) = solver.try_next(self, true) {
+ self.reg
+ .shared
+ .provider
+ .report_errors(vec![L10nRegistryError::MissingResource {
+ locale: self.state.get_locale().clone(),
+ resource_id: self.resource_ids[idx].clone(),
+ }]);
+ }
+ self.state.put_back_solver(solver);
+ return;
+ }
+
+ if let Some(locale) = self.locales.next() {
+ let mut solver = SerialProblemSolver::new(
+ self.resource_ids.len(),
+ self.reg
+ .try_borrow_metasources()
+ .expect("Unable to get the MetaSources.")
+ .get(self.current_metasource)
+ .len(),
+ );
+ self.state = State::Locale(locale.clone());
+ if let Err(idx) = solver.try_next(self, true) {
+ self.reg
+ .shared
+ .provider
+ .report_errors(vec![L10nRegistryError::MissingResource {
+ locale,
+ resource_id: self.resource_ids[idx].clone(),
+ }]);
+ }
+ self.state.put_back_solver(solver);
+ }
+ }
+}
+
+impl<P, B> Iterator for GenerateBundlesSync<P, B>
+where
+ P: ErrorReporter,
+ B: BundleAdapter,
+{
+ type Item = Result<FluentBundle, (FluentBundle, Vec<FluentError>)>;
+
+ /// Synchronously generate a bundle based on a solver.
+ fn next(&mut self) -> Option<Self::Item> {
+ let metasources = self
+ .reg
+ .try_borrow_metasources()
+ .expect("Unable to get the MetaSources.");
+
+ if metasources.is_empty() {
+ // There are no metasources available, so no bundles can be generated.
+ return None;
+ }
+
+ loop {
+ if let State::Solver { .. } = self.state {
+ // A solver has already been set up, continue iterating through the
+ // resources and generating a bundle.
+ let mut solver = self.state.take_solver();
+ let solver_result = solver.try_next(self, false);
+
+ if let Ok(Some(order)) = solver_result {
+ // The solver resolved an ordering, and a bundle may be able
+ // to be generated.
+
+ let bundle = metasources.bundle_from_order(
+ self.current_metasource,
+ self.state.get_locale().clone(),
+ &order,
+ &self.resource_ids,
+ &self.reg.shared.provider,
+ self.reg.shared.bundle_adapter.as_ref(),
+ );
+
+ self.state.put_back_solver(solver);
+
+ if bundle.is_some() {
+ // The bundle was successfully generated.
+ return bundle;
+ }
+
+ // No bundle was generated, continue on.
+ continue;
+ }
+
+ // There is no bundle ordering available.
+
+ if self.current_metasource > 0 {
+ // There are more metasources, create a new solver and try the
+ // next metasource. If there is an error in the solver_result
+ // ignore it for now, since there are more metasources.
+ self.current_metasource -= 1;
+ let solver = SerialProblemSolver::new(
+ self.resource_ids.len(),
+ metasources.get(self.current_metasource).len(),
+ );
+ self.state = State::Solver {
+ locale: self.state.get_locale().clone(),
+ solver,
+ };
+ continue;
+ }
+
+ if let Err(idx) = solver_result {
+ // Since there are no more metasources, and there is an error,
+ // report it instead of ignoring it.
+ self.reg.shared.provider.report_errors(vec![
+ L10nRegistryError::MissingResource {
+ locale: self.state.get_locale().clone(),
+ resource_id: self.resource_ids[idx].clone(),
+ },
+ ]);
+ }
+
+ self.state = State::Empty;
+ continue;
+ }
+
+ // Try the next locale, or break out of the loop if there are none left.
+ let locale = self.locales.next()?;
+
+ // Restart at the end of the metasources for this locale, and iterate
+ // backwards.
+ let last_metasource_idx = metasources.len() - 1;
+ self.current_metasource = last_metasource_idx;
+
+ let solver = SerialProblemSolver::new(
+ self.resource_ids.len(),
+ metasources.get(self.current_metasource).len(),
+ );
+
+ // Continue iterating on the next solver.
+ self.state = State::Solver { locale, solver };
+ }
+ }
+}