1use std::str::FromStr;
6
7use alpm_parsers::{iter_str_context, prelude::*};
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 ascii::{line_ending, newline, space0, till_line_ending},
32 combinator::{
33 alt,
34 cut_err,
35 delimited,
36 eof,
37 not,
38 opt,
39 peek,
40 preceded,
41 repeat,
42 repeat_till,
43 terminated,
44 },
45 error::ErrMode,
46 token::take_while,
47};
48
49#[derive(Clone, Debug, Display, EnumString, Eq, Hash, PartialEq, VariantNames)]
55#[strum(serialize_all = "UPPERCASE")]
56pub enum SectionKeyword {
57 Filename,
59 Name,
61 Base,
63 Version,
65 Desc,
67 Groups,
69 CSize,
71 ISize,
73 Md5Sum,
75 Sha256Sum,
77 PgpSig,
79 Url,
81 License,
83 Arch,
85 BuildDate,
87 Packager,
89 Replaces,
91 Conflicts,
93 Provides,
95 Depends,
97 OptDepends,
99 MakeDepends,
101 CheckDepends,
103}
104
105impl SectionKeyword {
106 pub fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, 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<'a>(input: &mut Input<'a>) -> PResult<'a, ()> {
191 repeat(0.., line_ending).parse_next(input)
192}
193
194fn end_of_values<'a>(input: &mut Input<'a>) -> PResult<'a, ()> {
198 peek(alt((
199 line_ending.map(|_| ()),
200 SectionKeyword::parser.map(|_| ()),
201 eof.map(|_| ()),
202 )))
203 .parse_next(input)
204}
205
206fn try_value_from<'a, T>(layer: &str) -> impl Parser<Input<'a>, T, ErrMode<ParseStack<'a>>>
217where
218 T: FromStr,
219 T::Err: std::error::Error + Send + Sync + 'static,
220{
221 till_line_ending.try_map(T::from_str).layer(layer)
222}
223
224fn try_values_from<'a, T>(layer: &str) -> impl Parser<Input<'a>, Vec<T>, ErrMode<ParseStack<'a>>>
236where
237 T: FromStr,
238 T::Err: std::error::Error + Send + Sync + 'static,
239{
240 move |input: &mut Input<'a>| -> PResult<'a, Vec<T>> {
241 repeat_till(
242 0..,
243 terminated(
245 try_value_from(layer),
246 opt(terminated(line_ending, not(end_of_values))),
247 ),
248 end_of_values,
249 )
250 .map(|(outs, _)| outs)
251 .parse_next(input)
252 }
253}
254
255fn values<'a, T, P>(
266 type_parser: P,
267 layer: &str,
268) -> impl Parser<Input<'a>, Vec<T>, ErrMode<ParseStack<'a>>>
269where
270 P: Parser<Input<'a>, T, ErrMode<ParseStack<'a>>>,
271{
272 let mut type_parser = type_parser;
273 let parser = move |input: &mut Input<'a>| -> PResult<'a, Vec<T>> {
274 repeat_till(
275 0..,
276 terminated(
278 type_parser.by_ref(),
279 opt(terminated(line_ending, not(end_of_values))),
280 ),
281 end_of_values,
282 )
283 .map(|(outs, _)| outs)
284 .parse_next(input)
285 };
286
287 parser.layer(layer)
288}
289
290fn section<'a>(input: &mut Input<'a>) -> PResult<'a, Section> {
300 let section_keyword = cut_err(SectionKeyword::parser)
302 .description("expected a valid section name that is enclosed in `%` characters:")
303 .context_with(iter_str_context!([SectionKeyword::VARIANTS]))
304 .layer("section header")
305 .parse_next(input)?;
306
307 let section = match section_keyword {
310 SectionKeyword::Filename => {
311 Section::Filename(try_value_from("file name").parse_next(input)?)
312 }
313 SectionKeyword::Name => Section::Name(Name::parser_until_line_ending(input)?),
314 SectionKeyword::Base => Section::Base(PackageBaseName::parser_until_line_ending(input)?),
315 SectionKeyword::Version => Section::Version(FullVersion::parser_until_line_ending(input)?),
316 SectionKeyword::Desc => Section::Desc(
317 till_line_ending
318 .map(PackageDescription::new)
319 .parse_next(input)?,
320 ),
321 SectionKeyword::Groups => Section::Groups(try_values_from("group").parse_next(input)?),
322 SectionKeyword::CSize => {
323 Section::CSize(try_value_from("compressed size").parse_next(input)?)
324 }
325 SectionKeyword::ISize => {
326 Section::ISize(try_value_from("installed size").parse_next(input)?)
327 }
328 SectionKeyword::Md5Sum => Section::Md5Sum(Md5Checksum::parser_until_line_ending(input)?),
329 SectionKeyword::Sha256Sum => {
330 Section::Sha256Sum(Sha256Checksum::parser_until_line_ending(input)?)
331 }
332 SectionKeyword::PgpSig => {
333 Section::PgpSig(try_value_from("pgp signature").parse_next(input)?)
334 }
335 SectionKeyword::Url => Section::Url(
336 alt((
337 till_line_ending
339 .verify(|s: &str| s.trim().is_empty())
340 .map(|_s: &str| None),
341 till_line_ending
343 .try_map(Url::from_str)
344 .map(Some)
345 .layer("url field"),
346 ))
347 .parse_next(input)?,
348 ),
349 SectionKeyword::License => Section::License(try_values_from("license").parse_next(input)?),
350 SectionKeyword::Arch => Section::Arch(Architecture::parser_until_line_ending(input)?),
351 SectionKeyword::BuildDate => {
352 Section::BuildDate(try_value_from("build date").parse_next(input)?)
353 }
354 SectionKeyword::Packager => Section::Packager(Packager::parser_until_line_ending(input)?),
355 SectionKeyword::Replaces => Section::Replaces(
356 values(PackageRelation::parser_until_line_ending, "replaces").parse_next(input)?,
357 ),
358 SectionKeyword::Conflicts => Section::Conflicts(
359 values(PackageRelation::parser_until_line_ending, "conflicts").parse_next(input)?,
360 ),
361 SectionKeyword::Provides => Section::Provides(
362 values(RelationOrSoname::parser_until_line_ending, "provides").parse_next(input)?,
363 ),
364 SectionKeyword::Depends => Section::Depends(
365 values(RelationOrSoname::parser_until_line_ending, "depends").parse_next(input)?,
366 ),
367 SectionKeyword::OptDepends => Section::OptDepends(
368 values(OptionalDependency::parser_until_line_ending, "optdepends").parse_next(input)?,
369 ),
370 SectionKeyword::MakeDepends => Section::MakeDepends(
371 values(PackageRelation::parser_until_line_ending, "makedepends").parse_next(input)?,
372 ),
373 SectionKeyword::CheckDepends => Section::CheckDepends(
374 values(PackageRelation::parser_until_line_ending, "checkdepends").parse_next(input)?,
375 ),
376 };
377
378 alt((line_ending, eof)).parse_next(input)?;
380
381 Ok(section)
382}
383
384pub(crate) fn sections<'a>(input: &mut Input<'a>) -> PResult<'a, Vec<Section>> {
396 cut_err(repeat_till(
397 0..,
398 preceded(
399 opt(newline),
400 section.layer("alpm-db-desc file value section"),
401 ),
402 terminated(opt(newlines), eof),
403 ))
404 .parse_next(input)
405 .map(|(sections, _)| sections)
406}