diff options
author | Matthias Beyer <mail@beyermatthias.de> | 2019-04-24 10:13:39 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-04-24 10:13:39 +0200 |
commit | c83ef519fac21f0943252e5e303dd733edd2b870 (patch) | |
tree | da74650c8597fed1f194a123aa8d610943428f71 | |
parent | 4f3aebf1d71e98a8c78495838e229ea3999a8db1 (diff) | |
parent | 5f34e97efb8d67d7b3763d53d747d06661bde558 (diff) |
Merge pull request #6 from matthiasbeyer/feature-compare
Compare feature
-rw-r--r-- | Cargo.toml | 8 | ||||
-rw-r--r-- | librepology/src/v1/types/category.rs | 2 | ||||
-rw-r--r-- | librepology/src/v1/types/download.rs | 2 | ||||
-rw-r--r-- | librepology/src/v1/types/effname.rs | 2 | ||||
-rw-r--r-- | librepology/src/v1/types/license.rs | 2 | ||||
-rw-r--r-- | librepology/src/v1/types/maintainer.rs | 2 | ||||
-rw-r--r-- | librepology/src/v1/types/name.rs | 2 | ||||
-rw-r--r-- | librepology/src/v1/types/repo.rs | 2 | ||||
-rw-r--r-- | librepology/src/v1/types/summary.rs | 2 | ||||
-rw-r--r-- | librepology/src/v1/types/version.rs | 2 | ||||
-rw-r--r-- | librepology/src/v1/types/www.rs | 2 | ||||
-rw-r--r-- | src/cli.rs | 31 | ||||
-rw-r--r-- | src/compare.rs | 15 | ||||
-rw-r--r-- | src/frontend/json.rs | 41 | ||||
-rw-r--r-- | src/frontend/list.rs | 27 | ||||
-rw-r--r-- | src/frontend/mod.rs | 3 | ||||
-rw-r--r-- | src/frontend/table.rs | 40 | ||||
-rw-r--r-- | src/main.rs | 46 |
18 files changed, 220 insertions, 11 deletions
@@ -26,3 +26,11 @@ filters = "0.3" version = ">=2.33" default-features = false features = [ "suggestions", "color", "wrap_help" ] + +[dependencies.csv] +version = "1" +optional = true + +[features] +default = ["compare_csv"] +compare_csv = ["csv"]
\ No newline at end of file diff --git a/librepology/src/v1/types/category.rs b/librepology/src/v1/types/category.rs index 1bc8c02..975fe7f 100644 --- a/librepology/src/v1/types/category.rs +++ b/librepology/src/v1/types/category.rs @@ -1,7 +1,7 @@ use std::ops::Deref; // list of package categories -#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize, new)] pub struct Category(String); impl Deref for Category { diff --git a/librepology/src/v1/types/download.rs b/librepology/src/v1/types/download.rs index e337baa..d52a2db 100644 --- a/librepology/src/v1/types/download.rs +++ b/librepology/src/v1/types/download.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use url::Url; // list of package downloads -#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, new)] pub struct Download(#[serde(with = "url_serde")] Url); impl Deref for Download { diff --git a/librepology/src/v1/types/effname.rs b/librepology/src/v1/types/effname.rs index ae38fd7..118f56e 100644 --- a/librepology/src/v1/types/effname.rs +++ b/librepology/src/v1/types/effname.rs @@ -1,7 +1,7 @@ use std::ops::Deref; // list of package downloads -#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize, new)] pub struct EffName(String); impl Deref for EffName { diff --git a/librepology/src/v1/types/license.rs b/librepology/src/v1/types/license.rs index 7d67a2e..f755888 100644 --- a/librepology/src/v1/types/license.rs +++ b/librepology/src/v1/types/license.rs @@ -1,7 +1,7 @@ use std::ops::Deref; // list of package licenses -#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize, new)] pub struct License(String); impl Deref for License { diff --git a/librepology/src/v1/types/maintainer.rs b/librepology/src/v1/types/maintainer.rs index d76c879..f97fc91 100644 --- a/librepology/src/v1/types/maintainer.rs +++ b/librepology/src/v1/types/maintainer.rs @@ -1,7 +1,7 @@ use std::ops::Deref; // list of package maintainers -#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize, new)] pub struct Maintainer(String); impl Deref for Maintainer { diff --git a/librepology/src/v1/types/name.rs b/librepology/src/v1/types/name.rs index 649e70d..70e3317 100644 --- a/librepology/src/v1/types/name.rs +++ b/librepology/src/v1/types/name.rs @@ -1,7 +1,7 @@ use std::ops::Deref; // package name as in repository (if different from version) -#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize, new)] pub struct Name(String); impl Deref for Name { diff --git a/librepology/src/v1/types/repo.rs b/librepology/src/v1/types/repo.rs index 7305ec6..062358e 100644 --- a/librepology/src/v1/types/repo.rs +++ b/librepology/src/v1/types/repo.rs @@ -1,7 +1,7 @@ use std::ops::Deref; // name of repository for this package -#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize, new)] pub struct Repo(String); impl Deref for Repo { diff --git a/librepology/src/v1/types/summary.rs b/librepology/src/v1/types/summary.rs index 494e671..0595fef 100644 --- a/librepology/src/v1/types/summary.rs +++ b/librepology/src/v1/types/summary.rs @@ -1,7 +1,7 @@ use std::ops::Deref; // one-line description of the package -#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize, new)] pub struct Summary(String); impl Deref for Summary { diff --git a/librepology/src/v1/types/version.rs b/librepology/src/v1/types/version.rs index cad703a..7ae7b85 100644 --- a/librepology/src/v1/types/version.rs +++ b/librepology/src/v1/types/version.rs @@ -1,7 +1,7 @@ use std::ops::Deref; // package version (sanitized) -#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize, new)] pub struct Version(String); impl Deref for Version { diff --git a/librepology/src/v1/types/www.rs b/librepology/src/v1/types/www.rs index a59ef02..1b5b89e 100644 --- a/librepology/src/v1/types/www.rs +++ b/librepology/src/v1/types/www.rs @@ -2,7 +2,7 @@ use std::ops::Deref; use url::Url; // list of package webpages -#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, new)] pub struct Www(#[serde(with = "url_serde")] Url); impl Deref for Www { @@ -90,6 +90,37 @@ pub fn build_cli<'a>() -> App<'a, 'a> { .required(true)) ) + + .subcommand(SubCommand::with_name("compare") + .about("Compare a list of packages to distro repositories") + .arg(Arg::with_name("compare-list") + .index(1) + .required(true) + .multiple(false) + .takes_value(true) + .value_name("FILE") + .help("Compare the data from this list to a list of distros out there. Supports JSON and CSV, based on file extension (.json / .csv)")) + .arg(Arg::with_name("compare-distros") + .index(2) + .required(true) + .multiple(true) + .takes_value(true) + .value_name("DIST") + .help("A list of repology distribution names to compare to")) + + .after_help(r#" + Compare a list of packages to all supplied repology distributions. + The list of packages shall have the following format: + + * CSV: + Header: name;version;comment + + * JSON: + { "name": "...", "version": "...", "comment": "..." } + + "#) + ) + .after_help(r#" repolocli can read data from stdin, if you want to postprocess repology.org data you already fetched from repology.org/api/v1 via curl (or some other method). diff --git a/src/compare.rs b/src/compare.rs new file mode 100644 index 0000000..c13d01e --- /dev/null +++ b/src/compare.rs @@ -0,0 +1,15 @@ +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct ComparePackage { + name: String, + version: String, +} + +impl ComparePackage { + pub fn name(&self) -> &String { + &self.name + } + + pub fn version(&self) -> &String { + &self.version + } +}
\ No newline at end of file diff --git a/src/frontend/json.rs b/src/frontend/json.rs index 9cf5573..b6a34e5 100644 --- a/src/frontend/json.rs +++ b/src/frontend/json.rs @@ -4,10 +4,14 @@ use std::ops::Deref; use librepology::v1::types::Package; use librepology::v1::types::Problem; +use librepology::v1::types::Repo; use failure::Fallible as Result; use failure::Error; use crate::frontend::Frontend; +use crate::backend::Backend; +use crate::compare::ComparePackage; +use librepology::v1::api::Api; pub struct JsonFrontend(Stdout); @@ -29,5 +33,42 @@ impl Frontend for JsonFrontend { let mut outlock = self.0.lock(); writeln!(outlock, "{}", output).map_err(Error::from) } + + fn compare_packages(&self, packages: Vec<ComparePackage>, backend: &Backend, filter_repos: Vec<Repo>) -> Result<()> { + #[derive(Serialize)] + struct PackageComp { + // not optimal, as we have to clone the inner variables from the package + // but using references is too complicated right now + package_name: String, + local_version: String, + upstream_repo: String, + upstream_version: String, + } + + let mut output: Vec<PackageComp> = vec![]; + + for package in packages.iter() { + let mut list = backend + .project(package.name().deref())? + .into_iter() + .filter(|p| filter_repos.contains(p.repo())) + .map(|upstream_package| { + PackageComp { + package_name: package.name().clone(), + local_version: package.version().clone(), + upstream_repo: upstream_package.repo().deref().clone(), + upstream_version: upstream_package.version().deref().clone(), + } + }) + .collect::<Vec<_>>(); + + output.append(&mut list); + } + + let output = serde_json::ser::to_string_pretty(&output)?; + + let mut outlock = self.0.lock(); + writeln!(outlock, "{}", output).map_err(Error::from) + } } diff --git a/src/frontend/list.rs b/src/frontend/list.rs index 8cff146..e870ab0 100644 --- a/src/frontend/list.rs +++ b/src/frontend/list.rs @@ -4,10 +4,14 @@ use std::ops::Deref; use librepology::v1::types::Package; use librepology::v1::types::Problem; +use librepology::v1::types::Repo; use failure::Fallible as Result; use failure::Error; use crate::frontend::Frontend; +use crate::backend::Backend; +use crate::compare::ComparePackage; +use librepology::v1::api::Api; pub struct ListFrontend(Stdout); @@ -67,5 +71,28 @@ impl Frontend for ListFrontend { }) }) } + + fn compare_packages(&self, packages: Vec<ComparePackage>, backend: &Backend, filter_repos: Vec<Repo>) -> Result<()> { + let mut output = self.0.lock(); + + for package in packages { + backend + .project(package.name().deref())? + .into_iter() + .filter(|p| filter_repos.contains(p.repo())) + .map(|upstream_package| { + writeln!(output, + "{our_package_name} - {our_package_version} - {up_repo_name} - {up_package_version}", + our_package_name = package.name().deref(), + our_package_version = package.version().deref(), + up_repo_name = upstream_package.repo().deref(), + up_package_version = upstream_package.version().deref() + ).map_err(Error::from) + }) + .collect::<Result<Vec<()>>>()?; + } + + Ok(()) + } } diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index 41ab972..43e5b5e 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -7,10 +7,13 @@ use crate::config::Configuration; use crate::frontend::list::ListFrontend; use crate::frontend::json::JsonFrontend; use crate::frontend::table::TableFrontend; +use crate::compare::ComparePackage; +use crate::backend::Backend; pub trait Frontend { fn list_packages(&self, packages: Vec<Package>) -> Result<()>; fn list_problems(&self, problems: Vec<Problem>) -> Result<()>; + fn compare_packages(&self, packages: Vec<ComparePackage>, backend: &Backend, filter_repos: Vec<Repo>) -> Result<()>; } pub mod list; diff --git a/src/frontend/table.rs b/src/frontend/table.rs index ba4e77a..6a30e9c 100644 --- a/src/frontend/table.rs +++ b/src/frontend/table.rs @@ -3,11 +3,15 @@ use std::ops::Deref; use librepology::v1::types::Package; use librepology::v1::types::Problem; +use librepology::v1::types::Repo; use failure::Fallible as Result; use prettytable::format; use prettytable::Table; use crate::frontend::Frontend; +use crate::backend::Backend; +use crate::compare::ComparePackage; +use librepology::v1::api::Api; pub struct TableFrontend(Stdout); @@ -90,5 +94,41 @@ impl Frontend for TableFrontend { Ok(()) } + + fn compare_packages(&self, packages: Vec<ComparePackage>, backend: &Backend, filter_repos: Vec<Repo>) -> Result<()> { + let mut table = Table::new(); + let format = format::FormatBuilder::new() + .column_separator('|') + .borders('|') + .separators( + &[format::LinePosition::Title, format::LinePosition::Top, format::LinePosition::Bottom], + format::LineSeparator::new('-', '+', '+', '+') + ) + .padding(1, 1) + .build(); + table.set_format(format); + + table.set_titles(row!["Name", "Local Version", "Repo", "Upstream Version"]); + + for package in packages { + backend + .project(package.name().deref())? + .into_iter() + .filter(|p| filter_repos.contains(p.repo())) + .for_each(|upstream_package| { + table.add_row(row![ + package.name().deref().clone(), + package.version().deref().clone(), + upstream_package.repo().deref().clone(), + upstream_package.version().deref().clone(), + ]); + }); + } + + let mut outlock = self.0.lock(); + table.print(&mut outlock)?; + + Ok(()) + } } diff --git a/src/main.rs b/src/main.rs index dddbe42..9acc496 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,9 @@ extern crate reqwest; extern crate tokio; extern crate filters; +#[cfg(feature = "compare_csv")] +extern crate csv; + #[macro_use] extern crate serde_derive; #[macro_use] extern crate log; #[macro_use] extern crate failure; @@ -18,8 +21,13 @@ mod config; mod backend; mod frontend; mod cli; +mod compare; use std::path::PathBuf; + +#[cfg(feature = "compare_csv")] +use std::io::Cursor; + use failure::err_msg; use failure::Error; use failure::Fallible as Result; @@ -27,6 +35,7 @@ use clap::ArgMatches; use filters::filter::Filter; use config::Configuration; +use compare::ComparePackage; use librepology::v1::api::Api; use librepology::v1::types::Repo; @@ -57,6 +66,33 @@ fn initialize_logging(app: &ArgMatches) -> Result<()> { .map_err(Error::from) } +fn deserialize_package_list(s: String, filepath: &str) -> Result<Vec<ComparePackage>> { + let pb = PathBuf::from(filepath); + let ext = pb + .extension() + .ok_or_else(|| format_err!("Couldn't get file extension: {}", filepath))? + .to_str() + .ok_or_else(|| format_err!("Not valid Unicode: {}", filepath))?; + + match ext { + "json" => { + serde_json::from_str(&s).map_err(Error::from) + }, + + #[cfg(feature = "compare_csv")] + "csv" => { + let cursor = Cursor::new(s); + let mut v : Vec<ComparePackage> = vec![]; + for element in csv::Reader::from_reader(cursor).deserialize() { + v.push(element?); + } + Ok(v) + }, + + other => Err(format_err!("Unknown file extension: {}", other))?, + } +} + fn main() -> Result<()> { let app = cli::build_cli().get_matches(); initialize_logging(&app)?; @@ -145,7 +181,15 @@ fn main() -> Result<()> { .collect(); frontend.list_problems(problems)?; - } + }, + ("compare", Some(mtch)) => { + let repos = mtch.values_of("compare-distros").unwrap().map(|s| Repo::new(String::from(s))).collect(); + let file_path = mtch.value_of("compare-list").unwrap(); // safe by clap + let content = ::std::fs::read_to_string(file_path).map_err(Error::from)?; + let pkgs : Vec<ComparePackage> = deserialize_package_list(content, file_path)?; + + frontend.compare_packages(pkgs, &backend, repos)?; + }, (other, _mtch) => { if app.is_present("input_stdin") { |