1use std::path::PathBuf;
2
3use alpm_parsers::iter_str_context;
4use alpm_types::{Md5Checksum, Sha256Checksum};
5use winnow::{
6 ModalResult,
7 Parser as WinnowParser,
8 ascii::{digit1, line_ending, space0},
9 combinator::{
10 alt,
11 cut_err,
12 eof,
13 fail,
14 preceded,
15 repeat_till,
16 separated,
17 separated_pair,
18 terminated,
19 },
20 error::{StrContext, StrContextValue},
21 stream::AsChar,
22 token::{take_until, take_while},
23};
24
25use crate::path_decoder::decode_utf8_chars;
26
27#[derive(Clone, Debug)]
29pub enum Statement<'a> {
30 Ignored,
36 Set(Vec<SetProperty<'a>>),
38 Unset(Vec<UnsetProperty>),
40 Path {
42 path: PathBuf,
44 properties: Vec<PathProperty<'a>>,
46 },
47}
48
49#[derive(Clone, Debug)]
51pub enum SetProperty<'a> {
52 Uid(u32),
54 Gid(u32),
56 Mode(&'a str),
58 Type(PathType),
60}
61
62#[derive(Clone, Debug)]
64pub enum UnsetProperty {
65 Uid,
67 Gid,
69 Mode,
71 Type,
73}
74
75#[derive(Clone, Debug)]
77pub enum PathProperty<'a> {
78 Uid(u32),
80 Gid(u32),
82 Mode(&'a str),
84 Type(PathType),
86 Size(u64),
88 Link(PathBuf),
90 Md5Digest(Md5Checksum),
92 Sha256Digest(Sha256Checksum),
94 Time(i64),
96}
97
98#[derive(Clone, Copy, Debug)]
100pub enum PathType {
101 Dir,
103 File,
105 Link,
107}
108
109fn set_property<'s>(input: &mut &'s str) -> ModalResult<SetProperty<'s>> {
111 let keywords = ["uid", "gid", "type", "mode"];
113 let property_type = cut_err(alt(keywords))
114 .context(StrContext::Label("property"))
115 .context_with(iter_str_context!([keywords]))
116 .parse_next(input)?;
117
118 let _ = "=".parse_next(input)?;
120
121 let property = match property_type {
123 "type" => {
124 let path_types = ["dir", "file", "link"];
125 alt(path_types)
126 .map(|value| match value {
127 "dir" => SetProperty::Type(PathType::Dir),
128 "file" => SetProperty::Type(PathType::File),
129 "link" => SetProperty::Type(PathType::Link),
130 _ => unreachable!(),
131 })
132 .context(StrContext::Label("property file type"))
133 .context_with(iter_str_context!([path_types]))
134 .parse_next(input)?
135 }
136 "uid" => SetProperty::Uid(system_id("user id", input)?),
137 "gid" => SetProperty::Gid(system_id("group id", input)?),
138 "mode" => SetProperty::Mode(mode(input)?),
139 _ => unreachable!(),
140 };
141
142 Ok(property)
143}
144
145fn unset_property(input: &mut &str) -> ModalResult<UnsetProperty> {
147 let keywords = ["uid", "gid", "type", "mode"];
149 let property_type = cut_err(alt(keywords))
150 .context(StrContext::Label("property"))
151 .context_with(iter_str_context!([keywords]))
152 .parse_next(input)?;
153
154 let property = match property_type {
156 "type" => UnsetProperty::Type,
157 "uid" => UnsetProperty::Uid,
158 "gid" => UnsetProperty::Gid,
159 "mode" => UnsetProperty::Mode,
160 _ => unreachable!(),
161 };
162
163 Ok(property)
164}
165
166fn system_id(id_type: &'static str, input: &mut &str) -> ModalResult<u32> {
168 cut_err(digit1.parse_to())
169 .context(StrContext::Label(id_type))
170 .context(StrContext::Expected(StrContextValue::Description(
171 "a system id.",
172 )))
173 .parse_next(input)
174}
175
176fn timestamp(input: &mut &str) -> ModalResult<i64> {
180 let (timestamp, _) = cut_err(separated_pair(digit1.parse_to(), '.', digit1))
181 .context(StrContext::Label("unix epoch"))
182 .context(StrContext::Expected(StrContextValue::Description(
183 "A unix epoch in float notation.",
184 )))
185 .parse_next(input)?;
186
187 Ok(timestamp)
188}
189
190fn mode<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
194 cut_err(take_while(3..5, AsChar::is_oct_digit))
195 .context(StrContext::Label("file mode"))
196 .context(StrContext::Expected(StrContextValue::Description(
197 "octal string of length 3-5.",
198 )))
199 .parse_next(input)
200}
201
202fn sha256(input: &mut &str) -> ModalResult<Sha256Checksum> {
204 cut_err(take_while(64.., AsChar::is_hex_digit).parse_to())
205 .context(StrContext::Label("sha256 hash"))
206 .context(StrContext::Expected(StrContextValue::Description(
207 "64 char long hexadecimal string",
208 )))
209 .parse_next(input)
210}
211
212fn md5(input: &mut &str) -> ModalResult<Md5Checksum> {
214 cut_err(take_while(32.., AsChar::is_hex_digit).parse_to())
215 .context(StrContext::Label("md5 hash"))
216 .context(StrContext::Expected(StrContextValue::Description(
217 "32 char long hexadecimal string",
218 )))
219 .parse_next(input)
220}
221
222fn link(input: &mut &str) -> ModalResult<String> {
226 take_while(0.., |c| c != ' ' && c != '\n')
227 .and_then(decode_utf8_chars)
228 .parse_next(input)
229}
230
231fn size(input: &mut &str) -> ModalResult<u64> {
233 cut_err(take_while(0.., |c| c != ' ' && c != '\n').parse_to())
234 .context(StrContext::Label("file size"))
235 .context(StrContext::Expected(StrContextValue::Description(
236 "a positive integer representing the file's size.",
237 )))
238 .parse_next(input)
239}
240
241fn property<'s>(input: &mut &'s str) -> ModalResult<PathProperty<'s>> {
243 let keywords = [
245 "type",
246 "uid",
247 "gid",
248 "mode",
249 "size",
250 "link",
251 "md5digest",
252 "sha256digest",
253 "time",
254 ];
255 let property_type = cut_err(alt(keywords))
256 .context(StrContext::Label("file property type"))
257 .context_with(iter_str_context!([keywords]))
258 .parse_next(input)?;
259
260 let _ = "=".parse_next(input)?;
262
263 let property = match property_type {
265 "type" => alt(("dir", "file", "link"))
266 .map(|value| match value {
267 "dir" => PathProperty::Type(PathType::Dir),
268 "file" => PathProperty::Type(PathType::File),
269 "link" => PathProperty::Type(PathType::Link),
270 _ => unreachable!(),
271 })
272 .context(StrContext::Label("property file type"))
273 .context(StrContext::Expected(StrContextValue::Description(
274 "'dir', 'file' or 'link'",
275 )))
276 .parse_next(input)?,
277 "uid" => PathProperty::Uid(system_id("user id", input)?),
278 "gid" => PathProperty::Gid(system_id("group id", input)?),
279 "mode" => PathProperty::Mode(mode(input)?),
280 "size" => PathProperty::Size(size.parse_next(input)?),
281 "link" => PathProperty::Link(PathBuf::from(link.parse_next(input)?)),
282 "md5digest" => PathProperty::Md5Digest(md5(input)?),
283 "sha256digest" => PathProperty::Sha256Digest(sha256(input)?),
284 "time" => PathProperty::Time(timestamp(input)?),
285 _ => unreachable!(),
286 };
287
288 Ok(property)
289}
290
291fn properties<'s>(input: &mut &'s str) -> ModalResult<Vec<PathProperty<'s>>> {
298 cut_err(terminated(separated(0.., property, " "), line_ending)).parse_next(input)
299}
300
301fn set_properties<'s>(input: &mut &'s str) -> ModalResult<Vec<SetProperty<'s>>> {
307 cut_err(terminated(separated(0.., set_property, " "), line_ending)).parse_next(input)
308}
309
310fn unset_properties(input: &mut &str) -> ModalResult<Vec<UnsetProperty>> {
316 cut_err(terminated(separated(0.., unset_property, " "), line_ending)).parse_next(input)
317}
318
319fn statement<'s>(input: &mut &'s str) -> ModalResult<Statement<'s>> {
321 let statement_type: String = alt((
323 preceded(
330 space0,
331 terminated((".", take_until(0.., " ")).take(), alt((' ', '\n'))),
332 ).and_then(decode_utf8_chars),
333 terminated("/set", " ").map(|s: &str| s.to_string()),
334 terminated("/unset", " ").map(|s: &str| s.to_string()),
335 preceded(("#", take_until(0.., "\n")), line_ending).map(|s: &str| s.to_string()),
337 preceded(space0, line_ending).map(|s: &str| s.to_string()),
339 fail.context(StrContext::Label("statement"))
341 .context(StrContext::Expected(StrContextValue::Description(
342 "'/set', '/unset', or a relative local path (./some/path) followed by their respective properties.",
343 )))
344 ))
345 .parse_next(input)?;
346
347 if statement_type.trim().is_empty() {
349 return Ok(Statement::Ignored);
350 }
351
352 let statement = match statement_type.as_str() {
354 "/set" => Statement::Set(set_properties.parse_next(input)?),
355 "/unset" => Statement::Unset(unset_properties.parse_next(input)?),
356 path => Statement::Path {
357 path: PathBuf::from(path),
358 properties: properties.parse_next(input)?,
359 },
360 };
361
362 Ok(statement)
363}
364
365pub fn mtree<'s>(input: &mut &'s str) -> ModalResult<Vec<Statement<'s>>> {
375 let (statements, _eof): (Vec<Statement<'s>>, _) =
376 repeat_till(0.., statement, eof).parse_next(input)?;
377
378 Ok(statements)
379}