larry/
lib.rs

1use std::fmt;
2use std::fs::File;
3use std::io::{BufRead, BufReader, Read, Seek, SeekFrom};
4use std::path::Path;
5use std::str;
6
7struct Line {
8    offset: u64,
9    text: Option<String>,
10}
11
12impl Line {
13    fn new(offset: u64) -> Line {
14        Line { offset, text: None }
15    }
16    fn set_text(&mut self, bytes: &[u8]) {
17        self.text = Some(str::from_utf8(bytes).unwrap().to_string())
18    }
19}
20
21/// An error made by Larry
22#[derive(Debug)]
23pub enum Lerror {
24    OutOfBounds(String),
25    IO(std::io::Error),
26}
27
28impl fmt::Display for Lerror {
29    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
30        match self {
31            Lerror::OutOfBounds(s) => write!(f, "Lerror::OutOfBounds({:?})", s),
32            Lerror::IO(err) => write!(f, "Lerror::IO({})", err),
33        }
34    }
35}
36
37impl std::error::Error for Lerror {}
38
39/// A `Larry` is a "line array". It allows one to access a file as a
40/// lazily-read array of lines. This allows efficient random access to large
41/// files such as log files.
42pub struct Larry {
43    lines: Vec<Line>,
44    pub file: File,
45    length: u64,
46}
47
48impl Larry {
49    /// Constructs a new `Larry`.
50    ///
51    /// Construction requires that the file be scanned for line-terminal byte
52    /// sequences.
53    ///
54    /// # Examples
55    /// ```
56    /// # use std::error::Error;
57    /// # fn demo() -> Result<(), Box<Error>> {
58    /// use larry::Larry;
59    /// use std::path::Path;
60    ///
61    /// let mut larry = Larry::new(Path::new("production.log"))?;
62    /// # Ok(()) }
63    /// ```
64    /// # Errors
65    /// Any `std::io::Error` arising while opening the file and scanning its contents
66    /// for line endings will be returned.
67    pub fn new(path: &Path) -> Result<Larry, std::io::Error> {
68        match File::open(&path) {
69            Ok(file) => {
70                let mut reader = BufReader::new(file);
71                let mut length = 0;
72                let mut offset = 0;
73                let mut last_was_0a = false;
74                let mut last_was_0d = false;
75                let mut lines = Vec::new();
76                loop {
77                    let length = {
78                        match reader.fill_buf() {
79                            Ok(buffer) => {
80                                if buffer.len() == 0 {
81                                    break;
82                                }
83                                for i in 0..buffer.len() {
84                                    length += 1;
85                                    match buffer[i] {
86                                        0x0A => {
87                                            if last_was_0d {
88                                                last_was_0a = false;
89                                                last_was_0d = false;
90                                                lines.push(Line::new(offset));
91                                                offset += length;
92                                                length = 0;
93                                            } else {
94                                                if last_was_0a {
95                                                    lines.push(Line::new(offset));
96                                                    offset += length - 1;
97                                                    length = 1;
98                                                } else {
99                                                    last_was_0a = true;
100                                                }
101                                            }
102                                        }
103                                        0x0D => {
104                                            if last_was_0a {
105                                                last_was_0a = false;
106                                                last_was_0d = false;
107                                                lines.push(Line::new(offset));
108                                                offset += length;
109                                                length = 0;
110                                            } else {
111                                                if last_was_0d {
112                                                    lines.push(Line::new(offset));
113                                                    offset += length - 1;
114                                                    length = 1;
115                                                } else {
116                                                    last_was_0d = true;
117                                                }
118                                            }
119                                        }
120                                        _ => {
121                                            if last_was_0a || last_was_0d {
122                                                last_was_0a = false;
123                                                last_was_0d = false;
124                                                length -= 1;
125                                                lines.push(Line::new(offset));
126                                                offset += length;
127                                                length = 1;
128                                            }
129                                        }
130                                    }
131                                }
132                                buffer.len()
133                            }
134                            Err(io_err) => {
135                                return Err(io_err);
136                            }
137                        }
138                    };
139                    reader.consume(length);
140                }
141                if length > 0 {
142                    lines.push(Line::new(offset));
143                    offset += length;
144                }
145                Ok(Larry {
146                    lines: lines,
147                    file: File::open(&path).unwrap(),
148                    length: offset,
149                })
150            }
151            Err(error) => Err(error),
152        }
153    }
154    /// Obtain a particular line.
155    ///
156    /// # Examples
157    /// ```
158    /// # use std::error::Error;
159    /// # fn demo() -> Result<(), Box<Error>> {
160    /// use larry::Larry;
161    /// use std::path::Path;
162    ///
163    /// let mut larry = Larry::new(Path::new("production.log"))?;
164    /// // print the last line of the file
165    /// let last_line_index = larry.len() - 1;
166    /// print!("{}", larry.get(last_line_index)?);
167    /// # Ok(()) }
168    /// ```
169    /// # Errors
170    /// Index bound errors if you ask for a line beyond the end of the file and
171    /// IO errors if the file has changed since the larry was created.
172    pub fn get(&mut self, i: usize) -> Result<&str, Lerror> {
173        if i >= self.lines.len() {
174            Err(Lerror::OutOfBounds(format!(
175                "index {} in file of only {} lines",
176                i,
177                self.lines.len()
178            )))
179        } else {
180            if self.lines[i].text.is_some() {
181                Ok(self.lines[i].text.as_ref().unwrap())
182            } else {
183                let length = if i == self.lines.len() - 1 {
184                    self.length - self.lines[i].offset
185                } else {
186                    self.lines[i + 1].offset - self.lines[i].offset
187                };
188                let line = &mut self.lines[i];
189                let mut buffer = vec![0; length as usize];
190                if let Err(io_err) = self.file.seek(SeekFrom::Start(line.offset)) {
191                    Err(Lerror::IO(io_err))
192                } else if let Err(io_err) = self.file.read(&mut buffer) {
193                    Err(Lerror::IO(io_err))
194                } else {
195                    line.set_text(&buffer);
196                    Ok(line.text.as_ref().unwrap())
197                }
198            }
199        }
200    }
201    /// Returns the byte offset of line i from the start of the file.
202    ///
203    /// # Examples
204    /// ```
205    /// # use std::error::Error;
206    /// # fn demo() -> Result<(), Box<Error>> {
207    /// use larry::Larry;
208    /// use std::path::Path;
209    ///
210    /// let larry = Larry::new(Path::new("production.log"))?;
211    /// // print the last line of the file
212    /// let last_line_index = larry.len() - 1;
213    /// print!("{}", larry.offset(last_line_index)?);
214    /// # Ok(()) }
215    /// ```
216    /// # Errors
217    /// Index bound errors if you ask for a line beyond the end of the file
218    pub fn offset(&self, i: usize) -> Result<u64, Lerror> {
219        if i >= self.lines.len() {
220            Err(Lerror::OutOfBounds(format!(
221                "index {} in file of only {} lines",
222                i,
223                self.lines.len()
224            )))
225        } else {
226            Ok(self.lines[i].offset)
227        }
228    }
229    /// Returns number of lines.
230    ///
231    /// # Examples
232    /// ```
233    /// # use std::error::Error;
234    /// # fn demo() -> Result<(), Box<Error>> {
235    /// use larry::Larry;
236    /// use std::path::Path;
237    ///
238    /// let mut larry = Larry::new(Path::new("production.log"))?;
239    /// println!("number of lines line: {}", larry.len());
240    /// # Ok(()) }
241    /// ```
242    pub fn len(&self) -> usize {
243        self.lines.len()
244    }
245}