summaryrefslogtreecommitdiffstats
path: root/intl/l10n/rust/l10nregistry-rs/src/registry/synchronous.rs
diff options
context:
space:
mode:
Diffstat (limited to 'intl/l10n/rust/l10nregistry-rs/src/registry/synchronous.rs')
-rw-r--r--intl/l10n/rust/l10nregistry-rs/src/registry/synchronous.rs307
1 files changed, 307 insertions, 0 deletions
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 };
+ }
+ }
+}