From e8324cf3b065cb977adbc9529b1ef5ee18c3dd47 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Thu, 8 Aug 2024 15:04:15 +0200 Subject: [PATCH] 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 --- cli/Cargo.toml | 3 +- cli/src/main.rs | 169 ++++++++++++++++++++++++++++-------------------- 2 files changed, 102 insertions(+), 70 deletions(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index c61060d..e07a928 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -57,4 +57,5 @@ tracing-subscriber.workspace = true serde_yaml.workspace = true dirs.workspace = true -color-print = "0.3.6" +anstream = "0.6.15" +owo-colors = "4.0.0" diff --git a/cli/src/main.rs b/cli/src/main.rs index 4a4a7a8..6a954a9 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -4,14 +4,14 @@ use std::{path::PathBuf, str::FromStr, time::Duration}; use clap::{Parser, Subcommand, ValueEnum}; -use color_print::{cprint, cprintln}; use futures::stream::{self, StreamExt}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use owo_colors::OwoColorize; use rustypipe::{ client::{ClientType, RustyPipe}, model::{ richtext::ToPlaintext, traits::YtEntity, ArtistId, MusicSearchResult, TrackItem, UrlTarget, - YouTubeItem, + Verification, YouTubeItem, }, param::{search_filter, ChannelVideoTab, Country, Language, StreamFilter}, }; @@ -329,9 +329,9 @@ fn print_data(data: &T, format: Format, pretty: bool) { fn print_entities(items: &[impl YtEntity]) { for e in items { - cprint!("[{}] {}", e.id(), e.name()); + anstream::print!("[{}] {}", e.id(), e.name().bold()); if let Some(n) = e.channel_name() { - cprint!(" - {}", n); + anstream::print!(" - {}", n.cyan()); } println!(); } @@ -340,9 +340,9 @@ fn print_entities(items: &[impl YtEntity]) { fn print_tracks(tracks: &[TrackItem]) { for t in tracks { if let Some(n) = t.track_nr { - cprint!("{n:02} "); + anstream::print!("{} ", format!("{n:02}").yellow().bold()); } - cprint!("[{}] {} - ", t.id, t.name); + anstream::print!("[{}] {} - ", t.id, t.name.bold()); print_artists(&t.artists); print_duration(t.duration); println!(); @@ -354,7 +354,7 @@ fn print_artists(artists: &[ArtistId]) { if i > 0 { print!(", "); } - cprint!("{}", a.name); + anstream::print!("{}", a.name.cyan()); if let Some(id) = &a.id { print!(" [{id}]"); } @@ -368,9 +368,9 @@ fn print_duration(duration: Option) { let minutes = (d / 60) % 60; let seconds = d % 60; if hours > 0 { - cprint!("{hours:02}:"); + anstream::print!("{}", format!("{hours:02}:").yellow()); } - cprint!("{minutes:02}:{seconds:02}"); + anstream::print!("{}", format!("{minutes:02}:{seconds:02}").yellow()); } } @@ -382,7 +382,7 @@ fn print_music_search( ) { if txt { if let Some(corr) = &data.corrected_query { - cprintln!("Did you mean `{}`?", corr); + anstream::println!("Did you mean `{}`?", corr.magenta()); } print_entities(&data.items.items); } else { @@ -394,13 +394,21 @@ fn print_description(desc: Option) { if let Some(desc) = desc { if !desc.is_empty() { print_h2("Description"); - cprintln!("{}", desc); + println!("{}", desc); } } } fn print_h2(title: &str) { - cprintln!("\n{}:", 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( @@ -706,34 +714,36 @@ async fn main() { } if txt { - cprintln!( - "[Video]\n{} [{}]", - details.name, + anstream::println!( + "{}\n{} [{}]", + "[Video]".on_green().black(), + details.name.green().bold(), details.id ); - cprintln!( - "Channel: {} [{}]", + anstream::println!( + "{} {} [{}]", + "Channel:".blue(), details.channel.name, details.channel.id ); if let Some(subs) = details.channel.subscriber_count { - cprintln!("Subscribers: {}", subs); + anstream::println!("{} {}", "Subscribers:".blue(), subs); } if let Some(date) = details.publish_date { - cprintln!("Date: {}", date); + anstream::println!("{} {}", "Date:".blue(), date); } - cprintln!("Views: {}", details.view_count); + anstream::println!("{} {}", "Views:".blue(), details.view_count); if let Some(likes) = details.like_count { - cprintln!("Likes: {}", likes); + anstream::println!("{} {}", "Likes:".blue(), likes); } if let Some(comments) = details.top_comments.count { - cprintln!("Comments: {}", comments); + anstream::println!("{} {}", "Comments:".blue(), comments); } if details.is_ccommons { - cprintln!("Creative Commons"); + anstream::println!("{}", "Creative Commons".green()); } if details.is_live { - cprintln!("Livestream"); + anstream::println!("{}", "Livestream".red()); } print_description(Some(details.description.to_plaintext())); if !details.recommended.is_empty() { @@ -748,18 +758,23 @@ async fn main() { print_h2("Comments"); for c in comment_list { if let Some(author) = &c.author { - cprint!("{} [{}]", author.name, author.id); + anstream::print!("{} [{}]", author.name.cyan(), author.id); + print_verification(author.verification); } else { - cprint!("Unknown author"); + anstream::print!("{}", "Unknown author".magenta()); } if c.by_owner { print!(" (Owner)"); } println!(); println!("{}", c.text.to_plaintext()); - cprint!("Likes: {}", c.like_count.unwrap_or_default()); + anstream::print!( + "{} {}", + "Likes:".blue(), + c.like_count.unwrap_or_default() + ); if c.hearted { - cprint!(" "); + anstream::print!(" {}", "♥".red()); } println!("\n"); } @@ -773,31 +788,37 @@ async fn main() { if music { let artist = rp.query().music_artist(&id, true).await.unwrap(); if txt { - cprintln!( - "[Artist]\n{} [{}]", - artist.name, + anstream::println!( + "{}\n{} [{}]", + "[Artist]".on_green().black(), + artist.name.green().bold(), artist.id ); if let Some(subs) = artist.subscriber_count { - cprintln!("Subscribers: {subs}"); + anstream::println!("{} {}", "Subscribers:".blue(), subs); } if let Some(url) = artist.wikipedia_url { - cprintln!("Wikipedia: {url}"); + anstream::println!("{} {}", "Wikipedia:".blue(), url); } if let Some(id) = artist.tracks_playlist_id { - cprintln!("All tracks: {id}"); + anstream::println!("{} {}", "All tracks:".blue(), id); } if let Some(id) = artist.videos_playlist_id { - cprintln!("All videos: {id}"); + anstream::println!("{} {}", "All videos:".blue(), id); } if let Some(id) = artist.radio_id { - cprintln!("Radio: {id}"); + anstream::println!("{} {}", "Radio:".blue(), id); } print_description(artist.description); if !artist.albums.is_empty() { print_h2("Albums"); for b in artist.albums { - cprint!("[{}] {} ({:?}", b.id, b.name, b.album_type); + anstream::print!( + "[{}] {} ({:?}", + b.id, + b.name.bold(), + b.album_type + ); if let Some(y) = b.year { print!(", {y}"); } @@ -834,16 +855,18 @@ async fn main() { .unwrap(); if txt { - cprintln!( - "[Channel {:?}]\n{} [{}]", - tab, - channel.name, + anstream::print!( + "{}\n{} [{}]", + format!("[Channel {tab:?}]").on_green().black(), + channel.name.green().bold(), channel.id ); - print_description(Some(channel.description)); + print_verification(channel.verification); + println!(); if let Some(subs) = channel.subscriber_count { - cprintln!("Subscribers: {subs}"); + anstream::println!("{} {}", "Subscribers:".blue(), subs); } + print_description(Some(channel.description)); println!(); print_entities(&channel.content.items); } else { @@ -854,15 +877,15 @@ async fn main() { let channel = rp.query().channel_playlists(&id).await.unwrap(); if txt { - cprintln!( - "[Channel {:?}]\n{} [{}]", - tab, - channel.name, + anstream::println!( + "{}\n{} [{}]", + format!("[Channel {tab:?}]").on_green().black(), + channel.name.green().bold(), channel.id ); print_description(Some(channel.description)); if let Some(subs) = channel.subscriber_count { - cprintln!("Subscribers: {subs}"); + anstream::println!("{} {}", "Subscribers:".blue(), subs); } println!(); print_entities(&channel.content.items); @@ -874,27 +897,28 @@ async fn main() { let info = rp.query().channel_info(&id).await.unwrap(); if txt { - cprintln!( - "[Channel info]\nID:{}", + anstream::println!( + "{}\nID:{}", + "[Channel info]".on_green().black(), info.id ); print_description(Some(info.description)); if let Some(subs) = info.subscriber_count { - cprintln!("Subscribers: {subs}"); + anstream::println!("{} {}", "Subscribers:".blue(), subs); } if let Some(vids) = info.video_count { - cprintln!("Videos: {vids}"); + anstream::println!("{} {}", "Videos:".blue(), vids); } if let Some(views) = info.view_count { - cprintln!("Views: {views}"); + anstream::println!("{} {}", "Views:".blue(), views); } if let Some(created) = info.create_date { - cprintln!("Created on: {created}"); + anstream::println!("{} {}", "Created on:".blue(), created); } if !info.links.is_empty() { print_h2("Links"); for (name, url) in &info.links { - cprintln!("{name}: {url}"); + anstream::println!("{} {}", name.blue(), url); } } } else { @@ -913,14 +937,16 @@ async fn main() { .await .unwrap(); if txt { - cprintln!( - "[MusicPlaylist]\n{} [{}]\nTracks: {}", - playlist.name, + anstream::println!( + "{}\n{} [{}]\n{} {}", + "[MusicPlaylist]".on_green().black(), + playlist.name.green().bold(), playlist.id, + "Tracks:".blue(), playlist.track_count.unwrap_or_default(), ); if let Some(n) = playlist.channel_name() { - cprint!("Author: {n}"); + anstream::print!("{} {}", "Author:".blue(), n.bold()); if let Some(id) = playlist.channel_id() { print!(" [{id}]"); } @@ -940,19 +966,23 @@ async fn main() { .await .unwrap(); if txt { - cprintln!( - "[Playlist]\n{} [{}]\nVideos: {}", - playlist.name, playlist.id, playlist.video_count, + anstream::println!( + "{}\n{} [{}]\n{} {}", + "[Playlist]".on_green().black(), + playlist.name.green().bold(), + playlist.id, + "Videos:".blue(), + playlist.video_count, ); if let Some(n) = playlist.channel_name() { - cprint!("Author: {n}"); + anstream::print!("{} {}", "Author:".blue(), n.bold()); if let Some(id) = playlist.channel_id() { print!(" [{id}]"); } println!(); } if let Some(last_update) = playlist.last_update { - cprintln!("Last update: {last_update}"); + anstream::println!("{} {}", "Last update:".blue(), last_update); } print_description(playlist.description.map(|d| d.to_plaintext())); println!(); @@ -965,9 +995,10 @@ async fn main() { UrlTarget::Album { id } => { let album = rp.query().music_album(&id).await.unwrap(); if txt { - cprint!( - "[Album]\n{} [{}] ({:?}", - album.name, + anstream::print!( + "{}\n{} [{}] ({:?}", + "[Album]".on_green().black(), + album.name.green().bold(), album.id, album.album_type ); @@ -976,7 +1007,7 @@ async fn main() { } println!(")"); if let Some(n) = album.channel_name() { - cprint!("Artist: {}", n); + anstream::print!("{} {}", "Artist:".blue(), n); if let Some(id) = album.channel_id() { print!(" [{id}]"); } @@ -1024,7 +1055,7 @@ async fn main() { if txt { if let Some(corr) = res.corrected_query { - cprintln!("Did you mean `{}`?", corr); + anstream::println!("Did you mean `{}`?", corr.magenta()); } print_entities(&res.items.items); } else {