Консольное приложение

Первая задача - заставить minigrep принимать два аргумента командной строки: путь к файлу и строку для поиска.

cargo run -- searchstring example-filename.txt

итераторы генерируют серию значений, и мы можем вызвать метод collect у итератора, чтобы создать из него коллекцию, например вектор, который будет содержать все элементы, произведённые итератором.

Первый шаг программы

  1. Подключили библиотеку для работы с окружением и аргументами Описание библиотеки ENV
  2. Подключили библиотеку для работы с файлами Описание библиотеки FS
use std::env;
use std::fs;

fn main() {
    let args: Vec<String> = env::args().collect();
//    dbg!(args);

    let query = &args[1];
    let file_path = &args[2];
    
    println!("Поисковый запрос {query}");
    println!("В файле {file_path}");

    let contents = fs::read_to_string(file_path)
        .expect("Файл должен быть доступен для чтения");

    println!("Файл содержит текст:\n{contents}");

}

Рефакторинг программы

  1. Разделите код программы на два файла main.rs и lib.rs. Перенесите всю логику работы программы в файл lib.rs.
  2. Функциональные обязанности, которые остаются в функции main:
    • Вызов логики разбора командной строки со значениями аргументов
    • Настройка любой другой конфигурации
    • Вызов функции run в lib.rs
    • Обработка ошибки, если run возвращает ошибку

Выделение структуры

#![allow(unused)]
fn main() {
struct Config {
    query: String,
    file_path: String,
}
}

Создание конструктора new

#![allow(unused)]
fn main() {
impl Config {
    fn new(args: &[String]) -> Config {
        let query = args[1].clone();
        let file_path = args[2].clone();
        Config { query, file_path }
    }
}
}

Изменение вызова

#![allow(unused)]
fn main() {
    let config = Config::new(&args);
}

Обработка ошибок

Поставить проверку и panic

#![allow(unused)]
fn main() {
impl Config {
    fn new(args: &[String]) -> Config {
	if args.len() < 3 {
	    panic!("Не достаточно аргументов: -- 1:строка поиска 2:имя файла");
	}

}

Обработать через Result

#![allow(unused)]
fn main() {
impl Config {
    fn new(args: &[String]) -> Result<Config, &'static str> {
	if args.len() < 3 {
	    return Err("Не достаточно аргументов: -- 1:строка поиска 2:имя файла");
        }

        let query = args[1].clone();
        let file_path = args[2].clone();

        Ok(Config { query, file_path })
    }
}
}

в main вызываем:

#![allow(unused)]
fn main() {
    let config = Config::build(&args).unwrap_or_else(|err| {
        println!("Problem parsing arguments: {err}");
        process::exit(1);
    });
}

Разделение на lib.rs и main.rs

src/main.rs

use std::env; //для чтения переменных окружения
use std::process; //для завершения приложения

use minigrep::Config; // подключаем lib

fn main() {
    let args: Vec<String> = env::args().collect();//считываем аргументы из командной строки

    let config = Config::new(&args).unwrap_or_else(|err| { //инициализируем открытие файла с проверкой на ошибки
	eprintln!("Проблема распознавания аргументов: {err}");//выводим ошибки в поток ошибок
	process::exit(1);  //закрываем программу если ошибка
    });

    //    dbg!(args);

    println!("Поисковый запрос: [{}]", config.query);
    println!("В файле: [{}]", config.file_path);

    let key = "PATH"; //это вывод содержимого переменной окружения
match env::var(key) {
    Ok(val) => println!("Значение переменной {key}: {val:?}"),
    Err(e) => println!("couldn't interpret {key}: {e}"),
}

    if let Err(e) = minigrep::run(config) { //выполняем поиск и проверяем возможные ошибки
	eprintln!("Приложение содержит ошибку {e}"); //если ошибка, то выводим информацию в поток ошибок
	process::exit(1);
    }
}

src/lib.rs

#![allow(unused)]
fn main() {
use std::error::Error;
use std::fs;
use std::env;

pub struct Config { //структура для работы с ключами
    pub query: String, //структура и все ее компоненты pub
    pub file_path: String,
    pub ignore_case: bool,
}
impl Config { //инициализация структуры по Config::new
    pub fn new(args: &[String]) -> Result<Config, &'static str> {//метод инициализации pub
        if args.len() < 3 {
            return Err("Не достаточно аргументов: -- 1:строка поиска 2:имя файла");
        }
        let query = args[1].clone();//создаю копии элементов для владения
        let file_path = args[2].clone();
		let ignore_case = env::var("IGNORE_CASE").is_ok();
        Ok(Config {
	    query,
	    file_path,
	    ignore_case,
	})
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {//запуск run  с параметрами Config
    let contents = fs::read_to_string(config.file_path)?;// если ошибка передаем в main

    let results = if config.ignore_case {//проверяем наличие переменной среды
        search_case_insensitive(&config.query, &contents)
    } else {
        search(&config.query, &contents)
    };
    for line in results {
        println!("{line}");
    }

    Ok(())
}

fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {//поиск чувствительный к регистру
    let mut result = Vec::new();

    for line in contents.lines() {
        if line.contains(query) {
            result.push(line);
        }
    }
    result
}
fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {//поиск нечувствительный к регистру
    let mut result = Vec::new();
    let query = query.to_lowercase();

    for line in contents.lines() {
        if line.to_lowercase().contains(&query) {
            result.push(line);
        }
    }
    result
}
//тесты библиотеки
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn one_result() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
uduct mabuct";

        assert_eq!(
            vec!["safe, fast, productive.", "uduct mabuct"],
            search(query, contents)
        );
    }
}

#[test]
fn case_sensitive() {
    let query = "duct";
    let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";

    assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}

#[test]
fn case_insensitive() {
    let query = "rUsT";
    let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.
Trust me.";

    assert_eq!(
        vec!["Rust:", "Trust me."],
        search_case_insensitive(query, contents)
    );
}

}