Moved Display impl from types to cli crate. added pager to feed.

This commit is contained in:
Mahesh Bansod 2021-11-07 18:29:14 +05:30
parent 31faf60285
commit 2fcfbe9e67
6 changed files with 268 additions and 140 deletions

92
Cargo.lock generated
View File

@ -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"

View File

@ -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"
rpassword = "5.0.1"
minus = { version = "4.0.2", features = ["static_output"] }
chrono = "0.4.19"

145
cli/src/entities.rs Normal file
View File

@ -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<Post> for SVPost {
fn from(post: Post) -> Self {
Self(post)
}
}
impl std::convert::From<Profile> 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(|| "<unavailable>".to_string()),
self.0
.source
.as_ref()
.unwrap_or(&"<not applicable>".to_owned()),
self.0.text.as_ref().unwrap_or(&"<no text>".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::<Utc>::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(|| "<not applicable>".to_string()),
self.0
.repost_count
.map(|x| x.to_string())
.unwrap_or_else(|| "<not applicable>".to_string()),
self.0
.quote_count
.map(|x| x.to_string())
.unwrap_or_else(|| "<not applicable>".to_string()),
self.0
.reply_count
.map(|x| x.to_string())
.unwrap_or_else(|| "<not applicable>".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)
}
)
}
}

View File

@ -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 {

View File

@ -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"
tokio = {version = "1.11.0", features = ["full"]}

View File

@ -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<String>,
name: String,
biography: Option<String>,
location: Option<String>,
url: Option<String>,
followers_count: u32,
following_count: u32,
display_picture_sizes: Vec<DisplayPictureSize>,
pub first_name: String,
pub last_name: Option<String>,
pub name: String,
pub biography: Option<String>,
pub location: Option<String>,
pub url: Option<String>,
pub followers_count: u32,
pub following_count: u32,
pub display_picture_sizes: Vec<DisplayPictureSize>,
}
/// 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("<unavailable>".to_string()),
self.source
.as_ref()
.unwrap_or(&"<not applicable>".to_owned()),
self.text.as_ref().unwrap_or(&"<no text>".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::<Utc>::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("<not applicable>".to_string()),
self.repost_count
.map(|x| x.to_string())
.unwrap_or("<not applicable>".to_string()),
self.quote_count
.map(|x| x.to_string())
.unwrap_or("<not applicable>".to_string()),
self.reply_count
.map(|x| x.to_string())
.unwrap_or("<not applicable>".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)
}
)
}
}