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}