1use {
2 crate::{
3 col::*,
4 },
5 lfs_core::*,
6 std::{
7 fmt,
8 str::FromStr,
9 },
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ColOperator {
14 Lower,
15 LowerOrEqual,
16 Like,
17 Equal,
18 NotEqual,
19 GreaterOrEqual,
20 Greater,
21}
22
23impl ColOperator {
24 pub fn eval<T: PartialOrd+PartialEq>(self, a: T, b: T) -> bool {
25 match self {
26 Self::Lower => a < b,
27 Self::LowerOrEqual => a <= b,
28 Self::Equal | Self::Like => a == b,
29 Self::NotEqual => a != b,
30 Self::GreaterOrEqual => a >= b,
31 Self::Greater => a > b,
32 }
33 }
34 pub fn eval_option<T: PartialOrd+PartialEq>(self, a: Option<T>, b: T) -> bool {
35 match a {
36 Some(a) => self.eval(a, b),
37 None => false,
38 }
39 }
40 pub fn eval_str(self, a: &str, b: &str) -> bool {
41 match self {
42 Self::Like => a.to_lowercase().contains(&b.to_lowercase()),
43 _ => self.eval(a, b),
44 }
45 }
46 pub fn eval_option_str(self, a: Option<&str>, b: &str) -> bool {
47 match (a, self) {
48 (Some(a), Self::Like) => a.to_lowercase().contains(&b.to_lowercase()),
49 _ => self.eval_option(a, b),
50 }
51 }
52}
53
54#[derive(Debug, Clone, PartialEq)]
57pub struct ColExpr {
58 col: Col,
59 operator: ColOperator,
60 value: String,
61}
62
63impl ColExpr {
64 #[cfg(test)]
65 pub fn new<S: Into<String>>(col: Col, operator: ColOperator, value: S) -> Self {
66 Self {
67 col,
68 operator,
69 value: value.into(),
70 }
71 }
72 pub fn eval(&self, mount: &Mount) -> Result<bool, EvalExprError> {
73 Ok(match self.col {
74 Col::Id => self.operator.eval_option(
75 mount.info.id,
76 self.value.parse::<MountId>()
77 .map_err(|_| EvalExprError::NotAnId(self.value.to_string()))?,
78 ),
79 Col::Dev => self.operator.eval(
80 mount.info.dev,
81 self.value.parse::<DeviceId>()
82 .map_err(|_| EvalExprError::NotADeviceId(self.value.to_string()))?,
83 ),
84 Col::Filesystem => self.operator.eval_str(
85 &mount.info.fs,
86 &self.value,
87 ),
88 Col::Label => self.operator.eval_option_str(
89 mount.fs_label.as_deref(),
90 &self.value,
91 ),
92 Col::Type => self.operator.eval_str(
93 &mount.info.fs_type,
94 &self.value,
95 ),
96 Col::Remote => self.operator.eval(
97 mount.info.is_remote(),
98 parse_bool(&self.value)?,
99 ),
100 Col::Disk => self.operator.eval_option_str(
101 mount.disk.as_ref().map(|d| d.disk_type()),
102 &self.value,
103 ),
104 Col::Used => self.operator.eval_option(
105 mount.stats().as_ref().map(|s| s.used()),
106 parse_integer(&self.value)?,
107 ),
108 Col::Use | Col::UsePercent => self.operator.eval_option(
109 mount.stats().as_ref().map(|s| s.use_share()),
110 parse_float(&self.value)?,
111 ),
112 Col::Free | Col::FreePercent => self.operator.eval_option(
113 mount.stats().as_ref().map(|s| s.available()),
114 parse_integer(&self.value)?,
115 ),
116 Col::Size => self.operator.eval_option(
117 mount.stats().as_ref().map(|s| s.size()),
118 parse_integer(&self.value)?,
119 ),
120 Col::InodesUsed => self.operator.eval_option(
121 mount.inodes().as_ref().map(|i| i.used()),
122 parse_integer(&self.value)?,
123 ),
124 Col::InodesUse | Col::InodesUsePercent => self.operator.eval_option(
125 mount.inodes().as_ref().map(|i| i.use_share()),
126 parse_float(&self.value)?,
127 ),
128 Col::InodesFree => self.operator.eval_option(
129 mount.inodes().as_ref().map(|i| i.favail),
130 parse_integer(&self.value)?,
131 ),
132 Col::InodesCount => self.operator.eval_option(
133 mount.inodes().as_ref().map(|i| i.files),
134 parse_integer(&self.value)?,
135 ),
136 Col::MountPoint => self.operator.eval_str(
137 &mount.info.mount_point.to_string_lossy(),
138 &self.value,
139 ),
140 Col::Uuid => self.operator.eval_option_str(
141 mount.uuid.as_deref(),
142 &self.value,
143 ),
144 Col::PartUuid => self.operator.eval_option_str(
145 mount.part_uuid.as_deref(),
146 &self.value,
147 ),
148 })
149 }
150}
151
152#[derive(Debug)]
153pub struct ParseExprError {
154 pub raw: String,
156 pub message: String,
158}
159impl ParseExprError {
160 pub fn new<R: Into<String>, M: Into<String>>(raw: R, message: M) -> Self {
161 Self {
162 raw: raw.into(),
163 message: message.into(),
164 }
165 }
166}
167impl fmt::Display for ParseExprError {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 write!(
170 f,
171 "{:?} can't be parsed as an expression: {}",
172 self.raw,
173 self.message
174 )
175 }
176}
177impl std::error::Error for ParseExprError {}
178
179impl FromStr for ColExpr {
180 type Err = ParseExprError;
181 fn from_str(input: &str) -> Result<Self, ParseExprError> {
182 let mut chars_indices = input.char_indices();
183 let mut op_idx = 0;
184 for (idx, c) in &mut chars_indices {
185 if c == '<' || c == '>' || c == '=' {
186 op_idx = idx;
187 break;
188 }
189 }
190 if op_idx == 0 {
191 return Err(ParseExprError::new(input, "Invalid expression; expected <column><operator><value>"));
192 }
193 let mut val_idx = op_idx + 1;
194 for (idx, c) in &mut chars_indices {
195 if c != '<' && c != '>' && c != '=' {
196 val_idx = idx;
197 break;
198 }
199 }
200 if val_idx == input.len() {
201 return Err(ParseExprError::new(input, "no value"));
202 }
203 let col = &input[..op_idx];
204 let col = col.parse()
205 .map_err(|e: ParseColError| ParseExprError::new(input, e.to_string()))?;
206 let operator = match &input[op_idx..val_idx] {
207 "<" => ColOperator::Lower,
208 "<=" => ColOperator::LowerOrEqual,
209 "=" => ColOperator::Like,
210 "==" => ColOperator::Equal,
211 "<>" => ColOperator::NotEqual,
212 ">=" => ColOperator::GreaterOrEqual,
213 ">" => ColOperator::Greater,
214 op => {
215 return Err(ParseExprError::new(
216 input,
217 format!("unknown operator: {:?}", op),
218 ));
219 }
220 };
221 let value = &input[val_idx..];
222 let value = value.into();
223 Ok(Self { col, operator, value })
224 }
225}
226
227#[test]
228fn test_col_filter_parsing() {
229 assert_eq!(
230 "remote=false".parse::<ColExpr>().unwrap(),
231 ColExpr::new(Col::Remote, ColOperator::Like, "false"),
232 );
233 assert_eq!(
234 "size<32G".parse::<ColExpr>().unwrap(),
235 ColExpr::new(Col::Size, ColOperator::Lower, "32G"),
236 );
237}
238
239#[derive(Debug, PartialEq)]
240#[allow(clippy::enum_variant_names)]
241pub enum EvalExprError {
242 NotANumber(String),
243 NotAnId(String),
244 NotADeviceId(String),
245 NotABool(String),
246}
247impl EvalExprError {
248}
249impl fmt::Display for EvalExprError {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 match self {
252 Self::NotANumber(s) => {
253 write!(f, "{:?} can't be evaluated as a number", &s)
254 }
255 Self::NotAnId(s) => {
256 write!(f, "{:?} can't be evaluated as an id", &s)
257 }
258 Self::NotADeviceId(s) => {
259 write!(f, "{:?} can't be evaluated as a device id", &s)
260 }
261 Self::NotABool(s) => {
262 write!(f, "{:?} can't be evaluated as a boolean", &s)
263 }
264 }
265 }
266}
267impl std::error::Error for EvalExprError {}
268
269fn parse_bool(input: &str) -> Result<bool, EvalExprError> {
270 let s = input.to_lowercase();
271 match s.as_ref() {
272 "x" | "t" | "true" | "1" | "y" | "yes" => Ok(true),
273 "f" | "false" | "0" | "n" | "no" => Ok(false),
274 _ => Err(EvalExprError::NotABool(input.to_string())),
275 }
276}
277
278fn parse_integer(input: &str) -> Result<u64, EvalExprError> {
280 let s = input.to_lowercase();
281 let s = s.trim_end_matches('b');
282 let (s, binary) = match s.strip_suffix('i') {
283 Some(s) => (s, true),
284 None => (s, false),
285 };
286 let cut = s.find(|c: char| !(c.is_ascii_digit() || c=='.'));
287 let (digits, factor): (&str, u64) = match cut {
288 Some(idx) => (
289 &s[..idx],
290 match (&s[idx..], binary) {
291 ("k", false) => 1000,
292 ("k", true) => 1024,
293 ("m", false) => 1000*1000,
294 ("m", true) => 1024*1024,
295 ("g", false) => 1000*1000*1000,
296 ("g", true) => 1024*1024*1024,
297 ("t", false) => 1000*1000*1000*1000,
298 ("t", true) => 1024*1024*1024*1024,
299 _ => {
300 return Err(EvalExprError::NotANumber(input.to_string()));
302 }
303 }
304 ),
305 None => (s, 1),
306 };
307 match digits.parse::<f64>() {
308 Ok(n) => Ok((n * factor as f64).ceil() as u64),
309 _ => Err(EvalExprError::NotANumber(input.to_string())),
310 }
311}
312
313#[test]
314fn test_parse_integer(){
315 assert_eq!(parse_integer("33"), Ok(33));
316 assert_eq!(parse_integer("55G"), Ok(55_000_000_000));
317 assert_eq!(parse_integer("1.23kiB"), Ok(1260));
318}
319
320fn parse_float(input: &str) -> Result<f64, EvalExprError> {
322 let s = input.to_lowercase();
323 let (s, percent) = match s.strip_suffix('%') {
324 Some(s) => (s, true),
325 None => (s.as_str(), false),
326 };
327 let mut n = s.parse::<f64>()
328 .map_err(|_| EvalExprError::NotANumber(input.to_string()))?;
329 if percent {
330 n /= 100.0;
331 }
332 Ok(n)
333}
334
335#[test]
336fn test_parse_float(){
337 assert_eq!(parse_float("50%").unwrap().to_string(), "0.5".to_string());
338}
339