Skip to content

Commit 4d78a64

Browse files
authored
feat: add report formatter trait for custom reporter output (#158)
* Add report formatter concept for customization of report output * Add default implementation * Use `format_external` in `format_terms` * Add previous example * Use new formatter * Add docs to trait method * Clippy * Remove outdated comment in example * Use generic
1 parent 2b2d8d4 commit 4d78a64

File tree

3 files changed

+325
-72
lines changed

3 files changed

+325
-72
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
3+
use pubgrub::error::PubGrubError;
4+
use pubgrub::range::Range;
5+
use pubgrub::report::Reporter;
6+
use pubgrub::solver::{resolve, OfflineDependencyProvider};
7+
use pubgrub::version::SemanticVersion;
8+
9+
use pubgrub::report::{DefaultStringReporter, External, ReportFormatter};
10+
use pubgrub::term::Term;
11+
use pubgrub::type_aliases::Map;
12+
use std::fmt::{self, Display};
13+
14+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
15+
pub enum Package {
16+
Root,
17+
Package(String),
18+
}
19+
20+
impl Display for Package {
21+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22+
match self {
23+
Package::Root => write!(f, "root"),
24+
Package::Package(name) => write!(f, "{}", name),
25+
}
26+
}
27+
}
28+
29+
#[derive(Debug, Default)]
30+
struct CustomReportFormatter;
31+
32+
impl ReportFormatter<Package, Range<SemanticVersion>> for CustomReportFormatter {
33+
type Output = String;
34+
35+
fn format_terms(&self, terms: &Map<Package, Term<Range<SemanticVersion>>>) -> String {
36+
let terms_vec: Vec<_> = terms.iter().collect();
37+
match terms_vec.as_slice() {
38+
[] => "version solving failed".into(),
39+
[(package @ Package::Root, Term::Positive(_))] => {
40+
format!("{package} is forbidden")
41+
}
42+
[(package @ Package::Root, Term::Negative(_))] => {
43+
format!("{package} is mandatory")
44+
}
45+
[(package @ Package::Package(_), Term::Positive(range))] => {
46+
format!("{package} {range} is forbidden")
47+
}
48+
[(package @ Package::Package(_), Term::Negative(range))] => {
49+
format!("{package} {range} is mandatory")
50+
}
51+
[(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => {
52+
External::FromDependencyOf(p1, r1.clone(), p2, r2.clone()).to_string()
53+
}
54+
[(p1, Term::Negative(r1)), (p2, Term::Positive(r2))] => {
55+
External::FromDependencyOf(p2, r2.clone(), p1, r1.clone()).to_string()
56+
}
57+
slice => {
58+
let str_terms: Vec<_> = slice.iter().map(|(p, t)| format!("{p} {t}")).collect();
59+
str_terms.join(", ") + " are incompatible"
60+
}
61+
}
62+
}
63+
64+
fn format_external(&self, external: &External<Package, Range<SemanticVersion>>) -> String {
65+
match external {
66+
External::NotRoot(package, version) => {
67+
format!("we are solving dependencies of {package} {version}")
68+
}
69+
External::NoVersions(package, set) => {
70+
if set == &Range::full() {
71+
format!("there is no available version for {package}")
72+
} else {
73+
format!("there is no version of {package} in {set}")
74+
}
75+
}
76+
External::UnavailableDependencies(package, set) => {
77+
if set == &Range::full() {
78+
format!("dependencies of {package} are unavailable")
79+
} else {
80+
format!("dependencies of {package} at version {set} are unavailable")
81+
}
82+
}
83+
External::FromDependencyOf(package, package_set, dependency, dependency_set) => {
84+
if package_set == &Range::full() && dependency_set == &Range::full() {
85+
format!("{package} depends on {dependency}")
86+
} else if package_set == &Range::full() {
87+
format!("{package} depends on {dependency} {dependency_set}")
88+
} else if dependency_set == &Range::full() {
89+
if matches!(package, Package::Root) {
90+
// Exclude the dummy version for root packages
91+
format!("{package} depends on {dependency}")
92+
} else {
93+
format!("{package} {package_set} depends on {dependency}")
94+
}
95+
} else {
96+
if matches!(package, Package::Root) {
97+
// Exclude the dummy version for root packages
98+
format!("{package} depends on {dependency} {dependency_set}")
99+
} else {
100+
format!("{package} {package_set} depends on {dependency} {dependency_set}")
101+
}
102+
}
103+
}
104+
}
105+
}
106+
}
107+
108+
fn main() {
109+
let mut dependency_provider =
110+
OfflineDependencyProvider::<Package, Range<SemanticVersion>>::new();
111+
// Define the root package with a dependency on a package we do not provide
112+
dependency_provider.add_dependencies(
113+
Package::Root,
114+
(0, 0, 0),
115+
vec![(
116+
Package::Package("foo".to_string()),
117+
Range::singleton((1, 0, 0)),
118+
)],
119+
);
120+
121+
// Run the algorithm
122+
match resolve(&dependency_provider, Package::Root, (0, 0, 0)) {
123+
Ok(sol) => println!("{:?}", sol),
124+
Err(PubGrubError::NoSolution(derivation_tree)) => {
125+
eprintln!("No solution.\n");
126+
127+
eprintln!("### Default report:");
128+
eprintln!("```");
129+
eprintln!("{}", DefaultStringReporter::report(&derivation_tree));
130+
eprintln!("```\n");
131+
132+
eprintln!("### Report with custom formatter:");
133+
eprintln!("```");
134+
eprintln!(
135+
"{}",
136+
DefaultStringReporter::report_with_formatter(
137+
&derivation_tree,
138+
&CustomReportFormatter
139+
)
140+
);
141+
eprintln!("```");
142+
std::process::exit(1);
143+
}
144+
Err(err) => panic!("{:?}", err),
145+
};
146+
}

src/internal/incompatibility.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use std::fmt;
99
use crate::internal::arena::{Arena, Id};
1010
use crate::internal::small_map::SmallMap;
1111
use crate::package::Package;
12-
use crate::report::{DefaultStringReporter, DerivationTree, Derived, External};
12+
use crate::report::{
13+
DefaultStringReportFormatter, DerivationTree, Derived, External, ReportFormatter,
14+
};
1315
use crate::term::{self, Term};
1416
use crate::version_set::VersionSet;
1517

@@ -251,7 +253,7 @@ impl<P: Package, VS: VersionSet> fmt::Display for Incompatibility<P, VS> {
251253
write!(
252254
f,
253255
"{}",
254-
DefaultStringReporter::string_terms(&self.package_terms.as_map())
256+
DefaultStringReportFormatter.format_terms(&self.package_terms.as_map())
255257
)
256258
}
257259
}

0 commit comments

Comments
 (0)