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( &self, metasource: usize, locale: LanguageIdentifier, source_order: &[usize], resource_ids: &[ResourceId], error_reporter: &P, bundle_adapter: Option<&B>, ) -> Option)>> 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 L10nRegistry 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, ) -> GenerateBundlesSync { 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, resource_ids: Vec, ) -> GenerateBundlesSync { 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 { reg: L10nRegistry, locales: std::vec::IntoIter, current_metasource: usize, resource_ids: Vec, state: State, } impl GenerateBundlesSync { fn new( reg: L10nRegistry, locales: std::vec::IntoIter, resource_ids: Vec, ) -> Self { Self { reg, locales, current_metasource: 0, resource_ids, state: State::Empty, } } } impl SyncTester for GenerateBundlesSync { 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 BundleIterator for GenerateBundlesSync 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 Iterator for GenerateBundlesSync where P: ErrorReporter, B: BundleAdapter, { type Item = Result)>; /// Synchronously generate a bundle based on a solver. fn next(&mut self) -> Option { 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 }; } } }