1 use std::io::prelude::*;
2 use std::{io, fs, thread, process, cmp};
3 use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
12 use self::OutputMode::*;
17 output_mode: OutputMode,
26 impl PartialEq for Line {
27 fn eq(&self, other: &Line) -> bool {
28 self.data.eq(&other.data)
31 impl PartialOrd for Line {
32 fn partial_cmp(&self, other: &Line) -> Option<cmp::Ordering> {
33 self.data.partial_cmp(&other.data)
37 fn read_files(options: Arc<Options>, out_channel: SyncSender<Line>) {
38 for (fileidx, file) in options.files.iter().enumerate() {
39 let file = fs::File::open(file).unwrap();
40 let file = io::BufReader::new(file);
41 for (lineidx, line) in file.lines().enumerate() {
42 let line = Line { data: line.unwrap(), file: fileidx, line: lineidx };
43 out_channel.send(line).unwrap();
48 fn filter_lines(options: Arc<Options>, in_channel: Receiver<Line>, out_channel: SyncSender<Line>) {
49 for line in in_channel.iter() {
50 if line.data.contains(&options.pattern) {
51 out_channel.send(line).unwrap();
56 fn sort<T: PartialOrd>(data: &mut [T]) {
57 if data.len() < 2 { return; }
60 let mut rpos = data.len();
61 // Invariant: pivot is data[0]; (0,lpos) is <= pivot; [rpos,len) is >= pivot; lpos < rpos
63 while lpos < rpos && data[lpos] <= data[0] {
66 while rpos > lpos && data[rpos-1] >= data[0] {
73 data.swap(lpos, rpos-1);
76 data.swap(0, lpos-1); // put pivot in the right place
78 let (part1, part2) = data.split_at_mut(lpos);
79 sort(&mut part1[..lpos-1]);
83 fn output_lines(options: Arc<Options>, in_channel: Receiver<Line>) {
84 match options.output_mode {
86 for line in in_channel.iter() {
87 println!("{}:{}: {}", options.files[line.file], line.line, line.data);
91 let count = in_channel.iter().count();
92 println!("{} hits for {}.", count, options.pattern);
95 let mut data: Vec<Line> = in_channel.iter().collect();
97 for line in data.iter() {
98 println!("{}:{}: {}", options.files[line.file], line.line, line.data);
104 static USAGE: &'static str = "
105 Usage: rgrep [-c] [-s] <pattern> <file>...
108 -c, --count Count number of matching lines (rather than printing them).
109 -s, --sort Sort the lines before printing.
112 fn get_options() -> Options {
115 // Parse argv and exit the program with an error message if it fails.
116 let args = Docopt::new(USAGE).and_then(|d| d.parse()).unwrap_or_else(|e| e.exit());
117 let count = args.get_bool("-c");
118 let sort = args.get_bool("-s");
119 let pattern = args.get_str("<pattern>");
120 let files = args.get_vec("<file>");
122 println!("Setting both '-c' and '-s' at the same time does not make any sense.");
126 // We need to make the strings owned to construct the `Options` instance.
128 files: files.iter().map(|file| file.to_string()).collect(),
129 pattern: pattern.to_string(),
130 output_mode: if count { Count } else if sort { SortAndPrint } else { Print },
134 fn run(options: Options) {
135 let options = Arc::new(options);
137 // This sets up the chain of threads. Use `sync_channel` with buffer-size of 16 to avoid needlessly filling RAM.
138 let (line_sender, line_receiver) = sync_channel(16);
139 let (filtered_sender, filtered_receiver) = sync_channel(16);
141 let options1 = options.clone();
142 let handle1 = thread::spawn(move || read_files(options1, line_sender));
143 let options2 = options.clone();
144 let handle2 = thread::spawn(move || filter_lines(options2, line_receiver, filtered_sender));
145 let options3 = options.clone();
146 let handle3 = thread::spawn(move || output_lines(options3, filtered_receiver));
147 handle1.join().unwrap();
148 handle2.join().unwrap();
149 handle3.join().unwrap();