Bài này chúng ta sẽ tìm hiểu các thao tác với File, Directory và xử Text trong Rust. Đây là các kỹ năng cần thiết

Outline

  • Chương 0: Thao tác với File
  • Chương 1: Thao tác với thư mục
  • Chương 2: Text Processing
  • Chương 3: Một số thư viện

References:

Chương 0: Thao tác với File

Các kiểu thao tác vói file

  • Mở một file
  • Tạo mới một file
  • Đóng một file
  • Ghi tới file
  • Đọc metadata từ file
  • Đọc dữ liệu từ file

Eg 1:

Eg 2: Tạo một file mới và ghi

Chú ý hàm write cần mảng các bytes &[u8]

Mở một file

let f = File::open("hello.txt").expect("Could not open file");

Cách khác sử dụng OpenOptions

fn open_with_open_options() {
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("foo.txt")
        .unwrap();
}
   
  • Mở với quyền đọc, ghi, ghi thêm
pub fn read(&mut self, read: bool) -> &mut Self
pub fn write(&mut self, write: bool) -> &mut Self
pub fn append(&mut self, append: bool) -> &mut Self
pub fn truncate(&mut self, truncate: bool) -> &mut Self
pub fn create(&mut self, create: bool) -> &mut Self
pub fn create_new(&mut self, create_new: bool) -> &mut Self

Khác nhau của createcreate_newcreate_new sẽ lỗi nếu file đã tồn tại

Tạo một file mới

fn create_file() {
    let mut f = File::create("hello2.txt").expect("Could not create file");
}

Đóng một file

fn write_file() {
    let mut f = File::create("hello2.txt").expect("Could not create file");
    f.write("buffer".as_bytes())
        .expect("Could not write to file");

    f.close();
}

Ghi tới file

fn write_file() {
    let mut f = File::create("hello2.txt").expect("Could not create file");
    f.write("buffer".as_bytes())
        .expect("Could not write to file");
}

Đọc metadata của file

  • File type
  • is dir or not?
  • symbol link ?
  • len
  • permission
  • modified time
  • access time
  • created time

fn main() -> std::io::Result<()> {
    use std::fs;

    let metadata = fs::metadata("foo.txt")?;

    println!("{:?}", metadata.file_type());
    assert!(!metadata.is_dir());
    Ok(())
}

refer: https://doc.rust-lang.org/std/fs/struct.Metadata.html

Đọc dữ liệu từ file

  • Đọc theo từng single byte
  • Đọc vào buffer
  • Đọc theo từng chunk bytes
  • Đọc theo từng dòng
  • Đọc đến cuối
  • Đọc từ cuối (Di chuyển con trỏ để đọc)

Đọc theo từng single byte

fn read_with_single_bytes() {
    let mut file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("hello.txt")
        .unwrap();
    for i in file.bytes() {
        println!("Byte: {:?}", i.unwrap());
    }
}

Đọc vào buffer

fn read_with_single_bytes() {
    let mut file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("hello.txt")
        .unwrap();

    let mut buffer = [0; 10];
    file.read(&mut buffer).unwrap();
    println!("Buffer contents: {:?}", buffer);
}

Đọc theo từng chunk bytes

fn read_with_buffer() {
    let mut file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("hello.txt")
        .unwrap();

    let mut buffer = [0; 10];
    file.read(&mut buffer).unwrap();
    println!("Buffer contents: {:?}", buffer);
}

Đọc đến cuối

Nó nhận đầu vào là 1 vector

fn read_to_end() {
    let mut file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("hello.txt")
        .unwrap();

    let mut buffer = vec![0; 1000];
    file.read_to_end(&mut buffer).unwrap();
    println!("Buffer contents: {:?}", buffer);
}

Đọc từ cuối (Di chuyển con trỏ để đọc)

fn read_with_reader4_from_end() {
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("hello.txt")
        .unwrap();
    const CAP: usize = 1024 * 128; // 128 KB
    let mut reader = BufReader::with_capacity(CAP, file);
    // let mut reader = BufReader::new(file);
    let r = reader.seek(std::io::SeekFrom::End(0));
    let mut buf = String::new();
    reader.seek_relative(-10);
    reader.read_to_string(&mut buf);
    println!("Line: {:?}", buf);
}

Đọc sử dụng BufReader

  • fill_buf: Trở về nội dung của buffer bên trong, fill it với nhiều dữ liệu nhất
  • consume: Nói rằng buffer đã tiêu thụ n bytes và không được trở về từ hàm read nữa.
  • read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> Result<usize>: Đọc đến ký tự byte thì dừng lại
  • skip_until: Bỏ qua đến khi gặp ký tự
  • read_lin đọc đến khi gặp 0xA thì dừng
  • rewind: Quay lại đầu file, stream
  • seek: Di chuyển con trỏ đọc đến vị trí
  pub enum SeekFrom {
    Start(u64),
    End(i64),
    Current(i64),
}

Homework 1.1: Read the n th last line of a file

Homework 1.2: Figure out about std::path::Path

Chương 1: Thao tác với thư mục

https://rust-lang-nursery.github.io/rust-cookbook/file/dir.html

List các danh sách files từ một folder

  • Cách 1: Hàm fs::read_dir
fn dir_read_paths() {
    let paths = fs::read_dir("./").unwrap();
    for path in paths {
        println!("Name: {}", path.unwrap().path().display())
    }
}
  • Cách 2:
use glob::glob;
fn main() {
    for e in glob("../*").expect("Failed to read glob pattern") {
        println!("{}", e.unwrap().display());
    }
}
  • Cách 3:
use walkdir::WalkDir;
fn main() {
    for e in WalkDir::new(".").into_iter().filter_map(|e| e.ok()) {
        if e.metadata().unwrap().is_file() {
            println!("{}", e.path().display());
        }
    }
}

List recusely files

fn recurse(path: impl AsRef<Path>) -> Vec<PathBuf> {
    let Ok(entries) = read_dir(path) else { return vec![] };
    entries.flatten().flat_map(|entry| {
        let Ok(meta) = entry.metadata() else { return vec![] };
        if meta.is_dir() { return recurse(entry.path()); }
        if meta.is_file() { return vec![entry.path()]; }
        vec![]
    }).collect()
}

Homework 2: Read the n th last line of a file

Chương 2: Text Processing

Refer: https://docs.rs/regex/latest/regex/

Regex

[dependencies]
regex = "1.3.9"

Nhắc lại một số kiến thức về regex

  • Bắt đầu một chuỗi: ^
  • Kết thúc chuỗi: $
  • Ký tự digit: \d
  • Một trong các ký tự: [aebc]
  • Bất kỳ ký tự: .
  • Các ký tự chữ: [a-zA-Z]
  • Repetition:
    • ha*: h theo sau bởi 0 hoặc nhiều a
    • ha+: h theo sau bởi 1 hoặc nhiều a
    • ha?: h theo sau bởi 0 hoặc 1 a
    • ha{3}: h theo sau bởi 3 lần 3
  • Caputures:
    • Sử dụng (). Eg:let re = Regex::new(r"Hello (?<name>\w+)!").unwrap();. Truy xấut
    • Đặt tên cho captures: (?<name>....) .Eg: let re = Regex::new(r"Hello (?<name>\w+)!").unwrap();
use regex::Regex;
pub fn regex1() {
    let re = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
    assert!(re.is_match("2024-07-22"));

    // Anchors
    let re = Regex::new(r"^hello").unwrap(); // Must start with hello
    let re = Regex::new(r"hello$").unwrap(); // Must end with hello

    // Dot
    let re = Regex::new(r"h.llo").unwrap(); // Matches h+any char+llo

    // Character class
    let re = Regex::new(r"h[ae]llo").unwrap(); // Matches hallo or hello

    // Repetition
    let re = Regex::new(r"ha*").unwrap(); // h followed by 0 or more a
    let re = Regex::new(r"ha+").unwrap(); // h followed by 1 or more a
    let re = Regex::new(r"ha?").unwrap(); // h followed by 0 or 1 a

    // Grouping
    let re = Regex::new(r"(ha){3}").unwrap(); // ha ha ha
    let re = Regex::new(r"(ha){2,4}").unwrap(); // ha ha ha {2 or 4 times}
}
  • Validate email
pub fn validate_email() {
    let re = Regex::new(r"^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+\.[a-z]{2,4}$").unwrap();
    assert!(re.is_match("hello@example.com"));
    assert!(re.is_match("tuanpa_abc_xyz@example.org"));
    assert!(re.is_match("tuanpa_abc_xyz.kitto@example.vn"));
}
  • Validate phone number
pub fn validate_phone_number() {
    let re = Regex::new(r"^\+?\d{1,3}?\d{9,10}$").unwrap();
    assert!(re.is_match("+84901234567"));
    assert!(re.is_match("0901234567"));
    assert!(re.is_match("84901234567"));
    assert!(re.is_match("841234567"));
}
  • Validate URL
pub fn validate_url() {
    let re = Regex::new(r"^https?://[a-z0-9-.]{2,}\.[a-z]{2,4}(:[0-9]{2,5})?/?.*$").unwrap();
    assert!(re.is_match("https://example.com"));
    assert!(!re.is_match("example.com")); // Missing https
}
  • Validate hashtag
pub fn validate_hashtag() {
    let re = Regex::new(r"^#[a-zA-Z0-9_]+$").unwrap();
    assert!(re.is_match("#hello"));
    assert!(re.is_match("#hello_world"));
    assert!(!re.is_match("#hello world")); // No space allowed
    assert!(!re.is_match("#hello-world")); // No dash allowed
}
  • Tìm kiếm số trong string: find_iter
pub fn find_all_numbers_in_string() {
    let re = Regex::new(r"\d+").unwrap();
    let text = "Rust 2022";
    let numbers: Vec<&str> = re.find_iter(text).map(|m| m.as_str()).collect();
    assert_eq!(numbers, vec!["2022"]);
}
  • Thay thế các số: replace_all
pub fn replace_occurrences() {
    let re = Regex::new(r"\d+").unwrap();
    let text = "Rust 2022";
    let new_text = re.replace_all(text, "2023");
    assert_eq!(new_text, "Rust 2023");
}
  • Chia tách: split
pub fn split_strings() {
    let re = Regex::new(r"\s+").unwrap();
    let splits = re.split("Hello  world!     How");
    assert_eq!(
        splits.collect::<Vec<&str>>(),
        &["Hello", "world!", "", "How"]
    );
}
  • UTF8
pub fn regex_on_utf8_strings2() {
    let unicode_re = Regex::new(r"ça").unwrap();
    assert!(unicode_re.is_match("ça va?"));

    let byte_re = Regex::new(r"(?-u)ça").unwrap();
    assert!(!byte_re.is_match("ça va?")); // Fails on byte string
}

Một sộ giống nhau

[0-9]
(?-u:\d)
[[:digit:]]
[\d&&\p{ascii}]

Json file

  • from_str

  • to_string()

  • Thư viện serde ,bosh

use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct User {
    fingerprint: String,
    location: String,
}

fn main() {
    // The type of `j` is `&str`
    let j = "
        {
            \"fingerprint\": \"0xF9BA143B95FF6D82\",
            \"location\": \"Menlo Park, CA\"
        }";

    let u: User = serde_json::from_str(j).unwrap();
    println!("{:#?}", u);


    let address = Address {
        street: "10 Downing Street".to_owned(),
        city: "London".to_owned(),
    };

    // Serialize it to a JSON string.
    let j = serde_json::to_string(&address)?;

}

Nom library

  • Phong cách để xử lý text bởi duyệt qua từ byte một

Problem 1:

Given a input #6AFA23. Parse this input and produce a struct

pub Color {
    red: u8, // 0x6A
    blue: u8, // 0xFA
    green: u8 // 0x23
}

Solution:

use nom::{
    bytes::complete::{tag, take_while_m_n},
    combinator::map_res,
    sequence::tuple,
    IResult,
};
#[derive(Debug, PartialEq)]
pub struct Color {
    red: u8,   // 0x6A
    blue: u8,  // 0xFA
    green: u8, // 0x23
}
fn from_hex(input: &str) -> Result<u8, std::num::ParseIntError> {
    let a = u8::from_str_radix(input, 16);
    a
}

fn is_hex_digit(c: char) -> bool {
    c.is_digit(16)
}

fn hex_primary(input: &str) -> IResult<&str, u8> {
    map_res(take_while_m_n(2, 2, is_hex_digit), from_hex)(input)
}

fn hex_color(input: &str) -> IResult<&str, Color> {
    let (input, _) = tag("#")(input)?;
    let (input, (red, green, blue)) = tuple((hex_primary, hex_primary, hex_primary))(input)?;

    Ok((input, Color { red, green, blue }))
}

#[cfg(test)]
mod tests {
    type Error = Box<dyn std::error::Error>;
    type Result<T> = core::result::Result<T, Error>; // For tests.

    use super::*;

    fn test_hex_color() {
        assert_eq!(
            hex_color("#2F14DF"),
            Ok((
                "",
                Color {
                    red: 47,
                    green: 20,
                    blue: 223,
                }
            ))
        );
    }
}

What is nom?

  • nom is a parser library
  • nom is a paser combinator library
  • nom is not bassed on grammars or generators.

The Parser Trait

trait Parser<I, O, E> {
    fn parse(&mut self, input: I) -> IResult<I, O, E>;
    ...
}
  • Thực hiện F: FnMut(I) -> IResult<I, O, E>

The IResult Type

  • Encodes the result of a parse
  • type IResult<I, O, E> = Result<(I, O), Err(E)>

Possible Parse Outcomes

  • Success: OK(I,O)
  • Error: Err(nom::Err:Error(E))
  • Failure: Err(nom::Err::Failure(E))
  • Incomplete: Err(nom::Err::Incomplete(Needed))

Homework 3: HTTP parser using nom library

nom, nom_superme...