diff options
author | Matthias Beyer <mail@beyermatthias.de> | 2021-04-06 10:25:25 +0200 |
---|---|---|
committer | Matthias Beyer <mail@beyermatthias.de> | 2021-04-06 10:25:25 +0200 |
commit | f27ff3c73f91e3c5821ed54bbc9b7e6f641dbf53 (patch) | |
tree | ed59bb1e2e8a5db37177ed9e216709fe37f33292 | |
parent | 9251445a5ac5315dca3921b7e77ce4a37cd42497 (diff) | |
parent | a6caaff3c89bb21fd1ed83fae023bb7df9ce59b3 (diff) |
Merge remote-tracking branch 'github/master'
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
-rw-r--r-- | CHANGELOG.md | 8 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | README.md | 9 | ||||
-rw-r--r-- | librepology/.gitignore | 1 | ||||
-rw-r--r-- | librepology/Cargo.toml | 9 | ||||
-rw-r--r-- | librepology/src/lib.rs | 9 | ||||
-rw-r--r-- | librepology/src/packagefilters.rs | 81 | ||||
-rw-r--r-- | librepology/src/v1/restapi.rs | 5 | ||||
-rw-r--r-- | librepology/src/v1/types/package.rs | 38 | ||||
-rw-r--r-- | repolocli.toml | 16 | ||||
-rw-r--r-- | shell.nix | 25 | ||||
-rw-r--r-- | src/backend.rs | 3 | ||||
-rw-r--r-- | src/cli.rs | 19 | ||||
-rw-r--r-- | src/config.rs | 30 | ||||
-rw-r--r-- | src/frontend/list.rs | 3 | ||||
-rw-r--r-- | src/frontend/table.rs | 8 | ||||
-rw-r--r-- | src/main.rs | 72 |
17 files changed, 179 insertions, 159 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..223c195 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## next + +## v0.1.0-alpha.1 + +* Initial release. + @@ -22,7 +22,6 @@ serde = "1" serde_derive = "1" serde_json = "1" toml = "0.5" -toml-query = "0.8" url = "1.7" url_serde = "0.2" xdg = "2" @@ -32,6 +31,7 @@ prettytable-rs = "0.8" filters = "0.3" boolinator = "2" itertools = "0.8" +semver = "0.10" [dependencies.clap] version = ">=2.33" @@ -5,8 +5,17 @@ This repository is a WIP project to bring repology to the commandline. There's a library under `./librepology/` and a commandline interface for viewing repology data. + +## Stability + +This is alpha-quality software, use at your own risk. + MSRV: 1.42.0 +As the repology.org API is [not stable](https://repology.org/api), this program +won't be stable for a long time. Still, 0.x.y releases may happen. + + ## License Library: MPL 2.0 diff --git a/librepology/.gitignore b/librepology/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/librepology/.gitignore @@ -0,0 +1 @@ +target diff --git a/librepology/Cargo.toml b/librepology/Cargo.toml index 7dadd2f..51afe45 100644 --- a/librepology/Cargo.toml +++ b/librepology/Cargo.toml @@ -11,7 +11,6 @@ readme = "./README.md" keywords = ["api", "repology"] categories = ["data-structures", "api-bindings"] license = "MPL-2.0" -license-file = "./LICENSE" [badges] maintenance = { status = "actively-developed" } @@ -25,12 +24,6 @@ url_serde = "0.2" failure = "0.1" log = "0.4" derive_more = "0.14" +derive-new = "0.5" curl = "0.4" -filters = { version = "0.3", optional = true } -derive-new = { version = "0.5", optional = true } - -[features] -# By default, we include the filters functionality -default = [ "packagefilters" ] -packagefilters = ["filters", "derive-new"] diff --git a/librepology/src/lib.rs b/librepology/src/lib.rs index 76992fd..d0904a4 100644 --- a/librepology/src/lib.rs +++ b/librepology/src/lib.rs @@ -5,16 +5,9 @@ extern crate url; extern crate url_serde; extern crate curl; -#[cfg(feature = "packagefilters")] -extern crate filters; -#[cfg(feature = "packagefilters")] -#[macro_use] extern crate derive_new; - #[macro_use] extern crate serde_derive; #[macro_use] extern crate log; #[macro_use] extern crate derive_more; +#[macro_use] extern crate derive_new; pub mod v1; - -#[cfg(feature = "packagefilters")] -pub mod packagefilters; diff --git a/librepology/src/packagefilters.rs b/librepology/src/packagefilters.rs deleted file mode 100644 index d1293d4..0000000 --- a/librepology/src/packagefilters.rs +++ /dev/null @@ -1,81 +0,0 @@ -use filters::filter::Filter; - -use crate::v1::types::{Package, Repo, Name, Status, Version, License, Maintainer}; - - -#[derive(new, Debug)] -pub struct PackageRepoNameFilter(Repo); - -/// Filter implementation for PackageRepoNameFilter -/// -/// filters based on _equality_! -impl Filter<Package> for PackageRepoNameFilter { - fn filter(&self, package: &Package) -> bool { - self.0 == *package.repo() - } -} - - -#[derive(new, Debug)] -pub struct PackageNameFilter(Name); - -/// Filter implementation for PackageNameFilter -/// -/// filters based on _equality_! -impl Filter<Package> for PackageNameFilter { - fn filter(&self, package: &Package) -> bool { - self.0 == *package.name() - } -} - - -#[derive(new, Debug)] -pub struct PackageVersionFilter(Version); - -/// Filter implementation for PackageVersionFilter -/// -/// filters based on _equality_! -impl Filter<Package> for PackageVersionFilter { - fn filter(&self, package: &Package) -> bool { - self.0 == *package.version() - } -} - - -#[derive(new, Debug)] -pub struct PackageStatusFilter(Status); - -/// Filter implementation for PackageStatusFilter -/// -/// filters based on _equality_! -impl Filter<Package> for PackageStatusFilter { - fn filter(&self, package: &Package) -> bool { - package.status().map(|s| self.0 == *s).unwrap_or(false) - } -} - - -#[derive(new, Debug)] -pub struct PackageLicenseFilter(License); - -/// Filter implementation for PackageLicenseFilter -/// -/// filters based on _equality_! -impl Filter<Package> for PackageLicenseFilter { - fn filter(&self, package: &Package) -> bool { - package.licenses().map(|lcs| lcs.iter().any(|l| self.0 == *l)).unwrap_or(false) - } -} - - -#[derive(new, Debug)] -pub struct PackageMaintainerFilter(Maintainer); - -/// Filter implementation for PackageMaintainerFilter -/// -/// filters based on _equality_! -impl Filter<Package> for PackageMaintainerFilter { - fn filter(&self, package: &Package) -> bool { - package.maintainers().map(|mts| mts.iter().any(|m| self.0 == *m)).unwrap_or(false) - } -} diff --git a/librepology/src/v1/restapi.rs b/librepology/src/v1/restapi.rs index bc1a693..1e8711c 100644 --- a/librepology/src/v1/restapi.rs +++ b/librepology/src/v1/restapi.rs @@ -42,17 +42,20 @@ impl Api for RestApi { fn project<N: AsRef<str>>(&self, name: N) -> Result<Vec<Package>> { let url = format!("{}api/v1/project/{}", self.repology, name.as_ref()); + trace!("Request: {}", url); serde_json::from_str(&self.send_request(url)?).map_err(Error::from) } fn problems_for_repo<R: AsRef<str>>(&self, repo: R) -> Result<Vec<Problem>> { let url = format!("{}api/v1/repository/{}/problems", self.repology, repo.as_ref()); + trace!("Request: {}", url); serde_json::from_str(&self.send_request(url)?).map_err(Error::from) } fn problems_for_maintainer<M: AsRef<str>>(&self, maintainer: M) -> Result<Vec<Problem>> { let url = format!("{}api/v1/maintainer/{}/problems", self.repology, maintainer.as_ref()); + trace!("Request: {}", url); serde_json::from_str(&self.send_request(url)?).map_err(Error::from) } -}
\ No newline at end of file +} diff --git a/librepology/src/v1/types/package.rs b/librepology/src/v1/types/package.rs index fc66faa..4623fd3 100644 --- a/librepology/src/v1/types/package.rs +++ b/librepology/src/v1/types/package.rs @@ -5,8 +5,17 @@ pub struct Package { /// name of repository for this package repo: Repo, - /// name - name: Name, + /// package name(s) as used in repository - generic one and/or source package name and/or binary package name, whichever is applicable + name: Option<Name>, + + /// package name(s) as used in repository - generic one and/or source package name and/or binary package name, whichever is applicable + srcname: Option<Name>, + + /// package name(s) as used in repository - generic one and/or source package name and/or binary package name, whichever is applicable + binname: Option<Name>, + + /// package name as shown to the user by Repology + visiblename: Option<Name>, /// version version: Version, @@ -35,8 +44,29 @@ impl Package { &self.repo } - pub fn name(&self) -> &Name { - &self.name + pub fn name(&self) -> Option<&Name> { + self.name.as_ref() + } + + pub fn srcname(&self) -> Option<&Name> { + self.srcname.as_ref() + } + + pub fn binname(&self) -> Option<&Name> { + self.binname.as_ref() + } + + pub fn visiblename(&self) -> Option<&Name> { + self.visiblename.as_ref() + } + + /// Get name, srcname, binname or visiblename, whatever is set + /// (in this order) + pub fn any_name(&self) -> Option<&Name> { + self.name() + .or_else(|| self.srcname()) + .or_else(|| self.binname()) + .or_else(|| self.visiblename()) } pub fn version(&self) -> &Version { diff --git a/repolocli.toml b/repolocli.toml index d414a57..c25f7ce 100644 --- a/repolocli.toml +++ b/repolocli.toml @@ -12,19 +12,3 @@ blacklist = [ "Arch", # sorry Archers! "scientific_7x", ] - - -# If you're a packager, and you have packages locally you care about, -# repolocli can show you differences between your packages and "upstream" packages, if you want that. -# For that, you have to list these packages here (in repology manner, so you have to use the -# same names as repology! -# -# Yes, you have to manually keep that list up to date, sorry about that. - -[[local_packages]] -name = "jq" # if you don't know jq, go check it out NOW! -local_version = "1.5" - -[[local_packages]] -name = "languagetool" -local_version = "4.4.1" diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..80c97c2 --- /dev/null +++ b/shell.nix @@ -0,0 +1,25 @@ +{ ... }: + +let + moz_overlay = import ( + builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz + ); + + pkgs = import <nixpkgs> { overlays = [ moz_overlay ]; }; +in +pkgs.mkShell { + buildInputs = with pkgs; [ + rustChannels.stable.rust-std + rustChannels.stable.rust + rustChannels.stable.rustc + rustChannels.stable.cargo + + openssl + cmake + gcc + + pkgconfig + ]; + LIBCLANG_PATH = "${pkgs.llvmPackages.libclang}/lib"; +} + diff --git a/src/backend.rs b/src/backend.rs index fe1fa7e..c29b05e 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -43,9 +43,10 @@ impl Api for Backend { pub fn new_backend(app: &ArgMatches, config: &Configuration) -> Result<Backend> { if app.is_present("input_stdin") { + trace!("Building new STDIN backend"); Ok(Backend::Stdin(StdinWrapper::from(::std::io::stdin()))) } else { - debug!("Constructing backend"); + trace!("Building new remote backend"); let url = config.repology_url().as_str().into(); trace!("url = {}", url); Ok(Backend::RepologyOrg(RestApi::new(url))) @@ -79,6 +79,23 @@ pub fn build_cli<'a>() -> App<'a, 'a> { .help("Sort output by repository") .conflicts_with("sort-version") ) + .arg(Arg::with_name("latest") + .long("latest") + .required(false) + .multiple(false) + .takes_value(false) + .help("Try to find the lastest version (version is string-compared if not used with --semver)") + .conflicts_with("sort-version") + .conflicts_with("sort-repo") + ) + .arg(Arg::with_name("semver") + .long("semver") + .required(false) + .multiple(false) + .takes_value(false) + .requires("latest") + .help("Try to find latest version using semver. If semver could not be parsed, equality is assumed, which might yield bogus results.") + ) ) .subcommand(SubCommand::with_name("problems") @@ -161,4 +178,4 @@ pub fn build_cli<'a>() -> App<'a, 'a> { In this case, repolocli is only a easier-to-use 'jq' (if you don't know jq, look it up NOW!). "#) -}
\ No newline at end of file +} diff --git a/src/config.rs b/src/config.rs index 7d47004..fde6c1e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,3 @@ -use std::ops::Deref; - use url::Url; #[derive(Debug, Serialize, Deserialize)] @@ -13,9 +11,6 @@ pub struct Configuration { #[serde(rename = "blacklist")] blacklist: Vec<String>, - - #[serde(rename = "local_packages")] - local_packages: Option<Vec<Package>>, } impl Configuration { @@ -31,30 +26,5 @@ impl Configuration { &self.blacklist } - // unused - //pub fn local_packages(&self) -> Option<&Vec<Package>> { - // self.local_packages.as_ref() - //} - -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Package { - #[serde(rename = "name")] - name: String, - - #[serde(rename = "local_version")] - local_version: Version, } -/// Not reusing the librepology type here because it might change -#[derive(Debug, Serialize, Deserialize)] -pub struct Version(String); - -impl Deref for Version { - type Target = String; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/src/frontend/list.rs b/src/frontend/list.rs index e4ed7d7..950cfe2 100644 --- a/src/frontend/list.rs +++ b/src/frontend/list.rs @@ -2,6 +2,7 @@ use std::io::Stdout; use std::io::Write; use std::ops::Deref; +use librepology::v1::types::Name; use librepology::v1::types::Package; use librepology::v1::types::Problem; use librepology::v1::types::Repo; @@ -48,7 +49,7 @@ impl Frontend for ListFrontend { writeln!(outlock, "{name:10} - {version:8} - {repo:15} - {status:5} - {www}", - name = package.name().deref(), + name = package.any_name().map(Name::deref).map(String::deref).unwrap_or_else(|| "<unknown>"), version = package.version().deref(), repo = package.repo().deref(), status = status, diff --git a/src/frontend/table.rs b/src/frontend/table.rs index 2bef1b8..e7b4840 100644 --- a/src/frontend/table.rs +++ b/src/frontend/table.rs @@ -1,6 +1,7 @@ use std::io::Stdout; use std::ops::Deref; +use librepology::v1::types::Name; use librepology::v1::types::Package; use librepology::v1::types::Problem; use librepology::v1::types::Repo; @@ -64,7 +65,12 @@ impl Frontend for TableFrontend { String::from("") }; // not optimal, but works for now - table.add_row(row![package.name(), package.version(), package.repo(), status, url]); + let name = package.any_name() + .map(Name::deref) + .map(String::clone) + .unwrap_or_else(|| String::from("<unknown>")); + + table.add_row(row![name, package.version(), package.repo(), status, url]); }); self.print(table) } diff --git a/src/main.rs b/src/main.rs index eea89f4..8b7e67b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,13 @@ extern crate serde; extern crate serde_json; extern crate toml; -extern crate toml_query; extern crate url; extern crate xdg; extern crate flexi_logger; extern crate filters; extern crate boolinator; extern crate itertools; +extern crate semver; #[cfg(feature = "compare_csv")] extern crate csv; @@ -24,6 +24,7 @@ mod cli; mod compare; use std::path::PathBuf; +use std::cmp::Ordering; #[cfg(feature = "compare_csv")] use std::io::Cursor; @@ -36,11 +37,13 @@ use clap::ArgMatches; use filters::filter::Filter; use boolinator::Boolinator; use itertools::Itertools; +use semver::Version as SemverVersion; use config::Configuration; use compare::ComparePackage; use librepology::v1::api::Api; use librepology::v1::types::Repo; +use librepology::v1::types::Package; fn initialize_logging(app: &ArgMatches) -> Result<()> { let verbosity = app.occurrences_of("verbose"); @@ -86,7 +89,12 @@ fn deserialize_package_list(s: String, filepath: &str) -> Result<Vec<ComparePack "csv" => { let cursor = Cursor::new(s); let mut v : Vec<ComparePackage> = vec![]; - for element in csv::Reader::from_reader(cursor).deserialize() { + let mut reader = csv::ReaderBuilder::new() + .has_headers(true) + .delimiter(b';') + .from_reader(cursor); + + for element in reader.deserialize() { v.push(element?); } Ok(v) @@ -104,8 +112,10 @@ fn app() -> Result<()> { .value_of("config") .map(PathBuf::from) { + debug!("Found passed configuration file at {}", path.display()); Ok(path) } else { + debug!("Searching for configuration in XDG"); xdg::BaseDirectories::new()? .find_config_file("repolocli.toml") .ok_or_else(|| err_msg("Cannot find repolocli.toml")) @@ -121,8 +131,13 @@ fn app() -> Result<()> { }?; trace!("Config deserialized"); + debug!("Initializing Backend"); let backend = crate::backend::new_backend(&app, &config)?; + debug!("Backend initialized"); + + debug!("Initializing Frontend"); let frontend = crate::frontend::new_frontend(&app, &config)?; + debug!("Frontend initialized"); let repository_filter = { let blacklist_filter = |repo: &Repo| -> bool { @@ -147,10 +162,13 @@ fn app() -> Result<()> { blacklist_filter.or(whitelist_filter) }; + debug!("Repository filter constructed successfully"); match app.subcommand() { ("project", Some(mtch)) => { - trace!("Handling project"); + debug!("Subcommand: 'project'"); + trace!("sort-versions: {}", mtch.is_present("sort-version")); + trace!("sort-repository: {}", mtch.is_present("sort-repo")); let name = if app.is_present("input_stdin") { // Ugly, but works: @@ -162,31 +180,64 @@ fn app() -> Result<()> { mtch.value_of("project_name").unwrap() // safe by clap }; - let packages = { + let mut packages: Vec<Package> = { + debug!("Fetching packages"); let iter = backend .project(&name)? .into_iter() .filter(|package| repository_filter.filter(package.repo())); - if mtch.is_present("sort-versions"){ + if mtch.is_present("sort-version"){ + trace!("Sorting by version"); iter.sorted_by(|a, b| Ord::cmp(a.version(), b.version())) .collect() } else if mtch.is_present("sort-repo") { + trace!("Sorting by repository"); iter.sorted_by(|a, b| Ord::cmp(a.repo(), b.repo())) .collect() } else { + trace!("Not sorting"); iter.collect() } }; + + let packages = if mtch.is_present("latest") { + if mtch.is_present("semver") { + let comp = |a: &Package, b: &Package| { + let av = SemverVersion::parse(a.version()); + let bv = SemverVersion::parse(b.version()); + + if let (Ok(av), Ok(bv)) = (av, bv) { + av.partial_cmp(&bv).unwrap_or(Ordering::Equal) + } else { + Ordering::Equal + } + }; + + packages.sort_by(comp); + } else { + packages.sort_by(|a, b| a.version().partial_cmp(b.version()).unwrap_or(Ordering::Equal)); + } + packages.pop().into_iter().collect::<Vec<_>>() + } else { + packages + }; + + debug!("Listing packages in frontend"); frontend.list_packages(packages) }, + ("problems", Some(mtch)) => { - trace!("Handling problems"); + debug!("Subcommand: 'problems'"); let repo = mtch.value_of("repo"); let maintainer = mtch.value_of("maintainer"); + trace!("repo = {:?}", repo); + trace!("maintainer = {:?}", maintainer); + let problems = { + debug!("Finding problems..."); let iter = match (repo, maintainer) { (Some(r), None) => backend.problems_for_repo(&r)?, (None, Some(m)) => backend.problems_for_maintainer(&m)?, @@ -197,28 +248,36 @@ fn app() -> Result<()> { .filter(|problem| repository_filter.filter(problem.repo())); if mtch.is_present("sort-maintainer") { + trace!("Sorting problems by maintainer"); iter.sorted_by(|a, b| Ord::cmp(a.maintainer(), b.maintainer())) .collect() } else if mtch.is_present("sort-repo") { + trace!("Sorting problems by repo"); iter.sorted_by(|a, b| Ord::cmp(a.repo(), b.repo())) .collect() } else { + trace!("Not sorting problems"); iter.collect() } }; + debug!("Listing problems in frontend"); frontend.list_problems(problems) }, + ("compare", Some(mtch)) => { + debug!("Subcommand: 'compare'"); let repos = mtch.values_of("compare-distros").unwrap().map(String::from).map(Repo::new).collect(); let file_path = mtch.value_of("compare-list").unwrap(); // safe by clap let content = ::std::fs::read_to_string(file_path)?; let pkgs : Vec<ComparePackage> = deserialize_package_list(content, file_path)?; + debug!("Comparing packages..."); frontend.compare_packages(pkgs, &backend, repos) }, (other, _mtch) => { + debug!("Subcommand: {}", other); app.is_present("input_stdin") .as_result((), format_err!("Input not from stdin")) .and_then(|_| { @@ -232,6 +291,7 @@ fn app() -> Result<()> { .filter(|package| repository_filter.filter(package.repo())) .collect(); + debug!("Listing packages"); frontend.list_packages(packages) }) .map_err(|_| format_err!("Unknown command: {}", other)) |