diff options
author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2025-07-18 18:06:42 +0200 |
---|---|---|
committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2025-07-18 18:06:42 +0200 |
commit | e2e88fdabe9bfb3ed236983e6e737b9790d50cd2 (patch) | |
tree | 170644c481a5cfe4811bd00b2772b064bfdca25a | |
parent | chore(crates/yt/Cargo.toml): Add `pretty-assertions` for tests (diff) | |
download | yt-e2e88fdabe9bfb3ed236983e6e737b9790d50cd2.zip |
fix(crates/yt): **Always** honor the `config.global.display_colors` config setting
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | crates/colors/Cargo.toml | 26 | ||||
-rw-r--r-- | crates/colors/src/custom.rs | 65 | ||||
-rw-r--r-- | crates/colors/src/lib.rs | 87 | ||||
-rw-r--r-- | crates/colors/src/list.rs | 223 | ||||
-rw-r--r-- | crates/colors/src/support.rs | 116 | ||||
-rw-r--r-- | crates/yt/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/yt/src/config/mod.rs | 16 | ||||
-rw-r--r-- | crates/yt/src/storage/db/insert/playlist.rs | 3 | ||||
-rw-r--r-- | crates/yt/src/videos/mod.rs | 210 | ||||
-rw-r--r-- | crates/yt/tests/_testenv/run.rs | 8 | ||||
-rw-r--r-- | crates/yt/tests/watch/mod.rs | 12 |
12 files changed, 730 insertions, 41 deletions
diff --git a/Cargo.toml b/Cargo.toml index 64b8091..f985372 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,12 @@ [workspace] resolver = "2" members = [ - "crates/yt_dlp", + "crates/colors", "crates/libmpv2", "crates/libmpv2/libmpv2-sys", "crates/termsize", "crates/yt", + "crates/yt_dlp", ] [workspace.package] @@ -33,6 +34,7 @@ yt_dlp = { path = "./crates/yt_dlp" } libmpv2 = { path = "./crates/libmpv2" } termsize = { path = "./crates/termsize" } uu_fmt = { path = "./crates/fmt" } +colors = { path = "./crates/colors" } # Shared pyo3 = { version = "0.25.1", features = ["macros"], default-features = false } diff --git a/crates/colors/Cargo.toml b/crates/colors/Cargo.toml new file mode 100644 index 0000000..4edefcf --- /dev/null +++ b/crates/colors/Cargo.toml @@ -0,0 +1,26 @@ +# yt - A fully featured command line YouTube client +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# Copyright (C) 2025 uutils developers +# SPDX-License-Identifier: MIT +# +# This file is part of Yt. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. + +[package] +name = "colors" +authors.workspace = true +license.workspace = true +description = "A owo-colors inspired color crate." +version.workspace = true +edition.workspace = true +repository.workspace = true +rust-version.workspace = true +publish = false + +[dependencies] + +[lints] +workspace = true diff --git a/crates/colors/src/custom.rs b/crates/colors/src/custom.rs new file mode 100644 index 0000000..2adcfa9 --- /dev/null +++ b/crates/colors/src/custom.rs @@ -0,0 +1,65 @@ +// Taken from <https://github.com/owo-colors/owo-colors/blob/61f8bba2f5f80e9f4fa600fbfdf2c21656f1d523/src/colors/custom.rs> +// at 2025-07-16T18:05:55 CEST. + +const U8_TO_STR: [[u8; 3]; 256] = generate_lookup(); + +const fn generate_lookup() -> [[u8; 3]; 256] { + let mut table = [[0, 0, 0]; 256]; + + let mut i = 0; + while i < 256 { + table[i] = [ + b'0' + (i / 100) as u8, + b'0' + (i / 10 % 10) as u8, + b'0' + (i % 10) as u8, + ]; + i += 1; + } + + table +} + +#[derive(Clone, Copy)] +pub(crate) enum Plane { + Fg, + Bg, +} + +pub(crate) const fn rgb_to_ansi(r: u8, g: u8, b: u8, plane: Plane) -> [u8; 18] { + let mut buf = *b"\x1b[p8;2;rrr;ggg;bbb"; + + let r = U8_TO_STR[r as usize]; + let g = U8_TO_STR[g as usize]; + let b = U8_TO_STR[b as usize]; + + // p 2 + buf[2] = match plane { + Plane::Fg => b'3', + Plane::Bg => b'4', + }; + + // r 7 + buf[7] = r[0]; + buf[8] = r[1]; + buf[9] = r[2]; + + // g 11 + buf[11] = g[0]; + buf[12] = g[1]; + buf[13] = g[2]; + + // b 15 + buf[15] = b[0]; + buf[16] = b[1]; + buf[17] = b[2]; + + buf +} + +/// This exists since [`unwrap()`] isn't const-safe (it invokes formatting infrastructure) +pub(crate) const fn bytes_to_str(bytes: &'static [u8]) -> &'static str { + match core::str::from_utf8(bytes) { + Ok(o) => o, + Err(_e) => panic!("Const parsing &[u8] to a string failed!"), + } +} diff --git a/crates/colors/src/lib.rs b/crates/colors/src/lib.rs new file mode 100644 index 0000000..ee2c1f8 --- /dev/null +++ b/crates/colors/src/lib.rs @@ -0,0 +1,87 @@ +use std::fmt::{Display, Write}; + +use crate::{ + list::{elements, methods}, + support::{CSE, CSI, elements_inner}, +}; + +pub mod custom; +mod list; +mod support; + +#[derive(Debug)] +pub struct Canvas<I: Display>(I); + +impl<I: Display> Colorize for Canvas<I> { + fn render_into(self, base: &mut String, use_colors: bool) { + write!(base, "{}", self.0).expect("Is written into a string"); + + if use_colors { + // Reset the color and style, if we used colours. + base.write_str(CSI).expect("In-memory write"); + base.write_str("0").expect("In-memory write"); + base.write_str(CSE).expect("In-memory write"); + } + } +} + +pub trait IntoCanvas: Display + Sized { + fn into_canvas(self) -> Canvas<Self> { + Canvas(self) + } + + methods! { IntoCanvas } +} + +impl<I: Display> IntoCanvas for I {} + +pub trait Colorize: Sized { + /// Turn this colorized struct into a string, by writing into the base. + fn render_into(self, base: &mut String, use_colors: bool); + + /// Turn this colorized struct into a string for consumption. + fn render(self, use_colors: bool) -> String { + let mut base = String::new(); + self.render_into(&mut base, use_colors); + base + } + + methods! { Colorize } +} + +elements! {} + +#[cfg(test)] +mod tests { + use crate::{Colorize, IntoCanvas}; + + #[test] + fn test_colorize_basic() { + let base = "Base".green().render(true); + #[rustfmt::skip] + let expected = concat!( + "\x1b[32m", + "Base", + "\x1b[0m", + ); + + assert_eq!(base.as_str(), expected); + } + + #[test] + fn test_colorize_combo() { + let base = "Base".green().on_red().bold().strike_through().render(true); + + #[rustfmt::skip] + let expected = concat!( + "\x1b[9m", // strike_through + "\x1b[1m", // bold + "\x1b[41m", // on_red + "\x1b[32m", // green + "Base", + "\x1b[0m", + ); + + assert_eq!(base.as_str(), expected); + } +} diff --git a/crates/colors/src/list.rs b/crates/colors/src/list.rs new file mode 100644 index 0000000..ecbe465 --- /dev/null +++ b/crates/colors/src/list.rs @@ -0,0 +1,223 @@ +use crate::support::prepend_input; + +prepend_input! { + crate::support::methods_inner as methods (($tt:tt) -> {$tt}), + crate::support::elements_inner as elements, + + <shared_input> + { + // Colors + Black black 30, + OnBlack on_black 40, + + Red red 31, + OnRed on_red 41, + + Green green 32, + OnGreen on_green 42, + + Yellow yellow 33, + OnYellow on_yellow 43, + + Blue blue 34, + OnBlue on_blue 44, + + Magenta magenta 35, + OnMagenta on_magenta 45, + + Cyan cyan 36, + OnCyan on_cyan 46, + + White white 37, + OnWhite on_white 47, + + Default default 39, + OnDefault on_default 49, + + // Bright bright colors + BrightBlack bright_black 90, + OnBrightBlack on_bright_black 100, + + BrightRed bright_red 91, + OnBrightRed on_bright_red 101, + + BrightGreen bright_green 92, + OnBrightGreen on_bright_green 102, + + BrightYellow bright_yellow 93, + OnBrightYellow on_bright_yellow 103, + + BrightBlue bright_blue 94, + OnBrightBlue on_bright_blue 104, + + BrightMagenta bright_magenta 95, + OnBrightMagenta on_bright_magenta 105, + + BrightCyan bright_cyan 96, + OnBrightCyan on_bright_cyan 106, + + BrightWhite bright_white 97, + OnBrightWhite on_bright_white 107, + + // CSS colors + // TODO(@bpeetz): Also support background colors with these values. <2025-07-16> + AliceBlue alice_blue (240, 248, 255), + AntiqueWhite antique_white (250, 235, 215), + Aqua aqua (0, 255, 255), + Aquamarine aquamarine (127, 255, 212), + Azure azure (240, 255, 255), + Beige beige (245, 245, 220), + Bisque bisque (255, 228, 196), + // Black black (0, 0, 0), + BlanchedAlmond blanched_almond (255, 235, 205), + // Blue blue (0, 0, 255), + BlueViolet blue_violet (138, 43, 226), + Brown brown (165, 42, 42), + BurlyWood burly_wood (222, 184, 135), + CadetBlue cadet_blue (95, 158, 160), + Chartreuse chartreuse (127, 255, 0), + Chocolate chocolate (210, 105, 30), + Coral coral (255, 127, 80), + CornflowerBlue cornflower_blue (100, 149, 237), + Cornsilk cornsilk (255, 248, 220), + Crimson crimson (220, 20, 60), + DarkBlue dark_blue (0, 0, 139), + DarkCyan dark_cyan (0, 139, 139), + DarkGoldenRod dark_golden_rod (184, 134, 11), + DarkGray dark_gray (169, 169, 169), + DarkGrey dark_grey (169, 169, 169), + DarkGreen dark_green (0, 100, 0), + DarkKhaki dark_khaki (189, 183, 107), + DarkMagenta dark_magenta (139, 0, 139), + DarkOliveGreen dark_olive_green (85, 107, 47), + DarkOrange dark_orange (255, 140, 0), + DarkOrchid dark_orchid (153, 50, 204), + DarkRed dark_red (139, 0, 0), + DarkSalmon dark_salmon (233, 150, 122), + DarkSeaGreen dark_sea_green (143, 188, 143), + DarkSlateBlue dark_slate_blue (72, 61, 139), + DarkSlateGray dark_slate_gray (47, 79, 79), + DarkSlateGrey dark_slate_grey (47, 79, 79), + DarkTurquoise dark_turquoise (0, 206, 209), + DarkViolet dark_violet (148, 0, 211), + DeepPink deep_pink (255, 20, 147), + DeepSkyBlue deep_sky_blue (0, 191, 255), + DimGray dim_gray (105, 105, 105), + DimGrey dim_grey (105, 105, 105), + DodgerBlue dodger_blue (30, 144, 255), + FireBrick fire_brick (178, 34, 34), + FloralWhite floral_white (255, 250, 240), + ForestGreen forest_green (34, 139, 34), + Fuchsia fuchsia (255, 0, 255), + Gainsboro gainsboro (220, 220, 220), + GhostWhite ghost_white (248, 248, 255), + Gold gold (255, 215, 0), + GoldenRod golden_rod (218, 165, 32), + Gray gray (128, 128, 128), + Grey grey (128, 128, 128), + // Green green (0, 128, 0), + GreenYellow green_yellow (173, 255, 47), + HoneyDew honey_dew (240, 255, 240), + HotPink hot_pink (255, 105, 180), + IndianRed indian_red (205, 92, 92), + Indigo indigo (75, 0, 130), + Ivory ivory (255, 255, 240), + Khaki khaki (240, 230, 140), + Lavender lavender (230, 230, 250), + LavenderBlush lavender_blush (255, 240, 245), + LawnGreen lawn_green (124, 252, 0), + LemonChiffon lemon_chiffon (255, 250, 205), + LightBlue light_blue (173, 216, 230), + LightCoral light_coral (240, 128, 128), + LightCyan light_cyan (224, 255, 255), + LightGoldenRodYellow light_golden_rod_yellow (250, 250, 210), + LightGray light_gray (211, 211, 211), + LightGrey light_grey (211, 211, 211), + LightGreen light_green (144, 238, 144), + LightPink light_pink (255, 182, 193), + LightSalmon light_salmon (255, 160, 122), + LightSeaGreen light_sea_green (32, 178, 170), + LightSkyBlue light_sky_blue (135, 206, 250), + LightSlateGray light_slate_gray (119, 136, 153), + LightSlateGrey light_slate_grey (119, 136, 153), + LightSteelBlue light_steel_blue (176, 196, 222), + LightYellow light_yellow (255, 255, 224), + Lime lime (0, 255, 0), + LimeGreen lime_green (50, 205, 50), + Linen linen (250, 240, 230), + // Magenta magenta (255, 0, 255), + Maroon maroon (128, 0, 0), + MediumAquaMarine medium_aqua_marine (102, 205, 170), + MediumBlue medium_blue (0, 0, 205), + MediumOrchid medium_orchid (186, 85, 211), + MediumPurple medium_purple (147, 112, 219), + MediumSeaGreen medium_sea_green (60, 179, 113), + MediumSlateBlue medium_slate_blue (123, 104, 238), + MediumSpringGreen medium_spring_green (0, 250, 154), + MediumTurquoise medium_turquoise (72, 209, 204), + MediumVioletRed medium_violet_red (199, 21, 133), + MidnightBlue midnight_blue (25, 25, 112), + MintCream mint_cream (245, 255, 250), + MistyRose misty_rose (255, 228, 225), + Moccasin moccasin (255, 228, 181), + NavajoWhite navajo_white (255, 222, 173), + Navy navy (0, 0, 128), + OldLace old_lace (253, 245, 230), + Olive olive (128, 128, 0), + OliveDrab olive_drab (107, 142, 35), + Orange orange (255, 165, 0), + OrangeRed orange_red (255, 69, 0), + Orchid orchid (218, 112, 214), + PaleGoldenRod pale_golden_rod (238, 232, 170), + PaleGreen pale_green (152, 251, 152), + PaleTurquoise pale_turquoise (175, 238, 238), + PaleVioletRed pale_violet_red (219, 112, 147), + PapayaWhip papaya_whip (255, 239, 213), + PeachPuff peach_puff (255, 218, 185), + Peru peru (205, 133, 63), + Pink pink (255, 192, 203), + Plum plum (221, 160, 221), + PowderBlue powder_blue (176, 224, 230), + Purple purple (128, 0, 128), + RebeccaPurple rebecca_purple (102, 51, 153), + // Red red (255, 0, 0), + RosyBrown rosy_brown (188, 143, 143), + RoyalBlue royal_blue (65, 105, 225), + SaddleBrown saddle_brown (139, 69, 19), + Salmon salmon (250, 128, 114), + SandyBrown sandy_brown (244, 164, 96), + SeaGreen sea_green (46, 139, 87), + SeaShell sea_shell (255, 245, 238), + Sienna sienna (160, 82, 45), + Silver silver (192, 192, 192), + SkyBlue sky_blue (135, 206, 235), + SlateBlue slate_blue (106, 90, 205), + SlateGray slate_gray (112, 128, 144), + SlateGrey slate_grey (112, 128, 144), + Snow snow (255, 250, 250), + SpringGreen spring_green (0, 255, 127), + SteelBlue steel_blue (70, 130, 180), + Tan tan (210, 180, 140), + Teal teal (0, 128, 128), + Thistle thistle (216, 191, 216), + Tomato tomato (255, 99, 71), + Turquoise turquoise (64, 224, 208), + Violet violet (238, 130, 238), + Wheat wheat (245, 222, 179), + // White white (255, 255, 255), + WhiteSmoke white_smoke (245, 245, 245), + // Yellow yellow (255, 255, 0), + YellowGreen yellow_green (154, 205, 50), + + // Styles + Bold bold 1, + Dim dim 2, + Italic italic 3, + Underline underline 4, + Blink blink 5, + BlinkFast blink_fast 6, + Reversed reversed 7, + Hidden hidden 8, + StrikeThrough strike_through 9, + } +} diff --git a/crates/colors/src/support.rs b/crates/colors/src/support.rs new file mode 100644 index 0000000..b42ce5d --- /dev/null +++ b/crates/colors/src/support.rs @@ -0,0 +1,116 @@ +pub(super) const CSI: &str = "\x1b["; +pub(super) const CSE: &str = "m"; + +macro_rules! elements_inner { + ( + $( + $name:ident $_:ident $number:tt + ),* + $(,)? + ) => { + $( + #[derive(Debug)] + pub struct $name<I: Colorize>(I); + + impl<I: Colorize> Colorize for $name<I> { + fn render_into(self, base: &mut String, use_colors: bool) { + elements_inner! {@parse_number $number} + + if use_colors { + base.write_str(CSI).expect("In-memory write"); + base.write_str(NUMBERS).expect("In-memory write"); + base.write_str(CSE).expect("In-memory write"); + } + self.0.render_into(base, use_colors); + // The canvas is resetting the colours again. + } + } + )* + }; + + (@parse_number $single:literal) => { + const NUMBERS: &str = stringify!($single); + }; + (@parse_number ($red:literal, $green:literal, $blue:literal)) => { + const NUMBERS_U8: [u8; 18] = $crate::custom::rgb_to_ansi($red, $green, $blue, $crate::custom::Plane::Fg); + + const NUMBERS: &str = $crate::custom::bytes_to_str(&NUMBERS_U8); + } +} +pub(super) use elements_inner; + +macro_rules! methods_inner { + ( + Colorize + + $( + $struct_name:ident $fn_name:ident $_:tt + ),* + $(,)? + ) => { + $( + fn $fn_name(self) -> $struct_name<Self> { + $struct_name(self) + } + )* + }; + ( + IntoCanvas + + $( + $struct_name:ident $fn_name:ident $_:tt + ),* + $(,)? + ) => { + $( + fn $fn_name(self) -> $struct_name<Canvas<Self>> { + $struct_name(Canvas(self)) + } + )* + }; +} +pub(super) use methods_inner; + +macro_rules! prepend_input { + ( + $( + $existing_macro_name:path as $new_macro_name:ident $(($macro_rule:tt -> $macro_apply:tt))? + ),* + $(,)? + + <shared_input> + $shared_input:tt + ) => { + $( + prepend_input! { + @generate_macro + $existing_macro_name as $new_macro_name $(($macro_rule -> $macro_apply))? + <shared_input> + $shared_input + } + )* + }; + + ( + @generate_macro + $existing_macro_name:path as $new_macro_name:ident $((($($macro_rule:tt)*) -> {$($macro_apply:tt)*}))? + + <shared_input> + { + $( + $shared_input:tt + )* + } + ) => { + macro_rules! $new_macro_name { + ($($($macro_rule)*)?) => { + $existing_macro_name! { + $($($macro_apply)*)? + $($shared_input)* + } + } + } + pub(super) use $new_macro_name; + } +} +pub(crate) use prepend_input; diff --git a/crates/yt/Cargo.toml b/crates/yt/Cargo.toml index d36751c..54a7ed7 100644 --- a/crates/yt/Cargo.toml +++ b/crates/yt/Cargo.toml @@ -30,6 +30,7 @@ chrono = { version = "0.4.41", features = ["now"] } chrono-humanize = "0.2.3" clap = { version = "4.5.40", features = ["derive"] } clap_complete = { version = "4.5.54", features = ["unstable-dynamic"] } +colors.workspace = true futures = "0.3.31" owo-colors = "4.2.2" regex = "1.11.1" diff --git a/crates/yt/src/config/mod.rs b/crates/yt/src/config/mod.rs index adbafdd..52962ab 100644 --- a/crates/yt/src/config/mod.rs +++ b/crates/yt/src/config/mod.rs @@ -1,8 +1,20 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + use crate::config::support::mk_config; mod paths; mod support; +pub(crate) static SHOULD_DISPLAY_COLOR: AtomicBool = AtomicBool::new(false); + +// We need to do both things to comply with what the config expects. +#[allow(clippy::trivially_copy_pass_by_ref, clippy::unnecessary_wraps)] +fn set_static_should_display_color(value: &bool) -> anyhow::Result<()> { + SHOULD_DISPLAY_COLOR.store(*value, Ordering::Relaxed); + + Ok(()) +} + mk_config! { use std::path::PathBuf; use std::io::IsTerminal; @@ -10,6 +22,8 @@ mk_config! { use crate::shared::bytes::Bytes; + use super::set_static_should_display_color; + use super::paths::get_config_path; use super::paths::get_runtime_path; use super::paths::get_data_path; @@ -27,7 +41,7 @@ mk_config! { .unwrap_or_else(|| std::io::stderr().is_terminal()) ) ) - }, + } => set_static_should_display_color, }, select: SelectConfig = { /// The playback speed to use, when it is not overridden. diff --git a/crates/yt/src/storage/db/insert/playlist.rs b/crates/yt/src/storage/db/insert/playlist.rs index 2613fb3..dc474ce 100644 --- a/crates/yt/src/storage/db/insert/playlist.rs +++ b/crates/yt/src/storage/db/insert/playlist.rs @@ -1,6 +1,7 @@ use std::{cmp::Ordering, time::Duration}; use anyhow::{Context, Result}; +use colors::Colorize; use libmpv2::Mpv; use log::{debug, trace}; @@ -198,7 +199,7 @@ impl Playlist { debug!( "Setting the watch progress for the current_video '{}' to {}s", - current_video.title_fmt_no_color(), + current_video.title_fmt().render(false), watch_progress.as_secs(), ); diff --git a/crates/yt/src/videos/mod.rs b/crates/yt/src/videos/mod.rs index 673d46e..3775e0f 100644 --- a/crates/yt/src/videos/mod.rs +++ b/crates/yt/src/videos/mod.rs @@ -9,51 +9,197 @@ // You should have received a copy of the License along with this program. // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -use anyhow::Result; -use futures::{TryStreamExt, stream::FuturesUnordered}; +use std::fmt::Write; -pub(crate) mod display; +use anyhow::{Context, Result}; +use colors::{Colorize, IntoCanvas}; +use url::Url; use crate::{ app::App, - storage::db::video::{Video, VideoStatusMarker}, + select::duration::MaybeDuration, + storage::db::video::{TimeStamp, Video, VideoStatus}, }; -async fn to_line_display_owned(video: Video, app: &App, format: Option<String>) -> Result<String> { - video.to_line_display(app, format).await +pub(crate) mod format_video; + +macro_rules! get { + ($value:expr, $key:ident, $name:expr, $code:tt) => { + if let Some(value) = &$value.$key { + $code(value) + } else { + concat!("[No ", $name, "]").to_owned() + } + }; } -pub(crate) async fn query( - app: &App, - limit: Option<usize>, - search_query: Option<String>, - format: Option<String>, -) -> Result<()> { - let all_videos = Video::in_states(app, VideoStatusMarker::ALL).await?; +pub(crate) trait RenderWithApp: Colorize { + fn to_string(self, app: &App) -> String { + self.render(app.config.global.display_colors) + } +} +impl<C: Colorize> RenderWithApp for C {} + +impl Video { + #[must_use] + pub(crate) fn cache_path_fmt(&self) -> impl Colorize { + let cache_path = if let VideoStatus::Cached { + cache_path, + is_focused: _, + } = &self.status + { + cache_path.to_string_lossy().to_string() + } else { + "[No Cache Path]".to_owned() + }; + + cache_path.blue().bold() + } - // turn one video to a color display, to pre-warm the hash shrinking cache - if let Some(val) = all_videos.first() { - val.to_line_display(app, format.clone()).await?; + #[must_use] + pub(crate) fn description_fmt(&self) -> impl Colorize { + get!( + self, + description, + "Description", + (|value: &str| value.to_owned()) + ) + .into_canvas() } - let limit = limit.unwrap_or(all_videos.len()); + #[must_use] + pub(crate) fn duration_fmt(&self) -> impl Colorize { + self.duration.cyan().bold() + } - let all_video_strings: Vec<String> = all_videos - .into_iter() - .take(limit) - .map(|vid| to_line_display_owned(vid, app, format.clone())) - .collect::<FuturesUnordered<_>>() - .try_collect::<Vec<String>>() - .await?; + #[must_use] + pub(crate) fn watch_progress_fmt(&self) -> impl Colorize { + MaybeDuration::from_std(self.watch_progress).cyan().bold() + } - if let Some(query) = search_query { - all_video_strings - .into_iter() - .filter(|video| video.to_lowercase().contains(&query.to_lowercase())) - .for_each(|video| println!("{video}")); - } else { - println!("{}", all_video_strings.join("\n")); + pub(crate) async fn extractor_hash_fmt(&self, app: &App) -> Result<impl Colorize> { + let hash = self + .extractor_hash + .as_short_hash(app) + .await + .with_context(|| { + format!( + "Failed to format extractor hash, whilst formatting video: '{}'", + self.title + ) + })?; + + Ok(hash.purple().bold().italic()) } - Ok(()) + #[must_use] + pub(crate) fn in_playlist_fmt(&self) -> impl Colorize { + let output = match &self.status { + VideoStatus::Pick + | VideoStatus::Watch + | VideoStatus::Watched + | VideoStatus::Drop + | VideoStatus::Dropped => "Not in the playlist", + VideoStatus::Cached { is_focused, .. } => { + if *is_focused { + "In the playlist and focused" + } else { + "In the playlist" + } + } + }; + output.yellow().italic() + } + #[must_use] + pub(crate) fn last_status_change_fmt(&self) -> impl Colorize { + self.last_status_change.bright_cyan() + } + + #[must_use] + pub(crate) fn parent_subscription_name_fmt(&self) -> impl Colorize { + let psn = get!( + self, + parent_subscription_name, + "author", + (|sub: &str| sub.replace('"', "'")) + ); + + psn.bright_magenta() + } + + #[must_use] + pub(crate) fn priority_fmt(&self) -> impl Colorize { + self.priority.into_canvas() + } + + #[must_use] + pub(crate) fn publish_date_fmt(&self) -> impl Colorize { + let date = get!( + self, + publish_date, + "release date", + (|date: &TimeStamp| date.to_string()) + ); + + date.bright_white().bold() + } + + #[must_use] + pub(crate) fn status_fmt(&self) -> impl Colorize { + // TODO: We might support `.trim()`ing that, as the extra whitespace could be bad in the + // selection file. <2024-10-07> + let status = self.status.as_marker().as_command().to_owned(); + + status.red().bold() + } + + #[must_use] + pub(crate) fn thumbnail_url_fmt(&self) -> impl Colorize { + get!( + self, + thumbnail_url, + "thumbnail URL", + (|url: &Url| url.to_string()) + ) + .into_canvas() + } + + #[must_use] + pub(crate) fn title_fmt(&self) -> impl Colorize { + let title = self.title.replace(['"', '„', '”', '“'], "'"); + + title.green().bold() + } + + #[must_use] + pub(crate) fn url_fmt(&self) -> impl Colorize { + let url = self.url.as_str().replace('"', "\\\""); + + url.italic() + } + + pub(crate) fn video_options_fmt(&self, app: &App) -> impl Colorize { + let video_options = { + let mut opts = String::new(); + + if let Some(playback_speed) = self.playback_speed { + if (playback_speed - app.config.select.playback_speed).abs() > f64::EPSILON { + write!(opts, " --playback-speed '{}'", playback_speed).expect("In-memory"); + } + } + + if let Some(subtitle_langs) = &self.subtitle_langs { + if subtitle_langs != &app.config.select.subtitle_langs { + write!(opts, " --subtitle-langs '{}'", subtitle_langs).expect("In-memory"); + } + } + + let opts = opts.trim().to_owned(); + + let opts_white = if opts.is_empty() { "" } else { " " }; + format!("{opts_white}{opts}") + }; + + video_options.bright_green() + } } diff --git a/crates/yt/tests/_testenv/run.rs b/crates/yt/tests/_testenv/run.rs index 954e112..74f5e86 100644 --- a/crates/yt/tests/_testenv/run.rs +++ b/crates/yt/tests/_testenv/run.rs @@ -3,7 +3,7 @@ use std::{ process::{self, Stdio}, }; -use owo_colors::OwoColorize; +use colors::{Colorize, IntoCanvas}; use crate::testenv::TestEnv; @@ -161,7 +161,11 @@ impl TestEnv { cmd.args(args); - eprintln!("{} `yt {}`", self.name.blue().italic(), args.join(" ")); + eprintln!( + "{} `yt {}`", + self.name.blue().italic().render(true), + args.join(" ") + ); cmd } diff --git a/crates/yt/tests/watch/mod.rs b/crates/yt/tests/watch/mod.rs index 534c210..9c5a203 100644 --- a/crates/yt/tests/watch/mod.rs +++ b/crates/yt/tests/watch/mod.rs @@ -5,7 +5,7 @@ use std::{ path::PathBuf, }; -use owo_colors::OwoColorize; +use colors::{Colorize, IntoCanvas}; use serde_json::json; use yt_dlp::{json_cast, json_get, progress_hook::__priv::vm::common::atomic::Radium}; @@ -72,7 +72,11 @@ impl MpvControl { .current_request_id .fetch_add(1, std::sync::atomic::Ordering::Relaxed); - eprint!("{} `mpv {}`", self.name.blue().italic(), args.join(" ")); + eprint!( + "{} `mpv {}`", + self.name.blue().italic().render(true), + args.join(" ") + ); writeln!( self.stream, @@ -102,11 +106,11 @@ impl MpvControl { } }; - eprintln!(", {}: {data}", "output".bright_blue(),); + eprintln!(", {}: {data}", "output".bright_blue().render(true),); return Ok(data); } - eprintln!(", {}: {error}", "error".bright_red()); + eprintln!(", {}: {error}", "error".bright_red().render(true)); return Err(error.to_owned()); } } |