
Build A unigrep software
本文最后更新于 2024-03-29,本文发布时间距今超过 90 天, 文章内容可能已经过时。最新内容请以官方内容为准
Build A unigrep software
Introduction
What is unigrep?
unigrep
is a simple tool to search for a specific word in a text file.
What is the purpose of this project?
This project is to build a simple tool to search for a specific word in a text file.
Main purpose of this project is to learn how to use rust
.
Build the project
prerequisites: rust
, cargo
, VSCode
create a new rust project
mkdir rustproj # create a new directory
cd rustproj # change directory to the new directory
cargo new unigrep # create a new rust project
cd unigrep # change directory to the new project
code . # open the project in VSCode
create a new trate named lib.rs
The content of lib.rs
// import required modules
use std::{
env,
error::Error,
fs::{self, read_to_string},
};
// predefine some error messages
const ERROR_INPUT_PARAMS_MSG: &str =
"ERROR: Query param and file path are needed.\r\nUsage: unigrep [query_string] [file_path]";
const ERROR_INPUT_PATH_MSG: &str =
"ERROR: File path is invalid.\r\nUsage: unigrep [query_string] [file_path]";
// **************************************
// ************ main structure **********
// **************************************
// Define a structure to hold input parameters for the program.
pub struct InputParams {
pub query_string: String, // The string to search for in the file.
pub file_path: String, // The path to the file to be searched.
pub case_sensitive: bool, // A flag indicating whether the search should be case sensitive.
}
impl InputParams {
// A constructor for InputParams that takes parameters from command line arguments.
pub fn new() -> Result<InputParams, &'static str> {
let mut sys_args = env::args(); // Get the command line arguments.
sys_args.next(); // Skip the first argument, which is the program's name.
// Attempt to get the query string, file path, and case sensitivity from the arguments.
let query_string = sys_args.next();
let file_path = sys_args.next();
let case_sensitive = sys_args.next();
// Use the obtained arguments to create a new InputParams instance.
Self::new_with_params(&query_string, &file_path, &case_sensitive)
}
// A helper constructor that takes parameters as options.
pub fn new_with_params(
query: &Option<String>,
file_path: &Option<String>,
case_sensitive: &Option<String>,
) -> Result<InputParams, &'static str> {
// Ensure that the query string and file path are provided.
if query.is_none() || file_path.is_none() {
return Err(ERROR_INPUT_PARAMS_MSG);
}
// Determine if the search should be case sensitive.
let is_case_sensitive: bool;
match case_sensitive {
Some(s) => {
is_case_sensitive = s.to_lowercase().contains("case_sensitive");
}
None => is_case_sensitive = false,
}
// Unwrap the options to get the actual values.
let query_string = query.as_ref().unwrap();
let file_path = file_path.as_ref().unwrap();
// Validate the file path.
if !is_file_path(&file_path) {
return Err(ERROR_INPUT_PATH_MSG);
}
// Create and return a new InputParams instance.
Ok(InputParams {
query_string: query_string.to_string(),
file_path: file_path.to_string(),
case_sensitive: is_case_sensitive,
})
}
}
// *********************************
// ************ functions **********
// *********************************
// The main function that runs the program with the provided input parameters.
pub fn run(params: InputParams) -> Result<(), Box<dyn Error>> {
// Read the content of the file specified by the input parameters.
let file_content = read_to_string(¶ms.file_path)?;
// Search for the query string in the file content based on the case sensitivity flag.
let found_lines =
search_in_content(¶ms.query_string, &file_content, ¶ms.case_sensitive);
// Create a title for the output that includes the number of found instances and the file path.
let title = format!(
"---Found {} of {} in the file: {}---\r\n",
Vec::len(&found_lines),
¶ms.query_string,
¶ms.file_path
);
// Print the title to the console.
println!("{}", &title);
// Iterate over the found lines and print them with line numbers.
for (index, line) in found_lines.iter().enumerate() {
println!("{}. {}", index + 1, line);
}
// Create a footer for the output that matches the length of the title, excluding the newline characters.
let footer = std::iter::repeat('-')
.take(title.len() - 2) // Subtract 2 to remove the newline characters at the end of the title.
.collect::<String>();
// Print the footer to the console.
println!("\r\n{}", footer);
// Return Ok(()) to indicate that the function executed successfully.
Ok(())
}
// A function that searches for a query string within file content and returns the lines that contain it.
pub fn search_in_content<'a>(
query: &str,
file_content: &'a str,
case_sensitive: &bool,
) -> Vec<&'a str> {
// Iterate over the lines of the file content.
file_content
.lines()
// Filter the lines to include only those that contain the query string.
.filter(|line| {
if *case_sensitive {
// If case sensitive, check for the query string as is.
line.contains(query)
} else {
// If not case sensitive, convert both the line and query to lowercase and check for the query string.
let lowercase_query = query.to_lowercase();
line.to_lowercase().contains(&lowercase_query)
}
})
// Collect the filtered lines into a vector.
.collect()
}
// A function for checking if a path is a valid file path.
fn is_file_path(path: &str) -> bool {
match fs::metadata(path) {
Ok(_file) => _file.is_file(),
_ => false,
}
}
using new created crate in the main.rs
The contents of main.rs
should be as follows:
use std::process;
// The entry point of the program.
fn main() {
// Attempt to create new input parameters from command line arguments.
let params = match unigrep::InputParams::new() {
// If successful, use the resulting InputParams.
Ok(r) => r,
// If there's an error, print the error message and exit the program with a status code of 1.
Err(e) => {
eprintln!("Input params: {}", e);
process::exit(1);
}
};
// Run the program with the provided input parameters.
// If an error occurs during execution, print the error message and exit the program with a status code of 1.
if let Err(e) = unigrep::run(params) {
eprintln!("Application Error: {}", e);
process::exit(1);
};
}
running the program
To run the program, use the following command in the terminal:
insensitive
query
# run the program with the query "Ha" and the file "poem.txt" but with case `insensitive` search.
$ cargo run Ha ./assets/poem.txt
Compiling unigrep v0.1.0 (/root/vscode/rust/projs/unigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.16s
Running `target/debug/unigrep Ha ./assets/poem.txt`
---Found 8 of Ha in the file: ./assets/poem.txt---
1. In the realm of dreams, where shadows dance,
2. In the realm of dreams, where shadows play,
3. In the realm of dreams, where shadows weep,
4. In the realm of dreams, where shadows collide,
5. A battle rages, with swords that glide.
6. In the realm of dreams, where shadows part,
7. In the realm of dreams, where shadows wane,
8. In the realm of dreams, where shadows fly,
--------------------------------------------------
case_sensitive
query
# run the program with the query "heart" and the file "poem.txt" but with case `sensitive` search.
cargo run heart ./assets/poem.txt case_sensitive
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/unigrep heart ./assets/poem.txt case_sensitive`
---Found 4 of heart in the file: ./assets/poem.txt---
1. In the heart of night, where stars align,
2. In the heart of night, where dreams subsist,
3. In the heart of night, where dreams reside,
4. In the heart of night, where dreams converge,
-----------------------------------------------------
conclusion
The program works as expected. It reads the content of the file specified by the input parameters, searches for the query string in the file content based on the case sensitivity flag, and prints the lines that contain the query string with line numbers. The program also creates a title and footer for the output that includes the number of found instances and the file path.