1use std::{fmt::Display, str::FromStr};
6
7use alpm_parsers::iter_str_context;
8use alpm_types::{
9 Architecture,
10 Base64OpenPGPSignature,
11 BuildDate,
12 CompressedSize,
13 FullVersion,
14 Group,
15 InstalledSize,
16 License,
17 Md5Checksum,
18 Name,
19 OptionalDependency,
20 PackageBaseName,
21 PackageDescription,
22 PackageFileName,
23 PackageRelation,
24 Packager,
25 RelationOrSoname,
26 Sha256Checksum,
27 Url,
28};
29use strum::{Display, EnumString, VariantNames};
30use winnow::{
31 ModalResult,
32 Parser,
33 ascii::{line_ending, newline, space0, till_line_ending},
34 combinator::{
35 alt,
36 cut_err,
37 delimited,
38 eof,
39 opt,
40 peek,
41 preceded,
42 repeat,
43 repeat_till,
44 terminated,
45 },
46 error::{StrContext, StrContextValue},
47 token::take_while,
48};
49
50#[derive(Clone, Debug, Display, EnumString, Eq, Hash, PartialEq, VariantNames)]
56#[strum(serialize_all = "UPPERCASE")]
57pub enum SectionKeyword {
58 Filename,
60 Name,
62 Base,
64 Version,
66 Desc,
68 Groups,
70 CSize,
72 ISize,
74 Md5Sum,
76 Sha256Sum,
78 PgpSig,
80 Url,
82 License,
84 Arch,
86 BuildDate,
88 Packager,
90 Replaces,
92 Conflicts,
94 Provides,
96 Depends,
98 OptDepends,
100 MakeDepends,
102 CheckDepends,
104}
105
106impl SectionKeyword {
107 pub fn parser(input: &mut &str) -> ModalResult<Self> {
127 let section = delimited("%", take_while(1.., |c| c != '%'), "%");
128 terminated(
129 preceded(space0, section.try_map(Self::from_str)),
130 line_ending,
131 )
132 .parse_next(input)
133 }
134}
135
136#[derive(Clone, Debug)]
138pub enum Section {
139 Filename(PackageFileName),
141 Name(Name),
143 Base(PackageBaseName),
145 Version(FullVersion),
147 Desc(PackageDescription),
149 Groups(Vec<Group>),
151 CSize(CompressedSize),
153 ISize(InstalledSize),
155 Md5Sum(Md5Checksum),
157 Sha256Sum(Sha256Checksum),
159 PgpSig(Base64OpenPGPSignature),
161 Url(Option<Url>),
163 License(Vec<License>),
165 Arch(Architecture),
167 BuildDate(BuildDate),
169 Packager(Packager),
171 Replaces(Vec<PackageRelation>),
173 Conflicts(Vec<PackageRelation>),
175 Provides(Vec<RelationOrSoname>),
177 Depends(Vec<RelationOrSoname>),
179 OptDepends(Vec<OptionalDependency>),
181 MakeDepends(Vec<PackageRelation>),
183 CheckDepends(Vec<PackageRelation>),
185}
186
187fn newlines(input: &mut &str) -> ModalResult<()> {
191 repeat(0.., line_ending).parse_next(input)
192}
193
194fn value<T>(input: &mut &str) -> ModalResult<T>
202where
203 T: FromStr + Display,
204 T::Err: Display,
205{
206 let value = till_line_ending.parse_to().parse_next(input)?;
208
209 alt((line_ending, eof)).parse_next(input)?;
211
212 Ok(value)
213}
214
215fn opt_value<T>(input: &mut &str) -> ModalResult<Option<T>>
216where
217 T: FromStr + Display,
218 T::Err: Display,
219{
220 let value = opt(till_line_ending.parse_to()).parse_next(input)?;
222
223 alt((line_ending, eof)).parse_next(input)?;
225
226 Ok(value)
227}
228
229fn values<T>(input: &mut &str) -> ModalResult<Vec<T>>
239where
240 T: FromStr + Display,
241 T::Err: Display,
242{
243 let next_section = peek(preceded(newline, SectionKeyword::parser)).map(|_| ());
244
245 let blank_line = terminated(space0, newline).map(|_| ());
247
248 repeat_till(0.., value, alt((next_section, blank_line, eof.map(|_| ()))))
249 .context(StrContext::Label("values"))
250 .context(StrContext::Expected(StrContextValue::Description(
251 "a list of values in the database desc file",
252 )))
253 .parse_next(input)
254 .map(|(outs, _)| outs)
255}
256
257fn section(input: &mut &str) -> ModalResult<Section> {
267 let section_keyword = cut_err(SectionKeyword::parser)
269 .context(StrContext::Label("section name"))
270 .context(StrContext::Expected(StrContextValue::Description(
271 "a section name that is enclosed in `%` characters",
272 )))
273 .context_with(iter_str_context!([SectionKeyword::VARIANTS]))
274 .parse_next(input)?;
275
276 let section = match section_keyword {
278 SectionKeyword::Filename => Section::Filename(value(input)?),
279 SectionKeyword::Name => Section::Name(value(input)?),
280 SectionKeyword::Base => Section::Base(value(input)?),
281 SectionKeyword::Version => Section::Version(value(input)?),
282 SectionKeyword::Desc => Section::Desc(value(input)?),
283 SectionKeyword::Groups => Section::Groups(values(input)?),
284 SectionKeyword::CSize => Section::CSize(value(input)?),
285 SectionKeyword::ISize => Section::ISize(value(input)?),
286 SectionKeyword::Md5Sum => Section::Md5Sum(value(input)?),
287 SectionKeyword::Sha256Sum => Section::Sha256Sum(value(input)?),
288 SectionKeyword::PgpSig => Section::PgpSig(value(input)?),
289 SectionKeyword::Url => Section::Url(opt_value(input)?),
290 SectionKeyword::License => Section::License(values(input)?),
291 SectionKeyword::Arch => Section::Arch(value(input)?),
292 SectionKeyword::BuildDate => Section::BuildDate(value(input)?),
293 SectionKeyword::Packager => Section::Packager(value(input)?),
294 SectionKeyword::Replaces => Section::Replaces(values(input)?),
295 SectionKeyword::Conflicts => Section::Conflicts(values(input)?),
296 SectionKeyword::Provides => Section::Provides(values(input)?),
297 SectionKeyword::Depends => Section::Depends(values(input)?),
298 SectionKeyword::OptDepends => Section::OptDepends(values(input)?),
299 SectionKeyword::MakeDepends => Section::MakeDepends(values(input)?),
300 SectionKeyword::CheckDepends => Section::CheckDepends(values(input)?),
301 };
302
303 Ok(section)
304}
305
306pub(crate) fn sections(input: &mut &str) -> ModalResult<Vec<Section>> {
318 cut_err(repeat_till(
319 0..,
320 preceded(opt(newline), section),
321 terminated(opt(newlines), eof),
322 ))
323 .context(StrContext::Label("sections"))
324 .context(StrContext::Expected(StrContextValue::Description(
325 "a section in the database desc file",
326 )))
327 .parse_next(input)
328 .map(|(sections, _)| sections)
329}