Skip to main content

alpm_types/
compression.rs

1//! File compression related types.
2
3use std::{
4    path::{Path, PathBuf},
5    str::FromStr,
6};
7
8use alpm_parsers::{iter_str_context, traits::AlpmParser};
9use serde::{Deserialize, Serialize};
10use strum::{AsRefStr, Display, EnumString, IntoStaticStr, VariantNames};
11use winnow::{
12    Parser,
13    ascii::alphanumeric1,
14    error::{ContextError, ErrMode, StrContext, StrContextValue},
15};
16
17/// The file extension of a compression algorithm.
18///
19/// Compression may be used for a set of different files in the ALPM context (e.g. [alpm-package],
20/// alpm-source-package, alpm-repo-db).
21/// Each algorithm uses a distinct file extension.
22///
23/// [alpm-package]: https://alpm.archlinux.page/specifications/alpm-package.7.html
24#[derive(
25    AsRefStr,
26    Clone,
27    Copy,
28    Debug,
29    Default,
30    Deserialize,
31    Display,
32    EnumString,
33    Eq,
34    IntoStaticStr,
35    PartialEq,
36    Serialize,
37    VariantNames,
38)]
39#[serde(untagged)]
40pub enum CompressionAlgorithmFileExtension {
41    /// The file extension for files compressed using the [compress] compression algorithm.
42    ///
43    /// [compress]: https://man.archlinux.org/man/compress.1
44    #[serde(rename = "Z")]
45    #[strum(to_string = "Z")]
46    Compress,
47
48    /// The file extension for files compressed using the [bzip2] compression algorithm.
49    ///
50    /// [bzip2]: https://man.archlinux.org/man/bzip2.1
51    #[serde(rename = "bz2")]
52    #[strum(to_string = "bz2")]
53    Bzip2,
54
55    /// The file extension for files compressed using the [gzip] compression algorithm.
56    ///
57    /// [gzip]: https://man.archlinux.org/man/gzip.1
58    #[serde(rename = "gz")]
59    #[strum(to_string = "gz")]
60    Gzip,
61
62    /// The file extension for files compressed using the [lrzip] compression algorithm.
63    ///
64    /// [lrzip]: https://man.archlinux.org/man/lrzip.1
65    #[serde(rename = "lrz")]
66    #[strum(to_string = "lrz")]
67    Lrzip,
68
69    /// The file extension for files compressed using the [lzip] compression algorithm.
70    ///
71    /// [lzip]: https://man.archlinux.org/man/lzip.1
72    #[serde(rename = "lz")]
73    #[strum(to_string = "lz")]
74    Lzip,
75
76    /// The file extension for files compressed using the [lz4] compression algorithm.
77    ///
78    /// [lz4]: https://man.archlinux.org/man/lz4.1
79    #[serde(rename = "lz4")]
80    #[strum(to_string = "lz4")]
81    Lz4,
82
83    /// The file extension for files compressed using the [lzop] compression algorithm.
84    ///
85    /// [lzop]: https://man.archlinux.org/man/lzop.1
86    #[serde(rename = "lzo")]
87    #[strum(to_string = "lzo")]
88    Lzop,
89
90    /// The file extension for files compressed using the [xz] compression algorithm.
91    ///
92    /// [xz]: https://man.archlinux.org/man/xz.1
93    #[serde(rename = "xz")]
94    #[strum(to_string = "xz")]
95    Xz,
96
97    /// The file extension for files compressed using the [zstd] compression algorithm.
98    ///
99    /// [zstd]: https://man.archlinux.org/man/zstd.1
100    #[default]
101    #[serde(rename = "zst")]
102    #[strum(to_string = "zst")]
103    Zstd,
104}
105
106impl AlpmParser for CompressionAlgorithmFileExtension {
107    /// Recognizes a [`CompressionAlgorithmFileExtension`] in a string slice.
108    ///
109    /// # Errors
110    ///
111    /// Returns an error if `input` does not begin with a valid variant
112    /// of [`CompressionAlgorithmFileExtension`].
113    fn parser(input: &mut &str) -> Result<Self, ErrMode<ContextError>> {
114        alphanumeric1
115            .try_map(CompressionAlgorithmFileExtension::from_str)
116            .context(StrContext::Label("compression algorithm file extension"))
117            .context_with(iter_str_context!([
118                CompressionAlgorithmFileExtension::VARIANTS
119            ]))
120            .parse_next(input)
121    }
122
123    fn delimiter_error_context<'a, O, P>(
124        parser: P,
125    ) -> impl Parser<&'a str, O, ErrMode<ContextError>>
126    where
127        P: Parser<&'a str, O, ErrMode<ContextError>>,
128    {
129        parser
130            .context(StrContext::Label("compression algorithm file extension"))
131            .context(StrContext::Expected(StrContextValue::Description(
132                "an alphanumeric string",
133            )))
134    }
135}
136
137impl TryFrom<&Path> for CompressionAlgorithmFileExtension {
138    type Error = crate::Error;
139
140    /// Creates a [`CompressionAlgorithmFileExtension`] from a [`Path`] by extracting the file
141    /// extension.
142    ///
143    /// # Errors
144    ///
145    /// Returns an error if the file extension does not match a
146    /// [`CompressionAlgorithmFileExtension`] variant.
147    fn try_from(path: &Path) -> Result<Self, Self::Error> {
148        path.extension()
149            .and_then(|ext| ext.to_str())
150            .and_then(|ext| Self::from_str(ext).ok())
151            .ok_or(strum::ParseError::VariantNotFound.into())
152    }
153}
154
155impl TryFrom<PathBuf> for CompressionAlgorithmFileExtension {
156    type Error = crate::Error;
157
158    /// Creates a [`CompressionAlgorithmFileExtension`] from a [`PathBuf`] by extracting the file
159    /// extension.
160    ///
161    /// Delegates to [`TryFrom<&Path>`][`TryFrom::try_from`].
162    ///
163    /// # Errors
164    ///
165    /// Returns an error if the file extension does not match a
166    /// [`CompressionAlgorithmFileExtension`] variant.
167    fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
168        path.as_path().try_into()
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use rstest::*;
175
176    use super::*;
177
178    #[rstest]
179    #[case("Z", CompressionAlgorithmFileExtension::Compress)]
180    #[case("bz2", CompressionAlgorithmFileExtension::Bzip2)]
181    #[case("gz", CompressionAlgorithmFileExtension::Gzip)]
182    #[case("lrz", CompressionAlgorithmFileExtension::Lrzip)]
183    #[case("lz", CompressionAlgorithmFileExtension::Lzip)]
184    #[case("lz4", CompressionAlgorithmFileExtension::Lz4)]
185    #[case("lzo", CompressionAlgorithmFileExtension::Lzop)]
186    #[case("xz", CompressionAlgorithmFileExtension::Xz)]
187    #[case("zst", CompressionAlgorithmFileExtension::Zstd)]
188    fn compression_algorithm_file_extension_from_str(
189        #[case] input: &str,
190        #[case] expected: CompressionAlgorithmFileExtension,
191    ) {
192        let parsed = CompressionAlgorithmFileExtension::from_str(input).unwrap();
193        assert_eq!(parsed, expected);
194    }
195
196    #[rstest]
197    #[case("archive.Z", CompressionAlgorithmFileExtension::Compress)]
198    #[case("data.bz2", CompressionAlgorithmFileExtension::Bzip2)]
199    #[case("doc.gz", CompressionAlgorithmFileExtension::Gzip)]
200    #[case("video.lrz", CompressionAlgorithmFileExtension::Lrzip)]
201    #[case("binary.lz", CompressionAlgorithmFileExtension::Lzip)]
202    #[case("dump.lz4", CompressionAlgorithmFileExtension::Lz4)]
203    #[case("image.lzo", CompressionAlgorithmFileExtension::Lzop)]
204    #[case("package.xz", CompressionAlgorithmFileExtension::Xz)]
205    #[case("/var/cache/repo.zst", CompressionAlgorithmFileExtension::Zstd)]
206    fn compression_algorithm_file_extension_try_from_path(
207        #[case] filename: &str,
208        #[case] expected: CompressionAlgorithmFileExtension,
209    ) -> testresult::TestResult {
210        let path = PathBuf::from(filename);
211        let parsed = CompressionAlgorithmFileExtension::try_from(path)?;
212        assert_eq!(parsed, expected);
213        Ok(())
214    }
215
216    #[rstest]
217    #[case("file.txt")]
218    #[case("unknown.abc")]
219    #[case("noext")]
220    fn invalid_compression_file_extension(#[case] filename: &str) {
221        let path = Path::new(filename);
222        let error = CompressionAlgorithmFileExtension::try_from(path).unwrap_err();
223        assert!(matches!(error, crate::Error::InvalidVariant(_)));
224    }
225}