Skip to main content

arch_pkg_text/desc/query/
memo.rs

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