this repo has no description

feat: no_color

closes https://github.com/tgirlcloud/lix-diff/issues/17

+146 -38
+62
src/color.rs
··· 1 + use std::sync::OnceLock; 2 + 3 + static NO_COLOR: OnceLock<bool> = OnceLock::new(); 4 + 5 + /// Initialize the color configuration based on CLI flag and environment. 6 + /// Should be called once at startup. 7 + pub fn init(cli_no_color: bool) { 8 + let no_color = cli_no_color || should_disable_color_from_env(); 9 + NO_COLOR.set(no_color).ok(); 10 + } 11 + 12 + /// Returns true if colors should be disabled. 13 + pub fn no_color() -> bool { 14 + *NO_COLOR.get().unwrap_or(&false) 15 + } 16 + 17 + /// Returns true if colors are enabled. 18 + pub fn color_enabled() -> bool { 19 + !no_color() 20 + } 21 + 22 + /// Check environment variables to determine if color should be disabled. 23 + fn should_disable_color_from_env() -> bool { 24 + // NO_COLOR standard: https://no-color.org/ 25 + // If NO_COLOR exists (with any value), disable color 26 + if std::env::var("NO_COLOR").is_ok() { 27 + return true; 28 + } 29 + 30 + // Generic CI detection 31 + if std::env::var("CI").is_ok_and(|v| v == "true") { 32 + return true; 33 + } 34 + 35 + // GitHub actions 36 + if std::env::var("GITHUB_ACTIONS").is_ok_and(|v| v == "true") { 37 + return true; 38 + } 39 + 40 + // GitLab CI 41 + if std::env::var("GITLAB_CI").is_ok() { 42 + return true; 43 + } 44 + 45 + // Travis CI 46 + if std::env::var("TRAVIS").is_ok_and(|v| v == "true") { 47 + return true; 48 + } 49 + 50 + // Jenkins 51 + if std::env::var("JENKINS_URL").is_ok() { 52 + return true; 53 + } 54 + 55 + // Check if output is not a TTY (piped) 56 + // This is a common convention for disabling colors 57 + if std::env::var("TERM").is_ok_and(|v| v == "dumb") { 58 + return true; 59 + } 60 + 61 + false 62 + }
+51 -24
src/diff.rs
··· 3 3 use terminal_light::luma; 4 4 5 5 use super::{ 6 + color, 6 7 package::{DiffType, Package, SizeDelta}, 7 8 parser::DiffRoot, 8 9 }; ··· 93 94 } 94 95 95 96 fn display_by_category(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 96 - let text_color = if luma().is_ok_and(|luma| luma > 0.6) { 97 - Color::DarkGray 98 - } else { 99 - Color::LightGray 100 - }; 101 - let title_style = nu_ansi_term::Style::new().underline().bold().fg(text_color); 97 + let name_width = self.longest_name + 2; 102 98 103 - let name_width = self.longest_name + 2; 99 + if color::color_enabled() { 100 + let text_color = if luma().is_ok_and(|luma| luma > 0.6) { 101 + Color::DarkGray 102 + } else { 103 + Color::LightGray 104 + }; 105 + let title_style = nu_ansi_term::Style::new().underline().bold().fg(text_color); 104 106 105 - if !self.changed.is_empty() { 106 - writeln!(f, "{}", &title_style.paint("Changed"))?; 107 - for (name, package) in &self.changed { 108 - writeln!(f, "[{}] {name:name_width$}{package}", Yellow.paint("C"))?; 107 + if !self.changed.is_empty() { 108 + writeln!(f, "{}", &title_style.paint("Changed"))?; 109 + for (name, package) in &self.changed { 110 + writeln!(f, "[{}] {name:name_width$}{package}", Yellow.paint("C"))?; 111 + } 112 + writeln!(f)?; 109 113 } 110 - writeln!(f)?; 111 - } 112 114 113 - if !self.added.is_empty() { 114 - writeln!(f, "{}", &title_style.paint("Added"))?; 115 - for (name, package) in &self.added { 116 - write!(f, "[{}] {name:name_width$}{package}", Green.paint("A"))?; 115 + if !self.added.is_empty() { 116 + writeln!(f, "{}", &title_style.paint("Added"))?; 117 + for (name, package) in &self.added { 118 + write!(f, "[{}] {name:name_width$}{package}", Green.paint("A"))?; 119 + writeln!(f)?; 120 + } 117 121 writeln!(f)?; 118 122 } 119 - writeln!(f)?; 120 - } 123 + 124 + if !self.removed.is_empty() { 125 + writeln!(f, "{}", &title_style.paint("Removed"))?; 126 + for (name, package) in &self.removed { 127 + writeln!(f, "[{}] {name:name_width$}{package}", Red.paint("R"))?; 128 + } 129 + writeln!(f)?; 130 + } 131 + } else { 132 + if !self.changed.is_empty() { 133 + writeln!(f, "Changed")?; 134 + for (name, package) in &self.changed { 135 + writeln!(f, "[C] {name:name_width$}{package}")?; 136 + } 137 + writeln!(f)?; 138 + } 139 + 140 + if !self.added.is_empty() { 141 + writeln!(f, "Added")?; 142 + for (name, package) in &self.added { 143 + writeln!(f, "[A] {name:name_width$}{package}")?; 144 + } 145 + writeln!(f)?; 146 + } 121 147 122 - if !self.removed.is_empty() { 123 - writeln!(f, "{}", &title_style.paint("Removed"))?; 124 - for (name, package) in &self.removed { 125 - writeln!(f, "[{}] {name:name_width$}{package}", Red.paint("R"))?; 148 + if !self.removed.is_empty() { 149 + writeln!(f, "Removed")?; 150 + for (name, package) in &self.removed { 151 + writeln!(f, "[R] {name:name_width$}{package}")?; 152 + } 153 + writeln!(f)?; 126 154 } 127 - writeln!(f)?; 128 155 } 129 156 130 157 Ok(())
+22 -9
src/main.rs
··· 6 6 use nu_ansi_term::{Color, Style}; 7 7 use terminal_light::luma; 8 8 9 + mod color; 9 10 mod diff; 10 11 mod package; 11 12 mod parser; ··· 30 31 /// sort by size difference 31 32 #[arg(short, long)] 32 33 size: bool, 34 + 35 + /// disable colored output (also respects NO_COLOR env var and CI environments) 36 + #[arg(long)] 37 + no_color: bool, 33 38 } 34 39 35 40 fn main() -> Result<()> { 36 41 let args = Args::parse(); 42 + 43 + // Initialize color configuration 44 + color::init(args.no_color); 45 + 37 46 let before = args.before; 38 47 let after = args.after; 39 48 let lix_bin = args.lix_bin; ··· 61 70 let mut packages: PackageListDiff = PackageListDiff::new(); 62 71 packages.by_size = args.size; 63 72 packages.from_diff_root(packages_diff); 64 - 65 - let text_color = if luma().is_ok_and(|luma| luma > 0.6) { 66 - Color::DarkGray 67 - } else { 68 - Color::LightGray 69 - }; 70 - let arrow_style = Style::new().bold().fg(text_color); 71 73 72 74 let before_text = format!("<<< {}", before.display()); 73 75 let after_text = format!(">>> {}", after.display()); 74 76 75 - println!("{}", arrow_style.paint(before_text)); 76 - println!("{}\n", arrow_style.paint(after_text)); 77 + if color::color_enabled() { 78 + let text_color = if luma().is_ok_and(|luma| luma > 0.6) { 79 + Color::DarkGray 80 + } else { 81 + Color::LightGray 82 + }; 83 + let arrow_style = Style::new().bold().fg(text_color); 84 + println!("{}", arrow_style.paint(&before_text)); 85 + println!("{}\n", arrow_style.paint(&after_text)); 86 + } else { 87 + println!("{before_text}"); 88 + println!("{after_text}\n"); 89 + } 77 90 78 91 println!("{packages}"); 79 92
+11 -5
src/versioning.rs
··· 1 1 use nu_ansi_term::Color::{Green, Red, Yellow}; 2 2 use std::{cmp::Ordering, fmt::Display}; 3 3 4 + use super::color; 5 + 4 6 #[derive(Debug)] 5 7 pub struct VersionComponent(String, Ordering); 6 8 ··· 34 36 let val = &component.0; 35 37 let cmp = component.1; 36 38 37 - let text = if cmp == Ordering::Less { 38 - format!("{}", Red.paint(val)) 39 - } else if cmp == Ordering::Greater { 40 - format!("{}", Green.paint(val)) 39 + let text = if color::color_enabled() { 40 + if cmp == Ordering::Less { 41 + format!("{}", Red.paint(val)) 42 + } else if cmp == Ordering::Greater { 43 + format!("{}", Green.paint(val)) 44 + } else { 45 + format!("{}", Yellow.paint(val)) 46 + } 41 47 } else { 42 - format!("{}", Yellow.paint(val)) 48 + val.clone() 43 49 }; 44 50 45 51 out.push_str(&text);