implement rgrep, and write part 12 (draft) about it
[rust-101.git] / solutions / src / rgrep.rs
diff --git a/solutions/src/rgrep.rs b/solutions/src/rgrep.rs
new file mode 100644 (file)
index 0000000..a3b74cc
--- /dev/null
@@ -0,0 +1,113 @@
+use std::io::prelude::*;
+use std::{io, fs, thread, process};
+use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
+use std::sync::Arc;
+
+#[derive(Clone,Copy)]
+enum OutputMode {
+    Print,
+    SortAndPrint,
+    Count,
+}
+use self::OutputMode::*;
+
+struct Options {
+    files: Vec<String>,
+    pattern: String,
+    output_mode: OutputMode,
+}
+
+struct Line {
+    data: String,
+    file: usize,
+    line: usize,
+}
+
+fn read_files(options: Arc<Options>, out_channel: SyncSender<Line>) {
+    for (fileidx, file) in options.files.iter().enumerate() {
+        let file = fs::File::open(file).unwrap();
+        let file = io::BufReader::new(file);
+        for (lineidx, line) in file.lines().enumerate() {
+            let line = Line { data: line.unwrap(), file: fileidx, line: lineidx };
+            out_channel.send(line).unwrap();
+        }
+    }
+}
+
+fn filter_lines(options: Arc<Options>, in_channel: Receiver<Line>, out_channel: SyncSender<Line>) {
+    for line in in_channel.iter() {
+        if line.data.contains(&options.pattern) {
+            out_channel.send(line).unwrap();
+        }
+    }
+}
+
+fn output_lines(options: Arc<Options>, in_channel: Receiver<Line>) {
+    match options.output_mode {
+        Print => {
+            for line in in_channel.iter() {
+                println!("{}:{}: {}", options.files[line.file], line.line, line.data);
+            }
+        },
+        Count => {
+            let count = in_channel.iter().count();
+            println!("{} hits for {}.", count, options.pattern);
+        },
+        SortAndPrint => {
+            let _data: Vec<Line> = in_channel.iter().collect();
+            unimplemented!()
+        }
+    }
+}
+
+static USAGE: &'static str = "
+Usage: rgrep [-c] [-s] <pattern> <file>...
+
+Options:
+    -c, --count  Count number of matching lines (rather than printing them).
+    -s, --sort   Sort the lines before printing.
+";
+
+fn get_options() -> Options {
+    use docopt::Docopt;
+
+    // Parse argv and exit the program with an error message if it fails.
+    let args = Docopt::new(USAGE).and_then(|d| d.parse()).unwrap_or_else(|e| e.exit());
+    let count = args.get_bool("-c");
+    let sort = args.get_bool("-s");
+    let pattern = args.get_str("<pattern>");
+    let files = args.get_vec("<file>");
+    if count && sort {
+        println!("Setting both '-c' and '-s' at the same time does not make any sense.");
+        process::exit(1);
+    }
+
+    // We need to make the strings owned to construct the `Options` instance.
+    Options {
+        files: files.iter().map(|file| file.to_string()).collect(),
+        pattern: pattern.to_string(),
+        output_mode: if count { Count } else if sort { SortAndPrint } else { Print },
+    }
+}
+
+fn run(options: Options) {
+    let options = Arc::new(options);
+
+    // Set up the chain of threads. Use `sync_channel` with buffer-size of 16 to avoid needlessly filling RAM.
+    let (line_sender, line_receiver) = sync_channel(16);
+    let (filtered_sender, filtered_receiver) = sync_channel(16);
+
+    let options1 = options.clone();
+    let handle1 = thread::spawn(move || read_files(options1, line_sender));
+    let options2 = options.clone();
+    let handle2 = thread::spawn(move || filter_lines(options2, line_receiver, filtered_sender));
+    let options3 = options.clone();
+    let handle3 = thread::spawn(move || output_lines(options3, filtered_receiver));
+    handle1.join().unwrap();
+    handle2.join().unwrap();
+    handle3.join().unwrap();
+}
+
+pub fn main() {
+    run(get_options());
+}