From ae9b55206d174a89a84453c7499ca87b3ce6df0f Mon Sep 17 00:00:00 2001 From: bryan newbold Date: Mon, 20 Feb 2023 02:03:17 -0800 Subject: cli: initial support for pretty-printing feeds --- adenosine-cli/src/bin/adenosine.rs | 40 ++++++++++++++-- adenosine-cli/src/lib.rs | 2 + adenosine-cli/src/pretty.rs | 98 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 adenosine-cli/src/pretty.rs diff --git a/adenosine-cli/src/bin/adenosine.rs b/adenosine-cli/src/bin/adenosine.rs index 169e5d7..aae6a87 100644 --- a/adenosine-cli/src/bin/adenosine.rs +++ b/adenosine-cli/src/bin/adenosine.rs @@ -1,3 +1,4 @@ +use adenosine::app_bsky; use adenosine::auth::parse_did_from_jwt; use adenosine::created_at_now; use adenosine::identifiers::*; @@ -312,7 +313,7 @@ fn require_auth_did(opt: &Opt, xrpc_client: &mut XrpcClient) -> Result { xrpc_client.auth_login(handle, passwd)?; } else { return Err(anyhow!( - "command requires auth, but have neither token orhandle/password" + "command requires auth, but have neither token or handle/password" )); } xrpc_client.auth_did() @@ -586,16 +587,47 @@ fn run(opt: Opt) -> Result<()> { .map(|v| v.to_string()) .unwrap_or(require_auth_did(&opt, &mut xrpc_client)?.to_string()); params.insert("author".to_string(), name); - xrpc_client.get( + let resp = xrpc_client.get( &Nsid::from_str("app.bsky.feed.getAuthorFeed")?, Some(params), - )? + )?; + let resp = resp.ok_or(anyhow!("expected resp from getAuthorFeed"))?; + if atty::is(atty::Stream::Stdout) { + for val in resp["feed"] + .as_array() + .ok_or(anyhow!("expected feed from getAuthorFeed"))? + .iter() + { + let val: serde_json::Value = val.clone(); + let fi: app_bsky::FeedPostView = serde_json::from_value(val)?; + pretty::pp_post_view(&fi.post)?; + } + None + } else { + Some(resp) + } } Command::Bsky { cmd: BskyCommand::Timeline, } => { require_auth_did(&opt, &mut xrpc_client)?; - xrpc_client.get(&Nsid::from_str("app.bsky.feed.getTimeline")?, None)? + let resp = xrpc_client.get(&Nsid::from_str("app.bsky.feed.getTimeline")?, None)?; + let resp = resp.ok_or(anyhow!("expected resp from getTimeline"))?; + if atty::is(atty::Stream::Stdout) { + for val in resp["feed"] + .as_array() + .ok_or(anyhow!("expected feed from getTimeline"))? + .iter() + { + let val: serde_json::Value = val.clone(); + //print_result_json(Some(val.clone()))?; + let fi: app_bsky::FeedPostView = serde_json::from_value(val)?; + pretty::pp_post_view(&fi.post)?; + } + None + } else { + Some(resp) + } } Command::Bsky { cmd: BskyCommand::Notifications, diff --git a/adenosine-cli/src/lib.rs b/adenosine-cli/src/lib.rs index 3eb56cc..b21ed98 100644 --- a/adenosine-cli/src/lib.rs +++ b/adenosine-cli/src/lib.rs @@ -6,6 +6,8 @@ use serde_json::Value; use std::collections::HashMap; use std::str::FromStr; +pub mod pretty; + /// Represents fields/content specified on the command line. /// /// Sort of like HTTPie. Query parameters are '==', body values (JSON) are '='. Only single-level diff --git a/adenosine-cli/src/pretty.rs b/adenosine-cli/src/pretty.rs new file mode 100644 index 0000000..306c056 --- /dev/null +++ b/adenosine-cli/src/pretty.rs @@ -0,0 +1,98 @@ +use adenosine::app_bsky::PostView; +use anyhow::Result; +use std::io::Write; +use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; + +pub fn pp_post_view(pv: &PostView) -> Result<()> { + let mut stdout = StandardStream::stdout(ColorChoice::Always); + stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)).set_bold(true))?; + + write!(&mut stdout, "@{:<54.54}", pv.author.handle)?; + stdout.reset()?; + stdout.set_color(ColorSpec::new().set_dimmed(true))?; + writeln!(&mut stdout, "{}", pv.indexedAt)?; + stdout.reset()?; + + write!(&mut stdout, " ")?; + if let Some(entities) = &pv.record.entities { + let mut cur: usize = 0; + for ent in entities { + write!( + &mut stdout, + "{}", + &pv.record.text[cur..ent.index.start as usize] + )?; + match ent.r#type.as_str() { + "mention" => stdout + .set_color(ColorSpec::new().set_fg(Some(Color::Magenta)).set_bold(true))?, + "hashtag" => { + stdout.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true))? + } + "link" => stdout.set_color( + ColorSpec::new() + .set_fg(Some(Color::Blue)) + .set_underline(true), + )?, + _ => {} + } + write!( + &mut stdout, + "{}", + &pv.record.text[ent.index.start as usize..ent.index.end as usize] + )?; + stdout.reset()?; + cur = ent.index.end as usize; + } + writeln!(&mut stdout, "{}", &pv.record.text[cur..])?; + } else if !pv.record.text.is_empty() { + writeln!(&mut stdout, "{}", &pv.record.text)?; + } + + if let Some(embed) = &pv.embed { + if let Some(ext) = &embed.external { + let desc = format!("{}: {}", ext.title, ext.description); + stdout.set_color( + ColorSpec::new() + .set_fg(Some(Color::Green)) + .set_dimmed(true) + .set_underline(false), + )?; + writeln!(&mut stdout, " {:<70.70}", desc)?; + write!(&mut stdout, " ")?; + stdout.set_color( + ColorSpec::new() + .set_fg(Some(Color::Green)) + .set_dimmed(true) + .set_underline(true), + )?; + writeln!(&mut stdout, "{}", &ext.uri)?; + stdout.reset()?; + } + if let Some(images) = &embed.images { + for img in images.iter() { + if !img.alt.is_empty() { + stdout.set_color( + ColorSpec::new() + .set_fg(Some(Color::Green)) + .set_dimmed(true) + .set_underline(false), + )?; + writeln!(&mut stdout, " {:<70.70}", img.alt)?; + } + write!(&mut stdout, " ")?; + stdout.set_color( + ColorSpec::new() + .set_fg(Some(Color::Green)) + .set_dimmed(true) + .set_underline(true), + )?; + writeln!(&mut stdout, "{}", &img.fullsize)?; + stdout.reset()?; + } + } + } + + writeln!(&mut stdout, "\n")?; + stdout.reset()?; + Ok(()) +} -- cgit v1.2.3