X-Git-Url: https://git.ralfj.de/rust-101.git/blobdiff_plain/a9c7d7471bf6f06a2a4710daa306c26fc5324557..5f6e02d64e3789115ea4327a045b8ad3c39b1808:/solutions/src/rgrep.rs diff --git a/solutions/src/rgrep.rs b/solutions/src/rgrep.rs new file mode 100644 index 0000000..a3b74cc --- /dev/null +++ b/solutions/src/rgrep.rs @@ -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, + pattern: String, + output_mode: OutputMode, +} + +struct Line { + data: String, + file: usize, + line: usize, +} + +fn read_files(options: Arc, out_channel: SyncSender) { + 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, in_channel: Receiver, out_channel: SyncSender) { + for line in in_channel.iter() { + if line.data.contains(&options.pattern) { + out_channel.send(line).unwrap(); + } + } +} + +fn output_lines(options: Arc, in_channel: Receiver) { + 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 = in_channel.iter().collect(); + unimplemented!() + } + } +} + +static USAGE: &'static str = " +Usage: rgrep [-c] [-s] ... + +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(""); + let files = args.get_vec(""); + 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()); +}