arch_pkg_text/desc/query/
memo.rs

1use super::QueryMut;
2use crate::desc::{
3    field::{FieldName, ParsedField, RawField},
4    misc::{ReuseAdvice, True},
5};
6
7/// [Query](QueryMut) with a cache.
8#[derive(Debug, Clone)]
9pub struct MemoQuerier<'a> {
10    text: &'a str,
11    cache: Cache<'a>,
12    last: Option<(&'a str, RawField<'a>)>,
13}
14
15impl<'a> MemoQuerier<'a> {
16    /// Query the `text` with a cache.
17    pub fn new(text: &'a str) -> Self {
18        MemoQuerier {
19            text,
20            cache: Cache::default(),
21            last: None,
22        }
23    }
24
25    /// Parse the next key-value pair, save it to cache and return it.
26    fn next_entry(&mut self) -> Option<(RawField<'a>, &'a str)> {
27        let mut lines = self.text.lines();
28
29        let (field_str, raw_field) = if let Some((field_str, raw_field)) = self.last {
30            lines.next()?;
31            (field_str, raw_field)
32        } else {
33            let field_str = lines.next()?.trim();
34            let raw_field = RawField::parse_raw(field_str).ok()?;
35            (field_str, raw_field)
36        };
37
38        let value_start_offset =
39            field_str.as_ptr() as usize + field_str.len() - self.text.as_ptr() as usize;
40        let next = lines.find_map(|line| -> Option<(&'a str, RawField<'a>)> {
41            let field_str = line.trim();
42            let raw_field = RawField::parse_raw(field_str).ok()?;
43            Some((field_str, raw_field))
44        });
45
46        let Some((next_field_str, next_raw_field)) = next else {
47            let value = self.text[value_start_offset..].trim_matches(['\n', '\r']);
48            self.text = "";
49            self.last = None;
50            return Some((raw_field, value));
51        };
52
53        let value_end_offset = next_field_str.as_ptr() as usize - self.text.as_ptr() as usize;
54        let value = self.text[value_start_offset..value_end_offset].trim_matches(['\n', '\r']);
55
56        // prepare for the next call
57        self.last = Some((next_field_str, next_raw_field));
58        self.text = &self.text[value_end_offset..];
59
60        Some((raw_field, value))
61    }
62
63    /// Private function for testing the internal cache.
64    #[doc(hidden)]
65    pub fn __has_cache(&self, field: FieldName) -> bool {
66        self.cache.get(&field).is_some()
67    }
68}
69
70impl<'a> QueryMut<'a> for MemoQuerier<'a> {
71    fn query_raw_text_mut(&mut self, field: ParsedField) -> Option<&'a str> {
72        if let Some(value) = self.cache.get(field.name()) {
73            return value;
74        }
75
76        while let Some((raw_field, value)) = self.next_entry() {
77            let Ok(parsed_field) = raw_field.to_parsed::<FieldName>() else {
78                continue;
79            };
80            let value = if value.is_empty() { None } else { Some(value) };
81            self.cache.add(&parsed_field, value);
82            if parsed_field == field {
83                return value;
84            }
85        }
86
87        None
88    }
89}
90
91macro_rules! def_cache {
92    ($(
93        $(#[$attrs:meta])*
94        $field:ident $(,)? $(;)?
95    )*) => {
96        #[derive(Debug, Clone, Copy)]
97        enum CacheErr {
98            OccupiedWithNone,
99            Unoccupied,
100        }
101
102        #[derive(Debug, Clone)]
103        #[allow(non_snake_case, reason = "We don't access the field names directly, keep it simple.")]
104        struct Cache<'a> {$(
105            $(#[$attrs])*
106            $field: Result<&'a str, CacheErr>, // Result<&str, CacheErr> uses less memory than Option<Option<&str>>
107        )*}
108
109        impl<'a> Cache<'a> {
110            fn get(&self, field: &FieldName) -> Option<Option<&'a str>> {
111                match field {$(
112                    FieldName::$field => match self.$field {
113                        Ok(value) => Some(Some(value)),
114                        Err(CacheErr::OccupiedWithNone) => Some(None),
115                        Err(CacheErr::Unoccupied) => None,
116                    },
117                )*}
118            }
119
120            fn add(&mut self, field: &FieldName, value: Option<&'a str>) {
121                match (field, value) {$(
122                    (FieldName::$field, Some(value)) => self.$field = Ok(value),
123                    (FieldName::$field, None) => self.$field = Err(CacheErr::OccupiedWithNone),
124                )*}
125            }
126        }
127
128        impl<'a> Default for Cache<'a> {
129            fn default() -> Self {
130                Cache {$(
131                    $field: Err(CacheErr::Unoccupied),
132                )*}
133            }
134        }
135
136        #[test]
137        fn test_cache_fields() {$({
138            use pretty_assertions::assert_eq;
139            let field = &FieldName::$field;
140            let mut cache = Cache::default();
141            assert_eq!(cache.get(field), None);
142            cache.add(field, None);
143            assert_eq!(cache.get(field), Some(None));
144            cache.add(field, Some("foo"));
145            assert_eq!(cache.get(field), Some(Some("foo")));
146        })*}
147    };
148}
149
150def_cache!(
151    FileName Name Base Version Description Groups
152    CompressedSize InstalledSize Md5Checksum Sha256Checksum
153    PgpSignature Url License Architecture BuildDate Packager
154    Dependencies CheckDependencies MakeDependencies OptionalDependencies
155    Provides Conflicts Replaces
156);
157
158impl ReuseAdvice for MemoQuerier<'_> {
159    /// [`MemoQuerier`] costs O(1) time to construct. Performing a lookup on it
160    /// costs O(n) the first time and O(1) after that.
161    ///
162    /// This struct is designed to be reused.
163    type ShouldReuse = True;
164}