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}