1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use glob::Pattern;
use std::path::Path;
use crate::block::Comparator;
use crate::Token;
use crate::fileset::FileKind;
use crate::report::{
Confidence, ErrorKey, ErrorLoc, LogReportMetadata, LogReportPointers, Severity, err,
};
use crate::token::Loc;
/// Determines whether a given Report should be printed.
/// If a report is matched by both the blacklist and the whitelist, it will not be printed.
#[derive(Default, Debug)]
pub struct ReportFilter {
/// Whether to log errors in vanilla CK3 files
pub show_vanilla: bool,
/// Whether to log errors in other loaded mods
pub show_loaded_mods: bool,
/// A complex trigger that evaluates a report to assess whether it should be printed.
pub predicate: FilterRule,
}
impl ReportFilter {
/// Returns true iff the report should be printed.
/// A print will be rejected if the report matches at least one of the following conditions:
/// - Its Severity or Confidence level is too low.
/// - It's from vanilla or a loaded mod and the program is configured to ignore those locations.
/// - The filter has a trigger, and the report doesn't match it.
pub fn should_print_report(
&self,
report: &LogReportMetadata,
pointers: &LogReportPointers,
) -> bool {
if report.key == ErrorKey::Config {
// Any errors concerning the Config should be easy to fix and will fundamentally
// undermine the operation of the application. They must always be printed.
return true;
}
// If every single Loc in the chain is out of scope, the report is out of scope.
let out_of_scope = pointers.iter().map(|p| &p.loc).all(|loc| {
(loc.kind.counts_as_vanilla() && !self.show_vanilla)
|| (matches!(loc.kind, FileKind::LoadedMod(_)) && !self.show_loaded_mods)
});
if out_of_scope {
return false;
}
self.predicate.apply(report, pointers)
}
/// TODO: Check the filter rules to be more sure.
pub fn should_maybe_print(&self, key: ErrorKey, loc: Loc) -> bool {
if key == ErrorKey::Config {
// Any errors concerning the Config should be easy to fix and will fundamentally
// undermine the operation of the application. They must always be printed.
return true;
}
if (loc.kind.counts_as_vanilla() && !self.show_vanilla)
|| (matches!(loc.kind, FileKind::LoadedMod(_)) && !self.show_loaded_mods)
{
return false;
}
true
}
}
#[derive(Default, Debug)]
pub enum FilterRule {
/// Always true.
#[default]
Tautology,
/// Always false.
Contradiction,
/// Configured by the AND-key. The top-level rule is always a conjunction.
/// Reports must match all enclosed rules to match the conjunction.
Conjunction(Vec<FilterRule>),
/// Configured by the OR-key.
/// Reports must match at least one of the enclosed rules to match the disjunction.
Disjunction(Vec<FilterRule>),
/// Configured by the NOT-key.
/// Reports must not match the enclosed rule to match the negation.
Negation(Box<FilterRule>),
/// Reports must be within the given Severity range to match the rule.
/// The condition is built like `severity >= error` in the filter trigger.
Severity(Comparator, Severity),
/// Reports must be within the given Confidence range to match the rule.
/// The condition is built like `confidence > weak` in the filter trigger.
Confidence(Comparator, Confidence),
/// The report's `ErrorKey` must be the listed key for the report to match the rule.
Key(ErrorKey),
/// The report's pointers must contain the given file for the report to match the rule.
File(Pattern),
/// The report's msg must contain the given text for the report to match the rule.
Text(String),
}
impl FilterRule {
fn apply(&self, report: &LogReportMetadata, pointers: &LogReportPointers) -> bool {
match self {
FilterRule::Tautology => true,
FilterRule::Contradiction => false,
FilterRule::Conjunction(children) => {
children.iter().all(|child| child.apply(report, pointers))
}
FilterRule::Disjunction(children) => {
children.iter().any(|child| child.apply(report, pointers))
}
FilterRule::Negation(child) => !child.apply(report, pointers),
FilterRule::Severity(comparator, level) => match comparator {
Comparator::Equals(_) => report.severity == *level,
Comparator::NotEquals => report.severity != *level,
Comparator::GreaterThan => report.severity > *level,
Comparator::AtLeast => report.severity >= *level,
Comparator::LessThan => report.severity < *level,
Comparator::AtMost => report.severity <= *level,
},
FilterRule::Confidence(comparator, level) => match comparator {
Comparator::Equals(_) => report.confidence == *level,
Comparator::NotEquals => report.confidence != *level,
Comparator::GreaterThan => report.confidence > *level,
Comparator::AtLeast => report.confidence >= *level,
Comparator::LessThan => report.confidence < *level,
Comparator::AtMost => report.confidence <= *level,
},
FilterRule::Key(key) => report.key == *key,
FilterRule::File(pattern) => pointers.iter().any(|pointer| {
pattern.matches_path(pointer.loc.pathname())
|| pointer.loc.pathname().starts_with(Path::new(pattern.as_str()))
}),
FilterRule::Text(s) => {
report.msg.to_ascii_lowercase().contains(&s.to_ascii_lowercase())
}
}
}
pub fn file_from_token(token: &Token) -> Option<FilterRule> {
let pattern = Pattern::new(token.as_str());
match pattern {
Ok(p) => Some(FilterRule::File(p)),
Err(e) => {
err(ErrorKey::Config)
.msg("Expected valid file path or glob pattern")
.loc_msg(
{
let mut loc = token.into_loc();
loc.column += u32::try_from(e.pos).unwrap_or(0);
loc
},
e.msg,
)
.push();
None
}
}
}
}