use std::fmt::{self, Display};
use pubgrub::{
DefaultStringReporter, Derived, External, Map, OfflineDependencyProvider, PubGrubError, Ranges,
ReportFormatter, Reporter, SemanticVersion, Term, resolve,
};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Package {
Root,
Package(String),
}
impl Display for Package {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Package::Root => write!(f, "root"),
Package::Package(name) => write!(f, "{name}"),
}
}
}
#[derive(Debug, Default)]
struct CustomReportFormatter;
impl ReportFormatter<Package, Ranges<SemanticVersion>, String> for CustomReportFormatter {
type Output = String;
fn format_terms(&self, terms: &Map<Package, Term<Ranges<SemanticVersion>>>) -> String {
let terms_vec: Vec<_> = terms.iter().collect();
match terms_vec.as_slice() {
[] => "version solving failed".into(),
[(package @ Package::Root, Term::Positive(_))] => {
format!("{package} is forbidden")
}
[(package @ Package::Root, Term::Negative(_))] => {
format!("{package} is mandatory")
}
[(package @ Package::Package(_), Term::Positive(ranges))] => {
format!("{package} {ranges} is forbidden")
}
[(package @ Package::Package(_), Term::Negative(ranges))] => {
format!("{package} {ranges} is mandatory")
}
[(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => {
External::<_, _, String>::FromDependencyOf(p1, r1.clone(), p2, r2.clone())
.to_string()
}
[(p1, Term::Negative(r1)), (p2, Term::Positive(r2))] => {
External::<_, _, String>::FromDependencyOf(p2, r2.clone(), p1, r1.clone())
.to_string()
}
slice => {
let str_terms: Vec<_> = slice.iter().map(|(p, t)| format!("{p} {t}")).collect();
str_terms.join(", ") + " are incompatible"
}
}
}
fn format_external(
&self,
external: &External<Package, Ranges<SemanticVersion>, String>,
) -> String {
match external {
External::NotRoot(package, version) => {
format!("we are solving dependencies of {package} {version}")
}
External::NoVersions(package, set) => {
if set == &Ranges::full() {
format!("there is no available version for {package}")
} else {
format!("there is no version of {package} in {set}")
}
}
External::Custom(package, set, reason) => {
if set == &Ranges::full() {
format!("dependencies of {package} are unavailable because {reason}")
} else {
format!(
"dependencies of {package} at version {set} are unavailable because {reason}"
)
}
}
External::FromDependencyOf(package, package_set, dependency, dependency_set) => {
if package_set == &Ranges::full() && dependency_set == &Ranges::full() {
format!("{package} depends on {dependency}")
} else if package_set == &Ranges::full() {
format!("{package} depends on {dependency} {dependency_set}")
} else if dependency_set == &Ranges::full() {
if matches!(package, Package::Root) {
format!("{package} depends on {dependency}")
} else {
format!("{package} {package_set} depends on {dependency}")
}
} else if matches!(package, Package::Root) {
format!("{package} depends on {dependency} {dependency_set}")
} else {
format!("{package} {package_set} depends on {dependency} {dependency_set}")
}
}
}
}
fn explain_both_external(
&self,
external1: &External<Package, Ranges<SemanticVersion>, String>,
external2: &External<Package, Ranges<SemanticVersion>, String>,
current_terms: &Map<Package, Term<Ranges<SemanticVersion>>>,
) -> String {
format!(
"Because {} and {}, {}.",
self.format_external(external1),
self.format_external(external2),
self.format_terms(current_terms)
)
}
fn explain_both_ref(
&self,
ref_id1: usize,
derived1: &Derived<Package, Ranges<SemanticVersion>, String>,
ref_id2: usize,
derived2: &Derived<Package, Ranges<SemanticVersion>, String>,
current_terms: &Map<Package, Term<Ranges<SemanticVersion>>>,
) -> String {
format!(
"Because {} ({}) and {} ({}), {}.",
self.format_terms(&derived1.terms),
ref_id1,
self.format_terms(&derived2.terms),
ref_id2,
self.format_terms(current_terms)
)
}
fn explain_ref_and_external(
&self,
ref_id: usize,
derived: &Derived<Package, Ranges<SemanticVersion>, String>,
external: &External<Package, Ranges<SemanticVersion>, String>,
current_terms: &Map<Package, Term<Ranges<SemanticVersion>>>,
) -> String {
format!(
"Because {} ({}) and {}, {}.",
self.format_terms(&derived.terms),
ref_id,
self.format_external(external),
self.format_terms(current_terms)
)
}
fn and_explain_external(
&self,
external: &External<Package, Ranges<SemanticVersion>, String>,
current_terms: &Map<Package, Term<Ranges<SemanticVersion>>>,
) -> String {
format!(
"And because {}, {}.",
self.format_external(external),
self.format_terms(current_terms)
)
}
fn and_explain_ref(
&self,
ref_id: usize,
derived: &Derived<Package, Ranges<SemanticVersion>, String>,
current_terms: &Map<Package, Term<Ranges<SemanticVersion>>>,
) -> String {
format!(
"And because {} ({}), {}.",
self.format_terms(&derived.terms),
ref_id,
self.format_terms(current_terms)
)
}
fn and_explain_prior_and_external(
&self,
prior_external: &External<Package, Ranges<SemanticVersion>, String>,
external: &External<Package, Ranges<SemanticVersion>, String>,
current_terms: &Map<Package, Term<Ranges<SemanticVersion>>>,
) -> String {
format!(
"And because {} and {}, {}.",
self.format_external(prior_external),
self.format_external(external),
self.format_terms(current_terms)
)
}
}
fn main() {
let mut dependency_provider =
OfflineDependencyProvider::<Package, Ranges<SemanticVersion>>::new();
dependency_provider.add_dependencies(
Package::Root,
(0, 0, 0),
vec![(
Package::Package("foo".to_string()),
Ranges::singleton((1, 0, 0)),
)],
);
match resolve(&dependency_provider, Package::Root, (0, 0, 0)) {
Ok(sol) => println!("{sol:?}"),
Err(PubGrubError::NoSolution(derivation_tree)) => {
eprintln!("No solution.\n");
eprintln!("### Default report:");
eprintln!("```");
eprintln!("{}", DefaultStringReporter::report(&derivation_tree));
eprintln!("```\n");
eprintln!("### Report with custom formatter:");
eprintln!("```");
eprintln!(
"{}",
DefaultStringReporter::report_with_formatter(
&derivation_tree,
&CustomReportFormatter
)
);
eprintln!("```");
std::process::exit(1);
}
Err(err) => panic!("{err:?}"),
};
}