From 2fcfbe9e6706af3371ab5b7b652c88dbac45b81a Mon Sep 17 00:00:00 2001 From: Mahesh Bansod Date: Sun, 7 Nov 2021 18:29:14 +0530 Subject: [PATCH] Moved Display impl from types to cli crate. added pager to feed. --- Cargo.lock | 92 +++++++++++++++++++++++++++- cli/Cargo.toml | 5 +- cli/src/entities.rs | 145 ++++++++++++++++++++++++++++++++++++++++++++ cli/src/main.rs | 21 +++++-- types/Cargo.toml | 3 +- types/src/lib.rs | 142 ++++--------------------------------------- 6 files changed, 268 insertions(+), 140 deletions(-) create mode 100644 cli/src/entities.rs diff --git a/Cargo.lock b/Cargo.lock index f2f0798..284ac38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,7 +117,7 @@ dependencies = [ "atty", "bitflags", "strsim", - "textwrap", + "textwrap 0.11.0", "unicode-width", "vec_map", ] @@ -126,12 +126,15 @@ dependencies = [ name = "cli" version = "0.1.0" dependencies = [ + "chrono", + "minus", "rpassword", "serde", "serde_json", "shellexpand", "socialvoid", "socialvoid_rawclient", + "socialvoid_types", "structopt", "tokio", ] @@ -161,6 +164,31 @@ dependencies = [ "libc", ] +[[package]] +name = "crossterm" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" +dependencies = [ + "winapi", +] + [[package]] name = "digest" version = "0.9.0" @@ -607,6 +635,17 @@ dependencies = [ "unicase", ] +[[package]] +name = "minus" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cfa68574733d70c28177f9608f2cd10f298ed109f55a900078886a90cbc265" +dependencies = [ + "crossterm", + "textwrap 0.13.4", + "thiserror", +] + [[package]] name = "mio" version = "0.7.13" @@ -1157,6 +1196,27 @@ dependencies = [ "dirs-next", ] +[[package]] +name = "signal-hook" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1215,7 +1275,6 @@ dependencies = [ name = "socialvoid_types" version = "0.1.0" dependencies = [ - "chrono", "serde", "serde_json", "tokio", @@ -1295,6 +1354,35 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd05616119e612a8041ef58f2b578906cc2531a6069047ae092cfb86a325d835" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.1.43" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index b540d7f..1154eac 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -12,9 +12,12 @@ path = "src/main.rs" [dependencies] "socialvoid" = { path = "../client" } "socialvoid_rawclient" = { path = "../rawclient" } +"socialvoid_types" = { path = "../types" } structopt = "0.3.23" serde = { version = "1.0", features = ["derive"]} serde_json = "1.0.67" shellexpand = "2.1.0" tokio = {version = "1.11.0", features = ["full"]} -rpassword = "5.0.1" \ No newline at end of file +rpassword = "5.0.1" +minus = { version = "4.0.2", features = ["static_output"] } +chrono = "0.4.19" \ No newline at end of file diff --git a/cli/src/entities.rs b/cli/src/entities.rs new file mode 100644 index 0000000..6f9a9bf --- /dev/null +++ b/cli/src/entities.rs @@ -0,0 +1,145 @@ +use socialvoid_types::*; + +use chrono::{DateTime, Utc}; +use std::time::{Duration, UNIX_EPOCH}; + +pub struct SVPost(Post); +pub struct SVProfile(Profile); + +impl std::convert::From for SVPost { + fn from(post: Post) -> Self { + Self(post) + } +} + +impl std::convert::From for SVProfile { + fn from(profile: Profile) -> Self { + Self(profile) + } +} + +impl std::fmt::Display for SVPost { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "Post Type: {} +ID: {} +Author: {} +Source: {} +---- +{} +---- +{} attachment(s) +Posted on: {} +Likes: {}, Reposts: {}, Quotes: {}, Replies: {}, +Flags: ", + match self.0.post_type { + PostType::Reply => format!( + "Reply to <{}>", + match &self.0.reply_to_post { + Some(reply_to_post) => reply_to_post.id.clone(), + None => String::new(), + } + ), + PostType::Quote => format!( + "Quoted post <{}>", + match &self.0.quoted_post.as_ref() { + Some(quoted_post) => quoted_post.id.clone(), + None => String::new(), + } + ), + PostType::Repost => format!( + "Reposted post <{}>", + match &self.0.reposted_post.as_ref() { + Some(reposted_post) => reposted_post.id.clone(), + None => String::new(), + } + ), + _ => format!("{:?}", self.0.post_type), + }, + self.0.id, + self.0 + .peer + .as_ref() + .map(|x| x.username.to_string()) + .unwrap_or_else(|| "".to_string()), + self.0 + .source + .as_ref() + .unwrap_or(&"".to_owned()), + self.0.text.as_ref().unwrap_or(&"".to_string()), + self.0.attachments.len(), //TODO: maybe show the document IDs + { + let d = UNIX_EPOCH + Duration::from_secs(self.0.posted_timestamp); + // Create DateTime from SystemTime + let datetime = DateTime::::from(d); + // Formats the combined date and time with the specified format string. + datetime.format("%Y-%m-%d %H:%M:%S.%f").to_string() + }, + self.0 + .like_count + .map(|x| x.to_string()) + .unwrap_or_else(|| "".to_string()), + self.0 + .repost_count + .map(|x| x.to_string()) + .unwrap_or_else(|| "".to_string()), + self.0 + .quote_count + .map(|x| x.to_string()) + .unwrap_or_else(|| "".to_string()), + self.0 + .reply_count + .map(|x| x.to_string()) + .unwrap_or_else(|| "".to_string()), + ) + } +} + +impl std::fmt::Display for SVProfile { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "First Name: {} +{} +Name: {} +{} +{} +{} +Followers: {} +Following: {} +Display Picture: {}", + self.0.first_name, + self.0 + .last_name + .as_ref() + .map(|x| format!("Last Name: {}", x)) + .unwrap_or_else(|| String::from("[No last name set]")), + self.0.name, + self.0 + .biography + .as_ref() + .map(|x| format!("Biography: {}", x)) + .unwrap_or_else(|| String::from("[No biography set]")), + self.0 + .location + .as_ref() + .map(|x| format!("Location: {}", x)) + .unwrap_or_else(|| String::from("[No location set]")), + self.0 + .url + .as_ref() + .map(|x| format!("URL: {}", x)) + .unwrap_or_else(|| String::from("[No URL set]")), + self.0.followers_count, + self.0.following_count, + if self.0.display_picture_sizes.is_empty() { + String::from("not set") + } else { + let count = self.0.display_picture_sizes.len(); + let name = &self.0.display_picture_sizes[0].document.file_name; + format!("'{}' ({} sizes available)", name, count) + } + ) + } +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 1de831f..c6693bf 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,8 +1,10 @@ use socialvoid::session::RegisterRequest; use structopt::StructOpt; +mod entities; mod error; mod utils; +use crate::entities::*; use crate::utils::*; use error::MyFriendlyError; @@ -166,7 +168,7 @@ async fn main() { } }, SocialVoidCommand::Profile { peer } => match sv.network.get_profile(peer).await { - Ok(profile) => println!("{}", profile), + Ok(profile) => println!("{}", SVProfile::from(profile)), Err(err) => println!( "An error occurred while trying to get the profile.\n{}", MyFriendlyError::from(err) @@ -196,10 +198,19 @@ async fn main() { } SocialVoidCommand::Feed { page } => match sv.timeline.retrieve_feed(page).await { Ok(feed) => { - for post in feed.iter() { - println!("================\n{}", post); + let mut pager = minus::Pager::new().unwrap(); + + let n_posts = feed.len(); + for post in feed.into_iter() { + let post = SVPost::from(post); + pager.push_str(format!("================\n{}", post)); } - println!("----Retrieved {} post(s) from the timeline.\n", feed.len()); + pager.push_str(format!( + "----Retrieved {} post(s) from the timeline.\n", + n_posts + )); + pager.set_prompt("Feed - Socialvoid"); + minus::page_all(pager).expect("Error with pager"); } Err(err) => println!("{}", MyFriendlyError::from(err)), }, @@ -212,7 +223,7 @@ async fn main() { Err(err) => println!("{}", MyFriendlyError::from(err)), }, SocialVoidCommand::GetPost { post_id } => match sv.timeline.get_post(post_id).await { - Ok(post) => println!("{}", post), + Ok(post) => println!("{}", SVPost::from(post)), Err(err) => println!("{}", MyFriendlyError::from(err)), }, SocialVoidCommand::DeletePost { post_id } => match sv.timeline.delete(post_id).await { diff --git a/types/Cargo.toml b/types/Cargo.toml index c5fd5eb..6bb91ca 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -8,5 +8,4 @@ edition = "2018" [dependencies] serde = { version = "1.0", features = ["derive"]} serde_json = "1.0.67" -tokio = {version = "1.11.0", features = ["full"]} -chrono = "0.4.19" \ No newline at end of file +tokio = {version = "1.11.0", features = ["full"]} \ No newline at end of file diff --git a/types/src/lib.rs b/types/src/lib.rs index 046450b..da89b6c 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -1,6 +1,4 @@ -use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use std::time::{Duration, UNIX_EPOCH}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct SessionIdentification { @@ -29,9 +27,9 @@ pub enum PeerType { #[derive(Serialize, Deserialize, Debug)] pub struct DisplayPictureSize { - width: u32, - height: u32, - document: Document, + pub width: u32, + pub height: u32, + pub document: Document, } #[derive(Serialize, Deserialize, Debug)] @@ -116,15 +114,15 @@ pub struct ServerInformation { #[derive(Serialize, Deserialize, Debug)] pub struct Profile { - first_name: String, - last_name: Option, - name: String, - biography: Option, - location: Option, - url: Option, - followers_count: u32, - following_count: u32, - display_picture_sizes: Vec, + pub first_name: String, + pub last_name: Option, + pub name: String, + pub biography: Option, + pub location: Option, + pub url: Option, + pub followers_count: u32, + pub following_count: u32, + pub display_picture_sizes: Vec, } /// Relationship of a peer with another peer. @@ -176,119 +174,3 @@ pub enum PostType { Quote, Repost, } - -impl std::fmt::Display for Post { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "Post Type: {} -ID: {} -Author: {} -Source: {} ----- -{} ----- -{} attachment(s) -Posted on: {} -Likes: {}, Reposts: {}, Quotes: {}, Replies: {}, -Flags: ", - match self.post_type { - PostType::Reply => format!( - "Reply to <{}>", - match &self.reply_to_post { - Some(reply_to_post) => reply_to_post.id.clone(), - None => String::new(), - } - ), - PostType::Quote => format!( - "Quoted post <{}>", - match &self.quoted_post.as_ref() { - Some(quoted_post) => quoted_post.id.clone(), - None => String::new(), - } - ), - PostType::Repost => format!( - "Reposted post <{}>", - match &self.reposted_post.as_ref() { - Some(reposted_post) => reposted_post.id.clone(), - None => String::new(), - } - ), - _ => format!("{:?}", self.post_type), - }, - self.id, - self.peer - .as_ref() - .map(|x| format!("{}", x.username)) - .unwrap_or("".to_string()), - self.source - .as_ref() - .unwrap_or(&"".to_owned()), - self.text.as_ref().unwrap_or(&"".to_string()), - self.attachments.len(), //TODO: maybe show the document IDs - { - let d = UNIX_EPOCH + Duration::from_secs(self.posted_timestamp); - // Create DateTime from SystemTime - let datetime = DateTime::::from(d); - // Formats the combined date and time with the specified format string. - datetime.format("%Y-%m-%d %H:%M:%S.%f").to_string() - }, - self.like_count - .map(|x| x.to_string()) - .unwrap_or("".to_string()), - self.repost_count - .map(|x| x.to_string()) - .unwrap_or("".to_string()), - self.quote_count - .map(|x| x.to_string()) - .unwrap_or("".to_string()), - self.reply_count - .map(|x| x.to_string()) - .unwrap_or("".to_string()), - ) - } -} - -impl std::fmt::Display for Profile { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "First Name: {} -{} -Name: {} -{} -{} -{} -Followers: {} -Following: {} -Display Picture: {}", - self.first_name, - self.last_name - .as_ref() - .map(|x| format!("Last Name: {}", x)) - .unwrap_or_else(|| String::from("[No last name set]")), - self.name, - self.biography - .as_ref() - .map(|x| format!("Biography: {}", x)) - .unwrap_or_else(|| String::from("[No biography set]")), - self.location - .as_ref() - .map(|x| format!("Location: {}", x)) - .unwrap_or_else(|| String::from("[No location set]")), - self.url - .as_ref() - .map(|x| format!("URL: {}", x)) - .unwrap_or_else(|| String::from("[No URL set]")), - self.followers_count, - self.following_count, - if self.display_picture_sizes.is_empty() { - String::from("not set") - } else { - let count = self.display_picture_sizes.len(); - let name = &self.display_picture_sizes[0].document.file_name; - format!("'{}' ({} sizes available)", name, count) - } - ) - } -}