diff --git a/Cargo.lock b/Cargo.lock index c6ad41f..1c45ac4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,6 +181,7 @@ dependencies = [ "similar", "strip-ansi-escapes", "tempfile", + "term_size", ] [[package]] @@ -435,6 +436,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "unicode-ident" version = "1.0.13" diff --git a/Cargo.toml b/Cargo.toml index ad75285..4846896 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ serde = { version = "1.0.210", features = ["derive"] } peg = "0.8.4" chumsky = "0.9.3" similar = "2.6.0" +term_size = "0.3.2" [dev-dependencies] strip-ansi-escapes = "0.2.0" diff --git a/src/lib.rs b/src/lib.rs index 666cee5..c9664a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -252,6 +252,37 @@ pub fn delete_env_vars( Ok(updated_lines) } +pub fn format_env_file(content: &str, prune: bool) -> Result, std::io::Error> { + let lines = parser::parser().parse(content).map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Error parsing .env file: {:?}", e), + ) + })?; + + let mut key_value_lines: Vec = lines + .into_iter() + .filter(|line| match line { + parser::Line::KeyValue { value, .. } => !value.is_empty(), + parser::Line::Comment(_) => !prune, + }) + .collect(); + + key_value_lines.sort_by(|a, b| { + if let ( + parser::Line::KeyValue { key: key_a, .. }, + parser::Line::KeyValue { key: key_b, .. }, + ) = (a, b) + { + key_a.cmp(key_b) + } else { + std::cmp::Ordering::Equal + } + }); + + Ok(key_value_lines) +} + fn needs_quoting(value: &str) -> bool { value.chars().any(|c| { c.is_whitespace() diff --git a/src/main.rs b/src/main.rs index e47e0e9..4be14ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,18 +13,31 @@ use envset::{ fn print_diff(old_content: &str, new_content: &str, use_color: bool) { let diff = TextDiff::from_lines(old_content, new_content); + let term_width = term_size::dimensions().map(|(w, _)| w).unwrap_or(80); for change in diff.iter_all_changes() { if use_color { match change.tag() { - ChangeTag::Delete => print!("{}", change.to_string().trim_end().on_bright_red()), - ChangeTag::Insert => print!("{}", change.to_string().trim_end().on_bright_green()), + ChangeTag::Delete => { + let line = change.to_string(); + let padding = " ".repeat(term_width.saturating_sub(line.trim_end().len())); + print!( + "{}", + (line.trim_end().to_string() + &padding).on_bright_red() + ); + println!(); + } + ChangeTag::Insert => { + let line = change.to_string(); + let padding = " ".repeat(term_width.saturating_sub(line.trim_end().len())); + print!( + "{}", + (line.trim_end().to_string() + &padding).on_bright_green() + ); + println!(); + } ChangeTag::Equal => print!("{}", change), } - // Print a newline after each colored line - if change.tag() != ChangeTag::Equal { - println!(); - } } else { let sign = match change.tag() { ChangeTag::Delete => "-", @@ -75,6 +88,12 @@ enum Commands { #[arg(required = true)] keys: Vec, }, + /// Format the .env file (sort keys and remove empty lines) + Fmt { + /// Remove whole line comments + #[arg(short = 'p', long = "prune")] + prune: bool, + }, } fn main() { @@ -146,6 +165,34 @@ fn main() { process::exit(1); } }, + Some(Commands::Fmt { prune }) => match read_env_file_contents(&cli.file) { + Ok(old_content) => match envset::format_env_file(&old_content, *prune) { + Ok(formatted_lines) => { + let mut buffer = Vec::new(); + if let Err(e) = print_env_file_contents(&formatted_lines, &mut buffer) { + eprintln!("Error writing formatted .env file contents: {}", e); + process::exit(1); + } + let new_content = String::from_utf8_lossy(&buffer); + + let use_color = atty::is(Stream::Stdout); + print_diff(&old_content, &new_content, use_color); + + if let Err(e) = std::fs::write(&cli.file, buffer) { + eprintln!("Error writing formatted .env file: {}", e); + process::exit(1); + } + } + Err(e) => { + eprintln!("Error formatting .env file: {}", e); + process::exit(1); + } + }, + Err(e) => { + eprintln!("Error reading .env file: {}", e); + process::exit(1); + } + }, None => {} }