use clap::Parser; use std::fs::File; use std::io::BufRead; use std::io::BufReader; #[derive(Parser, Debug)] #[command(version)] struct Args { /// Numérotation des lignes non vides #[arg(short = 'b', long)] number_nonblank: bool, /// Affichage d'un $ à la fin de chaque ligne #[arg(short = 'E', long)] show_ends: bool, /// Numérotation des lignes #[arg(short, long)] number: bool, /// Suppression des lignes vides répétées #[arg(short, long)] squeeze_blank: bool, /// Affichage des tabulations avec la séquence ^I #[arg(short = 'T', long)] show_tabs: bool, /// Ignoré (conservé par compatibilité) #[arg(short = 'u')] _u: bool, /// Utilisation de la notation ^ et M- sauf pour LFD et TAB #[arg(short = 'v', long)] show_nonprinting: bool, /// Equivalent à -vET #[arg(short = 'A', long)] show_all: bool, /// Equivalent à -vE #[arg(short = 'e')] show_all_no_tabs: bool, /// Equivalent à -vT #[arg(short = 't')] show_all_no_ends: bool, /// Liste de fichiers files: Vec, } fn main() { let mut args = Args::parse(); args.show_nonprinting = args.show_nonprinting || args.show_all || args.show_all_no_tabs || args.show_all_no_ends; args.show_ends = args.show_ends || args.show_all || args.show_all_no_tabs; args.show_tabs = args.show_tabs || args.show_all || args.show_all_no_ends; args.number_nonblank = args.number_nonblank && !args.number; let line_ends = if args.show_ends { "$" } else { "" }; let mut line_number = 0; let mut line_prefix: String; let mut last_was_blank = false; for file_path in args.files { let f = File::open(file_path).unwrap(); // File : structure pour accéder à un fichier let mut reader = BufReader::new(f); // BufRead : structure pour faire des lectures bufferisées loop { let mut line = String::new(); if let Ok(len) = reader.read_line(&mut line) { // read_line retourne une longueur nulle à la fin du fichier if len==0 { break; } let line_is_blank = line.len()==0; let skip_line = args.squeeze_blank && last_was_blank && line_is_blank; if args.number || (args.number_nonblank && !line_is_blank) { line_number += 1; line_prefix = format!("{:>6} ", line_number); // format! : macro de formatage (à la printf) if line_prefix.len()<8 { line_prefix.push(' '); } } else { line_prefix = String::from(""); } let mut line_to_display = String::from(""); for c in line.chars() { if c.len_utf8() == 1 { // Si le caractère est convertible en ASCII let ascii_ch = c as u8; if ascii_ch>=32 { line_to_display.push(c) } else if ascii_ch==9 && !args.show_tabs { line_to_display.push(c) } else if ascii_ch==10 { line_to_display.push(c) } else if ascii_ch==13 { line_to_display.push_str("^M") } else if ascii_ch!=13 && args.show_nonprinting { line_to_display.push_str("^"); line_to_display.push((ascii_ch + 64) as char) } } else { // On est sur un caractère UNICODE // Conversion des caractères non imprimables selon https://github.com/coreutils/coreutils/blob/5cecd703e57b2e1301767d82cbe5bb01cae88472/src/cat.c#L412 if args.show_nonprinting { let mut caracts_ut8 = [0; 4]; let _result = c.encode_utf8(&mut caracts_ut8); for sub in caracts_ut8 { if sub>=32 { if sub < 127 { line_to_display.push(char::from_u32(sub as u32).unwrap()) } else if sub == 127 { line_to_display.push_str("^?") } else { line_to_display.push_str("M-"); if sub >= 128 + 32 { if sub < 128 + 127 { line_to_display.push(char::from_u32(sub as u32 - 128).unwrap()); } else { line_to_display.push_str("^?") } } else { line_to_display.push('^'); line_to_display.push(char::from_u32(sub as u32 - 128 + 64).unwrap()); } } } else { if sub==0x9 && !args.show_tabs { line_to_display.push(char::from_u32(sub as u32).unwrap()) } } } } else { line_to_display.push(c) } } } if !skip_line { print!("{}{}{}", line_prefix, line_to_display, line_ends); } last_was_blank = line_is_blank; } } } }