diff options
-rw-r--r-- | fatcat-cli/src/commands.rs | 35 | ||||
-rw-r--r-- | fatcat-cli/src/download.rs | 59 | ||||
-rw-r--r-- | fatcat-cli/src/lib.rs | 5 | ||||
-rw-r--r-- | fatcat-cli/src/main.rs | 82 | ||||
-rw-r--r-- | fatcat-cli/src/specifier.rs | 34 |
5 files changed, 140 insertions, 75 deletions
diff --git a/fatcat-cli/src/commands.rs b/fatcat-cli/src/commands.rs index 624374a..4633af6 100644 --- a/fatcat-cli/src/commands.rs +++ b/fatcat-cli/src/commands.rs @@ -12,7 +12,7 @@ use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; use crate::{ entity_model_from_json_str, read_entity_file, ApiModelSer, EntityType, FatcatApiClient, - Mutation, Specifier, SearchEntityType, SearchResults, + Mutation, SearchEntityType, SearchResults, Specifier, }; // Want to show: @@ -285,22 +285,13 @@ pub fn print_search_table(results: SearchResults, entity_type: SearchEntityType) let mut tw = TabWriter::new(std::io::stdout()); match entity_type { SearchEntityType::Release => { - writeln!( - tw, - "ident\ttype\tstage\tyear\tcontainer_name\ttitle" - )?; + writeln!(tw, "ident\ttype\tstage\tyear\tcontainer_name\ttitle")?; } - SearchEntityType::Container=> { - writeln!( - tw, - "ident\tissnl\tname" - )?; + SearchEntityType::Container => { + writeln!(tw, "ident\tissnl\tname")?; } SearchEntityType::File => { - writeln!( - tw, - "ident\tsha1\tsize_bytes\tmimetype" - )?; + writeln!(tw, "ident\tsha1\tsize_bytes\tmimetype")?; } } for hit in results { @@ -313,7 +304,9 @@ pub fn print_search_table(results: SearchResults, entity_type: SearchEntityType) hit["ident"].as_str().unwrap_or("-"), hit["release_type"].as_str().unwrap_or("-"), hit["release_stage"].as_str().unwrap_or("-"), - hit["release_year"].as_u64().map_or("-".to_string(), |v| v.to_string()), + hit["release_year"] + .as_u64() + .map_or("-".to_string(), |v| v.to_string()), hit["container_name"].as_str().unwrap_or("-"), hit["title"].as_str().unwrap_or("-"), )?; @@ -333,7 +326,9 @@ pub fn print_search_table(results: SearchResults, entity_type: SearchEntityType) "file_{}\t{}\t{}\t{}", hit["ident"].as_str().unwrap_or("-"), hit["sha1"].as_str().unwrap_or("-"), - hit["size_bytes"].as_u64().map_or("-".to_string(), |v| v.to_string()), + hit["size_bytes"] + .as_u64() + .map_or("-".to_string(), |v| v.to_string()), hit["mimetype"].as_str().unwrap_or("-"), )?; } @@ -505,7 +500,9 @@ impl BatchGrouper { mutations: Vec<Mutation>, ) -> Result<models::EntityEdit> { let obj: serde_json::Value = serde_json::from_str(json_str)?; - let ident = obj["ident"].as_str().ok_or(anyhow!("expect entity JSON to have 'ident' field"))?; + let ident = obj["ident"] + .as_str() + .ok_or(anyhow!("expect entity JSON to have 'ident' field"))?; let editgroup_id = self.increment_editgroup(api_client)?; let mut entity = entity_model_from_json_str(self.entity_type, &json_str)?; entity.mutate(mutations)?; @@ -522,7 +519,9 @@ impl BatchGrouper { json_str: &str, ) -> Result<models::EntityEdit> { let obj: serde_json::Value = serde_json::from_str(json_str)?; - let ident = obj["ident"].as_str().ok_or(anyhow!("expect entity JSON to have 'ident' field"))?; + let ident = obj["ident"] + .as_str() + .ok_or(anyhow!("expect entity JSON to have 'ident' field"))?; let editgroup_id = self.increment_editgroup(api_client)?; api_client.delete_entity( Specifier::from_ident(self.entity_type, ident.to_string()), diff --git a/fatcat-cli/src/download.rs b/fatcat-cli/src/download.rs index d0f6c2e..2fdfeb9 100644 --- a/fatcat-cli/src/download.rs +++ b/fatcat-cli/src/download.rs @@ -1,17 +1,17 @@ +use crate::{ApiModelIdent, Specifier}; use anyhow::{anyhow, Context, Result}; +use crossbeam_channel as channel; use fatcat_openapi::models::{FileEntity, ReleaseEntity}; use indicatif::{ProgressBar, ProgressStyle}; -use log::{info, error}; +use log::{error, info}; use reqwest::header::USER_AGENT; +use sha1::Sha1; use std::fmt; use std::fs::File; use std::io::{self, BufRead}; use std::path::PathBuf; -use url::Url; -use crate::{ApiModelIdent, Specifier}; -use sha1::Sha1; use std::thread; -use crossbeam_channel as channel; +use url::Url; #[derive(Debug, PartialEq, Clone)] pub enum DownloadStatus { @@ -61,7 +61,10 @@ struct Sha1WriteWrapper<W> { impl<W> Sha1WriteWrapper<W> { fn new(writer: W) -> Self { - Sha1WriteWrapper { writer, hasher: Sha1::new() } + Sha1WriteWrapper { + writer, + hasher: Sha1::new(), + } } fn into_hexdigest(self) -> String { @@ -95,7 +98,6 @@ fn rewrite_wayback_url(url: Url) -> Result<Url> { } fn default_filename(specifier: &Specifier, fe: &FileEntity) -> Result<PathBuf> { - let file_suffix = match fe.mimetype.as_ref().map(String::as_str) { Some("application/pdf") => ".pdf", Some("application/postscript") => ".ps", @@ -112,7 +114,12 @@ fn default_filename(specifier: &Specifier, fe: &FileEntity) -> Result<PathBuf> { } /// Attempts to download a file entity, including verifying checksum. -pub fn download_file(fe: &FileEntity, specifier: &Specifier, output_path: Option<PathBuf>, show_progress: bool) -> Result<DownloadStatus> { +pub fn download_file( + fe: &FileEntity, + specifier: &Specifier, + output_path: Option<PathBuf>, + show_progress: bool, +) -> Result<DownloadStatus> { let expected_sha1 = match &fe.sha1 { Some(v) => v, None => return Ok(DownloadStatus::FileMissingMetadata), @@ -174,7 +181,10 @@ pub fn download_file(fe: &FileEntity, specifier: &Specifier, output_path: Option let client = reqwest::blocking::Client::new(); let mut resp = match client .get(url) - .header(USER_AGENT, format!("fatcat-cli/{}", env!("CARGO_PKG_VERSION"))) + .header( + USER_AGENT, + format!("fatcat-cli/{}", env!("CARGO_PKG_VERSION")), + ) .send() { Ok(r) => r, @@ -206,13 +216,13 @@ pub fn download_file(fe: &FileEntity, specifier: &Specifier, output_path: Option let result = resp.copy_to(&mut wrapped_file); let out_sha1 = wrapped_file.into_hexdigest(); (result, out_sha1) - }, + } false => { let mut wrapped_file = Sha1WriteWrapper::new(download_file); let result = resp.copy_to(&mut wrapped_file); let out_sha1 = wrapped_file.into_hexdigest(); (result, out_sha1) - }, + } }; let out_size = match write_result { @@ -239,7 +249,11 @@ pub fn download_file(fe: &FileEntity, specifier: &Specifier, output_path: Option )) } -pub fn download_release(re: &ReleaseEntity, output_path: Option<PathBuf>, show_progress: bool) -> Result<DownloadStatus> { +pub fn download_release( + re: &ReleaseEntity, + output_path: Option<PathBuf>, + show_progress: bool, +) -> Result<DownloadStatus> { let file_entities = match &re.files { None => { return Err(anyhow!( @@ -261,7 +275,11 @@ pub fn download_release(re: &ReleaseEntity, output_path: Option<PathBuf>, show_p } /// Tries either file or release -fn download_entity(json_str: String, output_path: Option<PathBuf>, show_progress: bool) -> Result<(DownloadStatus, String)> { +fn download_entity( + json_str: String, + output_path: Option<PathBuf>, + show_progress: bool, +) -> Result<(DownloadStatus, String)> { let release_attempt = serde_json::from_str::<ReleaseEntity>(&json_str); if let Ok(re) = release_attempt { if re.ident.is_some() && (re.title.is_some() || re.files.is_some()) { @@ -314,10 +332,14 @@ fn loop_printer( Ok(()) } -fn loop_download_tasks(task_receiver: channel::Receiver<DownloadTask>, output_sender: channel::Sender<String>) { +fn loop_download_tasks( + task_receiver: channel::Receiver<DownloadTask>, + output_sender: channel::Sender<String>, +) { let thread_result: Result<()> = (|| { for task in task_receiver { - let (_, status_line) = download_entity(task.json_str, task.output_path, task.show_progress)?; + let (_, status_line) = + download_entity(task.json_str, task.output_path, task.show_progress)?; output_sender.send(status_line)?; } Ok(()) @@ -328,7 +350,12 @@ fn loop_download_tasks(task_receiver: channel::Receiver<DownloadTask>, output_se thread_result.unwrap() } -pub fn download_batch(input_path: Option<PathBuf>, output_dir: Option<PathBuf>, limit: Option<u64>, jobs: u64) -> Result<u64> { +pub fn download_batch( + input_path: Option<PathBuf>, + output_dir: Option<PathBuf>, + limit: Option<u64>, + jobs: u64, +) -> Result<u64> { let mut count = 0; assert!(jobs > 0 && jobs <= 12); diff --git a/fatcat-cli/src/lib.rs b/fatcat-cli/src/lib.rs index beb9c97..81dbb7d 100644 --- a/fatcat-cli/src/lib.rs +++ b/fatcat-cli/src/lib.rs @@ -14,8 +14,7 @@ mod specifier; pub use api::FatcatApiClient; pub use commands::{ edit_entity_locally, print_changelog_entries, print_editgroups, print_entity_histories, - print_search_table, - BatchGrouper, BatchOp, ClientStatus, + print_search_table, BatchGrouper, BatchOp, ClientStatus, }; pub use download::{download_batch, download_file, download_release}; pub use entities::{ @@ -23,7 +22,7 @@ pub use entities::{ Mutation, }; pub use search::{crude_search, SearchResults}; -pub use specifier::{Specifier, EditgroupSpecifier}; +pub use specifier::{EditgroupSpecifier, Specifier}; #[derive(Debug, PartialEq, Clone, Copy)] pub enum EntityType { diff --git a/fatcat-cli/src/main.rs b/fatcat-cli/src/main.rs index 4b8322a..f5f5d8b 100644 --- a/fatcat-cli/src/main.rs +++ b/fatcat-cli/src/main.rs @@ -1,5 +1,6 @@ use crate::{path_or_stdin, BatchGrouper, BatchOp}; use anyhow::{anyhow, Context, Result}; +use colored_json::to_colored_json_auto; use fatcat_cli::*; #[allow(unused_imports)] use log::{self, debug, info}; @@ -7,7 +8,6 @@ use std::io::Write; use std::path::PathBuf; use structopt::StructOpt; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; -use colored_json::to_colored_json_auto; #[derive(StructOpt)] #[structopt(rename_all = "kebab-case", about = "CLI interface to Fatcat API")] @@ -145,7 +145,6 @@ enum BatchCommand { #[derive(StructOpt)] enum Command { - /// Fetch a single entity, by "ident" or external identifier Get { specifier: Specifier, @@ -387,7 +386,11 @@ fn run(opt: Opt) -> Result<()> { if toml { writeln!(&mut std::io::stdout(), "{}", result.to_toml_string()?)? } else if json || true { - writeln!(&mut std::io::stdout(), "{}", to_colored_json_auto(&result.to_json_value()?)?)? + writeln!( + &mut std::io::stdout(), + "{}", + to_colored_json_auto(&result.to_json_value()?)? + )? } } Command::Create { @@ -396,8 +399,16 @@ fn run(opt: Opt) -> Result<()> { editgroup_id, } => { let json_str = read_entity_file(input_path)?; - let ee = api_client.create_entity_from_json(entity_type, &json_str, editgroup_id.as_string())?; - writeln!(&mut std::io::stdout(), "{}", to_colored_json_auto(&serde_json::to_value(&ee)?)?)? + let ee = api_client.create_entity_from_json( + entity_type, + &json_str, + editgroup_id.as_string(), + )?; + writeln!( + &mut std::io::stdout(), + "{}", + to_colored_json_auto(&serde_json::to_value(&ee)?)? + )? } Command::Update { specifier, @@ -419,9 +430,16 @@ fn run(opt: Opt) -> Result<()> { (entity.to_json_string()?, entity.specifier()) } }; - let ee = - api_client.update_entity_from_json(exact_specifier, &json_str, editgroup_id.as_string())?; - writeln!(&mut std::io::stdout(), "{}", to_colored_json_auto(&serde_json::to_value(&ee)?)?)? + let ee = api_client.update_entity_from_json( + exact_specifier, + &json_str, + editgroup_id.as_string(), + )?; + writeln!( + &mut std::io::stdout(), + "{}", + to_colored_json_auto(&serde_json::to_value(&ee)?)? + )? } Command::Edit { specifier, @@ -437,7 +455,11 @@ fn run(opt: Opt) -> Result<()> { json, editing_command, )?; - writeln!(&mut std::io::stdout(), "{}", to_colored_json_auto(&serde_json::to_value(&ee)?)?)? + writeln!( + &mut std::io::stdout(), + "{}", + to_colored_json_auto(&serde_json::to_value(&ee)?)? + )? } Command::Changelog { limit, json } => { let resp = api_client @@ -503,10 +525,7 @@ fn run(opt: Opt) -> Result<()> { batch.run(&mut api_client, input_path, BatchOp::Delete, None)?; } Command::Batch { - cmd: BatchCommand::Download { - jobs, - output_dir, - }, + cmd: BatchCommand::Download { jobs, output_dir }, input_path, limit, } => { @@ -520,7 +539,9 @@ fn run(opt: Opt) -> Result<()> { return Err(anyhow!("--jobs=0 not implemented")); } if jobs > 12 { - return Err(anyhow!("please don't download more than 12 parallel requests")); + return Err(anyhow!( + "please don't download more than 12 parallel requests" + )); } download_batch(input_path, output_dir, limit, jobs)?; } @@ -604,7 +625,9 @@ fn run(opt: Opt) -> Result<()> { let hit = hit?; match (index_json, entity_json, entity_type) { (false, false, _) => unreachable!("case handled above"), - (true, _, _) => writeln!(&mut std::io::stdout(), "{}", hit.to_string())?, + (true, _, _) => { + writeln!(&mut std::io::stdout(), "{}", hit.to_string())? + } (false, true, SearchEntityType::Release) => { let specifier = Specifier::Release(hit["ident"].as_str().unwrap().to_string()); @@ -616,8 +639,9 @@ fn run(opt: Opt) -> Result<()> { writeln!(&mut std::io::stdout(), "{}", entity.to_json_string()?)? } (false, true, SearchEntityType::Container) => { - let specifier = - Specifier::Container(hit["ident"].as_str().unwrap().to_string()); + let specifier = Specifier::Container( + hit["ident"].as_str().unwrap().to_string(), + ); let entity = specifier.get_from_api( &mut api_client, expand.clone(), @@ -716,25 +740,41 @@ fn run(opt: Opt) -> Result<()> { cmd: EditgroupsCommand::Create { description }, } => { let eg = api_client.create_editgroup(Some(description))?; - writeln!(&mut std::io::stdout(), "{}", to_colored_json_auto(&serde_json::to_value(&eg)?)?)? + writeln!( + &mut std::io::stdout(), + "{}", + to_colored_json_auto(&serde_json::to_value(&eg)?)? + )? } Command::Editgroups { cmd: EditgroupsCommand::Accept { editgroup_id }, } => { let msg = api_client.accept_editgroup(editgroup_id.as_string())?; - writeln!(&mut std::io::stdout(), "{}", to_colored_json_auto(&serde_json::to_value(&msg)?)?)? + writeln!( + &mut std::io::stdout(), + "{}", + to_colored_json_auto(&serde_json::to_value(&msg)?)? + )? } Command::Editgroups { cmd: EditgroupsCommand::Submit { editgroup_id }, } => { let msg = api_client.update_editgroup_submit(editgroup_id.as_string(), true)?; - writeln!(&mut std::io::stdout(), "{}", to_colored_json_auto(&serde_json::to_value(&msg)?)?)? + writeln!( + &mut std::io::stdout(), + "{}", + to_colored_json_auto(&serde_json::to_value(&msg)?)? + )? } Command::Editgroups { cmd: EditgroupsCommand::Unsubmit { editgroup_id }, } => { let msg = api_client.update_editgroup_submit(editgroup_id.as_string(), false)?; - writeln!(&mut std::io::stdout(), "{}", to_colored_json_auto(&serde_json::to_value(&msg)?)?)? + writeln!( + &mut std::io::stdout(), + "{}", + to_colored_json_auto(&serde_json::to_value(&msg)?)? + )? } Command::Status { json } => { let status = ClientStatus::generate(&mut api_client)?; diff --git a/fatcat-cli/src/specifier.rs b/fatcat-cli/src/specifier.rs index cac4282..ab1a421 100644 --- a/fatcat-cli/src/specifier.rs +++ b/fatcat-cli/src/specifier.rs @@ -2,8 +2,8 @@ use crate::{ApiEntityModel, EntityType, FatcatApiClient}; use anyhow::{anyhow, Context, Result}; use lazy_static::lazy_static; use regex::Regex; -use std::str::FromStr; use std::fmt; +use std::str::FromStr; #[derive(Debug, PartialEq, Clone)] pub enum ReleaseLookupKey { @@ -61,9 +61,9 @@ pub enum FileLookupKey { impl fmt::Display for FileLookupKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::SHA1=> write!(f, "sha1"), - Self::SHA256=> write!(f, "sha256"), - Self::MD5=> write!(f, "md5"), + Self::SHA1 => write!(f, "sha1"), + Self::SHA256 => write!(f, "sha256"), + Self::MD5 => write!(f, "md5"), } } } @@ -560,10 +560,9 @@ impl FromStr for Specifier { // then try lookup prefixes lazy_static! { - static ref SPEC_LOOKUP_RE: Regex = Regex::new( - r"^(doi|pmcid|pmid|arxiv|issnl|orcid|sha1|sha256|md5|user):(\S+)$" - ) - .unwrap(); + static ref SPEC_LOOKUP_RE: Regex = + Regex::new(r"^(doi|pmcid|pmid|arxiv|issnl|orcid|sha1|sha256|md5|user):(\S+)$") + .unwrap(); } if let Some(caps) = SPEC_LOOKUP_RE.captures(s) { return match (&caps[1], &caps[2]) { @@ -618,7 +617,6 @@ impl FromStr for Specifier { pub struct EditgroupSpecifier(String); impl EditgroupSpecifier { - pub fn as_string(self) -> String { self.0 } @@ -629,15 +627,13 @@ impl FromStr for EditgroupSpecifier { fn from_str(s: &str) -> Result<Self, Self::Err> { lazy_static! { - static ref SPEC_ENTITY_RE: Regex = Regex::new(r"^(editgroup_)?([2-7a-z]{26})$").unwrap(); + static ref SPEC_ENTITY_RE: Regex = + Regex::new(r"^(editgroup_)?([2-7a-z]{26})$").unwrap(); } if let Some(caps) = SPEC_ENTITY_RE.captures(s) { - return Ok(EditgroupSpecifier(caps[2].to_string())) + return Ok(EditgroupSpecifier(caps[2].to_string())); } - Err(anyhow!( - "expecting an editgroup identifier, got: {}", - s - )) + Err(anyhow!("expecting an editgroup identifier, got: {}", s)) } } @@ -672,11 +668,15 @@ mod tests { fn test_editgroup_from_str() -> () { assert!(EditgroupSpecifier::from_str("release_asdf").is_err()); assert_eq!( - EditgroupSpecifier::from_str("editgroup_iimvc523xbhqlav6j3sbthuehu").unwrap().0, + EditgroupSpecifier::from_str("editgroup_iimvc523xbhqlav6j3sbthuehu") + .unwrap() + .0, "iimvc523xbhqlav6j3sbthuehu".to_string() ); assert_eq!( - EditgroupSpecifier::from_str("iimvc523xbhqlav6j3sbthuehu").unwrap().0, + EditgroupSpecifier::from_str("iimvc523xbhqlav6j3sbthuehu") + .unwrap() + .0, "iimvc523xbhqlav6j3sbthuehu".to_string() ); } |