From cc232721ad21e4f714467c56dfe064207e77db61 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Oct 2026 23:08:45 -0300 Subject: rewrote basic functionality to C. there are no colors just yet --- src/main.rs | 905 ------------------------------------------------------------ 1 file changed, 905 deletions(-) delete mode 100644 src/main.rs (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 3e6c430..0000000 --- a/src/main.rs +++ /dev/null @@ -1,905 +0,0 @@ -use clap::error::ErrorKind; -use clap::{CommandFactory, Parser}; -use crossterm::QueueableCommand; -use crossterm::style::{Print, ResetColor}; -use crossterm::terminal::size; -use std::error::Error; -use std::fmt; -use std::io::{Write, stdout}; -use std::str::FromStr; -use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; - -mod color; -mod mikuart; -use color::{Color, ColorMode, detect_color_mode, lerp_color_hsv}; -use mikuart::get_miku_art; - -#[derive(Parser)] -#[command(version, about, long_about = None)] -pub struct Args { - /// Text to display in the speech bubble - pub text: Option, - - /// Style of the Miku art. A random one is chosen if not specified - #[arg(short, long, value_parser = clap::value_parser!(i32).range(0..))] - pub style: Option, - - /// List all available art styles with their indices - #[arg(short, long)] - pub list: bool, - - /// Apply a smooth rainbow gradient across the ASCII art - #[arg(long, conflicts_with_all = ["color", "gradient"])] - pub rainbow: bool, - - /// Saturation level for rainbow gradient (0-100) - #[arg(long, default_value_t = 100, value_parser = clap::value_parser!(u8).range(0..=100))] - pub saturation: u8, - - /// Brightness level for rainbow gradient (0-100) - #[arg(long, default_value_t = 50, value_parser = clap::value_parser!(u8).range(0..=100))] - pub brightness: u8, - - /// Override with a single solid color - #[arg(long, value_name = "COLOR", conflicts_with = "gradient")] - pub color: Option, - - /// Define a custom two-color gradient (e.g., --gradient red:blue) - #[arg(long, value_name = "START:END")] - pub gradient: Option, - - /// Disable all coloring (respects NO_COLOR environment variable) - #[arg(long)] - pub no_color: bool, - /// Print color detection/debug info and exit - #[arg(long)] - pub color_debug: bool, -} - -#[derive(Debug)] -pub struct ColorConfig { - pub mode: ColorMode, - pub rainbow: bool, - pub saturation: u8, - pub brightness: u8, - pub single_color: Option, - pub gradient: Option<(Color, Color)>, - pub color_cache: Vec, - pub ansi_enabled: bool, - pub debug: bool, -} - -#[derive(Debug, PartialEq)] -pub struct ColorConfigError(String); - -impl fmt::Display for ColorConfigError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.0) - } -} - -impl Error for ColorConfigError {} - -const COLOR_FORMAT_HELP: &str = - "expected a named color, #RRGGBB, #RGB, R,G,B, hsl(H,S%,L%), or ANSI value 0-255"; - -pub fn parse_color_args(args: &Args) -> Result { - // Detect color mode - let mut color_mode = detect_color_mode(); - - // Override with --no-color if specified - if args.no_color { - color_mode = ColorMode::NoColor; - } - - // Parse single color if provided - let single_color = args - .color - .as_deref() - .map(|color_str| { - Color::from_str(color_str).map_err(|error| { - ColorConfigError(format!( - "invalid color '{color_str}': {error}; {COLOR_FORMAT_HELP}" - )) - }) - }) - .transpose()?; - - // Parse gradient if provided - let gradient = if let Some(gradient_str) = args.gradient.as_deref() { - let (start_str, end_str) = gradient_str.split_once(':').ok_or_else(|| { - ColorConfigError( - "invalid gradient: expected START:END (for example, red:blue)".to_string(), - ) - })?; - - if start_str.is_empty() || end_str.is_empty() || end_str.contains(':') { - return Err(ColorConfigError( - "invalid gradient: expected exactly two colors as START:END".to_string(), - )); - } - - let start = Color::from_str(start_str).map_err(|error| { - ColorConfigError(format!( - "invalid gradient start '{start_str}': {error}; {COLOR_FORMAT_HELP}" - )) - })?; - let end = Color::from_str(end_str).map_err(|error| { - ColorConfigError(format!( - "invalid gradient end '{end_str}': {error}; {COLOR_FORMAT_HELP}" - )) - })?; - - Some((start, end)) - } else { - None - }; - - // Keep this validation for direct callers as well as Clap-parsed CLI input. - if args.rainbow && single_color.is_some() { - return Err(ColorConfigError( - "--rainbow cannot be used with --color".to_string(), - )); - } - - if args.rainbow && gradient.is_some() { - return Err(ColorConfigError( - "--rainbow cannot be used with --gradient".to_string(), - )); - } - - if single_color.is_some() && gradient.is_some() { - return Err(ColorConfigError( - "--color cannot be used with --gradient".to_string(), - )); - } - - // Determine if ANSI is enabled (Windows toggle helper) — don't enable if --no-color - #[cfg(windows)] - let ansi_enabled = !args.no_color && color::enable_windows_ansi(); - #[cfg(not(windows))] - let ansi_enabled = false; - - Ok(ColorConfig { - mode: color_mode, - rainbow: args.rainbow, - saturation: args.saturation, - brightness: args.brightness, - single_color, - gradient, - color_cache: Vec::new(), - ansi_enabled, - debug: args.color_debug, - }) -} - -pub fn get_speech_bubble_lines(text: &str) -> Vec { - const MAX_BUBBLE_WIDTH: usize = 50; - const MIN_BUBBLE_WIDTH: usize = 20; - - let temp_lines = wrap_text(text, MAX_BUBBLE_WIDTH - 4); - - let content_width = temp_lines - .iter() - .map(|line| line.width()) - .max() - .unwrap_or(0); - - let bubble_width = (content_width + 4).clamp(MIN_BUBBLE_WIDTH, MAX_BUBBLE_WIDTH); - - let lines = wrap_text(text, bubble_width - 4); - let mut bubble_lines = Vec::new(); - - let top_left = '╭'; - let top_right = '╮'; - let bottom_left = '╰'; - let bottom_right = '╯'; - let horizontal = '─'; - let vertical = '│'; - - bubble_lines.push(format!( - "{}{}{}", - top_left, - horizontal.to_string().repeat(bubble_width), - top_right - )); - - for line in &lines { - let line_display_width = line.width(); - let padding_needed = (bubble_width - 2).saturating_sub(line_display_width); - let left_padding = padding_needed / 2; - let right_padding = padding_needed - left_padding; - - bubble_lines.push(format!( - "{} {}{}{} {}", - vertical, - " ".repeat(left_padding), - line, - " ".repeat(right_padding), - vertical - )); - } - - bubble_lines.push(format!( - "{}{}{}", - bottom_left, - horizontal.to_string().repeat(bubble_width), - bottom_right - )); - - bubble_lines.push("\\".to_string()); - bubble_lines.push(" \\".to_string()); - - bubble_lines -} - -pub fn wrap_text(text: &str, max_width: usize) -> Vec { - if max_width == 0 { - return if text.is_empty() { - Vec::new() - } else { - vec![text.to_string()] - }; - } - - let mut lines = Vec::new(); - let mut current_line = String::new(); - - for word in text.split_whitespace() { - let word_width = word.width(); - let current_width = current_line.width(); - let separator_width = usize::from(!current_line.is_empty()); - - if word_width <= max_width && current_width + separator_width + word_width <= max_width { - if !current_line.is_empty() { - current_line.push(' '); - } - current_line.push_str(word); - continue; - } - - if !current_line.is_empty() { - lines.push(std::mem::take(&mut current_line)); - } - - if word_width <= max_width { - current_line.push_str(word); - continue; - } - - let chunks = split_word_by_width(word, max_width); - let last_index = chunks.len().saturating_sub(1); - for (index, chunk) in chunks.into_iter().enumerate() { - if index == last_index && chunk.width() < max_width { - current_line = chunk; - } else { - lines.push(chunk); - } - } - } - - if !current_line.is_empty() { - lines.push(current_line); - } - - lines -} - -fn split_word_by_width(word: &str, max_width: usize) -> Vec { - let mut chunks = Vec::new(); - let mut chunk = String::new(); - let mut chunk_width = 0; - - for character in word.chars() { - let character_width = character.width().unwrap_or(0); - if !chunk.is_empty() && chunk_width + character_width > max_width { - chunks.push(std::mem::take(&mut chunk)); - chunk_width = 0; - } - chunk.push(character); - chunk_width += character_width; - } - - if !chunk.is_empty() { - chunks.push(chunk); - } - - chunks -} - -pub fn generate_rainbow_cache(color_config: &mut ColorConfig, total_chars: usize) { - color_config.color_cache.clear(); - - if total_chars == 0 { - return; - } - - // Generate smooth rainbow gradient across all characters - for i in 0..total_chars { - let t = i as f32 / total_chars as f32; - let hue = t * 360.0; // Full hue spectrum from 0 to 360 - - // Create color with specified saturation and brightness - let color = Color::Hsl( - hue.round() as u16, - color_config.saturation, - color_config.brightness, - ); - color_config.color_cache.push(color); - } -} - -pub fn generate_gradient_cache( - color_config: &mut ColorConfig, - start: &Color, - end: &Color, - total_chars: usize, -) { - color_config.color_cache.clear(); - - if total_chars == 0 { - return; - } - - // Generate smooth gradient across all characters - for i in 0..total_chars { - let t = if total_chars == 1 { - 0.0 - } else { - i as f32 / (total_chars - 1) as f32 - }; - let color = lerp_color_hsv(start, end, t); - color_config.color_cache.push(color); - } -} - -fn list_all_art_styles() { - println!("Available Miku art styles:"); - println!("=========================="); - - // Get all art styles to determine the count - let all_art = get_miku_art(None, Some(true)); - let styles_count = all_art.len(); - println!("Total styles: {}", styles_count); - - for i in 0..styles_count { - println!("\n--- Style {} ---", i); - let art = get_miku_art(Some(i as i32), Some(false)); - for line in art { - println!("{}", line); - } - } - - println!("\nUse --style to select a specific style, or omit for random selection."); - println!("\nColor Options:"); - println!(" --rainbow Apply a smooth rainbow gradient"); - println!(" --saturation <0-100> Set rainbow saturation level (default: 100)"); - println!(" --brightness <0-100> Set rainbow brightness level (default: 50)"); - println!( - " --color Set a single solid color (supports named colors, hex, rgb, hsl, ansi)" - ); - println!(" --gradient Define a custom two-color gradient"); - println!(" --no-color Disable all coloring"); - println!("\nColor Format Examples:"); - println!(" --color red Named color"); - println!(" --color #FF0000 Hex color"); - println!(" --color 255,0,128 RGB tuple"); - println!(" --color hsl(180,100%,50%) HSL value"); - println!(" --color 93 ANSI color code"); - println!(" --gradient red:blue Custom gradient"); -} - -fn draw_miku_says( - text: &str, - style: i32, - color_config: &mut ColorConfig, -) -> Result<(), Box> { - let speech_bubble_lines = get_speech_bubble_lines(text); - let miku_art = get_miku_art(Some(style), None); - - // Distribute colors across characters that are actually colored. - let total_chars: usize = miku_art - .iter() - .map(|line| { - line.chars() - .filter(|character| !character.is_whitespace()) - .count() - }) - .sum(); - - // Generate color cache if needed - if color_config.rainbow { - generate_rainbow_cache(color_config, total_chars); - } else if let Some((start, end)) = color_config.gradient.clone() { - generate_gradient_cache(color_config, &start, &end, total_chars); - } - - let (window_width, window_height) = size()?; - let available_width = window_width as usize; - - let mut all_content_lines = Vec::new(); - all_content_lines.extend(speech_bubble_lines.clone()); - all_content_lines.extend(miku_art.iter().map(|s| s.to_string())); - - let max_content_width = all_content_lines - .iter() - .map(|l| l.width()) - .max() - .unwrap_or(0); - - let overall_left_padding = if available_width > max_content_width { - (available_width - max_content_width) / 2 - } else { - 0 - }; - - let bubble_width = speech_bubble_lines - .first() - .map(|line| line.width()) - .unwrap_or(0); - - let first_miku_line = miku_art.first().unwrap_or(&""); - let tail_pos = first_miku_line - .chars() - .position(|c| !c.is_whitespace()) - .unwrap_or(0); - - let bubble_center = bubble_width / 2; - let miku_tail_absolute_pos = overall_left_padding + tail_pos; - - let bubble_left_padding = miku_tail_absolute_pos.saturating_sub(bubble_center); - - let bubble_line_count = speech_bubble_lines.len(); - - let content_height = all_content_lines.len(); - let available_height = window_height as usize; - - let top_padding = if content_height < available_height { - (available_height - content_height) / 2 - } else { - 0 - }; - - for _ in 0..top_padding { - println!(); - } - - // Track color index for non-whitespace characters - let mut color_index = 0; - - for (i, line) in all_content_lines.iter().enumerate() { - let is_bubble_line = i < bubble_line_count; - let is_tail_line = - is_bubble_line && (i == bubble_line_count - 2 || i == bubble_line_count - 1); - - if is_bubble_line { - if is_tail_line { - let tail_padding = overall_left_padding + tail_pos; - let horizontal_padding = " ".repeat(tail_padding); - println!("{horizontal_padding}{line}"); - } else { - let horizontal_padding = " ".repeat(bubble_left_padding); - println!("{horizontal_padding}{line}"); - } - } else { - let horizontal_padding = " ".repeat(overall_left_padding); - - // Apply coloring to Miku art lines - if color_config.mode != ColorMode::NoColor && !color_config.color_cache.is_empty() { - // Use crossterm queueing to color characters so it's platform-agnostic - let mut stdout = stdout(); - stdout.queue(Print(horizontal_padding.clone()))?; - - for c in line.chars() { - if c.is_whitespace() { - stdout.queue(Print(c))?; - } else if color_index < color_config.color_cache.len() { - let color = &color_config.color_cache[color_index]; - stdout.queue(Print(color.to_ansi_fg(color_config.mode)))?; - stdout.queue(Print(c))?; - stdout.queue(ResetColor)?; - color_index += 1; - } else { - stdout.queue(Print(c))?; - } - } - - stdout.queue(Print("\n"))?; - stdout.flush()?; - continue; - } else if color_config.mode != ColorMode::NoColor - && let Some(single_color) = &color_config.single_color - { - // Apply single solid color using crossterm - let mut stdout = stdout(); - stdout.queue(Print(horizontal_padding.clone()))?; - stdout.queue(Print(single_color.to_ansi_fg(color_config.mode)))?; - stdout.queue(Print(line))?; - stdout.queue(ResetColor)?; - stdout.queue(Print("\n"))?; - stdout.flush()?; - } else { - // No coloring - println!("{}{}", horizontal_padding, line); - } - } - } - - for _ in 0..top_padding { - println!(); - } - - Ok(()) -} - -fn main() -> Result<(), Box> { - let args = Args::parse(); - - if args.list { - list_all_art_styles(); - return Ok(()); - } - - let text = args.text.clone().unwrap_or_else(|| { - eprintln!("Error: Text is required when not using --list"); - std::process::exit(1); - }); - - let styles_count = get_miku_art(None, Some(true)).len(); - let style = match args.style { - Some(style) if style as usize >= styles_count => Args::command() - .error( - ErrorKind::InvalidValue, - format!( - "style {style} does not exist; choose a value between 0 and {}", - styles_count - 1 - ), - ) - .exit(), - Some(style) => style, - None => -1, - }; - - // Parse color-related arguments - let mut color_config = parse_color_args(&args).unwrap_or_else(|error| { - Args::command() - .error(ErrorKind::ValueValidation, error.to_string()) - .exit() - }); - - if color_config.debug { - println!("Detected color mode: {:?}", color_config.mode); - println!("ANSI enabled: {}", color_config.ansi_enabled); - println!("NO_COLOR: {:?}", std::env::var("NO_COLOR").ok()); - println!("COLORTERM: {:?}", std::env::var("COLORTERM").ok()); - println!("TERM: {:?}", std::env::var("TERM").ok()); - #[cfg(windows)] - { - println!("Windows shell hint: {:?}", color::detect_windows_shell()); - } - return Ok(()); - } - - draw_miku_says(&text, style, &mut color_config)?; - - Ok(()) -} - -/// MARK: Tests -#[cfg(test)] -mod tests { - use super::*; - use crate::color::{Color, ColorMode}; - - #[test] - fn test_wrap_text_basic() { - let text = "Hello world this is a test"; - let wrapped = wrap_text(text, 15); - assert_eq!(wrapped, vec!["Hello world", "this is a test"]); - } - - #[test] - fn test_wrap_text_empty() { - let text = ""; - let wrapped = wrap_text(text, 10); - assert_eq!(wrapped, Vec::::new()); - } - - #[test] - fn test_wrap_text_long_word() { - let text = "ThisIsAVeryLongWordThatExceedsMaxWidth"; - let wrapped = wrap_text(text, 10); - assert_eq!(wrapped.concat(), text); - assert!(wrapped.iter().all(|line| line.width() <= 10)); - } - - #[test] - fn test_wrap_text_exact_width() { - let text = "Hello world"; - let wrapped = wrap_text(text, 11); - assert_eq!(wrapped, vec!["Hello world"]); - } - - #[test] - fn test_get_speech_bubble_lines_basic() { - let text = "Hello"; - let bubble = get_speech_bubble_lines(text); - - // Should have: top border, text line, bottom border, 2 tail lines - assert_eq!(bubble.len(), 5); - - // Check top border - assert!(bubble[0].starts_with('╭')); - assert!(bubble[0].ends_with('╮')); - - // Check text line - assert!(bubble[1].contains("Hello")); - - // Check bottom border - assert!(bubble[2].starts_with('╰')); - assert!(bubble[2].ends_with('╯')); - - // Check tail - assert_eq!(bubble[3], "\\"); - assert_eq!(bubble[4], " \\"); - } - - #[test] - fn test_get_speech_bubble_lines_long_text() { - let text = "This is a very long text that should be wrapped into multiple lines"; - let bubble = get_speech_bubble_lines(text); - - // Should have: top border, multiple text lines, bottom border, 2 tail lines - assert!(bubble.len() > 5); - - // Check top border - assert!(bubble[0].starts_with('╭')); - assert!(bubble[0].ends_with('╮')); - - // Check bottom border - let bottom_idx = bubble.len() - 3; - assert!(bubble[bottom_idx].starts_with('╰')); - assert!(bubble[bottom_idx].ends_with('╯')); - } - - #[test] - fn test_parse_color_args_no_color() { - let args = Args { - text: Some("test".to_string()), - style: None, - list: false, - rainbow: false, - saturation: 100, - brightness: 50, - color: None, - gradient: None, - no_color: false, - color_debug: false, - }; - - let config = parse_color_args(&args).unwrap(); - // Don't assert exact color mode since it depends on environment - assert!(!config.rainbow); - assert_eq!(config.saturation, 100); - assert_eq!(config.brightness, 50); - assert!(config.single_color.is_none()); - assert!(config.gradient.is_none()); - assert!(config.color_cache.is_empty()); - } - - #[test] - fn test_parse_color_args_no_color_explicit() { - let args = Args { - text: Some("test".to_string()), - style: None, - list: false, - rainbow: false, - saturation: 100, - brightness: 50, - color: None, - gradient: None, - no_color: true, - color_debug: false, - }; - - let config = parse_color_args(&args).unwrap(); - assert_eq!(config.mode, ColorMode::NoColor); - } - - #[test] - fn test_wrap_text_edge_cases() { - // Test with single very long word - let text = "Supercalifragilisticexpialidocious"; - let wrapped = wrap_text(text, 10); - assert_eq!(wrapped.concat(), text); - assert!(wrapped.iter().all(|line| line.width() <= 10)); - - // Test with exact width match - let text = "Hello world"; - let wrapped = wrap_text(text, 11); - assert_eq!(wrapped, vec!["Hello world"]); - - // Test with multiple spaces - let text = "Hello world"; - let wrapped = wrap_text(text, 10); - assert_eq!(wrapped, vec!["Hello", "world"]); - } - - #[test] - fn test_get_speech_bubble_lines_edge_cases() { - // Test with empty string - let bubble = get_speech_bubble_lines(""); - assert_eq!(bubble.len(), 4); // Should have: top, empty line, bottom, tail (no extra tail line) - - // Test with very long word - let text = "Supercalifragilisticexpialidocious"; - let bubble = get_speech_bubble_lines(text); - assert!(bubble.len() >= 5); - let border_width = bubble[0].width(); - assert!( - bubble[..bubble.len() - 2] - .iter() - .all(|line| line.width() == border_width) - ); - } - - #[test] - fn test_wrap_text_splits_long_unicode_word_by_display_width() { - let wrapped = wrap_text("初音ミク初音ミク初音ミク", 8); - - assert!(wrapped.len() > 1); - assert!(wrapped.iter().all(|line| line.width() <= 8)); - } - - #[test] - fn test_generate_rainbow_cache_edge_cases() { - let mut config = ColorConfig { - mode: ColorMode::Truecolor, - rainbow: true, - saturation: 100, - brightness: 50, - single_color: None, - gradient: None, - color_cache: vec![Color::Hex("#000000".to_string())], // Pre-filled - ansi_enabled: false, - debug: false, - }; - - // Test with 0 characters - generate_rainbow_cache(&mut config, 0); - assert_eq!(config.color_cache.len(), 0); - - // Test with 1 character - generate_rainbow_cache(&mut config, 1); - assert_eq!(config.color_cache.len(), 1); - } - - #[test] - fn test_generate_gradient_cache_edge_cases() { - let mut config = ColorConfig { - mode: ColorMode::Truecolor, - rainbow: false, - saturation: 100, - brightness: 50, - single_color: None, - gradient: None, - color_cache: vec![Color::Hex("#000000".to_string())], // Pre-filled - ansi_enabled: false, - debug: false, - }; - - let start = Color::Hex("#FF0000".to_string()); - let end = Color::Hex("#0000FF".to_string()); - - // Test with 0 characters - generate_gradient_cache(&mut config, &start, &end, 0); - assert_eq!(config.color_cache.len(), 0); - - // Test with 1 character - generate_gradient_cache(&mut config, &start, &end, 1); - assert_eq!(config.color_cache.len(), 1); - } - - #[test] - fn test_generate_rainbow_cache() { - let mut config = ColorConfig { - mode: ColorMode::Truecolor, - rainbow: true, - saturation: 100, - brightness: 50, - single_color: None, - gradient: None, - color_cache: Vec::new(), - ansi_enabled: false, - debug: false, - }; - - generate_rainbow_cache(&mut config, 10); - assert_eq!(config.color_cache.len(), 10); - - // Check that colors are different (rainbow gradient) - let first_color = &config.color_cache[0]; - let last_color = &config.color_cache[9]; - assert_ne!(first_color, last_color); - } - - #[test] - fn test_rgb_to_ansi16_mapping() { - // Validate mapping for basic colors - assert_eq!(crate::color::rgb_to_ansi16(255, 0, 0), 31); - assert_eq!(crate::color::rgb_to_ansi16(255, 255, 255), 97); - assert_eq!(crate::color::rgb_to_ansi16(0, 0, 0), 30); - } - - #[test] - fn test_generate_gradient_cache() { - let mut config = ColorConfig { - mode: ColorMode::Truecolor, - rainbow: false, - saturation: 100, - brightness: 50, - single_color: None, - gradient: None, - color_cache: Vec::new(), - ansi_enabled: false, - debug: false, - }; - - let start = Color::Hex("#FF0000".to_string()); // Red - let end = Color::Hex("#0000FF".to_string()); // Blue - - generate_gradient_cache(&mut config, &start, &end, 10); - assert_eq!(config.color_cache.len(), 10); - - let first_color = &config.color_cache[0]; - let last_color = &config.color_cache[9]; - assert_eq!(first_color.to_rgb(), start.to_rgb()); - assert_eq!(last_color.to_rgb(), end.to_rgb()); - } - - #[test] - fn test_parse_color_args_reports_conflicts_without_exiting() { - let args = Args { - text: Some("test".to_string()), - style: None, - list: false, - rainbow: true, - saturation: 100, - brightness: 50, - color: Some("red".to_string()), - gradient: None, - no_color: false, - color_debug: false, - }; - - let error = parse_color_args(&args).unwrap_err(); - assert_eq!(error.to_string(), "--rainbow cannot be used with --color"); - } - - #[test] - fn test_color_conversion_respects_terminal_mode() { - let red = Color::Rgb(255, 0, 0); - assert_eq!(red.to_ansi_fg(ColorMode::Truecolor), "\x1b[38;2;255;0;0m"); - assert_eq!(red.to_ansi_fg(ColorMode::Ansi256), "\x1b[38;5;196m"); - assert_eq!(red.to_ansi_fg(ColorMode::Ansi16), "\x1b[31m"); - assert_eq!(red.to_ansi_fg(ColorMode::NoColor), ""); - } - - #[test] - fn test_parse_color_debug_flag() { - let args = Args { - text: Some("test".to_string()), - style: None, - list: false, - rainbow: false, - saturation: 100, - brightness: 50, - color: None, - gradient: None, - no_color: false, - color_debug: true, - }; - let config = parse_color_args(&args).unwrap(); - assert!(config.debug); - } -} -- cgit v1.3-2-g0d8e