chj_rustbin/io/
readwithcontext.rs

1//! File handling utilities that make life simpler for the common
2//! case.
3
4use anyhow::{anyhow, Context, Result};
5use std::{
6    fs::File,
7    io::{BufRead, BufReader},
8    path::Path,
9};
10
11pub fn trim(line: &mut String) {
12    if line.ends_with("\n") {
13        line.pop().unwrap();
14    }
15}
16
17/// Easy buffered file opening with context on open errors. But see
18/// `ReadWithContext` instead for keeping context across subsequent
19/// calls, too.
20pub fn open_file(path: &Path) -> Result<BufReader<File>> {
21    Ok(BufReader::new(
22        File::open(path).with_context(|| format!("opening file {:?}", path))?,
23    ))
24}
25
26/// "Clean" read_line function: returns true if it did read a line,
27/// false on EOF. Does overwrite `line`, not append to it. Removes
28/// trailing '\n' if present.
29pub fn easy_read_line(
30    inp: &mut BufReader<File>,
31    line: &mut String,
32) -> Result<bool> {
33    line.clear();
34    if inp.read_line(line)? != 0 {
35        trim(line);
36        Ok(true)
37    } else {
38        Ok(false)
39    }
40}
41
42/// Automatically count lines and report them and the path in error
43/// messages
44pub struct ReadWithContext<'p> {
45    path: &'p Path,
46    linenumber: i64,
47    reader: BufReader<File>,
48}
49
50impl<'p> ReadWithContext<'p> {
51    pub fn open_path(path: &'p Path) -> Result<ReadWithContext<'p>> {
52        Ok(ReadWithContext {
53            path,
54            linenumber: 0,
55            reader: open_file(path)?,
56        })
57    }
58    /// "Clean" read_line function: returns true if it did read a line,
59    /// false on EOF. Does overwrite `line`, not append to it. Removes
60    /// trailing '\n' if present.
61    pub fn easy_read_line(&mut self, line: &mut String) -> Result<bool> {
62        self.linenumber += 1;
63        easy_read_line(&mut self.reader, line).with_context(|| {
64            anyhow!("file {:?} line {}", self.path, self.linenumber)
65        })
66    }
67
68    /// Report an error in the context of this file and position
69    #[allow(unused)]
70    pub fn err_with_context<T>(
71        &self,
72        err: anyhow::Error,
73    ) -> Result<T, anyhow::Error> {
74        Err(err.context(anyhow!(
75            "file {:?} line {}",
76            self.path,
77            self.linenumber
78        )))
79    }
80
81    /// A Result in the context of this file and position
82    #[allow(unused)]
83    pub fn context<T>(
84        &self,
85        res: Result<T, anyhow::Error>,
86    ) -> Result<T, anyhow::Error> {
87        match res {
88            Ok(v) => Ok(v),
89            Err(e) => self.err_with_context(e),
90        }
91    }
92}