implement rgrep, and write part 12 (draft) about it
[rust-101.git] / solutions / src / rgrep.rs
1 use std::io::prelude::*;
2 use std::{io, fs, thread, process};
3 use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
4 use std::sync::Arc;
5
6 #[derive(Clone,Copy)]
7 enum OutputMode {
8     Print,
9     SortAndPrint,
10     Count,
11 }
12 use self::OutputMode::*;
13
14 struct Options {
15     files: Vec<String>,
16     pattern: String,
17     output_mode: OutputMode,
18 }
19
20 struct Line {
21     data: String,
22     file: usize,
23     line: usize,
24 }
25
26 fn read_files(options: Arc<Options>, out_channel: SyncSender<Line>) {
27     for (fileidx, file) in options.files.iter().enumerate() {
28         let file = fs::File::open(file).unwrap();
29         let file = io::BufReader::new(file);
30         for (lineidx, line) in file.lines().enumerate() {
31             let line = Line { data: line.unwrap(), file: fileidx, line: lineidx };
32             out_channel.send(line).unwrap();
33         }
34     }
35 }
36
37 fn filter_lines(options: Arc<Options>, in_channel: Receiver<Line>, out_channel: SyncSender<Line>) {
38     for line in in_channel.iter() {
39         if line.data.contains(&options.pattern) {
40             out_channel.send(line).unwrap();
41         }
42     }
43 }
44
45 fn output_lines(options: Arc<Options>, in_channel: Receiver<Line>) {
46     match options.output_mode {
47         Print => {
48             for line in in_channel.iter() {
49                 println!("{}:{}: {}", options.files[line.file], line.line, line.data);
50             }
51         },
52         Count => {
53             let count = in_channel.iter().count();
54             println!("{} hits for {}.", count, options.pattern);
55         },
56         SortAndPrint => {
57             let _data: Vec<Line> = in_channel.iter().collect();
58             unimplemented!()
59         }
60     }
61 }
62
63 static USAGE: &'static str = "
64 Usage: rgrep [-c] [-s] <pattern> <file>...
65
66 Options:
67     -c, --count  Count number of matching lines (rather than printing them).
68     -s, --sort   Sort the lines before printing.
69 ";
70
71 fn get_options() -> Options {
72     use docopt::Docopt;
73
74     // Parse argv and exit the program with an error message if it fails.
75     let args = Docopt::new(USAGE).and_then(|d| d.parse()).unwrap_or_else(|e| e.exit());
76     let count = args.get_bool("-c");
77     let sort = args.get_bool("-s");
78     let pattern = args.get_str("<pattern>");
79     let files = args.get_vec("<file>");
80     if count && sort {
81         println!("Setting both '-c' and '-s' at the same time does not make any sense.");
82         process::exit(1);
83     }
84
85     // We need to make the strings owned to construct the `Options` instance.
86     Options {
87         files: files.iter().map(|file| file.to_string()).collect(),
88         pattern: pattern.to_string(),
89         output_mode: if count { Count } else if sort { SortAndPrint } else { Print },
90     }
91 }
92
93 fn run(options: Options) {
94     let options = Arc::new(options);
95
96     // Set up the chain of threads. Use `sync_channel` with buffer-size of 16 to avoid needlessly filling RAM.
97     let (line_sender, line_receiver) = sync_channel(16);
98     let (filtered_sender, filtered_receiver) = sync_channel(16);
99
100     let options1 = options.clone();
101     let handle1 = thread::spawn(move || read_files(options1, line_sender));
102     let options2 = options.clone();
103     let handle2 = thread::spawn(move || filter_lines(options2, line_receiver, filtered_sender));
104     let options3 = options.clone();
105     let handle3 = thread::spawn(move || output_lines(options3, filtered_receiver));
106     handle1.join().unwrap();
107     handle2.join().unwrap();
108     handle3.join().unwrap();
109 }
110
111 pub fn main() {
112     run(get_options());
113 }