//! column -- format stdin/files into columns use std::ffi::OsString; use std::io::{self, BufRead, BufReader, Write}; pub fn main(args: Vec) -> i32 { let str_args: Vec = args .iter() .skip(1) .map(|a| a.to_string_lossy().to_string()) .collect(); let mut table_mode = false; let mut separator = String::new(); // empty = default whitespace splitting let mut filenames: Vec = Vec::new(); let mut i = 0; while i < str_args.len() { match str_args[i].as_str() { "-t" => table_mode = true, "-s" => { i += 1; if i >= str_args.len() { eprintln!("column: option '-s' requires an argument"); return 1; } separator = str_args[i].clone(); } s if s.starts_with("-s") && s.len() > 2 => { separator = s[2..].to_string(); } s if s.starts_with('-') && s.len() > 1 => { eprintln!("column: unknown option '{}'", s); return 1; } _ => filenames.push(str_args[i].clone()), } i += 1; } // Collect all input lines let mut lines: Vec = Vec::new(); if filenames.is_empty() { let stdin = io::stdin(); for line in stdin.lock().lines() { match line { Ok(l) => lines.push(l), Err(e) => { eprintln!("column: {}", e); return 1; } } } } else { for filename in &filenames { match std::fs::File::open(filename) { Ok(f) => { for line in BufReader::new(f).lines() { match line { Ok(l) => lines.push(l), Err(e) => { eprintln!("column: {}: {}", filename, e); return 1; } } } } Err(e) => { eprintln!("column: {}: {}", filename, e); return 1; } } } } let stdout = io::stdout(); let mut out = stdout.lock(); let result = if table_mode { format_table(&lines, &separator, &mut out) } else { format_columns(&lines, &mut out) } .and_then(|()| out.flush()); if let Err(error) = result { eprintln!("column: failed to write output: {error}"); return 1; } 0 } /// Table mode (-t): split each line into fields and pad to column widths. fn format_table(lines: &[String], separator: &str, out: &mut W) -> io::Result<()> { // Split lines into rows of fields let rows: Vec> = lines .iter() .map(|line| { if separator.is_empty() { line.split_whitespace().collect() } else { line.split(&separator[..]).collect() } }) .collect(); if rows.is_empty() { return Ok(()); } // Find max width for each column let max_cols = rows.iter().map(|r| r.len()).max().unwrap_or(0); let mut col_widths = vec![0usize; max_cols]; for row in &rows { for (j, field) in row.iter().enumerate() { let width = field.chars().count(); if width > col_widths[j] { col_widths[j] = width; } } } // Print padded rows for row in &rows { for (j, field) in row.iter().enumerate() { if j > 0 { write!(out, " ")?; } if j + 1 < row.len() { // Pad all columns except the last let width = field.chars().count(); write!(out, "{}", field)?; for _ in width..col_widths[j] { write!(out, " ")?; } } else { write!(out, "{}", field)?; } } writeln!(out)?; } Ok(()) } /// Default mode: fill columns across the terminal width (simplified: 80 chars). fn format_columns(lines: &[String], out: &mut W) -> io::Result<()> { // Filter out empty lines let entries: Vec<&str> = lines .iter() .map(|s| s.as_str()) .filter(|s| !s.is_empty()) .collect(); if entries.is_empty() { return Ok(()); } let term_width: usize = 80; let max_entry_width = entries.iter().map(|e| e.chars().count()).max().unwrap_or(0); let col_width = max_entry_width + 2; // 2-char padding if col_width == 0 || col_width > term_width { // One per line for entry in &entries { writeln!(out, "{}", entry)?; } return Ok(()); } let num_cols = term_width / col_width; let num_cols = if num_cols == 0 { 1 } else { num_cols }; for (i, entry) in entries.iter().enumerate() { let is_last_in_row = (i + 1) % num_cols == 0 || i + 1 == entries.len(); if is_last_in_row { writeln!(out, "{}", entry)?; } else { let width = entry.chars().count(); write!(out, "{}", entry)?; for _ in width..col_width { write!(out, " ")?; } } } Ok(()) }