1use std::borrow::Cow;
2use std::fmt::{Debug, Display};
3use std::sync::Arc;
4use regex::Regex;
5
6use crate::YamlShape;
7use crate::categories::structs::Category;
8
9
10#[derive(Debug, Clone, PartialEq)]
11pub struct YamlCorrectness {
12 flag: FlagCorrectness,
13 categories: CategoryCorrectness,
14 points: PointCorrectness,
15}
16
17#[derive(Debug, Clone)]
18pub enum FlagCorrectness {
19 None,
20 CompName(Cow<'static, str>),
21 Regex(Regex),
22}
23impl PartialEq for FlagCorrectness {
24 fn eq(&self, other: &Self) -> bool {
25 use FlagCorrectness::{CompName, None, Regex};
26 match (self, other) {
27 (None, None) => true,
28 (CompName(n1), CompName(n2)) => n1 == n2,
29 (Regex(r1), Regex(r2)) => r1.as_str() == r2.as_str(),
30 _ => false,
31 }
32 }
33}
34
35#[derive(Debug, Clone, PartialEq)]
36pub enum CategoryCorrectness {
37 AnyStr,
38 List {
39 names: Cow<'static, [Cow<'static, str>]>,
40 requires_case_match: bool,
41 }
42}
43
44pub trait CanBePred: Fn(u64) -> bool + Debug + Send + Sync {}
45
46#[derive(Clone)]
47pub enum PointCorrectness {
48 None,
49 Multiple(u64),
50 Pred(Arc<dyn CanBePred>),
51}
52
53impl PartialEq for PointCorrectness {
54 fn eq(&self, other: &Self) -> bool {
55 use PointCorrectness::{Multiple, None, Pred};
56 match (self, other) {
57 (None, None) => true,
58 (Multiple(n1), Multiple(n2)) => n1 == n2,
59 (Pred(p1), Pred(p2)) => std::ptr::eq(
60 Arc::as_ptr(p1) as *mut (),
61 Arc::as_ptr(p2) as *mut (),
62 ),
63 _ => false,
64 }
65 }
66}
67impl Debug for PointCorrectness {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 match self {
70 Self::None => write!(f, "None"),
71 Self::Multiple(n) => write!(f, "Multiple< of {n} >"),
72 Self::Pred(_) => write!(f, "Predicate< unknown >"),
73 }
74 }
75}
76
77impl YamlCorrectness {
78 pub fn check_flag(&self, flag: &str) -> bool { self.flag.check(flag) }
79 pub fn check_cats<'a>(&self, categories: impl Iterator<Item = &'a str>) -> bool { self.categories.check(categories) }
80 pub fn check_pnts(&self, points: u64) -> bool { self.points.check(points) }
81
82 pub fn verify<'a>(&self, shape: &'a YamlShape) -> Result<&'a YamlShape, YamlCorrectness> {
83 let flag_ok = self.check_flag(shape.flag.as_str());
84 let cats_ok = self.check_cats(shape.categories.iter().map(Category::as_str));
85 let pnts_ok = self.check_pnts(shape.points);
86 if flag_ok && cats_ok && pnts_ok {
87 Ok(shape)
88 } else {
89 Err(Self {
90 flag: if flag_ok { FlagCorrectness::None } else { self.flag.clone() },
91 categories: if cats_ok { CategoryCorrectness::AnyStr } else { self.categories.clone() },
92 points: if pnts_ok { PointCorrectness::None } else { self.points.clone() },
93 })
94 }
95 }
96}
97
98impl FlagCorrectness {
99 pub fn check(&self, flag: &str) -> bool {
100 match self {
101 Self::None => true,
102 Self::CompName(name) => {
103 if let Some(flag) = flag.strip_prefix(name.as_ref()) {
104 if let Some(flag) = flag.strip_prefix('{') {
105 flag.ends_with('}')
106 } else { false }
107 } else { false }
108 },
109 Self::Regex(regex) => regex.is_match(flag),
110 }
111 }
112}
113
114impl CategoryCorrectness {
115 pub fn check<'a>(&self, mut categories: impl Iterator<Item = &'a str>) -> bool {
116 match self {
117 Self::AnyStr => true,
118 Self::List { names, requires_case_match } => {
119 let case_match_predicate = |a: &str, b: &str| if *requires_case_match {
120 a == b
121 } else {
122 a.to_lowercase() == b.to_lowercase()
123 };
124
125 let pred = |check| names
126 .iter()
127 .any(|valid| case_match_predicate(valid, check));
128
129 categories.all(pred)
130 },
131 }
132 }
133}
134
135impl PointCorrectness {
136 pub fn check(&self, num: u64) -> bool {
137 match self {
138 Self::None => true,
139 Self::Multiple(factor) => num % factor == 0,
140 Self::Pred(pred) => pred(num),
141 }
142 }
143}
144
145impl Default for YamlCorrectness {
146 fn default() -> Self {
147 Self {
148 flag: FlagCorrectness::None,
149 categories: CategoryCorrectness::AnyStr,
150 points: PointCorrectness::None,
151 }
152 }
153}
154
155
156impl YamlCorrectness {
157 pub fn with_flag(self, flag: FlagCorrectness) -> Self { Self { flag, ..self } }
158 pub fn with_cats(self, categories: CategoryCorrectness) -> Self { Self { categories, ..self } }
159 pub fn with_pnts(self, points: PointCorrectness) -> Self { Self { points, ..self } }
160}
161
162
163impl YamlCorrectness {
164 pub fn show_issue(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 writeln!(f)?;
166 if !matches!(self.flag, FlagCorrectness::None) {
167 write!(f, " ")?;
168 self.flag.show_issue(f)?;
169 }
170 if !matches!(self.categories, CategoryCorrectness::AnyStr) {
171 write!(f, " ")?;
172 self.categories.show_issue(f)?;
173 }
174 if !matches!(self.points, PointCorrectness::None) {
175 write!(f, " ")?;
176 self.points.show_issue(f)?;
177 }
178
179 Ok(())
180 }
181}
182impl FlagCorrectness {
183 pub fn show_issue(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 use FlagCorrectness::*;
185 match self {
186 None => Ok(()),
187 CompName(name) => writeln!(f, "The flag should be in the format of: `{name}{{<contents>}}`"),
188 Regex(regex) => writeln!(f, "The flag must match the regex: `{}`", regex.as_str()),
189 }
190 }
191}
192impl CategoryCorrectness {
193 pub fn show_issue(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194 use CategoryCorrectness::*;
195 match self {
196 AnyStr => Ok(()),
197 List {
198 names,
199 requires_case_match
200 } => {
201 write!(f, "The categories should be one of {names:?} (case ")?;
202 if *requires_case_match {
203 writeln!(f, "SENSITIVE)")
204 } else {
205 writeln!(f, "insensitive)")
206 }
207 },
208 }
209 }
210}
211impl PointCorrectness {
212 pub fn show_issue(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 use PointCorrectness::*;
214 match self {
215 None => Ok(()),
216 Multiple(n) => writeln!(f, "The point value MUST be multiple of {n}"),
217 Pred(pred) => writeln!(f, "The flag must pass a predicate: `{pred:?}`"),
218 }
219 }
220}
221
222
223impl Display for YamlCorrectness {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 self.show_issue(f)
226 }
227}