fix: use anstream + owo-color for colorful CLI output

the color-print crate works very well, but it cannot disable styling if the terminal does not support it,
when saving the output to a file, etc
This commit is contained in:
ThetaDev 2024-08-08 15:04:15 +02:00
parent 9956048377
commit 65d8f28968
2 changed files with 102 additions and 70 deletions

View file

@ -57,4 +57,5 @@ tracing-subscriber.workspace = true
serde_yaml.workspace = true serde_yaml.workspace = true
dirs.workspace = true dirs.workspace = true
color-print = "0.3.6" anstream = "0.6.15"
owo-colors = "4.0.0"

View file

@ -4,14 +4,14 @@
use std::{path::PathBuf, str::FromStr, time::Duration}; use std::{path::PathBuf, str::FromStr, time::Duration};
use clap::{Parser, Subcommand, ValueEnum}; use clap::{Parser, Subcommand, ValueEnum};
use color_print::{cprint, cprintln};
use futures::stream::{self, StreamExt}; use futures::stream::{self, StreamExt};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use owo_colors::OwoColorize;
use rustypipe::{ use rustypipe::{
client::{ClientType, RustyPipe}, client::{ClientType, RustyPipe},
model::{ model::{
richtext::ToPlaintext, traits::YtEntity, ArtistId, MusicSearchResult, TrackItem, UrlTarget, richtext::ToPlaintext, traits::YtEntity, ArtistId, MusicSearchResult, TrackItem, UrlTarget,
YouTubeItem, Verification, YouTubeItem,
}, },
param::{search_filter, ChannelVideoTab, Country, Language, StreamFilter}, param::{search_filter, ChannelVideoTab, Country, Language, StreamFilter},
}; };
@ -329,9 +329,9 @@ fn print_data<T: Serialize>(data: &T, format: Format, pretty: bool) {
fn print_entities(items: &[impl YtEntity]) { fn print_entities(items: &[impl YtEntity]) {
for e in items { for e in items {
cprint!("[{}] <b>{}</b>", e.id(), e.name()); anstream::print!("[{}] {}", e.id(), e.name().bold());
if let Some(n) = e.channel_name() { if let Some(n) = e.channel_name() {
cprint!(" - <c>{}</c>", n); anstream::print!(" - {}", n.cyan());
} }
println!(); println!();
} }
@ -340,9 +340,9 @@ fn print_entities(items: &[impl YtEntity]) {
fn print_tracks(tracks: &[TrackItem]) { fn print_tracks(tracks: &[TrackItem]) {
for t in tracks { for t in tracks {
if let Some(n) = t.track_nr { if let Some(n) = t.track_nr {
cprint!("<y><bold>{n:02}</bold></y> "); anstream::print!("{} ", format!("{n:02}").yellow().bold());
} }
cprint!("[{}] <b>{}</b> - ", t.id, t.name); anstream::print!("[{}] {} - ", t.id, t.name.bold());
print_artists(&t.artists); print_artists(&t.artists);
print_duration(t.duration); print_duration(t.duration);
println!(); println!();
@ -354,7 +354,7 @@ fn print_artists(artists: &[ArtistId]) {
if i > 0 { if i > 0 {
print!(", "); print!(", ");
} }
cprint!("<c>{}</c>", a.name); anstream::print!("{}", a.name.cyan());
if let Some(id) = &a.id { if let Some(id) = &a.id {
print!(" [{id}]"); print!(" [{id}]");
} }
@ -368,9 +368,9 @@ fn print_duration(duration: Option<u32>) {
let minutes = (d / 60) % 60; let minutes = (d / 60) % 60;
let seconds = d % 60; let seconds = d % 60;
if hours > 0 { if hours > 0 {
cprint!("<y>{hours:02}:"); anstream::print!("{}", format!("{hours:02}:").yellow());
} }
cprint!("<y>{minutes:02}:{seconds:02}</y>"); anstream::print!("{}", format!("{minutes:02}:{seconds:02}").yellow());
} }
} }
@ -382,7 +382,7 @@ fn print_music_search<T: Serialize + YtEntity>(
) { ) {
if txt { if txt {
if let Some(corr) = &data.corrected_query { if let Some(corr) = &data.corrected_query {
cprintln!("Did you mean <m>`{}`</m>?", corr); anstream::println!("Did you mean `{}`?", corr.magenta());
} }
print_entities(&data.items.items); print_entities(&data.items.items);
} else { } else {
@ -394,13 +394,21 @@ fn print_description(desc: Option<String>) {
if let Some(desc) = desc { if let Some(desc) = desc {
if !desc.is_empty() { if !desc.is_empty() {
print_h2("Description"); print_h2("Description");
cprintln!("{}", desc); println!("{}", desc);
} }
} }
} }
fn print_h2(title: &str) { fn print_h2(title: &str) {
cprintln!("\n<g><u>{}:</u></g>", title); anstream::println!("\n{}", format!("{title}:").green().underline());
}
fn print_verification(verification: Verification) {
match verification {
Verification::None => {}
Verification::Verified => print!(""),
Verification::Artist => print!(""),
}
} }
async fn download_video( async fn download_video(
@ -706,34 +714,36 @@ async fn main() {
} }
if txt { if txt {
cprintln!( anstream::println!(
"<G><k>[Video]</k></G>\n<g><bold>{}</bold></g> [{}]", "{}\n{} [{}]",
details.name, "[Video]".on_green().black(),
details.name.green().bold(),
details.id details.id
); );
cprintln!( anstream::println!(
"<b>Channel:</b> {} [{}]", "{} {} [{}]",
"Channel:".blue(),
details.channel.name, details.channel.name,
details.channel.id details.channel.id
); );
if let Some(subs) = details.channel.subscriber_count { if let Some(subs) = details.channel.subscriber_count {
cprintln!("<b>Subscribers:</b> {}", subs); anstream::println!("{} {}", "Subscribers:".blue(), subs);
} }
if let Some(date) = details.publish_date { if let Some(date) = details.publish_date {
cprintln!("<b>Date:</b> {}", date); anstream::println!("{} {}", "Date:".blue(), date);
} }
cprintln!("<b>Views</b>: {}", details.view_count); anstream::println!("{} {}", "Views:".blue(), details.view_count);
if let Some(likes) = details.like_count { if let Some(likes) = details.like_count {
cprintln!("<b>Likes:</b> {}", likes); anstream::println!("{} {}", "Likes:".blue(), likes);
} }
if let Some(comments) = details.top_comments.count { if let Some(comments) = details.top_comments.count {
cprintln!("<b>Comments:</b> {}", comments); anstream::println!("{} {}", "Comments:".blue(), comments);
} }
if details.is_ccommons { if details.is_ccommons {
cprintln!("<g>Creative Commons</g>"); anstream::println!("{}", "Creative Commons".green());
} }
if details.is_live { if details.is_live {
cprintln!("<r>Livestream</r>"); anstream::println!("{}", "Livestream".red());
} }
print_description(Some(details.description.to_plaintext())); print_description(Some(details.description.to_plaintext()));
if !details.recommended.is_empty() { if !details.recommended.is_empty() {
@ -748,18 +758,23 @@ async fn main() {
print_h2("Comments"); print_h2("Comments");
for c in comment_list { for c in comment_list {
if let Some(author) = &c.author { if let Some(author) = &c.author {
cprint!("<c>{}</c> [{}]", author.name, author.id); anstream::print!("{} [{}]", author.name.cyan(), author.id);
print_verification(author.verification);
} else { } else {
cprint!("<m>Unknown author</m>"); anstream::print!("{}", "Unknown author".magenta());
} }
if c.by_owner { if c.by_owner {
print!(" (Owner)"); print!(" (Owner)");
} }
println!(); println!();
println!("{}", c.text.to_plaintext()); println!("{}", c.text.to_plaintext());
cprint!("<b>Likes:</b> {}", c.like_count.unwrap_or_default()); anstream::print!(
"{} {}",
"Likes:".blue(),
c.like_count.unwrap_or_default()
);
if c.hearted { if c.hearted {
cprint!(" <r>♥</r>"); anstream::print!(" {}", "".red());
} }
println!("\n"); println!("\n");
} }
@ -773,31 +788,37 @@ async fn main() {
if music { if music {
let artist = rp.query().music_artist(&id, true).await.unwrap(); let artist = rp.query().music_artist(&id, true).await.unwrap();
if txt { if txt {
cprintln!( anstream::println!(
"<G><k>[Artist]</k></G>\n<g><bold>{}</bold></g> [{}]", "{}\n{} [{}]",
artist.name, "[Artist]".on_green().black(),
artist.name.green().bold(),
artist.id artist.id
); );
if let Some(subs) = artist.subscriber_count { if let Some(subs) = artist.subscriber_count {
cprintln!("<b>Subscribers:</b> {subs}"); anstream::println!("{} {}", "Subscribers:".blue(), subs);
} }
if let Some(url) = artist.wikipedia_url { if let Some(url) = artist.wikipedia_url {
cprintln!("<b>Wikipedia:</b> {url}"); anstream::println!("{} {}", "Wikipedia:".blue(), url);
} }
if let Some(id) = artist.tracks_playlist_id { if let Some(id) = artist.tracks_playlist_id {
cprintln!("<b>All tracks:</b> {id}"); anstream::println!("{} {}", "All tracks:".blue(), id);
} }
if let Some(id) = artist.videos_playlist_id { if let Some(id) = artist.videos_playlist_id {
cprintln!("<b>All videos:</b> {id}"); anstream::println!("{} {}", "All videos:".blue(), id);
} }
if let Some(id) = artist.radio_id { if let Some(id) = artist.radio_id {
cprintln!("<b>Radio:</b> {id}"); anstream::println!("{} {}", "Radio:".blue(), id);
} }
print_description(artist.description); print_description(artist.description);
if !artist.albums.is_empty() { if !artist.albums.is_empty() {
print_h2("Albums"); print_h2("Albums");
for b in artist.albums { for b in artist.albums {
cprint!("[{}] <b>{}</b> ({:?}", b.id, b.name, b.album_type); anstream::print!(
"[{}] {} ({:?}",
b.id,
b.name.bold(),
b.album_type
);
if let Some(y) = b.year { if let Some(y) = b.year {
print!(", {y}"); print!(", {y}");
} }
@ -834,16 +855,18 @@ async fn main() {
.unwrap(); .unwrap();
if txt { if txt {
cprintln!( anstream::print!(
"<G><k>[Channel {:?}]</k></G>\n<g><bold>{}</bold></g> [{}]", "{}\n{} [{}]",
tab, format!("[Channel {tab:?}]").on_green().black(),
channel.name, channel.name.green().bold(),
channel.id channel.id
); );
print_description(Some(channel.description)); print_verification(channel.verification);
println!();
if let Some(subs) = channel.subscriber_count { if let Some(subs) = channel.subscriber_count {
cprintln!("<b>Subscribers:</b> {subs}"); anstream::println!("{} {}", "Subscribers:".blue(), subs);
} }
print_description(Some(channel.description));
println!(); println!();
print_entities(&channel.content.items); print_entities(&channel.content.items);
} else { } else {
@ -854,15 +877,15 @@ async fn main() {
let channel = rp.query().channel_playlists(&id).await.unwrap(); let channel = rp.query().channel_playlists(&id).await.unwrap();
if txt { if txt {
cprintln!( anstream::println!(
"<G><k>[Channel {:?}]</k></G>\n<g><bold>{}</bold></g> [{}]", "{}\n{} [{}]",
tab, format!("[Channel {tab:?}]").on_green().black(),
channel.name, channel.name.green().bold(),
channel.id channel.id
); );
print_description(Some(channel.description)); print_description(Some(channel.description));
if let Some(subs) = channel.subscriber_count { if let Some(subs) = channel.subscriber_count {
cprintln!("<b>Subscribers:</b> {subs}"); anstream::println!("{} {}", "Subscribers:".blue(), subs);
} }
println!(); println!();
print_entities(&channel.content.items); print_entities(&channel.content.items);
@ -874,27 +897,28 @@ async fn main() {
let info = rp.query().channel_info(&id).await.unwrap(); let info = rp.query().channel_info(&id).await.unwrap();
if txt { if txt {
cprintln!( anstream::println!(
"<G><k>[Channel info]</k></G>\n<b>ID:</b>{}", "{}\n<b>ID:</b>{}",
"[Channel info]".on_green().black(),
info.id info.id
); );
print_description(Some(info.description)); print_description(Some(info.description));
if let Some(subs) = info.subscriber_count { if let Some(subs) = info.subscriber_count {
cprintln!("<b>Subscribers:</b> {subs}"); anstream::println!("{} {}", "Subscribers:".blue(), subs);
} }
if let Some(vids) = info.video_count { if let Some(vids) = info.video_count {
cprintln!("<b>Videos:</b> {vids}"); anstream::println!("{} {}", "Videos:".blue(), vids);
} }
if let Some(views) = info.view_count { if let Some(views) = info.view_count {
cprintln!("<b>Views:</b> {views}"); anstream::println!("{} {}", "Views:".blue(), views);
} }
if let Some(created) = info.create_date { if let Some(created) = info.create_date {
cprintln!("<b>Created on:</b> {created}"); anstream::println!("{} {}", "Created on:".blue(), created);
} }
if !info.links.is_empty() { if !info.links.is_empty() {
print_h2("Links"); print_h2("Links");
for (name, url) in &info.links { for (name, url) in &info.links {
cprintln!("<b>{name}:</b> {url}"); anstream::println!("{} {}", name.blue(), url);
} }
} }
} else { } else {
@ -913,14 +937,16 @@ async fn main() {
.await .await
.unwrap(); .unwrap();
if txt { if txt {
cprintln!( anstream::println!(
"<G><k>[MusicPlaylist]</k></G>\n<g><bold>{}</bold></g> [{}]\n<b>Tracks:</b> {}", "{}\n{} [{}]\n{} {}",
playlist.name, "[MusicPlaylist]".on_green().black(),
playlist.name.green().bold(),
playlist.id, playlist.id,
"Tracks:".blue(),
playlist.track_count.unwrap_or_default(), playlist.track_count.unwrap_or_default(),
); );
if let Some(n) = playlist.channel_name() { if let Some(n) = playlist.channel_name() {
cprint!("<b>Author:</b> {n}"); anstream::print!("{} {}", "Author:".blue(), n.bold());
if let Some(id) = playlist.channel_id() { if let Some(id) = playlist.channel_id() {
print!(" [{id}]"); print!(" [{id}]");
} }
@ -940,19 +966,23 @@ async fn main() {
.await .await
.unwrap(); .unwrap();
if txt { if txt {
cprintln!( anstream::println!(
"<G><k>[Playlist]</k></G>\n<g><bold>{}</bold></g> [{}]\n<b>Videos:</b> {}", "{}\n{} [{}]\n{} {}",
playlist.name, playlist.id, playlist.video_count, "[Playlist]".on_green().black(),
playlist.name.green().bold(),
playlist.id,
"Videos:".blue(),
playlist.video_count,
); );
if let Some(n) = playlist.channel_name() { if let Some(n) = playlist.channel_name() {
cprint!("<b>Author:</b> {n}"); anstream::print!("{} {}", "Author:".blue(), n.bold());
if let Some(id) = playlist.channel_id() { if let Some(id) = playlist.channel_id() {
print!(" [{id}]"); print!(" [{id}]");
} }
println!(); println!();
} }
if let Some(last_update) = playlist.last_update { if let Some(last_update) = playlist.last_update {
cprintln!("<b>Last update:</b> {last_update}"); anstream::println!("{} {}", "Last update:".blue(), last_update);
} }
print_description(playlist.description.map(|d| d.to_plaintext())); print_description(playlist.description.map(|d| d.to_plaintext()));
println!(); println!();
@ -965,9 +995,10 @@ async fn main() {
UrlTarget::Album { id } => { UrlTarget::Album { id } => {
let album = rp.query().music_album(&id).await.unwrap(); let album = rp.query().music_album(&id).await.unwrap();
if txt { if txt {
cprint!( anstream::print!(
"<G><k>[Album]</k></G>\n<g><bold>{}</bold></g> [{}] ({:?}", "{}\n{} [{}] ({:?}",
album.name, "[Album]".on_green().black(),
album.name.green().bold(),
album.id, album.id,
album.album_type album.album_type
); );
@ -976,7 +1007,7 @@ async fn main() {
} }
println!(")"); println!(")");
if let Some(n) = album.channel_name() { if let Some(n) = album.channel_name() {
cprint!("<b>Artist:</b> {}", n); anstream::print!("{} {}", "Artist:".blue(), n);
if let Some(id) = album.channel_id() { if let Some(id) = album.channel_id() {
print!(" [{id}]"); print!(" [{id}]");
} }
@ -1024,7 +1055,7 @@ async fn main() {
if txt { if txt {
if let Some(corr) = res.corrected_query { if let Some(corr) = res.corrected_query {
cprintln!("Did you mean <m>`{}`</m>?", corr); anstream::println!("Did you mean `{}`?", corr.magenta());
} }
print_entities(&res.items.items); print_entities(&res.items.items);
} else { } else {