1use std::{fmt::Display, str::FromStr};
6
7use alpm_parsers::iter_str_context;
8use alpm_types::{
9 Architecture,
10 BuildDate,
11 ExtraData,
12 ExtraDataEntry,
13 Group,
14 InstalledSize,
15 License,
16 Name,
17 OptionalDependency,
18 PackageBaseName,
19 PackageDescription,
20 PackageInstallReason,
21 PackageRelation,
22 PackageValidation,
23 Packager,
24 Url,
25 Version,
26};
27use strum::{Display, EnumString, VariantNames};
28use winnow::{
29 ModalResult,
30 Parser,
31 ascii::{line_ending, newline, space0, till_line_ending},
32 combinator::{
33 alt,
34 cut_err,
35 delimited,
36 eof,
37 opt,
38 peek,
39 preceded,
40 repeat,
41 repeat_till,
42 terminated,
43 },
44 error::{ContextError, ErrMode, FromExternalError, StrContext, StrContextValue},
45 token::take_while,
46};
47
48#[derive(Clone, Debug, Display, EnumString, Eq, Hash, PartialEq, VariantNames)]
54#[strum(serialize_all = "UPPERCASE")]
55pub enum SectionKeyword {
56 Name,
58 Version,
60 Base,
62 Desc,
64 Url,
66 Arch,
68 BuildDate,
70 InstallDate,
72 Packager,
74 Size,
76 Groups,
78 Reason,
80 License,
82 Validation,
84 Replaces,
86 Depends,
88 OptDepends,
90 Conflicts,
92 Provides,
94 XData,
96}
97
98impl SectionKeyword {
99 pub fn parser(input: &mut &str) -> ModalResult<Self> {
119 let section = delimited("%", take_while(1.., |c| c != '%'), "%");
120 terminated(
121 preceded(space0, section.try_map(Self::from_str)),
122 line_ending,
123 )
124 .parse_next(input)
125 }
126}
127
128#[derive(Clone, Debug)]
130pub enum Section {
131 Name(Name),
133 Version(Version),
135 Base(PackageBaseName),
137 Desc(PackageDescription),
139 Url(Option<Url>),
141 Arch(Architecture),
143 BuildDate(BuildDate),
145 InstallDate(BuildDate),
147 Packager(Packager),
149 Size(InstalledSize),
151 Groups(Vec<Group>),
153 Reason(PackageInstallReason),
155 License(Vec<License>),
157 Validation(PackageValidation),
159 Replaces(Vec<PackageRelation>),
161 Depends(Vec<PackageRelation>),
163 OptDepends(Vec<OptionalDependency>),
165 Conflicts(Vec<PackageRelation>),
167 Provides(Vec<PackageRelation>),
169 XData(ExtraData),
171}
172
173fn newlines(input: &mut &str) -> ModalResult<()> {
177 repeat(0.., line_ending).parse_next(input)
178}
179
180fn value<T>(input: &mut &str) -> ModalResult<T>
188where
189 T: FromStr + Display,
190 T::Err: Display,
191{
192 let value = till_line_ending.parse_to().parse_next(input)?;
194
195 alt((line_ending, eof)).parse_next(input)?;
197
198 Ok(value)
199}
200
201fn opt_value<T>(input: &mut &str) -> ModalResult<Option<T>>
209where
210 T: FromStr + Display,
211 T::Err: Display,
212{
213 let value = opt(till_line_ending.parse_to()).parse_next(input)?;
215
216 alt((line_ending, eof)).parse_next(input)?;
218
219 Ok(value)
220}
221
222fn values<T>(input: &mut &str) -> ModalResult<Vec<T>>
232where
233 T: FromStr + Display,
234 T::Err: Display,
235{
236 let next_section = peek(preceded(newline, SectionKeyword::parser)).map(|_| ());
237
238 let blank_line = terminated(space0, newline).map(|_| ());
240
241 repeat_till(0.., value, alt((next_section, blank_line, eof.map(|_| ()))))
242 .context(StrContext::Label("values"))
243 .context(StrContext::Expected(StrContextValue::Description(
244 "a list of values in the database desc file",
245 )))
246 .parse_next(input)
247 .map(|(outs, _)| outs)
248}
249
250fn section(input: &mut &str) -> ModalResult<Section> {
260 let section_keyword = cut_err(SectionKeyword::parser)
262 .context(StrContext::Label("section name"))
263 .context(StrContext::Expected(StrContextValue::Description(
264 "a section name that is enclosed in `%` characters",
265 )))
266 .context_with(iter_str_context!([SectionKeyword::VARIANTS]))
267 .parse_next(input)?;
268
269 let section = match section_keyword {
271 SectionKeyword::Name => Section::Name(value(input)?),
272 SectionKeyword::Version => Section::Version(value(input)?),
273 SectionKeyword::Base => Section::Base(value(input)?),
274 SectionKeyword::Desc => Section::Desc(value(input)?),
275 SectionKeyword::Url => Section::Url(opt_value(input)?),
276 SectionKeyword::Arch => Section::Arch(value(input)?),
277 SectionKeyword::BuildDate => Section::BuildDate(value(input)?),
278 SectionKeyword::InstallDate => Section::InstallDate(value(input)?),
279 SectionKeyword::Packager => Section::Packager(value(input)?),
280 SectionKeyword::Size => Section::Size(value(input)?),
281 SectionKeyword::Groups => Section::Groups(values(input)?),
282 SectionKeyword::Reason => Section::Reason(value(input)?),
283 SectionKeyword::License => Section::License(values(input)?),
284 SectionKeyword::Validation => Section::Validation(value(input)?),
285 SectionKeyword::Replaces => Section::Replaces(values(input)?),
286 SectionKeyword::Depends => Section::Depends(values(input)?),
287 SectionKeyword::OptDepends => Section::OptDepends(values(input)?),
288 SectionKeyword::Conflicts => Section::Conflicts(values(input)?),
289 SectionKeyword::Provides => Section::Provides(values(input)?),
290 SectionKeyword::XData => {
291 let entries: Vec<ExtraDataEntry> = values(input)?;
292 let xdata = entries
293 .try_into()
294 .map_err(|e| ErrMode::Cut(ContextError::from_external_error(input, e)))?;
295 Section::XData(xdata)
296 }
297 };
298
299 Ok(section)
300}
301
302pub(crate) fn sections(input: &mut &str) -> ModalResult<Vec<Section>> {
314 cut_err(repeat_till(
315 0..,
316 preceded(opt(newline), section),
317 terminated(opt(newlines), eof),
318 ))
319 .context(StrContext::Label("sections"))
320 .context(StrContext::Expected(StrContextValue::Description(
321 "a section in the database desc file",
322 )))
323 .parse_next(input)
324 .map(|(sections, _)| sections)
325}