From ee0e580699373dc4d915372be583c6b1f9341b79 Mon Sep 17 00:00:00 2001 From: Jan Range <30547301+JR-1991@users.noreply.github.com> Date: Tue, 7 Oct 2025 12:42:30 +0200 Subject: [PATCH 1/3] Migrate CLI from structopt to clap Replaces structopt usage with clap throughout CLI modules for argument parsing and subcommand definitions. Updates derive macros, argument attributes, and adds custom styles for improved help output. Introduces a wrapper for file uploads in dataset CLI for better parsing and cloning. --- src/bin/cli.rs | 35 ++++++++--- src/cli/admin.rs | 35 +++++------ src/cli/auth.rs | 14 ++--- src/cli/collection.rs | 27 ++++---- src/cli/dataset.rs | 143 +++++++++++++++++++++--------------------- src/cli/file.rs | 30 ++++----- src/cli/info.rs | 11 ++-- 7 files changed, 147 insertions(+), 148 deletions(-) diff --git a/src/bin/cli.rs b/src/bin/cli.rs index a8a32e8..9bbb560 100644 --- a/src/bin/cli.rs +++ b/src/bin/cli.rs @@ -1,9 +1,9 @@ use std::error::Error; +use clap::Parser; use colored::Colorize; use dataverse::cli::admin::AdminSubCommand; use dataverse::cli::auth::{prompt_for_credentials, AuthProfile, AuthSubCommand}; -use structopt::StructOpt; use dataverse::cli::base::Matcher; use dataverse::cli::collection::CollectionSubCommand; @@ -13,42 +13,59 @@ use dataverse::cli::info::InfoSubCommand; use dataverse::client::BaseClient; use dataverse::search_api::query::SearchQuery; +fn get_styles() -> clap::builder::Styles { + clap::builder::Styles::styled() + .header(clap::builder::styling::AnsiColor::Green.on_default().bold()) + .usage(clap::builder::styling::AnsiColor::Green.on_default().bold()) + .literal(clap::builder::styling::AnsiColor::Cyan.on_default().bold()) + .placeholder(clap::builder::styling::AnsiColor::Magenta.on_default()) +} + static HEADER: &str = r#" --- Dataverse Command Line Interface (DVCLI) --- "#; -#[derive(StructOpt, Debug)] +#[derive(Parser, Debug)] struct GlobalOpts { /// Profile name to use for configuration - #[structopt(short, long)] + #[arg(short, long)] profile: Option, } -#[derive(StructOpt, Debug)] -#[structopt(about = "CLI to interact with Dataverse")] +#[derive(Parser, Debug)] +#[command( + about = "CLI to interact with Dataverse", + styles = get_styles() +)] #[allow(clippy::upper_case_acronyms)] struct CLI { - #[structopt(flatten)] + #[command(flatten)] global: GlobalOpts, - #[structopt(subcommand)] + #[command(subcommand)] cmd: DVCLI, } -#[derive(StructOpt, Debug)] +#[derive(clap::Subcommand, Debug)] #[allow(clippy::upper_case_acronyms)] enum DVCLI { + #[command(subcommand)] Info(InfoSubCommand), + #[command(subcommand)] Collection(CollectionSubCommand), + #[command(subcommand)] Dataset(DatasetSubCommand), + #[command(subcommand)] File(FileSubCommand), Search(SearchQuery), + #[command(subcommand)] Admin(AdminSubCommand), + #[command(subcommand)] Auth(AuthSubCommand), } fn main() { - let cli = CLI::from_args(); + let cli = CLI::parse(); // This is a special case for the Auth command, which is used to set the profile // and does not require a Dataverse instance. diff --git a/src/cli/admin.rs b/src/cli/admin.rs index de7da8c..670eef1 100644 --- a/src/cli/admin.rs +++ b/src/cli/admin.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; -use structopt::StructOpt; +use clap::Subcommand; use tokio::runtime::Runtime; use crate::client::BaseClient; @@ -17,49 +17,43 @@ use crate::native_api::admin::tools; use super::base::{evaluate_and_print_response, Matcher}; /// Subcommands for administrative tasks in a Dataverse instance -#[derive(StructOpt, Debug)] -#[structopt(about = "Handle admin tasks of the Dataverse instance")] +#[derive(Subcommand, Debug)] pub enum AdminSubCommand { - /// Get a list of available storage drivers for the Dataverse instance - #[structopt(about = "Retrieve the storage drivers available for the Dataverse instance")] + /// Retrieve the storage drivers available for the Dataverse instance StorageDrivers {}, - /// Configure a specific storage driver for a collection - #[structopt(about = "Set the storage driver for a collection")] + /// Set the storage driver for a collection SetStorage { /// The storage driver identifier to assign - #[structopt(short, long, help = "Storage driver to set")] + #[arg(short, long, help = "Storage driver to set")] driver: String, /// Collection alias to configure storage for - #[structopt(help = "Alias of the collection to set the storage driver for")] + #[arg(help = "Alias of the collection to set the storage driver for")] alias: String, }, - /// Retrieve the currently configured storage driver for a collection - #[structopt(about = "Get the storage driver for a collection")] + /// Get the storage driver for a collection GetStorage { /// Collection alias to get storage config from - #[structopt(help = "Alias of the collection to get the storage driver for")] + #[arg(help = "Alias of the collection to get the storage driver for")] alias: String, }, - /// Reset a collection's storage driver to the default - #[structopt(about = "Reset the storage driver for a collection")] + /// Reset the storage driver for a collection ResetStorage { /// Collection alias to reset storage for - #[structopt(help = "Alias of the collection to reset the storage driver for")] + #[arg(help = "Alias of the collection to reset the storage driver for")] alias: String, }, - /// Register an external tool - #[structopt(about = "Registers an external tool with the Dataverse instance")] + /// Registers an external tool with the Dataverse instance AddExternalTool { /// The tool manifest to register - #[structopt(help = "Path to the tool manifest file")] + #[arg(help = "Path to the tool manifest file")] manifest: PathBuf, /// Whether to overwrite an existing tool when it already exists. This will delete the existing tool and register a new one. - #[structopt( + #[arg( short, long, help = "Whether to overwrite an existing tool when it already exists. This will delete the existing tool and register a new one." @@ -67,8 +61,7 @@ pub enum AdminSubCommand { overwrite: bool, }, - /// List all external tools - #[structopt(about = "Lists all external tools registered with the Dataverse instance")] + /// Lists all external tools registered with the Dataverse instance ListExternalTools {}, } diff --git a/src/cli/auth.rs b/src/cli/auth.rs index b382e77..8a21dfa 100644 --- a/src/cli/auth.rs +++ b/src/cli/auth.rs @@ -12,7 +12,7 @@ use colored::Colorize; use dialoguer::Input; use keyring::{Entry, Result}; use rpassword::prompt_password; -use structopt::StructOpt; +use clap::Subcommand; use url::Url; use uuid::Uuid; @@ -75,22 +75,20 @@ pub fn prompt_for_credentials() -> std::result::Result<(String, String), Box, /// URL of the Dataverse server to authenticate against - #[structopt(short, long, help = "URL of the Dataverse server")] + #[arg(short, long, help = "URL of the Dataverse server")] url: Option, /// API token used for authentication with the Dataverse server - #[structopt(short, long, help = "API token for authentication")] + #[arg(short, long, help = "API token for authentication")] token: Option, }, } diff --git a/src/cli/collection.rs b/src/cli/collection.rs index 4c76952..b1995d3 100644 --- a/src/cli/collection.rs +++ b/src/cli/collection.rs @@ -8,7 +8,7 @@ use std::path::PathBuf; -use structopt::StructOpt; +use clap::Subcommand; use tokio::runtime::Runtime; use crate::client::BaseClient; @@ -19,18 +19,16 @@ use crate::native_api::collection::{content, delete}; use super::base::{evaluate_and_print_response, parse_file, Matcher}; /// Subcommands for managing collections in a Dataverse instance -#[derive(StructOpt, Debug)] -#[structopt(about = "Handle collections of a Dataverse instance")] +#[derive(Subcommand, Debug)] pub enum CollectionSubCommand { - /// Create a new collection in a parent dataverse - #[structopt(about = "Create a collection")] + /// Create a collection Create { /// Alias of the parent dataverse where the collection will be created - #[structopt(long, short, help = "Alias of the parent dataverse")] + #[arg(long, short, help = "Alias of the parent dataverse")] parent: String, /// Path to a JSON/YAML file containing the collection configuration - #[structopt( + #[arg( long, short, help = "Path to the JSON/YAML file containing the collection body" @@ -38,27 +36,24 @@ pub enum CollectionSubCommand { body: PathBuf, }, - /// Get the content/metadata of a collection - #[structopt(about = "Collection content")] + /// Collection content Content { /// Alias of the collection to get content for - #[structopt(help = "Alias of the collection")] + #[arg(help = "Alias of the collection")] alias: String, }, - /// Publish a collection, making it publicly visible - #[structopt(about = "Publish a collection")] + /// Publish a collection Publish { /// Alias of the collection to publish - #[structopt(help = "Alias of the collection to publish")] + #[arg(help = "Alias of the collection to publish")] alias: String, }, - /// Delete a collection from the Dataverse instance - #[structopt(about = "Delete a collection")] + /// Delete a collection Delete { /// Alias of the collection to delete - #[structopt(help = "Alias of the collection to delete")] + #[arg(help = "Alias of the collection to delete")] alias: String, }, } diff --git a/src/cli/dataset.rs b/src/cli/dataset.rs index 8a9dd5e..357890b 100644 --- a/src/cli/dataset.rs +++ b/src/cli/dataset.rs @@ -12,8 +12,8 @@ use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; +use clap::Subcommand; use colored_json::Paint; -use structopt::StructOpt; use tokio::runtime::Runtime; use crate::client::{print_error, BaseClient}; @@ -40,17 +40,33 @@ use crate::{data_access, direct_upload}; use super::base::{evaluate_and_print_response, parse_file, Matcher}; +/// A CLI-friendly wrapper for file uploads that can be easily cloned and parsed +#[derive(Debug, Clone)] +pub struct CliUploadFile(String); + +impl std::str::FromStr for CliUploadFile { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(CliUploadFile(s.to_string())) + } +} + +impl From for UploadFile { + fn from(cli_file: CliUploadFile) -> Self { + UploadFile::from(cli_file.0.as_str()) + } +} + /// Subcommands for managing datasets in a Dataverse instance -#[derive(StructOpt, Debug)] -#[structopt(about = "Handle datasets of the Dataverse instance")] +#[derive(Subcommand, Debug)] pub enum DatasetSubCommand { - /// Retrieve a dataset's metadata - #[structopt(about = "Retrieve a datasets metadata")] + /// Retrieve a datasets metadata Meta { - #[structopt(help = "(Peristent) identifier of the dataset to retrieve")] + #[arg(help = "(Peristent) identifier of the dataset to retrieve")] id: Identifier, - #[structopt( + #[arg( short, long, help = "Version of the dataset to retrieve. Defaults to ':latest' when there is no API token, and ':draft' when there is an API token." @@ -58,13 +74,12 @@ pub enum DatasetSubCommand { version: Option, }, - /// Create a new dataset in a collection - #[structopt(about = "Create a dataset")] + /// Create a dataset Create { - #[structopt(long, short, help = "Alias of the collection to create the dataset in")] + #[arg(long, short, help = "Alias of the collection to create the dataset in")] collection: String, - #[structopt( + #[arg( long, short, help = "Path to the JSON/YAML file containing the dataset body" @@ -72,13 +87,12 @@ pub enum DatasetSubCommand { body: PathBuf, }, - /// Publish a dataset version - #[structopt(about = "Publishes a dataset")] + /// Publishes a dataset Publish { - #[structopt(help = "Persistent identifier of the dataset to publish")] + #[arg(help = "Persistent identifier of the dataset to publish")] pid: String, - #[structopt( + #[arg( long, short, help = "Version of the dataset to publish (major, minor, updatecurrent)", @@ -87,73 +101,68 @@ pub enum DatasetSubCommand { version: Version, }, - /// Delete a dataset from the Dataverse instance - #[structopt(about = "Deletes a dataset")] + /// Deletes a dataset Delete { - #[structopt(help = "Identifier of the dataset to delete")] + #[arg(help = "Identifier of the dataset to delete")] id: i64, }, - /// Edit dataset metadata - #[structopt(about = "Edit the metadata of a dataset")] + /// Edit the metadata of a dataset Edit { - #[structopt(long, short, help = "Persistent identifier of the dataset to edit")] + #[arg(long, short, help = "Persistent identifier of the dataset to edit")] pid: String, - #[structopt( + #[arg( long, short, help = "Path to the JSON/YAML file containing the metadata to edit" )] body: PathBuf, - #[structopt(long, short, help = "Whether to replace the metadata or not")] + #[arg(long, short, help = "Whether to replace the metadata or not")] replace: bool, }, /// Link a dataset to another collection - #[structopt(about = "Link a dataset to another collection")] Link { - #[structopt(long, short, help = "(Persistent) identifier of the dataset to link")] + #[arg(long, short, help = "(Persistent) identifier of the dataset to link")] id: Identifier, - #[structopt(long, short, help = "Alias of the collection to link the dataset to")] + #[arg(long, short, help = "Alias of the collection to link the dataset to")] collection: String, }, /// Upload a file to a dataset - #[structopt(about = "Upload a file to a dataset")] Upload { - #[structopt( + #[arg( long, short, help = "(Persistent) Identifier of the dataset to upload the file to" )] id: Identifier, - #[structopt(help = "Path or URL to the file to upload")] - path: UploadFile, + #[arg(help = "Path or URL to the file to upload")] + path: CliUploadFile, - #[structopt(short, long, help = "Dataverse path to the file to upload")] + #[arg(short, long, help = "Dataverse path to the file to upload")] dv_path: Option, - #[structopt(long, help = "Path to the JSON/YAML file containing the file body")] + #[arg(long, help = "Path to the JSON/YAML file containing the file body")] body: Option, - #[structopt(long, short, help = "Replace the file if it already exists")] + #[arg(long, short, help = "Replace the file if it already exists")] replace: bool, }, /// Upload a file to a dataset using direct upload - #[structopt(about = "Upload a file to a dataset using direct upload")] DirectUpload { - #[structopt(long, short, help = "Identifier of the dataset to upload the file to")] + #[arg(long, short, help = "Identifier of the dataset to upload the file to")] id: Identifier, - #[structopt(help = "Path to the file to upload")] + #[arg(help = "Path to the file to upload")] paths: Vec, - #[structopt( + #[arg( long, short, help = "Number of files to upload in parallel", @@ -162,17 +171,16 @@ pub enum DatasetSubCommand { parallel: usize, }, - /// Download files from a dataset - #[structopt(about = "Download a file from a dataset")] + /// Download a file from a dataset Download { - #[structopt( + #[arg( short, long, help = "Identifier of the dataset to download the file from" )] id: Option, - #[structopt( + #[arg( short, long, help = "Directory to save the file to", @@ -180,29 +188,28 @@ pub enum DatasetSubCommand { )] out: PathBuf, - #[structopt( + #[arg( short, long, help = "Version of the dataset to download the file from. Defaults to ':latest' when there is no API token, and ':draft' when there is an API token." )] version: Option, - #[structopt(long, help = "Whether to download the entire dataset or a single file")] + #[arg(long, help = "Whether to download the entire dataset or a single file")] complete: bool, - #[structopt( + #[arg( help = "Path/ID/PID to the file to download. Required when downloading a single file." )] path: Option, }, /// List files in a dataset - #[structopt(about = "List files in a dataset")] ListFiles { - #[structopt(help = "Identifier of the dataset to list the files of")] + #[arg(help = "Identifier of the dataset to list the files of")] id: Identifier, - #[structopt( + #[arg( short, long, help = "Version of the dataset to list the files of. Defaults to ':latest' when there is no API token, and ':draft' when there is an API token." @@ -210,13 +217,12 @@ pub enum DatasetSubCommand { version: Option, }, - /// Get the total size of a dataset - #[structopt(about = "Retrieve the size of a dataset")] + /// Retrieve the size of a dataset Size { - #[structopt(help = "Identifier of the dataset to retrieve the size of")] + #[arg(help = "Identifier of the dataset to retrieve the size of")] id: Identifier, - #[structopt( + #[arg( short, long, help = "Version of the dataset to retrieve the size of. Defaults to ':latest' when there is no API token, and ':draft' when there is an API token." @@ -224,42 +230,40 @@ pub enum DatasetSubCommand { version: Option, }, - /// Export a dataset - #[structopt(about = "Export a dataset to a variety of formats")] + /// Export a dataset to a variety of formats Export { - #[structopt(short, long, help = "(Persistent) identifier of the dataset to export")] + #[arg(short, long, help = "(Persistent) identifier of the dataset to export")] id: Identifier, - #[structopt( + #[arg( short, long, help = "Format to use. E.g. 'ddi', 'oai_ddi', 'datacite', etc." )] format: String, - #[structopt(short, long, help = "Path to the file to save the export to")] + #[arg(short, long, help = "Path to the file to save the export to")] out: PathBuf, }, /// Get locks for a dataset - #[structopt(about = "Get locks for a dataset")] Locks { - #[structopt(help = "Identifier of the dataset to get locks for")] + #[arg(help = "Identifier of the dataset to get locks for")] id: Identifier, - #[structopt(short = "t", long = "type", help = "Lock type to set to or filter by")] + #[arg(short = 't', long = "type", help = "Lock type to set to or filter by")] lock_type: Option, - #[structopt( - short = "s", + #[arg( + short = 's', long = "set", help = "Whether to set the lock specified by '-t' on the dataset", conflicts_with = "remove" )] set: bool, - #[structopt( - short = "r", + #[arg( + short = 'r', long = "remove", help = "Whether to remove the lock specified by '-t' from the dataset", conflicts_with = "set" @@ -268,26 +272,25 @@ pub enum DatasetSubCommand { }, /// Submit a dataset for review - #[structopt(about = "Submit a dataset for review")] Review { - #[structopt(help = "Identifier of the dataset to submit for review")] + #[arg(help = "Identifier of the dataset to submit for review")] id: Identifier, - #[structopt( + #[arg( long, short, help = "Submit the dataset for review", conflicts_with = "reason", - required_unless = "reason" + required_unless_present = "reason" )] submit: bool, - #[structopt( + #[arg( long = "reason", short, help = "The reason for returning the dataset to the author", conflicts_with = "submit", - required_unless = "submit" + required_unless_present = "submit" )] reason: Option, }, @@ -345,7 +348,7 @@ impl Matcher for DatasetSubCommand { dv_path, } => { let body = Self::prepare_upload_body(body, &dv_path); - let mut path = path; + let mut path: UploadFile = path.into(); if let FileSource::RemoteUrl(_) = &path.file { if let Some(dv_path) = &dv_path { diff --git a/src/cli/file.rs b/src/cli/file.rs index 0599dc3..b634639 100644 --- a/src/cli/file.rs +++ b/src/cli/file.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; -use structopt::StructOpt; +use clap::Subcommand; use crate::data_access; use crate::data_access::datafile::DataFilePath; @@ -20,46 +20,42 @@ use crate::{client::BaseClient, native_api::dataset::upload::UploadBody}; use super::base::{evaluate_and_print_response, parse_file, Matcher}; /// Subcommands for managing files in a Dataverse instance -#[derive(StructOpt, Debug)] -#[structopt(about = "Handle files of a Dataverse instance")] +#[derive(Subcommand, Debug)] pub enum FileSubCommand { - /// Retrieves metadata for a specific file - #[structopt(about = "Get file metadata")] + /// Get file metadata Meta { - #[structopt(help = "Identifier of the file to get metadata for")] + #[arg(help = "Identifier of the file to get metadata for")] id: Identifier, }, - /// Replaces an existing file with a new version - #[structopt(about = "Replace a file")] + /// Replace a file Replace { - #[structopt(help = "Path to the file to replace")] + #[arg(help = "Path to the file to replace")] path: PathBuf, - #[structopt(long, short, help = "Identifier of the of the file to replace")] + #[arg(long, short, help = "Identifier of the of the file to replace")] id: String, - #[structopt( + #[arg( long, short, help = "Path to the JSON/YAML file containing the file body" )] body: Option, - #[structopt(long, short, help = "Force the replacement of the file")] + #[arg(long, short, help = "Force the replacement of the file")] force: bool, }, - /// Downloads a file from a dataset - #[structopt(about = "Download a file")] + /// Download a file Download { - #[structopt(help = "Identifier of the file to download")] + #[arg(help = "Identifier of the file to download")] file_id: DataFilePath, - #[structopt(short, long, help = "Path to save the file to")] + #[arg(short, long, help = "Path to save the file to")] path: PathBuf, - #[structopt(short, long, help = "Version of the dataset to download the file from")] + #[arg(short, long, help = "Version of the dataset to download the file from")] version: Option, }, } diff --git a/src/cli/info.rs b/src/cli/info.rs index f036761..128b1a2 100644 --- a/src/cli/info.rs +++ b/src/cli/info.rs @@ -8,9 +8,9 @@ use crate::client::BaseClient; use crate::native_api; +use clap::Subcommand; use colored::Colorize; use lazy_static::lazy_static; -use structopt::StructOpt; use super::base::{evaluate_and_print_response, Matcher}; @@ -27,15 +27,12 @@ You can use the key to export a dataset using the following command: } /// Subcommands for retrieving Dataverse instance information -#[derive(StructOpt, Debug)] -#[structopt(about = "Retrieve information about the Dataverse instance")] +#[derive(Subcommand, Debug)] pub enum InfoSubCommand { - /// Retrieves the version of the Dataverse instance - #[structopt(about = "Retrieve the version of the Dataverse instance")] + /// Retrieve the version of the Dataverse instance Version, - /// Retrieves the exporters of the Dataverse instance - #[structopt(about = "Retrieve the exporters of the Dataverse instance")] + /// Retrieve the exporters of the Dataverse instance Exporters, } From 605870f7da2234a861e6ba7957d0de6c86b2e03f Mon Sep 17 00:00:00 2001 From: Jan Range <30547301+JR-1991@users.noreply.github.com> Date: Tue, 7 Oct 2025 12:42:36 +0200 Subject: [PATCH 2/3] Rename crate to dvcli and remove structopt dependency Changed the crate name from 'rust-dataverse' to 'dvcli' in Cargo.toml and removed the structopt dependency. Updated Cargo.lock to reflect the removal of structopt and related packages, as well as the new crate name. --- Cargo.lock | 217 +++++++++++++---------------------------------------- Cargo.toml | 3 +- 2 files changed, 52 insertions(+), 168 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e423fb..30c7578 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,15 +63,6 @@ dependencies = [ "libc", ] -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anstream" version = "0.6.14" @@ -355,21 +346,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "ansi_term", - "atty", - "bitflags 1.3.2", - "strsim 0.8.0", - "textwrap", - "unicode-width 0.1.12", - "vec_map", -] - [[package]] name = "clap" version = "4.5.4" @@ -398,7 +374,7 @@ version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 2.0.106", @@ -454,7 +430,7 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.1", + "unicode-width", "windows-sys 0.61.1", ] @@ -715,6 +691,51 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dvcli" +version = "0.1.1" +dependencies = [ + "async_zip", + "atty", + "bon", + "bytes", + "chrono", + "clap", + "colored", + "colored_json", + "dialoguer", + "dirs", + "exitcode", + "futures", + "httpmock", + "indicatif", + "infer", + "keyring", + "lazy_static", + "libdbus-sys", + "md5", + "mime_guess", + "rand 0.9.0-alpha.1", + "regress", + "reqwest", + "rpassword", + "serde", + "serde_json", + "serde_yaml", + "sha1", + "sha2", + "sqlx", + "tempfile", + "tokio", + "tokio-stream", + "tokio-util", + "typify", + "url", + "uuid 1.12.0", + "variantly", + "walkdir", +] + [[package]] name = "dyn-clone" version = "1.0.17" @@ -1100,15 +1121,6 @@ dependencies = [ "http", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.5.0" @@ -1480,7 +1492,7 @@ checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" dependencies = [ "console", "portable-atomic", - "unicode-width 0.2.1", + "unicode-width", "unit-prefix", "web-time", ] @@ -1990,30 +2002,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" version = "1.0.101" @@ -2245,52 +2233,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rust-dataverse" -version = "0.1.1" -dependencies = [ - "async_zip", - "atty", - "bon", - "bytes", - "chrono", - "clap 4.5.4", - "colored", - "colored_json", - "dialoguer", - "dirs", - "exitcode", - "futures", - "httpmock", - "indicatif", - "infer", - "keyring", - "lazy_static", - "libdbus-sys", - "md5", - "mime_guess", - "rand 0.9.0-alpha.1", - "regress", - "reqwest", - "rpassword", - "serde", - "serde_json", - "serde_yaml", - "sha1", - "sha2", - "sqlx", - "structopt", - "tempfile", - "tokio", - "tokio-stream", - "tokio-util", - "typify", - "url", - "uuid 1.12.0", - "variantly", - "walkdir", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2714,7 +2656,7 @@ checksum = "882ceaa29cade31beca7129b6beeb05737f44f82dbe2a9806ecea5a7093d00b7" dependencies = [ "dotenvy", "either", - "heck 0.5.0", + "heck", "hex", "once_cell", "proc-macro2", @@ -2858,12 +2800,6 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "strsim" version = "0.10.0" @@ -2876,30 +2812,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "structopt" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" -dependencies = [ - "clap 2.34.0", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" -dependencies = [ - "heck 0.3.3", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "subtle" version = "2.6.1" @@ -2972,7 +2884,7 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fce91f2f0ec87dff7e6bcbbeb267439aa1188703003c6055193c821487400432" dependencies = [ - "unicode-width 0.2.1", + "unicode-width", ] [[package]] @@ -2988,15 +2900,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width 0.1.12", -] - [[package]] name = "thiserror" version = "2.0.12" @@ -3194,7 +3097,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "062879d46aa4c9dfe0d33b035bbaf512da192131645d05deacb7033ec8581a09" dependencies = [ - "heck 0.5.0", + "heck", "log", "proc-macro2", "quote", @@ -3261,18 +3164,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" -[[package]] -name = "unicode-segmentation" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - -[[package]] -name = "unicode-width" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" - [[package]] name = "unicode-width" version = "0.2.1" @@ -3355,12 +3246,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index a28d067..77b4d2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rust-dataverse" +name = "dvcli" version = "0.1.1" edition = "2021" repository = "https://github.com/gdcc/rust-dataverse" @@ -27,7 +27,6 @@ serde_json = "1.0.117" serde_yaml = "0.9.34" typify = "0.4.3" colored_json = "5.0.0" -structopt = "0.3.26" atty = "0.2.14" indicatif = "0.18.0" tokio = { version = "1.37.0", features = ["full"] } From 887e270dfbe2dcee35c8f8c87c3d50f7d094bf92 Mon Sep 17 00:00:00 2001 From: Jan Range <30547301+JR-1991@users.noreply.github.com> Date: Tue, 7 Oct 2025 12:42:43 +0200 Subject: [PATCH 3/3] Derive Clone for enums and migrate from StructOpt to clap Added Clone derivation to DataFilePath, SearchType, SortField, and Order enums to enable cloning. Migrated SearchQuery struct and related macro usage from structopt to clap for argument parsing, updating attribute macros accordingly. Removed structopt imports and replaced with clap and lazy_static where needed. --- src/data_access/datafile.rs | 2 +- src/search_api/query.rs | 41 ++++++++++++++++++------------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/data_access/datafile.rs b/src/data_access/datafile.rs index 9ebaf8f..7d96d48 100644 --- a/src/data_access/datafile.rs +++ b/src/data_access/datafile.rs @@ -142,7 +142,7 @@ async fn single_stream( } /// Represents different ways to identify a data file. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum DataFilePath { /// A file path as a string. Path(String), diff --git a/src/search_api/query.rs b/src/search_api/query.rs index 95d0b9c..6f7e724 100644 --- a/src/search_api/query.rs +++ b/src/search_api/query.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; use std::fmt; use std::str::FromStr; +use clap::Args; +use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; -use structopt::lazy_static::lazy_static; -use structopt::StructOpt; use tokio::runtime::Runtime; use crate::cli::base::{evaluate_and_print_response, Matcher}; @@ -72,63 +72,62 @@ macro_rules! insert_multiple_if_some { /// This struct encapsulates all possible parameters that can be used when /// searching a Dataverse instance. It provides a flexible way to construct /// search queries with different filtering, sorting, and pagination options. -#[derive(Debug, Serialize, Deserialize, StructOpt, Default)] -#[structopt(about = "Search a Dataverse instance")] +#[derive(Debug, Serialize, Deserialize, Args, Default)] pub struct SearchQuery { /// The search query string. This is the main search term. - #[structopt(short = "q", long = "query", help = "The search query string")] + #[arg(short = 'q', long = "query", help = "The search query string")] pub q: String, /// The type of search to perform (dataverse, dataset, or file). - #[structopt(short = "t", long = "type", help = "The type of search")] + #[arg(short = 't', long = "type", help = "The type of search")] pub search_type: Option>, // Represents "type" in the query parameters /// The subtree to search within, limiting results to a specific dataverse. - #[structopt(long, help = "The subtree to search within")] + #[arg(long, help = "The subtree to search within")] pub subtree: Option>, /// The field to sort results by (name or date). - #[structopt(long, help = "The field to sort by")] + #[arg(long, help = "The field to sort by")] pub sort: Option, /// The order of sorting (ascending or descending). - #[structopt(long, help = "The order of sorting")] + #[arg(long, help = "The order of sorting")] pub order: Option, /// The number of results to return per page. - #[structopt(long, help = "The number of results per page")] + #[arg(long, help = "The number of results per page")] pub per_page: Option, /// The starting index of the results for pagination. - #[structopt(long, help = "The starting index of the results")] + #[arg(long, help = "The starting index of the results")] pub start: Option, /// Whether to show relevance scores in the search results. - #[structopt(long, help = "Whether to show relevance scores")] + #[arg(long, help = "Whether to show relevance scores")] pub show_relevance: Option, /// Whether to show facets in the search results. - #[structopt(long, help = "Whether to show facets")] + #[arg(long, help = "Whether to show facets")] pub show_facets: Option, /// Filter queries to narrow down search results. - #[structopt(long = "filter", help = "The filter query")] + #[arg(long = "filter", help = "The filter query")] pub fq: Option>, /// Whether to show entity IDs in the search results. - #[structopt(long, help = "Whether to show entity IDs")] + #[arg(long, help = "Whether to show entity IDs")] pub show_entity_ids: Option, /// The geographic point for geo-spatial searches. - #[structopt(long, help = "The geographic point")] + #[arg(long, help = "The geographic point")] pub geo_point: Option, /// The geographic radius for geo-spatial searches. - #[structopt(long, help = "The geographic radius")] + #[arg(long, help = "The geographic radius")] pub geo_radius: Option, /// The metadata fields to search within, limiting the search scope. - #[structopt(long = "fields", help = "The metadata fields to search within")] + #[arg(long = "fields", help = "The metadata fields to search within")] pub metadata_fields: Option>, } @@ -217,7 +216,7 @@ impl Matcher for SearchQuery { /// - Dataverse: Search for dataverse containers /// - Dataset: Search for datasets /// - File: Search for individual files -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub enum SearchType { /// Search for dataverse containers Dataverse, @@ -255,7 +254,7 @@ impl FromStr for SearchType { /// This enum defines the available fields that can be used for sorting search results: /// - Name: Sort by name /// - Date: Sort by date -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub enum SortField { /// Sort by name Name, @@ -290,7 +289,7 @@ impl FromStr for SortField { /// This enum defines the available sort orders: /// - Asc: Ascending order (A-Z, oldest to newest) /// - Desc: Descending order (Z-A, newest to oldest) -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub enum Order { /// Ascending order Asc,