summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2021-04-06 10:25:25 +0200
committerMatthias Beyer <mail@beyermatthias.de>2021-04-06 10:25:25 +0200
commitf27ff3c73f91e3c5821ed54bbc9b7e6f641dbf53 (patch)
treeed59bb1e2e8a5db37177ed9e216709fe37f33292
parent9251445a5ac5315dca3921b7e77ce4a37cd42497 (diff)
parenta6caaff3c89bb21fd1ed83fae023bb7df9ce59b3 (diff)
Merge remote-tracking branch 'github/master'
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
-rw-r--r--CHANGELOG.md8
-rw-r--r--Cargo.toml2
-rw-r--r--README.md9
-rw-r--r--librepology/.gitignore1
-rw-r--r--librepology/Cargo.toml9
-rw-r--r--librepology/src/lib.rs9
-rw-r--r--librepology/src/packagefilters.rs81
-rw-r--r--librepology/src/v1/restapi.rs5
-rw-r--r--librepology/src/v1/types/package.rs38
-rw-r--r--repolocli.toml16
-rw-r--r--shell.nix25
-rw-r--r--src/backend.rs3
-rw-r--r--src/cli.rs19
-rw-r--r--src/config.rs30
-rw-r--r--src/frontend/list.rs3
-rw-r--r--src/frontend/table.rs8
-rw-r--r--src/main.rs72
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.
+
diff --git a/Cargo.toml b/Cargo.toml
index 935af97..b7cacfc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/README.md b/README.md
index 907df93..a21f078 100644
--- a/README.md
+++ b/README.md
@@ -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)))
diff --git a/src/cli.rs b/src/cli.rs
index c04052c..95ef9a2 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -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))