1use crate::{Error, Result};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Filter {
14 BlobNone,
16 BlobLimit(u64),
18 TreeDepth(usize),
21}
22
23impl Filter {
24 pub fn parse(spec: &str) -> Result<Self> {
30 let unsupported = |reason| Error::UnsupportedFilter {
31 spec: spec.to_owned(),
32 reason,
33 };
34 if spec == "blob:none" {
35 return Ok(Filter::BlobNone);
36 }
37 if let Some(limit) = spec.strip_prefix("blob:limit=") {
38 return parse_size(limit).map(Filter::BlobLimit).ok_or_else(|| {
39 unsupported("the size is not a number with an optional k/m/g suffix")
40 });
41 }
42 if let Some(depth) = spec.strip_prefix("tree:") {
43 return depth
44 .parse::<usize>()
45 .map(Filter::TreeDepth)
46 .map_err(|_| unsupported("the depth is not a number"));
47 }
48 if spec.starts_with("combine:") {
49 return Err(unsupported("gunnar implements one filter at a time"));
50 }
51 Err(unsupported(
52 "gunnar implements blob:none, blob:limit=<n> and tree:<depth>",
53 ))
54 }
55
56 pub(crate) fn keeps_tree(&self, depth: usize) -> bool {
58 match self {
59 Filter::BlobNone | Filter::BlobLimit(_) => true,
60 Filter::TreeDepth(max) => depth <= *max,
61 }
62 }
63
64 pub(crate) fn blob_limit(&self) -> Option<u64> {
66 match self {
67 Filter::BlobLimit(limit) => Some(*limit),
68 _ => None,
69 }
70 }
71
72 pub(crate) fn keeps_any_blob(&self) -> bool {
75 match self {
76 Filter::BlobNone => false,
77 Filter::BlobLimit(_) => true,
78 Filter::TreeDepth(max) => *max > 0,
81 }
82 }
83}
84
85fn parse_size(text: &str) -> Option<u64> {
86 let (digits, scale) = match text.as_bytes().last()? {
87 b'k' | b'K' => (&text[..text.len() - 1], 1024),
88 b'm' | b'M' => (&text[..text.len() - 1], 1024 * 1024),
89 b'g' | b'G' => (&text[..text.len() - 1], 1024 * 1024 * 1024),
90 _ => (text, 1),
91 };
92 digits.parse::<u64>().ok()?.checked_mul(scale)
93}