aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDaniel <kingdread@gmx.de>2019-06-03 02:02:44 +0200
committerDaniel <kingdread@gmx.de>2019-06-03 02:02:44 +0200
commit5d2f51ab8593946a0f24db367a887a37258901d5 (patch)
tree498f2af9584046ed63f256375169bbf5756bfb7d /src
parentc731b470fc162e56f6d81c475bacb41230a5e2d3 (diff)
downloadraidgrep-5d2f51ab8593946a0f24db367a887a37258901d5.tar.gz
raidgrep-5d2f51ab8593946a0f24db367a887a37258901d5.tar.bz2
raidgrep-5d2f51ab8593946a0f24db367a887a37258901d5.zip
[WIP] rewrite output logic as a pipeline
Diffstat (limited to 'src')
-rw-r--r--src/main.rs3
-rw-r--r--src/output.rs45
-rw-r--r--src/output/aggregators.rs32
-rw-r--r--src/output/formats.rs68
-rw-r--r--src/output/mod.rs26
-rw-r--r--src/output/pipeline.rs27
6 files changed, 155 insertions, 46 deletions
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<W: Write>(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<W: Write>(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<T: Format + ?Sized> Format for Box<T> {
+ 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<dyn Format> = 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<dyn Format>,
+ aggregator: Box<dyn Aggregator>,
+ writer: Mutex<Box<dyn Write>>,
+}
+
+
+impl Pipeline {
+ pub fn new<W: Write + 'static, F: Format + 'static, A: Aggregator + 'static>(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);
+ }
+}