From 5d2f51ab8593946a0f24db367a887a37258901d5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 3 Jun 2019 02:02:44 +0200 Subject: [WIP] rewrite output logic as a pipeline --- src/main.rs | 3 ++- src/output.rs | 45 ------------------------------- src/output/aggregators.rs | 32 ++++++++++++++++++++++ src/output/formats.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++ src/output/mod.rs | 26 ++++++++++++++++++ src/output/pipeline.rs | 27 +++++++++++++++++++ 6 files changed, 155 insertions(+), 46 deletions(-) delete mode 100644 src/output.rs create mode 100644 src/output/aggregators.rs create mode 100644 src/output/formats.rs create mode 100644 src/output/mod.rs create mode 100644 src/output/pipeline.rs (limited to 'src') diff --git a/src/main.rs b/src/main.rs index 89dab09..dbb8f36 100644 --- a/src/main.rs +++ b/src/main.rs @@ -285,6 +285,7 @@ fn is_log_file(entry: &DirEntry) -> bool { /// Run the grep search with the given options. fn grep(opt: &Opt) -> Result<(), RuntimeError> { + let pipeline = &output::build_pipeline(opt); rayon::scope(|s| { let walker = WalkDir::new(&opt.path); for entry in walker { @@ -294,7 +295,7 @@ fn grep(opt: &Opt) -> Result<(), RuntimeError> { let search = search_log(&entry, opt); match search { Ok(None) => (), - Ok(Some(result)) => output::output(io::stdout(), opt, &result).unwrap(), + Ok(Some(result)) => pipeline.push_item(&result), Err(err) => { debug!("Runtime error while scanning {:?}: {}", entry.path(), err); } diff --git a/src/output.rs b/src/output.rs deleted file mode 100644 index 148c088..0000000 --- a/src/output.rs +++ /dev/null @@ -1,45 +0,0 @@ -use super::errors::RuntimeError; -use super::{FightOutcome, LogResult, Opt}; - -use std::io::Write; - -/// Write the output to the given stream, according to the command line flags. -pub fn output(mut f: W, opt: &Opt, item: &LogResult) -> Result<(), RuntimeError> { - if opt.file_name_only { - writeln!(f, "{}", item.log_file.to_string_lossy())?; - } else { - colored(f, item)?; - } - Ok(()) -} - -/// Write the given log result to the given stream, using ANSI colors. -pub fn colored(mut f: W, item: &LogResult) -> Result<(), RuntimeError> { - use colored::Colorize; - writeln!(f, "{}: {:?}", "File".green(), item.log_file)?; - let outcome = match item.outcome { - FightOutcome::Success => "SUCCESS".green(), - FightOutcome::Wipe => "WIPE".red(), - }; - writeln!( - f, - "{}: {} - {}: {} {}", - "Date".green(), - item.time.format("%Y-%m-%d %H:%M:%S %a"), - "Boss".green(), - item.boss_name, - outcome, - )?; - for player in &item.players { - writeln!( - f, - " {:2} {:20} {:19} {}", - player.subgroup, - player.account_name.yellow(), - player.character_name.cyan(), - player.profession - )?; - } - writeln!(f)?; - Ok(()) -} diff --git a/src/output/aggregators.rs b/src/output/aggregators.rs new file mode 100644 index 0000000..9934fb3 --- /dev/null +++ b/src/output/aggregators.rs @@ -0,0 +1,32 @@ +//! Different aggregators for output. +//! +//! An aggregator is something that "controls the order" of the output. Aggregators can either save +//! all items that they're and output them once the search is finished, or write them straight +//! to the output stream. +//! +//! Aggregators must be shareable across threads, as the search will be multi-threaded. This is why +//! an Aggregator must make sure that the data is protected by a mutex or similar. +use super::{super::LogResult, formats::Format}; + +use std::{io::Write, sync::Mutex}; + +pub trait Aggregator: Sync { + fn push_item(&self, item: &LogResult, format: &Format, stream: &mut Write); + fn finish(self, format: &Format, stream: &mut Write); +} + + +/// An aggregator that just pushes through each item to the output stream without any sorting or +/// whatsoever. +pub struct WriteThrough; + + +impl Aggregator for WriteThrough { + fn push_item(&self, item: &LogResult, format: &Format, stream: &mut Write) { + let text = format.format_result(item); + println!("Aggregator::push_item {:?}", text); + stream.write_all(text.as_bytes()).unwrap(); + } + + fn finish(self, format: &Format, stream: &mut Write) {} +} diff --git a/src/output/formats.rs b/src/output/formats.rs new file mode 100644 index 0000000..fe6e982 --- /dev/null +++ b/src/output/formats.rs @@ -0,0 +1,68 @@ +//! A crate defining different output formats for search results. +use std::fmt::Write; + +use super::{LogResult, FightOutcome}; + +/// An output format +pub trait Format: Sync + Send { + /// Format a single log result + fn format_result(&self, item: &LogResult) -> String; +} + + +impl Format for Box { + fn format_result(&self, item: &LogResult) -> String { + (&*self).format_result(item) + } +} + + +/// The human readable, colored format. +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct HumanReadable; + + +impl Format for HumanReadable { + fn format_result(&self, item: &LogResult) -> String { + use colored::Colorize; + let mut result = String::new(); + + writeln!(result, "{}: {:?}", "File".green(), item.log_file).unwrap(); + let outcome = match item.outcome { + FightOutcome::Success => "SUCCESS".green(), + FightOutcome::Wipe => "WIPE".red(), + }; + writeln!( + result, + "{}: {} - {}: {} {}", + "Date".green(), + item.time.format("%Y-%m-%d %H:%M:%S %a"), + "Boss".green(), + item.boss_name, + outcome, + ).unwrap(); + for player in &item.players { + writeln!( + result, + " {:2} {:20} {:19} {}", + player.subgroup, + player.account_name.yellow(), + player.character_name.cyan(), + player.profession + ).unwrap(); + } + result + } +} + + +/// A format which outputs only the file-name +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct FileOnly; + + +impl Format for FileOnly { + fn format_result(&self, item: &LogResult) -> String { + item.log_file.to_string_lossy().into_owned() + } +} diff --git a/src/output/mod.rs b/src/output/mod.rs new file mode 100644 index 0000000..dfb3ea8 --- /dev/null +++ b/src/output/mod.rs @@ -0,0 +1,26 @@ +use super::errors::RuntimeError; +use super::{FightOutcome, LogResult, Opt}; + +use std::io; + +pub mod formats; +pub mod aggregators; +pub mod pipeline; + +use self::formats::Format; +use self::aggregators::Aggregator; +pub use self::pipeline::Pipeline; + + +/// Build an pipeline for the given command line options. +pub fn build_pipeline(opt: &Opt) -> Pipeline { + let stream = io::stdout(); + + let formatter: Box = if opt.file_name_only { + Box::new(formats::FileOnly) + } else { + Box::new(formats::HumanReadable) + }; + + Pipeline::new(stream, formatter, aggregators::WriteThrough) +} diff --git a/src/output/pipeline.rs b/src/output/pipeline.rs new file mode 100644 index 0000000..9b3c7e5 --- /dev/null +++ b/src/output/pipeline.rs @@ -0,0 +1,27 @@ +use super::{formats::Format, aggregators::Aggregator}; +use super::super::LogResult; + +use std::{io::Write, sync::Mutex}; + + +pub struct Pipeline { + format: Box, + aggregator: Box, + writer: Mutex>, +} + + +impl Pipeline { + pub fn new(writer: W, format: F, aggregator: A) -> Pipeline { + Pipeline { + format: Box::new(format), + aggregator: Box::new(aggregator), + writer: Mutex::new(Box::new(writer)), + } + } + + pub fn push_item(&self, item: &LogResult) { + let mut writer = self.writer.lock().unwrap(); + self.aggregator.push_item(item, &self.format, &mut *writer); + } +} -- cgit v1.2.3