1#[macro_use]
12extern crate display_derive;
13extern crate failure;
14extern crate flate2;
15extern crate itertools;
16#[macro_use]
17extern crate log;
18extern crate rayon;
19extern crate tar;
20extern crate version_compare;
21extern crate xz2;
22
23pub mod db;
24mod error;
25pub mod package;
26
27use std::fmt::{self, Formatter, Display};
28use std::path::Path;
29
30use version_compare::Version;
31
32use db::Db;
33use error::Result;
34use package::MetaPackage;
35
36pub struct Alpm {
41 pub local_db: Db,
42 pub sync_dbs: Vec<Db>
43}
44
45impl Alpm {
46 pub fn new(location: impl AsRef<Path> + Sync) -> Result<Alpm> {
48 let (local, sync) = rayon::join(
49 || Db::local_db(&location),
50 || Db::sync_dbs(&location)
51 );
52 Ok(Alpm {
53 local_db: local?,
54 sync_dbs: sync?
55 })
56 }
57
58 pub fn foreign_pkgs<'a>(&'a self) -> impl Iterator<Item = &'a MetaPackage> {
61 debug!("computing foreign packages");
62 self.local_db.packages.iter()
63 .filter(move |package| !is_pkg_foreign(&self.sync_dbs, &package.name) )
65 }
66}
67
68fn is_pkg_foreign(sync_dbs: &[Db], pkg_name: &str) -> bool {
69 sync_dbs.iter()
70 .any(|db| db.pkg(&pkg_name).is_some() )
71}
72
73#[derive(Debug)]
76pub struct PkgSpec {
77 pub name: String,
78 version: String,
79 release: String,
80 pub arch: Option<String>
81}
82
83impl PkgSpec {
84 pub fn split_specifier(specifier: &str) -> Option<PkgSpec> {
91 let specifier = if let Some(indx) = specifier.find('/') {
92 &specifier[..indx]
93 } else {
94 specifier
95 };
96 let mut parts = specifier.rsplit('-');
97 let rel = parts.next();
98 let version = parts.next();
99
100 let name: Vec<&str> = parts.rev().collect();
101 let name = name.join("-");
102
103 if let None = rel {
104 None
105 } else if let None = version {
106 None
107 } else {
108 Some(PkgSpec {
109 name: name,
110 version: version.unwrap().to_string(),
111 release: rel.unwrap().to_string(),
112 arch: None
113 })
114 }
115 }
116
117 pub fn split_pkgname(name: &str) -> Option<PkgSpec> {
122 if let Some(indx) = name.rfind('-') {
123 let pkgspec = &name[..indx];
124 if let Some(mut pkgspec) = PkgSpec::split_specifier(pkgspec) {
125 pkgspec.arch = Some(name[indx + 1..].to_string());
126 Some(pkgspec)
127 } else {
128 None
129 }
130 } else {
131 None
132 }
133 }
134
135 pub fn version_str(&self) -> String {
137 format!("{}-{}", self.version, self.release)
138 }
139}
140
141#[derive(Clone, Debug, Hash, Eq, PartialEq)]
142enum Comp {
143 Eq,
144 Lt,
145 Le,
146 Ge,
147 Gt,
148}
149
150impl Comp {
151 fn make_inclusive(self) -> Comp {
152 match self {
153 Comp::Lt => Comp::Le,
154 Comp::Gt => Comp::Ge,
155 _ => self
156 }
157 }
158}
159
160impl Display for Comp {
161 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
162 write!(f, "{}", match self {
163 Comp::Eq => "=",
164 Comp::Lt => "<",
165 Comp::Le => "<=",
166 Comp::Ge => ">=",
167 Comp::Gt => ">"
168 })
169 }
170}
171
172#[derive(Clone, Debug, Hash, Eq, PartialEq)]
175pub struct Provide {
176 pub name: String,
177 version: Option<String>,
178 comp: Comp,
179}
180
181impl Provide {
182 pub fn parse(data: impl AsRef<str>) -> Option<Provide> {
190 let data = data.as_ref();
191 let mut comp = Comp::Eq;
192
193 let mut data = data.splitn(2, |c| {
194 if c == '<' {
195 comp = Comp::Lt;
196 true
197 } else if c == '>' {
198 comp = Comp::Gt;
199 true
200 } else if c == '=' {
201 comp = Comp::Eq;
202 true
203 } else {
204 false
205 }
206 });
207
208 let name = data.next()?.to_string();
209
210 let version = if let Some(version) = data.next() {
211 if version.chars().nth(0) == Some('=') {
212 comp = comp.make_inclusive();
213 Some(version[1..].to_string())
214 } else {
215 Some(version.to_string())
216 }
217 } else { None };
218
219 Some(Provide { name, version, comp })
220 }
221
222 pub fn satisfies(&self, other: &Provide) -> bool {
226 self.name == other.name && if let Some(ref other_ver) = other.version() {
227 if let Some(ref ver) = self.version() {
228 match other.comp {
230 Comp::Eq => ver >= other_ver,
231 Comp::Lt => ver < other_ver,
232 Comp::Le => ver <= other_ver,
233 Comp::Ge => ver >= other_ver,
234 Comp::Gt => ver > other_ver,
235 }
236 } else {
237 false
240 }
241 } else {
242 true
244 }
245 }
246
247 pub fn version(&self) -> Option<Version> {
248 self.version.as_ref().and_then(|ver| {
249 Version::from(ver)
250 })
251 }
252}
253
254impl Display for Provide {
255 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
256 match &self.version {
257 Some(version) => write!(f, "{}{}{}", self.name, self.comp, version),
258 None => write!(f, "{}", self.name)
259 }
260 }
261}
262
263#[cfg(test)]
265mod tests {
266 use std::fs::create_dir_all;
267 use std::path::Path;
268 use std::process::Command;
269
270 use {Db, package::Package, PkgSpec, Provide};
271
272 #[test]
273 fn test_pkgload() {
274 let test_dir = Path::join(Path::new(env!("CARGO_MANIFEST_DIR")), "tests");
275 create_dir_all(&test_dir).unwrap(); let wget = Command::new("curl")
277 .arg("-O")
278 .arg("https://sgp.mirror.pkgbuild.com/community/os/x86_64/ascii-3.18-1-x86_64.pkg.tar.xz")
280 .arg("--output")
281 .arg("ascii-3.18-1-x86_64.pkg.tar.xz")
282 .current_dir(&test_dir)
283 .spawn().unwrap()
284 .wait().unwrap();
285
286 if wget.success() {
287 let filename = Path::join(&test_dir, "ascii-3.18-1-x86_64.pkg.tar.xz");
288 let pkg = Package::load(filename).unwrap();
289
290 assert_eq!(pkg.meta.name, "ascii".to_string());
291 assert_eq!(pkg.meta.version().unwrap().as_str(), "3.18-1");
292 }
293 }
294
295 #[test]
296 fn test_split_specifier() {
297 let pkgspec = PkgSpec::split_specifier("pacman-5.1.1-2").unwrap();
298 assert_eq!(&pkgspec.name, "pacman");
299 assert_eq!(&pkgspec.version, "5.1.1");
300 assert_eq!(pkgspec.release, 2.to_string());
301 assert!(pkgspec.arch.is_none());
302
303 let pkgspec = PkgSpec::split_specifier("pithos-git-1.4.1-1/").unwrap();
304 assert_eq!(&pkgspec.name, "pithos-git");
305 assert_eq!(&pkgspec.version, "1.4.1");
306 assert_eq!(pkgspec.release, 1.to_string());
307 assert!(pkgspec.arch.is_none());
308
309 let pkgspec = PkgSpec::split_specifier("acorn-5.7.2-1/desc").unwrap();
310 assert_eq!(&pkgspec.name, "acorn");
311 assert_eq!(&pkgspec.version, "5.7.2");
312 assert_eq!(pkgspec.release, 1.to_string());
313 assert!(pkgspec.arch.is_none());
314
315 }
323
324 #[test]
325 fn test_split_pkgname() {
326 let pkgspec = PkgSpec::split_pkgname("pacman-5.1.1-2-x86_64").unwrap();
327 assert_eq!(&pkgspec.name, "pacman");
328 assert_eq!(&pkgspec.version, "5.1.1");
329 assert_eq!(pkgspec.release, 2.to_string());
330 assert_eq!(&pkgspec.arch.unwrap(), "x86_64");
331
332 let pkgspec = PkgSpec::split_pkgname("pithos-git-1.4.1-1-any").unwrap();
333 assert_eq!(&pkgspec.name, "pithos-git");
334 assert_eq!(&pkgspec.version, "1.4.1");
335 assert_eq!(pkgspec.release, 1.to_string());
336 assert_eq!(&pkgspec.arch.unwrap(), "any");
337 }
338
339 #[test]
340 fn test_localdb_parse() {
341 let _db = Db::local_db("/var/lib/pacman").unwrap();
342 }
343
344 #[test]
345 fn test_syncdb_parse() {
346 let _dbs = Db::sync_dbs("/var/lib/pacman").unwrap();
347 }
348
349 #[test]
352 #[ignore]
353 fn test_is_package_provided() {
354 let db = Db::local_db("/var/lib/pacman").unwrap();
355 assert!(db.provides(&Provide { name: "java-environment".to_string(), version: Some("8".to_string()) }));
356 }
357}