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