2015-11-20 20 views
5

Desidero aprire un file e leggerne il contenuto come BufReader utilizzando lines(). Voglio anche essere in grado di cercare fino alla fine del file e scrivere alcune nuove righe.Come utilizzare un file con un BufReader ed essere ancora in grado di scrivere su di esso?

Utilizzando let mut file permette di me scrivere sul file, ma una volta ho dato il file nella BufReader non riesco più a scrivere su di esso, come la funzione principale non possiede più file:

fn main() { 
    let filename = "tt.txt"; 

    // open a tt.txt file in the local directory 
    let file = OpenOptions::new() 
     .read(true) 
     .write(true) 
     .create(true) 
     .open(filename) 
     .unwrap(); 

    // now read the whole file to get the latest state 
    let date_re = Regex::new(r"^(\d{4})-(\d{2})-(\d{2})").unwrap(); 
    let time_activity_re = Regex::new(r"^(\d{2}):(\d{2})\s*(.*)").unwrap(); 
    let reader = BufReader::new(file); 
    let mut latest_date: Option<Date<Local>> = None; 
    let mut latest_datetime: Option<DateTime<Local>> = None; 
    let mut latest_activity: Option<String> = None; 

    for wrapped_line in reader.lines() { 
     let line = wrapped_line.unwrap(); 
     println!("line: {}", line); 

     if date_re.is_match(&line) { 
      let captures = date_re.captures(&line).unwrap(); 
      let year = captures.at(1).unwrap().parse::<i32>().unwrap(); 
      let month = captures.at(2).unwrap().parse::<u32>().unwrap(); 
      let day = captures.at(3).unwrap().parse::<u32>().unwrap(); 
      latest_date = Some(Local.ymd(year, month, day)); 
      latest_datetime = None; 
      latest_activity = None; 
     } 

     if time_activity_re.is_match(&line) && latest_date != None { 
      let captures = time_activity_re.captures(&line).unwrap(); 
      let hour = captures.at(1).unwrap().parse::<u32>().unwrap(); 
      let minute = captures.at(2).unwrap().parse::<u32>().unwrap(); 
      let activity = captures.at(3).unwrap(); 

      latest_datetime = Some(latest_date.unwrap().and_hms(hour, minute, 0)); 

      latest_activity = if activity.len() > 0 { 
       // TODO: if latest_activity already constains a string, clear it and reuse it 
       // as per: https://stackoverflow.com/questions/33781625/how-to-allocate-a-string-before-you-know-how-big-it-needs-to-be 
       Some(activity.to_string()) 
      } else { 
       None 
      }; 

      println!("time activity: {} |{}|", latest_datetime.unwrap(), activity); 
     } 
    } 

    // FIXME: I have to open a second file descriptor to the same file, in order to be able to write to it 
    let mut out = OpenOptions::new() 
     .read(true) 
     .write(true) 
     .create(true) 
     .open(filename) 
     .unwrap(); 

    out.seek(End(0)); 

    let now = Local::now(); 
    if latest_date == None || latest_date.unwrap().year() != now.year() 
     || latest_date.unwrap().month() != now.month() 
     || latest_date.unwrap().day() != now.day() 
    { 
     if (latest_date != None) { 
      // not an empy file, as far as tt is concerned 
      out.write_all(b"\n\n"); 
     } 
     out.write_all(format!("{}\n", now.format("%Y-%m-%d")).as_bytes()); 
     out.write_all(b"\n"); 
    } 

    let activity = env::args().skip(1).join(" "); 
    if (activity.len() > 0) { 
     out.write_all(format!("{} {}\n", now.format("%H:%M"), activity).as_bytes()); 
    } else { 
     // if there was no latest activity *and* there is no activity, then there's no point in writing a second blank line with just a time 
     if latest_activity == None { 
      return; 
     } 
     out.write_all(format!("{}\n", now.format("%H:%M")).as_bytes()); 
    } 

    // FIXME: we're just relying on the program exit to close the two file descriptors (which point at the same file). 
} 

Come può Uso un singolo descrittore di file per leggere le righe esistenti e aggiungere nuove righe?

(Codice da https://github.com/chrisdew/tt/blob/e899f252014391f2e01c3cc9e281cab1ab88936f/src/main.rs)

risposta

7

per evitare di spostare un valore, è possibile utilizzare un riferimento e un nuovo ambito. Ecco come si potrebbe fare questo:

fn main() { 
    let filename = "tt.txt"; 

    // open a tt.txt file in the local directory 
    let mut file = OpenOptions::new() 
     .read(true) 
     .write(true) 
     .create(true) 
     .open(filename) 
     .unwrap(); 

    // now read the whole file to get the latest state 
    let date_re = Regex::new(r"^(\d{4})-(\d{2})-(\d{2})").unwrap(); 
    let time_activity_re = Regex::new(r"^(\d{2}):(\d{2})\s*(.*)").unwrap(); 
    { 
     // BufReader now borrows the value instead of taking ownership. 
     let reader = BufReader::new(&mut file); 
     let mut latest_date: Option<Date<Local>> = None; 
     let mut latest_datetime: Option<DateTime<Local>> = None; 
     let mut latest_activity: Option<String> = None; 

     for wrapped_line in reader.lines() { 
      let line = wrapped_line.unwrap(); 
      println!("line: {}", line); 

      if date_re.is_match(&line) { 
       let captures = date_re.captures(&line).unwrap(); 
       let year = captures.at(1).unwrap().parse::<i32>().unwrap(); 
       let month = captures.at(2).unwrap().parse::<u32>().unwrap(); 
       let day = captures.at(3).unwrap().parse::<u32>().unwrap(); 
       latest_date = Some(Local.ymd(year, month, day)); 
       latest_datetime = None; 
       latest_activity = None; 
      } 

      if time_activity_re.is_match(&line) && latest_date != None { 
       let captures = time_activity_re.captures(&line).unwrap(); 
       let hour = captures.at(1).unwrap().parse::<u32>().unwrap(); 
       let minute = captures.at(2).unwrap().parse::<u32>().unwrap(); 
       let activity = captures.at(3).unwrap(); 

       latest_datetime = Some(latest_date.unwrap().and_hms(hour, minute, 0)); 

       latest_activity = if activity.len() > 0 { 
        // TODO: if latest_activity already constains a string, clear it and reuse it 
        // as per: https://stackoverflow.com/questions/33781625/how-to-allocate-a-string-before-you-know-how-big-it-needs-to-be 
        Some(activity.to_string()) 
       } else { 
        None 
       }; 

       println!("time activity: {} |{}|", latest_datetime.unwrap(), activity); 
      } 
     } 
    } 
    // End of the scope, so now file is not borrowed anymore. 

    file.seek(End(0)); 

    let now = Local::now(); 
    if latest_date == None || latest_date.unwrap().year() != now.year() 
     || latest_date.unwrap().month() != now.month() 
     || latest_date.unwrap().day() != now.day() 
    { 
     if (latest_date != None) { 
      // not an empy file, as far as tt is concerned 
      file.write_all(b"\n\n"); 
     } 
     file.write_all(format!("{}\n", now.format("%Y-%m-%d")).as_bytes()); 
     file.write_all(b"\n"); 
    } 

    let activity = env::args().skip(1).join(" "); 
    if (activity.len() > 0) { 
     file.write_all(format!("{} {}\n", now.format("%H:%M"), activity).as_bytes()); 
    } else { 
     // if there was no latest activity *and* there is no activity, then there's no point in writing a second blank line with just a time 
     if latest_activity == None { 
      return; 
     } 
     file.write_all(format!("{}\n", now.format("%H:%M")).as_bytes()); 
    } 

    // FIXME: we're just relying on the program exit to close the two file descriptors (which point at the same file). 
} 
1

È possibile utilizzare BufReader::into_inner di "recuperare" il file dopo che è stato passato alla BufReader. Questo può essere utilizzato in combinazione con Read::by_ref per evitare di dare via la proprietà del BufReader<File>, in primo luogo:

use std::fs::File; 
use std::io::{BufRead, BufReader, Read, Write}; 

fn example(file: File) { 
    let mut reader = BufReader::new(file); 
    for _ in reader.by_ref().lines() {} 

    let mut out = reader.into_inner(); 

    out.write_all(b"new stuff").unwrap(); 
} 

Ecco antoyo's solution con simili Codice ridotta:

use std::fs::File; 
use std::io::{BufRead, BufReader, Write}; 

fn example(mut file: File) { 
    { 
     let reader = BufReader::new(&file); 
     for _ in reader.lines() {} 
    } 

    file.write_all(b"new stuff").unwrap(); 
} 

When vite non-lessicali (NLL) sono implementati, questo sarà in grado di essere semplificato rimuovendo le parentesi graffe extra.