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.
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...
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<String> = env::args().collect();
let argc = args.len();
if argc == 1 {
println!("{} <pattern>",&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<String>) -> 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.
cargo add regex
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<io::Error> for MyError {
fn from(error: io::Error) -> Self {
MyError::IoError(error)
}
}
impl From<regex::Error> for MyError {
fn from(error: regex::Error) -> Self {
MyError::RegexError(error)
}
}
fn main() {
let args: Vec<String> = env::args().collect();
let argc = args.len();
if argc == 1 {
println!("{} <pattern>",&args[0]);
std::process::exit(1);
}
let result = proc(&args[1],Vec::from(&args[2..]));
println!("Result: {:?}",result);
}
fn proc(pattern: &str, ifns: Vec<String>) -> 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
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<io::Error> for MyError {
fn from(error: io::Error) -> Self {
MyError::IoError(error)
}
}
impl From<regex::Error> for MyError {
fn from(error: regex::Error) -> Self {
MyError::RegexError(error)
}
}
fn main() {
let args: Vec<String> = 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
cargo add colored
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(())
}