1#![allow(clippy::pub_use)]
8
9use std::cmp::Ordering;
10use std::collections::HashMap;
11use std::str::FromStr;
12
13use crate::defs::{Mode, ParseError};
14use crate::version::Version;
15
16pub mod parser;
17
18pub use crate::defs::{CalcResult, Calculable};
19
20#[derive(Debug)]
22enum BoolOpKind {
23 LessThan,
25 LessThanOrEqual,
27 Equal,
29 GreaterThanOrEqual,
31 GreaterThan,
33}
34
35impl BoolOpKind {
36 const LT: &'static str = "<";
38 const LE: &'static str = "<=";
40 const EQ: &'static str = "=";
42 const GT: &'static str = ">";
44 const GE: &'static str = ">=";
46
47 const LT_S: &'static str = "lt";
49 const LE_S: &'static str = "le";
51 const EQ_S: &'static str = "eq";
53 const GE_S: &'static str = "ge";
55 const GT_S: &'static str = "gt";
57}
58
59impl FromStr for BoolOpKind {
60 type Err = ParseError;
61
62 fn from_str(value: &str) -> Result<Self, Self::Err> {
63 match value {
64 Self::LT | Self::LT_S => Ok(Self::LessThan),
65 Self::LE | Self::LE_S => Ok(Self::LessThanOrEqual),
66 Self::EQ | Self::EQ_S => Ok(Self::Equal),
67 Self::GE | Self::GE_S => Ok(Self::GreaterThanOrEqual),
68 Self::GT | Self::GT_S => Ok(Self::GreaterThan),
69 other => Err(ParseError::InvalidComparisonOperator(other.to_owned())),
70 }
71 }
72}
73
74#[derive(Debug)]
76struct BoolOp {
77 op: BoolOpKind,
79 left: Box<dyn Calculable + 'static>,
81 right: Box<dyn Calculable + 'static>,
83}
84
85impl BoolOp {
86 fn new(op: BoolOpKind, left: Box<dyn Calculable>, right: Box<dyn Calculable>) -> Self {
88 Self { op, left, right }
89 }
90}
91
92impl Calculable for BoolOp {
93 fn get_value(&self, features: &HashMap<String, Version>) -> Result<CalcResult, ParseError> {
94 let left = self.left.get_value(features)?;
95 let right = self.right.get_value(features)?;
96 if let CalcResult::Version(ver_left) = left {
97 if let CalcResult::Version(ver_right) = right {
98 let ncomp = ver_left.cmp(&ver_right);
99 match self.op {
100 BoolOpKind::LessThan => Ok(CalcResult::Bool(ncomp == Ordering::Less)),
101 BoolOpKind::LessThanOrEqual => Ok(CalcResult::Bool(ncomp != Ordering::Greater)),
102 BoolOpKind::Equal => Ok(CalcResult::Bool(ncomp == Ordering::Equal)),
103 BoolOpKind::GreaterThanOrEqual => Ok(CalcResult::Bool(ncomp != Ordering::Less)),
104 BoolOpKind::GreaterThan => Ok(CalcResult::Bool(ncomp == Ordering::Greater)),
105 }
106 } else {
107 Err(ParseError::CannotCompare(
108 format!("{ver_left:?}"),
109 format!("{right:?}"),
110 ))
111 }
112 } else {
113 Err(ParseError::Uncomparable(
114 format!("{left:?}"),
115 format!("{right:?}"),
116 ))
117 }
118 }
119}
120
121#[derive(Debug)]
123struct FeatureOp {
124 name: String,
126}
127
128impl FeatureOp {
129 fn new(name: &str) -> Self {
131 Self {
132 name: name.to_owned(),
133 }
134 }
135}
136
137impl Calculable for FeatureOp {
138 fn get_value(&self, features: &HashMap<String, Version>) -> Result<CalcResult, ParseError> {
139 Ok(features
140 .get(&self.name)
141 .map_or(CalcResult::Null, |value| CalcResult::Version(value.clone())))
142 }
143}
144
145#[derive(Debug)]
147struct VersionOp {
148 value: Version,
150}
151
152impl VersionOp {
153 const fn from_version(version: Version) -> Self {
155 Self { value: version }
156 }
157}
158
159impl Calculable for VersionOp {
160 fn get_value(&self, _features: &HashMap<String, Version>) -> Result<CalcResult, ParseError> {
161 Ok(CalcResult::Version(self.value.clone()))
162 }
163}
164
165#[inline]
174pub fn parse(expr: &str) -> Result<Mode, ParseError> {
175 parser::parse_expr(expr)
176}
177
178#[cfg(test)]
179mod tests {
180 #![allow(clippy::panic)]
181 #![allow(clippy::panic_in_result_fn)]
182 #![allow(clippy::unwrap_used)]
183 #![allow(clippy::use_debug)]
184 #![allow(clippy::wildcard_enum_match_arm)]
185
186 use std::collections::HashMap;
187 use std::error::Error;
188
189 use crate::defs::{CalcResult, Mode};
190
191 #[test]
192 fn test_parse_mode_simple_sign_no_space() -> Result<(), Box<dyn Error>> {
193 let mode = super::parse("hello<3.1")?;
194 let res = match mode {
195 Mode::Simple(res) => res,
196 other => panic!("{other:?}"),
197 };
198 match res.get_value(&HashMap::from([("hello".to_owned(), "2".parse()?)]))? {
199 CalcResult::Bool(true) => (),
200 other => panic!("{other:?}"),
201 };
202 match res.get_value(&HashMap::from([("hello".to_owned(), "4".parse()?)]))? {
203 CalcResult::Bool(false) => (),
204 other => panic!("{other:?}"),
205 };
206 res.get_value(&HashMap::new()).unwrap_err();
207 Ok(())
208 }
209
210 #[test]
211 fn test_parse_mode_simple_sign_space() -> Result<(), Box<dyn Error>> {
212 let mode = super::parse("hello < 3.1")?;
213 let res = match mode {
214 Mode::Simple(res) => res,
215 other => panic!("{other:?}"),
216 };
217 match res.get_value(&HashMap::from([("hello".to_owned(), "2".parse()?)]))? {
218 CalcResult::Bool(true) => (),
219 other => panic!("{other:?}"),
220 };
221 match res.get_value(&HashMap::from([("hello".to_owned(), "4".parse()?)]))? {
222 CalcResult::Bool(false) => (),
223 other => panic!("{other:?}"),
224 };
225 res.get_value(&HashMap::new()).unwrap_err();
226 Ok(())
227 }
228
229 #[test]
230 fn test_parse_mode_simple_word() -> Result<(), Box<dyn Error>> {
231 let mode = super::parse("hello lt 3.1")?;
232 let res = match mode {
233 Mode::Simple(res) => res,
234 other => panic!("{other:?}"),
235 };
236 match res.get_value(&HashMap::from([("hello".to_owned(), "2".parse()?)]))? {
237 CalcResult::Bool(true) => (),
238 other => panic!("{other:?}"),
239 };
240 match res.get_value(&HashMap::from([("hello".to_owned(), "4".parse()?)]))? {
241 CalcResult::Bool(false) => (),
242 other => panic!("{other:?}"),
243 };
244 res.get_value(&HashMap::new()).unwrap_err();
245 Ok(())
246 }
247
248 #[test]
249 fn test_parse_mode_single() -> Result<(), Box<dyn Error>> {
250 let mode = super::parse("hello")?;
251 let res = match mode {
252 Mode::Single(res) => res,
253 other => panic!("{other:?}"),
254 };
255 match res.get_value(&HashMap::from([("hello".to_owned(), "2".parse()?)]))? {
256 CalcResult::Version(ver) => assert_eq!(ver.as_ref(), "2"),
257 other => panic!("{other:?}"),
258 };
259 match res.get_value(&HashMap::new())? {
260 CalcResult::Null => (),
261 other => panic!("{other:?}"),
262 };
263 Ok(())
264 }
265}