1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#[macro_use]pub mod util;
pub mod cli;
pub mod search;
pub mod file;
mod line;
mod folder;
extern crate colored;

use crate::util::Result;
use crate::line::Line;
use std::io::BufRead;
use std::io::Write;
use colored::Colorize;
use atty::Stream;

pub fn process_folder<P>(root: P, options: &cli::Options, file_data: &mut file::Data) -> Result<()>
where P: AsRef<std::path::Path>
{
    let paths = folder::Scanner::new(root, &options)?.scan()?;

    if file_data.search_opt.is_some() {
        for path in &paths {
            process_file(path, options, file_data)?;
        }
    } else {
        for path in &paths {
            if options.null_separated_output {
                print!("{}\0", format!("{}", path.display()));
            } else {
                println!("{}", format!("{}", path.display()));
            }
        }
    }

    Ok(())
}

pub fn process_file(path: &std::path::PathBuf, options: &cli::Options, file_data: &mut file::Data) -> Result<()> {
    if options.output_only == Some(cli::OutputOnly::Folders) {
        return Ok(());
    }

    let console_output = options.console_output.unwrap_or(atty::is(Stream::Stdout));

    match file_data.load(path) {
        Err(_) => if options.verbose_level >= 1 {
            println!("Warning: Skipping '{}', could not load file", path.display());
        },

        Ok(()) => {
            file_data.split_in_lines()?;
            if file_data.search_for_matches() ^ options.invert_pattern {
                let search = file_data.search_opt.as_ref().unwrap();
                if options.output_only == Some(cli::OutputOnly::Filenames) {
                    if options.null_separated_output {
                        print!("{}\0", format!("{}", file_data.path.display()));
                    } else {
                        println!("{}", format!("{}", file_data.path.display()));
                    }
                } else {
                    if console_output {
                        println!("{}", format!("{}", file_data.path.display()).green().bold());
                    }
                    let content = file_data.content.as_slice();
                    //Iterator that is meant to be options.output_before behind the
                    //one driving the for loop. `delay` indicates the actual delay.
                    let mut delayed_line_iter = file_data.lines.iter();
                    let mut delay = 0;
                    //As long as output_count is Some(>0), we will output
                    let mut output_count = None;
                    for line in file_data.lines.iter() {
                        if !line.matches.is_empty() {
                            output_count = Some(delay+options.output_after+1);
                        }

                        if let Some(cnt) = output_count {
                            let delayed_line = delayed_line_iter.next().unwrap();
                            if cnt > 0 {
                                if !console_output {
                                    print!("{}:", file_data.path.display());
                                }
                                delayed_line.print_colored(delayed_line.as_slice(content), search, &file_data.replace_opt);
                                output_count = Some(cnt-1);
                            } else {
                                if console_output {
                                    println!("...");
                                }
                                output_count = None;
                            }
                        } else if delay < options.output_before {
                            delay += 1;
                        } else {
                            let _ = delayed_line_iter.next();
                        }
                    }
                    if console_output {
                        println!("");
                    }
                }

                if file_data.replace_opt.is_some() {
                    if !options.simulate_replace {
                        file_data.replace_and_write()?;
                    }
                }
            }
        },
    }

    Ok(())
}

pub fn process_stdin(options: &cli::Options) -> Result<()> {
    match &options.search_pattern_opt {
        None => Ok(()),
        Some(pattern) => {
            let (stdin, stdout) = (std::io::stdin(), std::io::stdout());
            let (mut stdin_handle, mut stdout_handle) = (stdin.lock(), stdout.lock());
            let search = search::Search::new(&pattern, options.word_boundary, options.case_sensitive).unwrap();

            let replace_opt = options.replace_opt.as_ref().map(|s|search::Replace::new(s, &options.capture_group_prefix_opt));
            let stdout_is_tty = atty::is(Stream::Stdout);

            let mut buffer: Vec<u8> = vec![];
            let mut buffer_replaced: Vec<u8> = vec![];
            let mut line_nr = 0;

            while let Ok(size) = stdin_handle.read_until(0x0a_u8, &mut buffer) {
                if size == 0 {
                    break;
                }

                line_nr += 1;

                let mut line = Line::new(line_nr, 0, buffer.len());

                let found_match = line.search_for(&search, &buffer) ^ options.invert_pattern;

                if replace_opt.is_some() && !stdout_is_tty {
                    //When we are _replacing_ with _redirected output_, we will keep _all_ the input lines,
                    //also those that do not match
                    if found_match && replace_opt.is_some() {
                        if options.output_only == Some(cli::OutputOnly::Match) {
                            line.only_matches(&buffer, &mut buffer_replaced);
                        } else {
                            line.replace_with(&buffer, &search, replace_opt.as_ref().unwrap(), &mut buffer_replaced);
                        }
                        stdout_handle.write(&buffer_replaced)?;
                    } else {
                        stdout_handle.write(&buffer)?;
                    }
                } else {
                    //Else, we only output matching lines
                    if found_match {
                        if options.output_only == Some(cli::OutputOnly::Match) {
                            line.print_colored_match(&buffer);
                        } else {
                            line.print_colored(&buffer, &search, &replace_opt);
                        }
                    }
                }

                buffer.clear();
            }
            Ok(())
        },
    }
}