arch_pkg_text/srcinfo/query/
memo.rs

1mod cache;
2
3use super::{
4    ChecksumType, ChecksumValue, ChecksumsMut, QueryChecksumItem, QueryMut, QueryRawTextItem,
5    Section,
6    utils::{parse_line, trimmed_line_is_blank},
7};
8use crate::{
9    parse::{ParseWithIssues, PartialParse, PartialParseResult},
10    srcinfo::{
11        field::FieldName,
12        misc::{ReuseAdvice, True},
13    },
14    value::{Architecture, Name},
15};
16use cache::Cache;
17use core::convert::Infallible;
18use core::str::Lines;
19use pipe_trait::Pipe;
20
21/// [Query](QueryMut) with a cache.
22#[derive(Debug, Clone)]
23pub struct MemoQuerier<'a> {
24    remaining_lines: Lines<'a>,
25    current_section: Section<'a>,
26    cache: Cache<'a>,
27}
28
29impl<'a> MemoQuerier<'a> {
30    /// Query the fields of a `.SRCINFO` file with a cache.
31    pub fn new(srcinfo: &'a str) -> Self {
32        MemoQuerier {
33            remaining_lines: srcinfo.lines(),
34            current_section: Section::Base,
35            cache: Cache::default(),
36        }
37    }
38
39    /// Shrink the cache's capacity to fit its length.
40    pub fn shrink_cache_to_fit(&mut self) {
41        self.cache.shrink_to_fit();
42    }
43
44    /// Private function for testing the internal cache.
45    #[doc(hidden)]
46    pub fn __has_cache(&self, field_name: FieldName, index: usize) -> bool {
47        self.cache.get(field_name, index).is_some()
48    }
49
50    /// Parse the next key-value pair, save it the cache and return it.
51    fn next_entry(&mut self) -> Option<(FieldName, QueryRawTextItem<'a>)> {
52        let line = self.remaining_lines.next()?.trim();
53        if trimmed_line_is_blank(line) {
54            return self.next_entry();
55        }
56        let (raw_field, value) = parse_line(line)?;
57        let Ok(field) = raw_field.to_parsed::<FieldName, &str>() else {
58            return self.next_entry();
59        };
60        if value.is_empty() {
61            return self.next_entry();
62        }
63        let architecture = field.architecture_str().map(Architecture);
64        if *field.name() == FieldName::Name && architecture.is_none() {
65            self.current_section = value.pipe(Name).pipe(Section::Derivative);
66        }
67        let item = QueryRawTextItem::from_tuple3((value, self.current_section, architecture));
68        self.cache.add(*field.name(), item);
69        Some((*field.name(), item))
70    }
71}
72
73/// Return type of [`QueryMut::query_raw_text_mut`] on an instance of [`MemoQuerier`].
74struct QueryIter<'a, 'r> {
75    querier: &'r mut MemoQuerier<'a>,
76    field_name: FieldName,
77    index: usize,
78}
79
80impl<'a, 'r> QueryIter<'a, 'r> {
81    /// Create an iterator that queries `field_name` from `querier`.
82    fn new(querier: &'r mut MemoQuerier<'a>, field_name: FieldName) -> Self {
83        QueryIter {
84            querier,
85            field_name,
86            index: 0,
87        }
88    }
89}
90
91impl<'a> Iterator for QueryIter<'a, '_> {
92    type Item = QueryRawTextItem<'a>;
93    fn next(&mut self) -> Option<Self::Item> {
94        let QueryIter {
95            querier,
96            field_name,
97            index,
98        } = self;
99        loop {
100            if let Some(item) = querier.cache.get(*field_name, *index) {
101                *index += 1;
102                return Some(item);
103            } else {
104                querier.next_entry()?;
105                continue;
106            }
107        }
108    }
109}
110
111impl<'a> QueryMut<'a> for MemoQuerier<'a> {
112    fn query_raw_text_mut(
113        &mut self,
114        field_name: FieldName,
115    ) -> impl Iterator<Item = QueryRawTextItem<'a>> {
116        QueryIter::new(self, field_name)
117    }
118}
119
120/// Return type of [`ChecksumsMut::checksums_mut`] on an instance of [`MemoQuerier`].
121struct ChecksumIter<'a, 'r> {
122    querier: &'r mut MemoQuerier<'a>,
123    checksum_type_id: usize,
124    checksum_index: usize,
125}
126
127impl<'a, 'r> ChecksumIter<'a, 'r> {
128    /// Create an iterator that queries all checksums from `querier`.
129    fn new(querier: &'r mut MemoQuerier<'a>) -> Self {
130        ChecksumIter {
131            querier,
132            checksum_type_id: 0,
133            checksum_index: 0,
134        }
135    }
136}
137
138impl<'a> Iterator for ChecksumIter<'a, '_> {
139    type Item = QueryChecksumItem<'a>;
140    fn next(&mut self) -> Option<Self::Item> {
141        let ChecksumIter {
142            querier,
143            checksum_type_id,
144            checksum_index,
145        } = self;
146        loop {
147            let checksum_type = *ChecksumType::TYPES.get(*checksum_type_id)?;
148            let field_name = checksum_type.into_field_name();
149            if let Some(item) = querier.cache.get(field_name, *checksum_index) {
150                *checksum_index += 1;
151                return item
152                    .map(move |value| ChecksumValue::new(checksum_type, value))
153                    .pipe(Some);
154            } else if querier.next_entry().is_none() {
155                *checksum_type_id += 1;
156                *checksum_index = 0;
157            }
158        }
159    }
160}
161
162impl<'a> ChecksumsMut<'a> for MemoQuerier<'a> {
163    fn checksums_mut(&mut self) -> impl Iterator<Item = QueryChecksumItem<'a>> {
164        ChecksumIter::new(self)
165    }
166}
167
168impl ReuseAdvice for MemoQuerier<'_> {
169    /// [`MemoQuerier`] costs O(1) time to construct. Performing a lookup on it
170    /// costs O(n) the first time and O(1) after that.
171    ///
172    /// This struct is designed to be reused.
173    type ShouldReuse = True;
174}
175
176impl<'a> From<&'a str> for MemoQuerier<'a> {
177    fn from(value: &'a str) -> Self {
178        MemoQuerier::new(value)
179    }
180}
181
182impl<'a> PartialParse<&'a str> for MemoQuerier<'a> {
183    type Error = Infallible;
184    fn partial_parse(text: &'a str) -> PartialParseResult<Self, Self::Error> {
185        MemoQuerier::parse_with_issues(text, ())
186    }
187}
188
189impl<'a, HandleIssue, Error> ParseWithIssues<&'a str, HandleIssue, Error> for MemoQuerier<'a> {
190    fn parse_with_issues(text: &'a str, _: HandleIssue) -> PartialParseResult<Self, Error> {
191        text.pipe(MemoQuerier::new)
192            .pipe(PartialParseResult::new_complete)
193    }
194}