diff --git a/examples/unsat_root_message_no_version.rs b/examples/unsat_root_message_no_version.rs index 4d7f92a0..9fe14d4a 100644 --- a/examples/unsat_root_message_no_version.rs +++ b/examples/unsat_root_message_no_version.rs @@ -6,11 +6,10 @@ use pubgrub::report::Reporter; use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::version::SemanticVersion; -use std::fmt::{self, Display}; - -use pubgrub::report::{DerivationTree, Derived, External}; +use pubgrub::report::{DefaultStringReporter, External, ReportFormatter}; use pubgrub::term::Term; use pubgrub::type_aliases::Map; +use std::fmt::{self, Display}; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Package { @@ -27,265 +26,13 @@ impl Display for Package { } } -/// Derivative of [`pubgrub::report::DefaultStringReporter`] for customized display -/// of package resolution errors. -pub struct CustomStringReporter { - /// Number of explanations already with a line reference. - ref_count: usize, - /// Shared nodes that have already been marked with a line reference. - /// The incompatibility ids are the keys, and the line references are the values. - shared_with_ref: Map, - /// Accumulated lines of the report already generated. - lines: Vec, -} - -impl CustomStringReporter { - /// Initialize the reporter. - fn new() -> Self { - Self { - ref_count: 0, - shared_with_ref: Map::default(), - lines: Vec::new(), - } - } - - fn build_recursive(&mut self, derived: &Derived>) { - self.build_recursive_helper(derived); - if let Some(id) = derived.shared_id { - if self.shared_with_ref.get(&id).is_none() { - self.add_line_ref(); - self.shared_with_ref.insert(id, self.ref_count); - } - }; - } - - fn build_recursive_helper(&mut self, current: &Derived>) { - match (&*current.cause1, &*current.cause2) { - (DerivationTree::External(external1), DerivationTree::External(external2)) => { - // Simplest case, we just combine two external incompatibilities. - self.lines.push(Self::explain_both_external( - external1, - external2, - ¤t.terms, - )); - } - (DerivationTree::Derived(derived), DerivationTree::External(external)) => { - // One cause is derived, so we explain this first - // then we add the one-line external part - // and finally conclude with the current incompatibility. - self.report_one_each(derived, external, ¤t.terms); - } - (DerivationTree::External(external), DerivationTree::Derived(derived)) => { - self.report_one_each(derived, external, ¤t.terms); - } - (DerivationTree::Derived(derived1), DerivationTree::Derived(derived2)) => { - // This is the most complex case since both causes are also derived. - match ( - self.line_ref_of(derived1.shared_id), - self.line_ref_of(derived2.shared_id), - ) { - // If both causes already have been referenced (shared_id), - // the explanation simply uses those references. - (Some(ref1), Some(ref2)) => self.lines.push(Self::explain_both_ref( - ref1, - derived1, - ref2, - derived2, - ¤t.terms, - )), - // Otherwise, if one only has a line number reference, - // we recursively call the one without reference and then - // add the one with reference to conclude. - (Some(ref1), None) => { - self.build_recursive(derived2); - self.lines - .push(Self::and_explain_ref(ref1, derived1, ¤t.terms)); - } - (None, Some(ref2)) => { - self.build_recursive(derived1); - self.lines - .push(Self::and_explain_ref(ref2, derived2, ¤t.terms)); - } - // Finally, if no line reference exists yet, - // we call recursively the first one and then, - // - if this was a shared node, it will get a line ref - // and we can simply recall this with the current node. - // - otherwise, we add a line reference to it, - // recursively call on the second node, - // and finally conclude. - (None, None) => { - self.build_recursive(derived1); - if derived1.shared_id.is_some() { - self.lines.push(String::new()); - self.build_recursive(current); - } else { - self.add_line_ref(); - let ref1 = self.ref_count; - self.lines.push(String::new()); - self.build_recursive(derived2); - self.lines - .push(Self::and_explain_ref(ref1, derived1, ¤t.terms)); - } - } - } - } - } - } - - /// Report a derived and an external incompatibility. - /// - /// The result will depend on the fact that the derived incompatibility - /// has already been explained or not. - fn report_one_each( - &mut self, - derived: &Derived>, - external: &External>, - current_terms: &Map>>, - ) { - match self.line_ref_of(derived.shared_id) { - Some(ref_id) => self.lines.push(Self::explain_ref_and_external( - ref_id, - derived, - external, - current_terms, - )), - None => self.report_recurse_one_each(derived, external, current_terms), - } - } - - /// Report one derived (without a line ref yet) and one external. - fn report_recurse_one_each( - &mut self, - derived: &Derived>, - external: &External>, - current_terms: &Map>>, - ) { - match (&*derived.cause1, &*derived.cause2) { - // If the derived cause has itself one external prior cause, - // we can chain the external explanations. - (DerivationTree::Derived(prior_derived), DerivationTree::External(prior_external)) => { - self.build_recursive(prior_derived); - self.lines.push(Self::and_explain_prior_and_external( - prior_external, - external, - current_terms, - )); - } - // If the derived cause has itself one external prior cause, - // we can chain the external explanations. - (DerivationTree::External(prior_external), DerivationTree::Derived(prior_derived)) => { - self.build_recursive(prior_derived); - self.lines.push(Self::and_explain_prior_and_external( - prior_external, - external, - current_terms, - )); - } - _ => { - self.build_recursive(derived); - self.lines - .push(Self::and_explain_external(external, current_terms)); - } - } - } - - // String explanations ##################################################### - - /// Simplest case, we just combine two external incompatibilities. - fn explain_both_external( - external1: &External>, - external2: &External>, - current_terms: &Map>>, - ) -> String { - // TODO: order should be chosen to make it more logical. - format!( - "Because {} and {}, {}.", - CustomExternal::from_pubgrub(external1.clone()), - CustomExternal::from_pubgrub(external2.clone()), - Self::string_terms(current_terms) - ) - } - - /// Both causes have already been explained so we use their refs. - fn explain_both_ref( - ref_id1: usize, - derived1: &Derived>, - ref_id2: usize, - derived2: &Derived>, - current_terms: &Map>>, - ) -> String { - // TODO: order should be chosen to make it more logical. - format!( - "Because {} ({}) and {} ({}), {}.", - Self::string_terms(&derived1.terms), - ref_id1, - Self::string_terms(&derived2.terms), - ref_id2, - Self::string_terms(current_terms) - ) - } - - /// One cause is derived (already explained so one-line), - /// the other is a one-line external cause, - /// and finally we conclude with the current incompatibility. - fn explain_ref_and_external( - ref_id: usize, - derived: &Derived>, - external: &External>, - current_terms: &Map>>, - ) -> String { - // TODO: order should be chosen to make it more logical. - format!( - "Because {} ({}) and {}, {}.", - Self::string_terms(&derived.terms), - ref_id, - CustomExternal::from_pubgrub(external.clone()), - Self::string_terms(current_terms) - ) - } - - /// Add an external cause to the chain of explanations. - fn and_explain_external( - external: &External>, - current_terms: &Map>>, - ) -> String { - format!( - "And because {}, {}.", - CustomExternal::from_pubgrub(external.clone()), - Self::string_terms(current_terms) - ) - } +#[derive(Debug, Default)] +struct CustomReportFormatter; - /// Add an already explained incompat to the chain of explanations. - fn and_explain_ref( - ref_id: usize, - derived: &Derived>, - current_terms: &Map>>, - ) -> String { - format!( - "And because {} ({}), {}.", - Self::string_terms(&derived.terms), - ref_id, - Self::string_terms(current_terms) - ) - } - - /// Add an already explained incompat to the chain of explanations. - fn and_explain_prior_and_external( - prior_external: &External>, - external: &External>, - current_terms: &Map>>, - ) -> String { - format!( - "And because {} and {}, {}.", - CustomExternal::from_pubgrub(prior_external.clone()), - CustomExternal::from_pubgrub(external.clone()), - Self::string_terms(current_terms) - ) - } +impl ReportFormatter> for CustomReportFormatter { + type Output = String; - /// Try to print terms of an incompatibility in a human-readable way. - pub fn string_terms(terms: &Map>>) -> String { + fn format_terms(&self, terms: &Map>>) -> String { let terms_vec: Vec<_> = terms.iter().collect(); match terms_vec.as_slice() { [] => "version solving failed".into(), @@ -315,117 +62,44 @@ impl CustomStringReporter { } } - // Helper functions ######################################################## - - fn add_line_ref(&mut self) { - let new_count = self.ref_count + 1; - self.ref_count = new_count; - if let Some(line) = self.lines.last_mut() { - *line = format!("{line} ({new_count})"); - } - } - - fn line_ref_of(&self, shared_id: Option) -> Option { - shared_id.and_then(|id| self.shared_with_ref.get(&id).copied()) - } -} - -impl Reporter> for CustomStringReporter { - type Output = String; - - fn report(derivation_tree: &DerivationTree>) -> Self::Output { - match derivation_tree { - DerivationTree::External(external) => { - CustomExternal::from_pubgrub(external.clone()).to_string() - } - DerivationTree::Derived(derived) => { - let mut reporter = Self::new(); - reporter.build_recursive(derived); - reporter.lines.join("\n") - } - } - } -} - -/// Derivative of [`pubgrub::report::External`] for customized display -/// for internal [`Package`]. -#[allow(clippy::large_enum_variant)] -#[derive(Debug, Clone)] -enum CustomExternal { - /// Initial incompatibility aiming at picking the root package for the first decision. - NotRoot(Package, SemanticVersion), - /// There are no versions in the given set for this package. - NoVersions(Package, Range), - /// Dependencies of the package are unavailable for versions in that set. - UnavailableDependencies(Package, Range), - /// Incompatibility coming from the dependencies of a given package. - FromDependencyOf( - Package, - Range, - Package, - Range, - ), -} - -impl CustomExternal { - fn from_pubgrub(external: External>) -> Self { + fn format_external(&self, external: &External>) -> String { match external { - External::NotRoot(p, v) => CustomExternal::NotRoot(p, v), - External::NoVersions(p, vs) => CustomExternal::NoVersions(p, vs), - External::UnavailableDependencies(p, vs) => { - CustomExternal::UnavailableDependencies(p, vs) - } - External::FromDependencyOf(p, vs, p_dep, vs_dep) => { - CustomExternal::FromDependencyOf(p, vs, p_dep, vs_dep) - } - } - } -} - -impl fmt::Display for CustomExternal { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::NotRoot(package, version) => { - write!(f, "we are solving dependencies of {package} {version}") + External::NotRoot(package, version) => { + format!("we are solving dependencies of {package} {version}") } - Self::NoVersions(package, set) => { + External::NoVersions(package, set) => { if set == &Range::full() { - write!(f, "there is no available version for {package}") + format!("there is no available version for {package}") } else { - write!(f, "there is no version of {package} in {set}") + format!("there is no version of {package} in {set}") } } - Self::UnavailableDependencies(package, set) => { + External::UnavailableDependencies(package, set) => { if set == &Range::full() { - write!(f, "dependencies of {package} are unavailable") + format!("dependencies of {package} are unavailable") } else { - write!( - f, - "dependencies of {package} at version {set} are unavailable" - ) + format!("dependencies of {package} at version {set} are unavailable") } } - Self::FromDependencyOf(package, package_set, dependency, dependency_set) => { + External::UnusableDependencies(..) => panic!("TODO: Remove me"), + External::FromDependencyOf(package, package_set, dependency, dependency_set) => { if package_set == &Range::full() && dependency_set == &Range::full() { - write!(f, "{package} depends on {dependency}") + format!("{package} depends on {dependency}") } else if package_set == &Range::full() { - write!(f, "{package} depends on {dependency} {dependency_set}") + format!("{package} depends on {dependency} {dependency_set}") } else if dependency_set == &Range::full() { if matches!(package, Package::Root) { // Exclude the dummy version for root packages - write!(f, "{package} depends on {dependency}") + format!("{package} depends on {dependency}") } else { - write!(f, "{package} {package_set} depends on {dependency}") + format!("{package} {package_set} depends on {dependency}") } } else { if matches!(package, Package::Root) { // Exclude the dummy version for root packages - write!(f, "{package} depends on {dependency} {dependency_set}") + format!("{package} depends on {dependency} {dependency_set}") } else { - write!( - f, - "{package} {package_set} depends on {dependency} {dependency_set}" - ) + format!("{package} {package_set} depends on {dependency} {dependency_set}") } } } @@ -449,18 +123,23 @@ fn main() { // Run the algorithm match resolve(&dependency_provider, Package::Root, (0, 0, 0)) { Ok(sol) => println!("{:?}", sol), - Err(PubGrubError::NoSolution(mut derivation_tree)) => { + Err(PubGrubError::NoSolution(derivation_tree)) => { eprintln!("No solution.\n"); eprintln!("### Default report:"); eprintln!("```"); - eprintln!("{}", CustomStringReporter::report(&derivation_tree)); + eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); eprintln!("```\n"); - derivation_tree.collapse_no_versions(); - eprintln!("### Report with `collapse_no_versions`:"); + eprintln!("### Report with custom formatter:"); eprintln!("```"); - eprintln!("{}", CustomStringReporter::report(&derivation_tree)); + eprintln!( + "{}", + DefaultStringReporter::report_with_formatter( + &derivation_tree, + &CustomReportFormatter::default() + ) + ); eprintln!("```"); std::process::exit(1); }