alpm_lint/
resources.rs

1//! Types to gather, represent and provide data for linting.
2
3use std::{fs::metadata, path::Path};
4
5use alpm_buildinfo::BuildInfo;
6use alpm_common::MetadataFile;
7use alpm_pkginfo::PackageInfo;
8use alpm_srcinfo::{SourceInfo, SourceInfoV1};
9use alpm_types::{MetadataFileName, PKGBUILD_FILE_NAME, SRCINFO_FILE_NAME};
10
11use crate::{Error, LintScope};
12
13/// The resources used by lints during a single lint run.
14// We allow the large enum variant, as we usually only have a single one or at most **very** few
15// of these in memory. Not boxing everything simply makes it more ergonomic to work with.
16#[allow(clippy::large_enum_variant)]
17#[derive(Clone, Debug)]
18pub enum Resources {
19    /// All resources of a package source repository.
20    SourceRepository {
21        /// The [SRCINFO] file generated from the [PKGBUILD].
22        ///
23        /// We cannot lint the [PKGBUILD] directly, hence we have to convert it into a
24        /// [`SourceInfo`] representation first.
25        ///
26        /// [PKGBUILD]: https://man.archlinux.org/man/PKGBUILD.5
27        /// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
28        package_build_source_info: SourceInfo,
29        /// The parsed [SRCINFO] file from the package source repository.
30        ///
31        /// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
32        source_info: SourceInfo,
33    },
34    /// All resources of a single package.
35    Package {
36        /// The parsed [PKGINFO] file.
37        ///
38        /// [PKGINFO]: https://alpm.archlinux.page/specifications/PKGINFO.5.html
39        package_info: PackageInfo,
40        /// The parsed [BUILDINFO] file.
41        ///
42        /// [BUILDINFO]: https://alpm.archlinux.page/specifications/BUILDINFO.5.html
43        build_info: BuildInfo,
44    },
45    /// A singular [BUILDINFO] file.
46    ///
47    /// [BUILDINFO]: https://alpm.archlinux.page/specifications/BUILDINFO.5.html
48    BuildInfo(BuildInfo),
49    /// A singular [PKGINFO] file.
50    ///
51    /// [PKGINFO]: https://alpm.archlinux.page/specifications/PKGINFO.5.html
52    PackageInfo(PackageInfo),
53    /// A singular [PKGBUILD] file.
54    ///
55    /// We cannot lint the [PKGBUILD] directly, hence we have to convert it into a [`SourceInfo`]
56    /// representation first.
57    ///
58    /// [PKGBUILD]: https://man.archlinux.org/man/PKGBUILD.5
59    /// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
60    PackageBuild(SourceInfo),
61    /// A singular [SRCINFO] file.
62    ///
63    /// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
64    SourceInfo(SourceInfo),
65}
66
67impl Resources {
68    /// Returns the [`LintScope`] for the [`Resources`].
69    pub fn scope(&self) -> LintScope {
70        match self {
71            Resources::SourceRepository { .. } => LintScope::SourceRepository,
72            Resources::Package { .. } => LintScope::Package,
73            Resources::BuildInfo(_) => LintScope::BuildInfo,
74            Resources::PackageInfo(_) => LintScope::PackageInfo,
75            Resources::PackageBuild(_) => LintScope::PackageBuild,
76            Resources::SourceInfo(_) => LintScope::SourceInfo,
77        }
78    }
79
80    /// Creates a [`Resources`] from a file path and a [`LintScope`].
81    ///
82    /// Gathers all files and other resources in a `path` in the context of a `scope`.
83    /// All ALPM related files are detected by their well-known file names.
84    ///
85    /// # Errors
86    ///
87    /// Returns an error if:
88    ///
89    /// - files that are required for a scope don't exist,
90    /// - files cannot be opened or read,
91    /// - or files contain invalid data and/or cannot be parsed successfully.
92    pub fn gather(path: &Path, scope: LintScope) -> Result<Self, Error> {
93        if scope.is_single_file() {
94            return Self::gather_file(path, scope);
95        }
96
97        // `metadata` automatically follows symlinks, so we get the target's metadata
98        let metadata = metadata(path).map_err(|source| Error::IoPath {
99            path: path.to_owned(),
100            context: "getting metadata of path",
101            source,
102        })?;
103
104        // Early check that we're indeed working with a directory
105        if !metadata.is_dir() {
106            return Err(Error::InvalidPathForLintScope {
107                path: path.to_owned(),
108                scope,
109                expected: "file",
110            });
111        }
112
113        let resource = match scope {
114            LintScope::BuildInfo
115            | LintScope::PackageBuild
116            | LintScope::PackageInfo
117            | LintScope::SourceInfo => {
118                return Err(Error::InvalidLintScope {
119                    scope,
120                    function: "Resource::gather_file",
121                    expected: "single file lint scope",
122                });
123            }
124            LintScope::SourceRepository => Resources::SourceRepository {
125                package_build_source_info: SourceInfo::V1(SourceInfoV1::from_pkgbuild(
126                    &path.join(PKGBUILD_FILE_NAME),
127                )?),
128                source_info: SourceInfo::from_file_with_schema(path.join(SRCINFO_FILE_NAME), None)?,
129            },
130            LintScope::Package => Resources::Package {
131                package_info: PackageInfo::from_file_with_schema(
132                    path.join(MetadataFileName::PackageInfo.to_string()),
133                    None,
134                )?,
135                build_info: BuildInfo::from_file_with_schema(
136                    path.join(MetadataFileName::BuildInfo.to_string()),
137                    None,
138                )?,
139            },
140        };
141
142        Ok(resource)
143    }
144
145    /// Creates a [`Resources`] from a single file at a path and a [`LintScope`].
146    ///
147    /// Gathers a single file at `path` in the context of a `scope`.
148    /// Since the path is direct, the filename is not important for this function.
149    /// The type of metadata file is pre-determined by the [`LintScope`].
150    ///
151    /// # Errors
152    ///
153    /// Returns an error if:
154    ///
155    /// - a scope that requires more than a single file is provided,
156    /// - the metadata of the file at `path` cannot be retrieved,
157    /// - `path` represents a directory,
158    /// - the file cannot be opened or read,
159    /// - or the file contains invalid data and/or cannot be parsed.
160    pub fn gather_file(path: &Path, scope: LintScope) -> Result<Self, Error> {
161        // `metadata` automatically follows symlinks, so we get the target's metadata
162        let metadata = metadata(path).map_err(|source| Error::IoPath {
163            path: path.to_owned(),
164            context: "getting metadata of path",
165            source,
166        })?;
167
168        // Check that we're indeed working with a file.
169        // If we're in a directory, append the expected filename.
170        let path = if metadata.is_dir() {
171            let filename = match scope {
172                LintScope::SourceRepository | LintScope::Package => {
173                    return Err(Error::InvalidLintScope {
174                        scope,
175                        function: "Resource::gather_file",
176                        expected: "single file lint scope",
177                    });
178                }
179                LintScope::BuildInfo => MetadataFileName::BuildInfo.to_string(),
180                LintScope::PackageBuild => PKGBUILD_FILE_NAME.to_string(),
181                LintScope::PackageInfo => MetadataFileName::PackageInfo.to_string(),
182                LintScope::SourceInfo => SRCINFO_FILE_NAME.to_string(),
183            };
184
185            path.join(filename)
186        } else {
187            path.to_owned()
188        };
189
190        let resource = match scope {
191            LintScope::SourceRepository | LintScope::Package => {
192                return Err(Error::InvalidLintScope {
193                    scope,
194                    function: "Resource::gather_file",
195                    expected: "single file lint scope",
196                });
197            }
198            LintScope::BuildInfo => Self::BuildInfo(BuildInfo::from_file_with_schema(path, None)?),
199            LintScope::PackageBuild => {
200                Self::PackageBuild(SourceInfo::V1(SourceInfoV1::from_pkgbuild(&path)?))
201            }
202            LintScope::PackageInfo => {
203                Self::PackageInfo(PackageInfo::from_file_with_schema(path, None)?)
204            }
205            LintScope::SourceInfo => {
206                Self::SourceInfo(SourceInfo::from_file_with_schema(path, None)?)
207            }
208        };
209
210        Ok(resource)
211    }
212}