tail/
lib.rs

1/*Coright (c) 2018 konstin
2
3Permission is hereby granted, free of charge, to any
4person obtaining a co of this software and associated
5documentation files (the "Software"), to deal in the
6Software without restriction, including without
7limitation the rights to use, co, modify, merge,
8publish, distribute, sublicense, and/or sell copies of
9the Software, and to permit persons to whom the Software
10is furnished to do so, subject to the following
11conditions:
12
13The above coright notice and this permission notice
14shall be included in all copies or substantial portions
15of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21SHALL THE AUTHORS OR CORIGHT HOLDERS BE LIABLE FOR ANY
22CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25DEALINGS IN THE SOFTWARE.*/
26
27
28use std::fs::{File};
29use std::io::{Read, Seek, SeekFrom};
30use std::io::{BufRead, BufReader};
31
32pub const BLOCK_SIZE: u64 = 1 << 16;
33pub struct ReverseChunks<'a> {
34    file: &'a File,
35    size: u64,
36    max_blocks_to_read: usize,
37    block_idx: usize,
38}
39
40impl<'a> ReverseChunks<'a> {
41    pub fn new(file: &'a mut File) -> ReverseChunks<'a> {
42        let size = file.seek(SeekFrom::End(0)).unwrap();
43        let max_blocks_to_read = (size as f64 / BLOCK_SIZE as f64).ceil() as usize;
44        let block_idx = 0;
45        ReverseChunks {
46            file,
47            size,
48            max_blocks_to_read,
49            block_idx,
50        }
51    }
52}
53
54impl<'a> Iterator for ReverseChunks<'a> {
55    type Item = Vec<u8>;
56
57    fn next(&mut self) -> Option<Self::Item> {
58        if self.block_idx >= self.max_blocks_to_read {
59            return None;
60        }
61        let block_size = if self.block_idx == self.max_blocks_to_read - 1 {
62            self.size % BLOCK_SIZE
63        } else {
64            BLOCK_SIZE
65        };
66        let mut buf = vec![0; BLOCK_SIZE as usize];
67        let pos = self
68            .file
69            .seek(SeekFrom::Current(-(block_size as i64)))
70            .unwrap();
71        self.file
72            .read_exact(&mut buf[0..(block_size as usize)])
73            .unwrap();
74        let pos2 = self
75            .file
76            .seek(SeekFrom::Current(-(block_size as i64)))
77            .unwrap();
78        assert_eq!(pos, pos2);
79
80        self.block_idx += 1;
81
82        Some(buf[0..(block_size as usize)].to_vec())
83    }
84}
85
86pub fn backward(file: &mut File, num_delimiters: u64, delimiter: u8) {
87    let mut counter = 0;
88    for (block_idx, slice) in ReverseChunks::new(file).enumerate() {
89        let mut iter = slice.iter().enumerate().rev();
90        if block_idx == 0 {
91            if let Some(c) = slice.last() {
92                if *c == delimiter {
93                    iter.next();
94                }
95            }
96        }
97        for (i, ch) in iter {
98            if *ch == delimiter {
99                counter += 1;
100                if counter >= num_delimiters {
101                    file.seek(SeekFrom::Current((i + 1) as i64)).unwrap();
102                    return;
103                }
104            }
105        }
106    }
107}
108
109// split lines based on the given delimiters
110pub fn extract_from_line(line: &str, begin_delimiter: &str, end_delimiter: &str)  -> String {
111    let mut value = line;
112    if begin_delimiter.is_empty() == true && end_delimiter.is_empty() == true {
113     value = line;
114    }
115    if begin_delimiter.is_empty() == false && end_delimiter.is_empty() == false {
116        value = line
117       .split(begin_delimiter)
118       .nth(1)
119       .unwrap()
120       .split(end_delimiter)
121       .next()
122       .unwrap();
123       }
124    return value.to_string();
125}
126
127
128// get the line in list format
129pub fn parse_line(line: &str, begin_delimiter: &str, end_delimiter: &str)-> String {
130    let r = extract_from_line(line, begin_delimiter, end_delimiter);
131    return r;
132}
133
134// return the total number of file lines
135pub  fn count_lines(path: &str) -> u64 {
136    let file = BufReader::new(File::open(path).expect("Unable to open file"));
137    let mut cnt  = 0;
138
139    for _ in file.lines() {
140        cnt = cnt + 1;
141    }
142
143    return cnt;
144}
145
146// drop line if it does not contains the given &str
147pub fn match_lines(matchers: &[&str], line: &str) -> bool {
148    for m in matchers {
149        if line.contains(m){
150            return true;
151        }
152    }
153    return false;
154}
155
156// search and || or ignore lines based on the given strings
157pub fn extract_lines(lines: String, begin_delimiter: &str, end_delimiter: &str, search: &str, ignore: &str) -> Vec<String> {
158    let mut parsed_lines = Vec::new();
159    // search with one or mores args "search1, search2" converted into list
160    let search_lines:  Vec<&str> = search
161                                  .split(",")
162                                  .map(|s| s.trim())
163                                  .filter(|s| !s.is_empty())
164                                  .collect::<Vec<_>>();
165    let ignore_lines: Vec<&str> = ignore
166                                  .split(",")
167                                  .map(|s| s.trim())
168                                  .filter(|s| !s.is_empty())
169                                  .collect::<Vec<_>>();
170    // search, ignore or both then return parsed lines
171    for line in lines.split("\n") {
172        if ignore != "" {
173            if match_lines(&ignore_lines, line) {
174                continue;
175            }
176        }
177        // chars in search_lines splited by must match with the actual line
178        if match_lines(&search_lines, line) {
179            let parsed_line = extract_from_line(line, begin_delimiter, end_delimiter);
180            // after parse the line we trim it case it's necessary, before push into the parsed lines
181            if !parsed_line.is_empty() {
182                let result_line;
183                let trim_line = parsed_line.trim();
184                if trim_line.len() < parsed_line.len() {
185                    result_line = trim_line.to_string();
186                }
187                else{
188                    result_line = parsed_line;
189                }
190                parsed_lines.push(result_line);
191            }
192        }
193    }
194    return parsed_lines;
195}
196
197
198pub fn fsearch(filename: &str, search: &str, ignore: &str, ignore_mode: bool, number_of_lines: u64) -> Vec<String> {
199    let mut n = number_of_lines;
200    //if argument for number_of_lines is 0, then use the entire file
201    if n == 0 {
202        n = count_lines(filename);
203    }
204    let r;
205    let mut contents =  File::open(&filename)
206                    .expect("Something went wrong reading the file");
207    if ignore_mode {
208        r = tail_parse(&mut contents, n, "", "", search, ignore);
209    }
210    else{
211        r = tail_parse(&mut contents, n, "", "", search, "");
212    }
213    return r;
214}
215
216pub fn fsearch_last_line(filename: &str, search: &str, ignore: &str, ignore_mode: bool) -> Vec<String> {
217    let mut counter = 0u64;
218    let mut search_found;
219    loop {
220        counter += 1;
221        if ignore_mode {
222            search_found = fsearch(filename, search, ignore, true, counter);
223        }
224        else{
225            search_found = fsearch(filename, search, "", false, counter);
226        }
227        if !search_found.is_empty() {
228            break;
229        }
230    }
231    return search_found;
232}
233
234
235pub fn search_last_line(filename: &str, search: &str) -> Vec<String> {
236    let r = fsearch_last_line(filename, search, "", false);
237    return r;
238}
239
240
241pub fn isearch_last_line(filename: &str, search: &str, ignore: &str) -> Vec<String> {
242    let r = fsearch_last_line(filename, search, ignore, true);
243    return r;
244}
245
246
247pub fn search_lines(filename: &str, search: &str, number_of_lines: u64) -> Vec<String> {
248    let r = fsearch(filename, search, "", false, number_of_lines);
249    return r;
250}
251
252
253pub fn search_all(filename: &str, search: &str) -> Vec<String> {
254    let r = fsearch(filename, search, "", false, 0);
255    return r;
256}
257
258
259pub fn isearch_all(filename: &str, search: &str, ignore: &str) -> Vec<String> {
260    let ignore_mode = true;
261    let r = fsearch(filename, search, ignore, ignore_mode, 0);
262    return r;
263}
264
265
266pub fn isearch_lines(filename: &str, search: &str, ignore: &str, number_of_lines: u64) -> Vec<String> {
267    let ignore_mode = true;
268    let r = fsearch(filename, search, ignore, ignore_mode, number_of_lines);
269    return r;
270}
271
272pub fn tail_parse(file: &mut File, lines_number: u64, begin_delimiter: &str, end_delimiter: &str, search: &str, ignore: &str) -> Vec<String> {
273    // Find the position in the file to start printing from.
274    let delimiter = b'\n';
275    backward(file, lines_number, delimiter);
276    let mut lines = String::new();
277    file.read_to_string(&mut lines).expect("The string cannot be read");
278    let vec = extract_lines(lines, begin_delimiter, end_delimiter, search, ignore);
279    return vec;
280}
281
282
283 pub fn lines(filename: &str, number_of_lines: u64)  -> Vec<String> {
284    let mut n = number_of_lines;
285    //if argument for number_of_lines is 0, then use the entire file
286    if n == 0 {
287        n = count_lines(filename);
288    }
289    let mut contents =  File::open(&filename)
290                    .expect("Something went wrong reading the file");
291    let r = tail_parse(&mut contents, n, "", "", "", "");
292    return r;
293}
294