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