The aim here is to build a rudimentary `grep` like program, for the sake of learning the basics of Rust. # 1 Simplest Viable Program Find lines containing `rain`. ```rust use std::fs::File; use std::io::{self,BufRead}; fn main() { let result = proc(); println!("Result: {:?}",result); } fn proc() -> io::Result<()> { let file = File::open("src.txt")?; let reader = io::BufReader::new(file); let pattern = "rain"; for line in reader.lines() { if let Ok(l) = line { if l.contains(&pattern) { println!("{}: {}",&pattern,l); } } } Ok(()) } ``` # 2 Take pattern and files from command line This is not robust: for example it will halt with an error on the first file-not-found or permission-denied. Anyway... ```rust use std::fs::File; use std::env; use std::io::{self,BufRead}; // main is responsible for parsing command line args // and sorting out parameters for proc() fn main() { let args: Vec = env::args().collect(); let argc = args.len(); if argc == 1 { println!("{} ",&args[0]); std::process::exit(1); } let result = proc(&args[1],Vec::from(&args[2..])); println!("Result: {:?}",result); } // proc() is the main procedure for the program. // this iterates over provided files, and delegates // the work for an individual file to procfile() fn proc(pattern: &str, ifns: Vec) -> io::Result<()> { for ifn in ifns { procfile(&pattern, &ifn)?; } Ok(()) } // this 'greps' for 'pattern' in the file named 'ifn' (short for input file name) fn procfile(pattern: &str, ifn: &str) -> io::Result<()> { let file = File::open(ifn)?; let reader = io::BufReader::new(file); for line in reader.lines() { if let Ok(l) = line { if l.contains(&pattern) { println!("{}: {}",&ifn,l); } } } Ok(()) } ``` # 3 Match Regular Expressions This illustrates how to use an `enum` to combine multiple different types of error into a single error type, for use with `Result`. ```bash cargo add regex ``` ```rust use std::fs::File; use std::env; use std::fmt; use std::io::{self,BufRead}; use regex::Regex; #[derive(Debug)] enum MyError { IoError(io::Error), RegexError(regex::Error), } impl fmt::Display for MyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { MyError::IoError(e) => write!(f, "IO error: {}", e), MyError::RegexError(e) => write!(f, "Regex error: {}", e), } } } impl From for MyError { fn from(error: io::Error) -> Self { MyError::IoError(error) } } impl From for MyError { fn from(error: regex::Error) -> Self { MyError::RegexError(error) } } fn main() { let args: Vec = env::args().collect(); let argc = args.len(); if argc == 1 { println!("{} ",&args[0]); std::process::exit(1); } let result = proc(&args[1],Vec::from(&args[2..])); println!("Result: {:?}",result); } fn proc(pattern: &str, ifns: Vec) -> Result<(),MyError> { for ifn in ifns { procfile(&pattern, &ifn)?; } Ok(()) } fn procfile(pattern: &str, ifn: &str) -> Result<(),MyError> { let file = File::open(ifn)?; let reader = io::BufReader::new(file); let re = Regex::new(pattern)?; for line in reader.lines() { if let Ok(l) = line { if re.is_match(&l) { println!("{}: {}",&ifn,l); } } } Ok(()) } ``` # 4 More with Errors ```rust use std::fs::File; use std::io::{self,BufRead}; use std::env; use std::fmt; use regex::Regex; #[derive(Debug)] enum MyError { IoError(io::Error), RegexError(regex::Error), } impl fmt::Display for MyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { MyError::IoError(e) => write!(f, "IO Error: {}", e), MyError::RegexError(e) => write!(f, "Regex Error: {}", e), } } } impl From for MyError { fn from(error: io::Error) -> Self { MyError::IoError(error) } } impl From for MyError { fn from(error: regex::Error) -> Self { MyError::RegexError(error) } } fn main() { let args: Vec = env::args().collect(); let argc = args.len(); if argc < 3 { println!("{} pat file...",&args[0]); std::process::exit(1); } let result = proc(&args[1],&args[2..]); println!("Result: {:?}",result); } fn proc(pattern: &str, ifns: &[String]) -> Result<(),MyError> { let re = Regex::new(pattern)?; for ifn in ifns { match procfile(&re,&ifn) { Err(err) => { println!("Error with {}: {:?}",&ifn,&err); }, Ok(_) => { } } } Ok(()) } fn procfile(re: &Regex, ifn: &str) -> Result<(),MyError> { let file = File::open(ifn)?; let reader = io::BufReader::new(file); for line in reader.lines() { if let Ok(l) = line { if re.is_match(&l) { println!("{}: {}",ifn,l); } } } Ok(()) } ``` # 5 Colorise Output ```bash cargo add colored ``` ```rust use colored::Colorize; ... fn procfile(re: &Regex, ifn: &String) -> Result<(),MyError> { let file = std::fs::File::open(ifn)?; let reader = std::io::BufReader::new(file); for line in reader.lines() { if let Ok(l) = line { if let Some(m) = re.find(&l) { let i = m.start(); let j = m.end(); let left = &l[0..i]; let mid = &l[i..j]; let right = &l[j..]; println!("{}: {}{}{}",&ifn,&left,&mid.red().bold(),&right); } } } Ok(()) } ```