From 95cc8da13b712ed52b37446fde173f547581c0e2 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Wed, 24 Apr 2019 09:44:28 +0200 Subject: Add "compare" functionality in CLI --- src/cli.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/cli.rs b/src/cli.rs index 25533cc..cf06dbe 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -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). -- cgit v1.2.3 From 0d39219b0a32d3024702da8cba97b67b6558a6a1 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Wed, 24 Apr 2019 09:45:06 +0200 Subject: Add Frontend::compare_packages() --- src/frontend/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index 41ab972..0027fd8 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -7,10 +7,12 @@ use crate::config::Configuration; use crate::frontend::list::ListFrontend; use crate::frontend::json::JsonFrontend; use crate::frontend::table::TableFrontend; +use crate::backend::Backend; pub trait Frontend { fn list_packages(&self, packages: Vec) -> Result<()>; fn list_problems(&self, problems: Vec) -> Result<()>; + fn compare_packages(&self, packages: Vec, backend: &Backend, filter_repos: Vec) -> Result<()>; } pub mod list; -- cgit v1.2.3 From 09f6efadcac7a3c6c78701518eb478c6e2bb4eab Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Wed, 24 Apr 2019 09:45:25 +0200 Subject: Implement ListFrontend::compare_packages() --- src/frontend/list.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/frontend/list.rs b/src/frontend/list.rs index 8cff146..b229ecd 100644 --- a/src/frontend/list.rs +++ b/src/frontend/list.rs @@ -4,10 +4,13 @@ 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 librepology::v1::api::Api; pub struct ListFrontend(Stdout); @@ -67,5 +70,28 @@ impl Frontend for ListFrontend { }) }) } + + fn compare_packages(&self, packages: Vec, backend: &Backend, filter_repos: Vec) -> 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::>>()?; + } + + Ok(()) + } } -- cgit v1.2.3 From 810ea6e400c9f52530ca089c24d1a93c035c182e Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Wed, 24 Apr 2019 09:45:47 +0200 Subject: Implement JsonFrontend::compare_packages() --- src/frontend/json.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/frontend/json.rs b/src/frontend/json.rs index 9cf5573..63b7bbe 100644 --- a/src/frontend/json.rs +++ b/src/frontend/json.rs @@ -4,10 +4,13 @@ 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 librepology::v1::api::Api; pub struct JsonFrontend(Stdout); @@ -29,5 +32,42 @@ impl Frontend for JsonFrontend { let mut outlock = self.0.lock(); writeln!(outlock, "{}", output).map_err(Error::from) } + + fn compare_packages(&self, packages: Vec, backend: &Backend, filter_repos: Vec) -> 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 = 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().deref().clone(), + local_version: package.version().deref().clone(), + upstream_repo: upstream_package.repo().deref().clone(), + upstream_version: upstream_package.version().deref().clone(), + } + }) + .collect::>(); + + 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) + } } -- cgit v1.2.3 From dc43c56e3d7ef77c0b83289c6a8990be384ba8c9 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Wed, 24 Apr 2019 09:45:58 +0200 Subject: Implement TableFrontend::compare_packages() --- src/frontend/table.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/frontend/table.rs b/src/frontend/table.rs index ba4e77a..f840482 100644 --- a/src/frontend/table.rs +++ b/src/frontend/table.rs @@ -3,11 +3,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 prettytable::format; use prettytable::Table; use crate::frontend::Frontend; +use crate::backend::Backend; +use librepology::v1::api::Api; pub struct TableFrontend(Stdout); @@ -90,5 +93,41 @@ impl Frontend for TableFrontend { Ok(()) } + + fn compare_packages(&self, packages: Vec, backend: &Backend, filter_repos: Vec) -> 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(()) + } } -- cgit v1.2.3 From 4eb2b6a4544f0c2cfd0d72b1fac27bfb2882a4b4 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Wed, 24 Apr 2019 09:46:43 +0200 Subject: Add "compare" functionality in main() Including the new dependency "csv" which is included by default. With this, one can pass a CSV file to compare against, as well as a JSON file. --- Cargo.toml | 8 ++++++++ src/main.rs | 45 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 877701b..290c61a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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/src/main.rs b/src/main.rs index dddbe42..62e193d 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; @@ -20,6 +23,10 @@ mod frontend; mod cli; use std::path::PathBuf; + +#[cfg(feature = "compare_csv")] +use std::io::Cursor; + use failure::err_msg; use failure::Error; use failure::Fallible as Result; @@ -29,6 +36,7 @@ use filters::filter::Filter; use config::Configuration; 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"); @@ -57,6 +65,33 @@ fn initialize_logging(app: &ArgMatches) -> Result<()> { .map_err(Error::from) } +fn deserialize_package_list(s: String, filepath: &str) -> Result> { + 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 = 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 +180,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 = deserialize_package_list(content, file_path)?; + + frontend.compare_packages(pkgs, &backend, repos)?; + }, (other, _mtch) => { if app.is_present("input_stdin") { -- cgit v1.2.3 From fcf973c16d4128dda76b809b0d5b9a2635afae47 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Wed, 24 Apr 2019 09:56:27 +0200 Subject: Add missing "ComparePackage" type Because we cannot build a "Package" from the compare file (because we shouldn't, because we only need _some_ information about the package), we should use a special helper type for deserializing the contents of the file. --- src/compare.rs | 15 +++++++++++++++ src/frontend/json.rs | 7 ++++--- src/frontend/list.rs | 3 ++- src/frontend/mod.rs | 3 ++- src/frontend/table.rs | 3 ++- src/main.rs | 9 +++++---- 6 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 src/compare.rs 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 63b7bbe..b6a34e5 100644 --- a/src/frontend/json.rs +++ b/src/frontend/json.rs @@ -10,6 +10,7 @@ use failure::Error; use crate::frontend::Frontend; use crate::backend::Backend; +use crate::compare::ComparePackage; use librepology::v1::api::Api; pub struct JsonFrontend(Stdout); @@ -33,7 +34,7 @@ impl Frontend for JsonFrontend { writeln!(outlock, "{}", output).map_err(Error::from) } - fn compare_packages(&self, packages: Vec, backend: &Backend, filter_repos: Vec) -> Result<()> { + fn compare_packages(&self, packages: Vec, backend: &Backend, filter_repos: Vec) -> Result<()> { #[derive(Serialize)] struct PackageComp { // not optimal, as we have to clone the inner variables from the package @@ -53,8 +54,8 @@ impl Frontend for JsonFrontend { .filter(|p| filter_repos.contains(p.repo())) .map(|upstream_package| { PackageComp { - package_name: package.name().deref().clone(), - local_version: package.version().deref().clone(), + package_name: package.name().clone(), + local_version: package.version().clone(), upstream_repo: upstream_package.repo().deref().clone(), upstream_version: upstream_package.version().deref().clone(), } diff --git a/src/frontend/list.rs b/src/frontend/list.rs index b229ecd..e870ab0 100644 --- a/src/frontend/list.rs +++ b/src/frontend/list.rs @@ -10,6 +10,7 @@ use failure::Error; use crate::frontend::Frontend; use crate::backend::Backend; +use crate::compare::ComparePackage; use librepology::v1::api::Api; pub struct ListFrontend(Stdout); @@ -71,7 +72,7 @@ impl Frontend for ListFrontend { }) } - fn compare_packages(&self, packages: Vec, backend: &Backend, filter_repos: Vec) -> Result<()> { + fn compare_packages(&self, packages: Vec, backend: &Backend, filter_repos: Vec) -> Result<()> { let mut output = self.0.lock(); for package in packages { diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index 0027fd8..43e5b5e 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -7,12 +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) -> Result<()>; fn list_problems(&self, problems: Vec) -> Result<()>; - fn compare_packages(&self, packages: Vec, backend: &Backend, filter_repos: Vec) -> Result<()>; + fn compare_packages(&self, packages: Vec, backend: &Backend, filter_repos: Vec) -> Result<()>; } pub mod list; diff --git a/src/frontend/table.rs b/src/frontend/table.rs index f840482..6a30e9c 100644 --- a/src/frontend/table.rs +++ b/src/frontend/table.rs @@ -10,6 +10,7 @@ use prettytable::Table; use crate::frontend::Frontend; use crate::backend::Backend; +use crate::compare::ComparePackage; use librepology::v1::api::Api; pub struct TableFrontend(Stdout); @@ -94,7 +95,7 @@ impl Frontend for TableFrontend { Ok(()) } - fn compare_packages(&self, packages: Vec, backend: &Backend, filter_repos: Vec) -> Result<()> { + fn compare_packages(&self, packages: Vec, backend: &Backend, filter_repos: Vec) -> Result<()> { let mut table = Table::new(); let format = format::FormatBuilder::new() .column_separator('|') diff --git a/src/main.rs b/src/main.rs index 62e193d..9acc496 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,7 @@ mod config; mod backend; mod frontend; mod cli; +mod compare; use std::path::PathBuf; @@ -34,9 +35,9 @@ use clap::ArgMatches; use filters::filter::Filter; 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"); @@ -65,7 +66,7 @@ fn initialize_logging(app: &ArgMatches) -> Result<()> { .map_err(Error::from) } -fn deserialize_package_list(s: String, filepath: &str) -> Result> { +fn deserialize_package_list(s: String, filepath: &str) -> Result> { let pb = PathBuf::from(filepath); let ext = pb .extension() @@ -81,7 +82,7 @@ fn deserialize_package_list(s: String, filepath: &str) -> Result> { #[cfg(feature = "compare_csv")] "csv" => { let cursor = Cursor::new(s); - let mut v : Vec = vec![]; + let mut v : Vec = vec![]; for element in csv::Reader::from_reader(cursor).deserialize() { v.push(element?); } @@ -185,7 +186,7 @@ fn main() -> Result<()> { 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 = deserialize_package_list(content, file_path)?; + let pkgs : Vec = deserialize_package_list(content, file_path)?; frontend.compare_packages(pkgs, &backend, repos)?; }, -- cgit v1.2.3 From 5f34e97efb8d67d7b3763d53d747d06661bde558 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Wed, 24 Apr 2019 09:44:03 +0200 Subject: types: Add "new" derive --- librepology/src/v1/types/category.rs | 2 +- librepology/src/v1/types/download.rs | 2 +- librepology/src/v1/types/effname.rs | 2 +- librepology/src/v1/types/license.rs | 2 +- librepology/src/v1/types/maintainer.rs | 2 +- librepology/src/v1/types/name.rs | 2 +- librepology/src/v1/types/repo.rs | 2 +- librepology/src/v1/types/summary.rs | 2 +- librepology/src/v1/types/version.rs | 2 +- librepology/src/v1/types/www.rs | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) 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 { -- cgit v1.2.3