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 FullVersion,
14 Group,
15 InstalledSize,
16 License,
17 Name,
18 OptionalDependency,
19 PackageBaseName,
20 PackageDescription,
21 PackageInstallReason,
22 PackageRelation,
23 PackageValidation,
24 Packager,
25 RelationOrSoname,
26 Url,
27};
28use strum::{Display, EnumString, VariantNames};
29use winnow::{
30 ModalResult,
31 Parser,
32 ascii::{line_ending, newline, space0, till_line_ending},
33 combinator::{
34 alt,
35 cut_err,
36 delimited,
37 eof,
38 opt,
39 peek,
40 preceded,
41 repeat,
42 repeat_till,
43 terminated,
44 },
45 error::{ContextError, ErrMode, FromExternalError, StrContext, StrContextValue},
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 Name,
59 Version,
61 Base,
63 Desc,
65 Url,
67 Arch,
69 BuildDate,
71 InstallDate,
73 Packager,
75 Size,
77 Groups,
79 Reason,
81 License,
83 Validation,
85 Replaces,
87 Depends,
89 OptDepends,
91 Conflicts,
93 Provides,
95 XData,
97}
98
99impl SectionKeyword {
100 pub fn parser(input: &mut &str) -> ModalResult<Self> {
120 let section = delimited("%", take_while(1.., |c| c != '%'), "%");
121 terminated(
122 preceded(space0, section.try_map(Self::from_str)),
123 line_ending,
124 )
125 .parse_next(input)
126 }
127}
128
129#[derive(Clone, Debug)]
131pub enum Section {
132 Name(Name),
134 Version(FullVersion),
136 Base(PackageBaseName),
138 Desc(PackageDescription),
140 Url(Option<Url>),
142 Arch(Architecture),
144 BuildDate(BuildDate),
146 InstallDate(BuildDate),
148 Packager(Packager),
150 Size(InstalledSize),
152 Groups(Vec<Group>),
154 Reason(PackageInstallReason),
156 License(Vec<License>),
158 Validation(Vec<PackageValidation>),
160 Replaces(Vec<PackageRelation>),
162 Depends(Vec<RelationOrSoname>),
164 OptDepends(Vec<OptionalDependency>),
166 Conflicts(Vec<PackageRelation>),
168 Provides(Vec<RelationOrSoname>),
170 XData(ExtraData),
172}
173
174fn newlines(input: &mut &str) -> ModalResult<()> {
178 repeat(0.., line_ending).parse_next(input)
179}
180
181fn value<T>(input: &mut &str) -> ModalResult<T>
189where
190 T: FromStr + Display,
191 T::Err: Display,
192{
193 let value = till_line_ending.parse_to().parse_next(input)?;
195
196 alt((line_ending, eof)).parse_next(input)?;
198
199 Ok(value)
200}
201
202fn opt_value<T>(input: &mut &str) -> ModalResult<Option<T>>
210where
211 T: FromStr + Display,
212 T::Err: Display,
213{
214 let value = opt(till_line_ending.parse_to()).parse_next(input)?;
216
217 alt((line_ending, eof)).parse_next(input)?;
219
220 Ok(value)
221}
222
223fn values<T>(input: &mut &str) -> ModalResult<Vec<T>>
233where
234 T: FromStr + Display,
235 T::Err: Display,
236{
237 let next_section = peek(preceded(newline, SectionKeyword::parser)).map(|_| ());
238
239 let blank_line = terminated(space0, newline).map(|_| ());
241
242 repeat_till(0.., value, alt((next_section, blank_line, eof.map(|_| ()))))
243 .context(StrContext::Label("values"))
244 .context(StrContext::Expected(StrContextValue::Description(
245 "a list of values in the database desc file",
246 )))
247 .parse_next(input)
248 .map(|(outs, _)| outs)
249}
250
251fn section(input: &mut &str) -> ModalResult<Section> {
261 let section_keyword = cut_err(SectionKeyword::parser)
263 .context(StrContext::Label("section name"))
264 .context(StrContext::Expected(StrContextValue::Description(
265 "a section name that is enclosed in `%` characters",
266 )))
267 .context_with(iter_str_context!([SectionKeyword::VARIANTS]))
268 .parse_next(input)?;
269
270 let section = match section_keyword {
272 SectionKeyword::Name => Section::Name(value(input)?),
273 SectionKeyword::Version => Section::Version(value(input)?),
274 SectionKeyword::Base => Section::Base(value(input)?),
275 SectionKeyword::Desc => Section::Desc(value(input)?),
276 SectionKeyword::Url => Section::Url(opt_value(input)?),
277 SectionKeyword::Arch => Section::Arch(value(input)?),
278 SectionKeyword::BuildDate => Section::BuildDate(value(input)?),
279 SectionKeyword::InstallDate => Section::InstallDate(value(input)?),
280 SectionKeyword::Packager => Section::Packager(value(input)?),
281 SectionKeyword::Size => Section::Size(value(input)?),
282 SectionKeyword::Groups => Section::Groups(values(input)?),
283 SectionKeyword::Reason => Section::Reason(value(input)?),
284 SectionKeyword::License => Section::License(values(input)?),
285 SectionKeyword::Validation => Section::Validation(values(input)?),
286 SectionKeyword::Replaces => Section::Replaces(values(input)?),
287 SectionKeyword::Depends => Section::Depends(values(input)?),
288 SectionKeyword::OptDepends => Section::OptDepends(values(input)?),
289 SectionKeyword::Conflicts => Section::Conflicts(values(input)?),
290 SectionKeyword::Provides => Section::Provides(values(input)?),
291 SectionKeyword::XData => {
292 let entries: Vec<ExtraDataEntry> = values(input)?;
293 let xdata = entries
294 .try_into()
295 .map_err(|e| ErrMode::Cut(ContextError::from_external_error(input, e)))?;
296 Section::XData(xdata)
297 }
298 };
299
300 Ok(section)
301}
302
303pub(crate) fn sections(input: &mut &str) -> ModalResult<Vec<Section>> {
315 cut_err(repeat_till(
316 0..,
317 preceded(opt(newline), section),
318 terminated(opt(newlines), eof),
319 ))
320 .context(StrContext::Label("sections"))
321 .context(StrContext::Expected(StrContextValue::Description(
322 "a section in the database desc file",
323 )))
324 .parse_next(input)
325 .map(|(sections, _)| sections)
326}