From 586461676df9abd2e011fbcb1bb7c784cdb2844c Mon Sep 17 00:00:00 2001 From: Till Höppner Date: Wed, 16 Mar 2016 15:14:03 +0100 Subject: Add filtering functionality --- base/src/event.rs | 74 +++++++++++++++++++++++++++++++++++++++++++------ cli/Cargo.toml | 1 + cli/src/lib.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- ops/Cargo.toml | 1 + ops/src/lib.rs | 23 ++-------------- 5 files changed, 149 insertions(+), 32 deletions(-) diff --git a/base/src/event.rs b/base/src/event.rs index 745cc64..3176a9a 100644 --- a/base/src/event.rs +++ b/base/src/event.rs @@ -150,25 +150,83 @@ pub enum Type<'a> { } impl<'a> Type<'a> { + pub fn actor(&self) -> Option<&str> { + use self::Type::*; + match self { + &Msg { ref from, .. } => Some(from), + &Action { ref from, .. } => Some(from), + &Join { ref nick, .. } => Some(nick), + &Part { ref nick, .. } => Some(nick), + &Quit { ref nick, .. } => Some(nick), + &Nick { ref old_nick, .. } => Some(old_nick), + &Notice { ref from, .. } => Some(from), + &Kick { ref kicking_nick, .. } => kicking_nick.as_ref().map(|s| &*s as &str), + &TopicChange { ref nick, .. } => nick.as_ref().map(|s| &*s as &str), + &Mode { ref nick, .. } => nick.as_ref().map(|s| &*s as &str), + _ => None, + } + } + pub fn involves(&self, needle: &str) -> bool { use self::Type::*; match self { - &Msg { ref from, .. } => from == needle, - &Action { ref from, .. } => from == needle, + &Msg { ref from, ref content, .. } => from == needle || content.contains(needle), + &Action { ref from, ref content, .. } => from == needle || content.contains(needle), &Join { ref nick, .. } => nick == needle, - &Part { ref nick, .. } => nick == needle, - &Quit { ref nick, .. } => nick == needle, + &Part { ref nick, ref reason, .. } => { + nick == needle || reason.as_ref().map_or(false, |r| r.contains(needle)) + } + &Quit { ref nick, ref reason, .. } => { + nick == needle || reason.as_ref().map_or(false, |r| r.contains(needle)) + } &Nick { ref old_nick, ref new_nick, .. } => old_nick == needle || new_nick == needle, - &Notice { ref from, .. } => from == needle, - &Kick { ref kicked_nick, ref kicking_nick, .. } => { + &Notice { ref from, ref content, .. } => from == needle || content.contains(needle), + &Kick { ref kicked_nick, ref kicking_nick, ref kick_message, .. } => { *kicked_nick == Cow::Borrowed(needle) || - kicking_nick.as_ref().map_or(false, |k| k.as_ref() == Cow::Borrowed(needle)) + kicking_nick.as_ref().map_or(false, |k| k.as_ref() == Cow::Borrowed(needle)) || + kick_message.as_ref().map_or(false, |k| k.as_ref() == Cow::Borrowed(needle)) + } + &TopicChange { ref nick, ref new_topic, .. } => { + nick.as_ref().map_or(false, |k| k.as_ref() == needle) || new_topic.contains(needle) } - &TopicChange { ref nick, .. } => nick.as_ref().map_or(false, |k| k.as_ref() == needle), &Mode { ref nick, .. } => { nick.as_ref().map_or(false, |k| k.as_ref() == Cow::Borrowed(needle)) } _ => false, } } + + pub fn type_desc(&self) -> &'static str { + use self::Type::*; + match self { + &Msg { .. } => "message", + &Action { .. } => "action", + &Join { .. } => "join", + &Part { .. } => "part", + &Quit { .. } => "quit", + &Nick { .. } => "nick", + &Notice { .. } => "notice", + &Topic { .. } => "topic", + &TopicChange { .. } => "topic_change", + &Kick { .. } => "kick", + &Mode { .. } => "mode", + &Connect { .. } => "connect", + &Disconnect { .. } => "disconnect", + } + } + + pub fn text(&self) -> Option<&str> { + use self::Type::*; + match self { + &Msg { ref content, .. } => Some(content), + &Action { ref content, .. } => Some(content), + &Part { ref reason, .. } => reason.as_ref().map(|s| s as &str), + &Quit { ref reason, .. } => reason.as_ref().map(|s| s as &str), + &Notice { ref content, .. } => Some(content), + &Kick { ref kick_message, .. } => kick_message.as_ref().map(|s| s as &str), + &Topic { ref topic, .. } => Some(topic), + &TopicChange { ref new_topic, .. } => Some(new_topic), + _ => None, + } + } } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index e958ef5..26006d3 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -15,6 +15,7 @@ default = ["ilc-format-weechat", "ilc-format-energymech"] log = "0.3.5" clap = "2.1.2" chrono = "0.2.19" +regex = "0.1.55" serde = "~0.7" serde_json = "~0.7" env_logger = "0.3.2" diff --git a/cli/src/lib.rs b/cli/src/lib.rs index eb2fb43..ac31636 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -11,17 +11,21 @@ extern crate env_logger; extern crate serde; extern crate serde_json; extern crate glob; +extern crate regex; use ilc_base::{Context, Decode, Encode}; +use ilc_ops::convert::{Filter, Operator, Subject}; use ilc_format_weechat::Weechat; use ilc_format_energymech::Energymech; -use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; +use clap::{App, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand}; use chrono::{FixedOffset, NaiveDate}; use glob::glob; +use regex::Regex; + use std::str::FromStr; use std::fs::File; use std::path::{Path, PathBuf}; @@ -30,6 +34,7 @@ use std::io::{self, BufRead, BufReader, BufWriter, Write}; use std::process; use std::error::Error; + mod chain; mod stats; @@ -121,7 +126,32 @@ pub fn main(cli: Cli) { .setting(AppSettings::AllowLeadingHyphen)) .subcommand(SubCommand::with_name("convert") .about("Convert from a source to a target format") - .setting(AppSettings::AllowLeadingHyphen)) + .setting(AppSettings::AllowLeadingHyphen) + .arg(Arg::with_name("subject") + .takes_value(true) + .requires("operator") + .long("if") + .possible_values(&["nick", "time", "type", "text"])) + .arg(Arg::with_name("op_not").long("not")) + .arg(Arg::with_name("op_exactly") + .takes_value(true) + .long("exactly")) + .arg(Arg::with_name("op_contains") + .takes_value(true) + .long("contains")) + .arg(Arg::with_name("op_greater") + .takes_value(true) + .long("greater")) + .arg(Arg::with_name("op_less").takes_value(true).long("less")) + .arg(Arg::with_name("op_matches") + .takes_value(true) + .long("matches")) + .group(ArgGroup::with_name("operator").args(&["op_exactly", + "op_contains", + "op_equal", + "op_greater", + "op_less", + "op_matches"]))) .subcommand(SubCommand::with_name("stats") .about("Analyse the activity of users by certain metrics") .setting(AppSettings::AllowLeadingHyphen)) @@ -157,11 +187,57 @@ pub fn main(cli: Cli) { } ("convert", Some(args)) => { let e = Environment(&args); + let subject = match args.value_of("subject") { + Some("nick") => Some(Subject::Nick), + Some("time") => Some(Subject::Time), + Some("type") => Some(Subject::Type), + Some("text") => Some(Subject::Text), + _ => None, + }; + + let op = { + if args.is_present("operator") { + if let Some(sub) = args.value_of("op_exactly") { + Some(Operator::Exactly(sub.into())) + } else if let Some(sub) = args.value_of("op_contains") { + Some(Operator::Contains(sub.into())) + } else if let Some(sub) = args.value_of("op_matches") { + match Regex::new(sub) { + Ok(regex) => Some(Operator::Matches(regex)), + Err(e) => error(Box::new(e)), + } + } else { + // must be numeric operator if not op_exactly, op_contains, or op_matches + // unwrap is safe because of .is_present("operator") earlier + let num = match args.value_of("operator").unwrap().parse::() { + Ok(n) => n, + Err(e) => error(Box::new(e)), + }; + + if args.is_present("op_equal") { + Some(Operator::Equal(num)) + } else if args.is_present("op_greater") { + Some(Operator::Greater(num)) + } else if args.is_present("op_less") { + Some(Operator::Less(num)) + } else { + None + } + } + } else { + None + } + }; + + let filter = subject.and_then(|s| op.map(|o| Filter(s, o))); + ilc_ops::convert::convert(&e.context(), &mut e.input(), &mut *e.decoder(), &mut *e.output(), - &*e.encoder()) + &*e.encoder(), + filter, + args.is_present("op_not")) } ("stats", Some(args)) => { let e = Environment(&args); diff --git a/ops/Cargo.toml b/ops/Cargo.toml index 6789aa2..85ca5a7 100644 --- a/ops/Cargo.toml +++ b/ops/Cargo.toml @@ -10,6 +10,7 @@ authors = ["Till Höppner "] [dependencies] log = "0.3.5" chrono = "0.2.19" +regex = "0.1.54" ilc-base = "~0.2" blist = "0.0.4" bit-set = "0.3.0" diff --git a/ops/src/lib.rs b/ops/src/lib.rs index 2ebe51f..6fd4cab 100644 --- a/ops/src/lib.rs +++ b/ops/src/lib.rs @@ -5,9 +5,11 @@ extern crate bit_set; extern crate serde; extern crate chrono; extern crate ilc_base; +extern crate regex; mod ageset; pub mod stats; +pub mod convert; /// No-op log parsing pub mod parse { @@ -27,27 +29,6 @@ pub mod parse { } } -/// Log format conversion -pub mod convert { - use ilc_base::{self, Context, Decode, Encode}; - use std::io::{BufRead, Write}; - - /// Convert from one format to another, not necessarily different, format. In combination with a - /// timezone offset, this can be used to correct the timestamps. - /// Will return `Err` and abort conversion if the decoder yields `Err` or re-encoding fails. - pub fn convert(ctx: &Context, - input: &mut BufRead, - decoder: &mut Decode, - output: &mut Write, - encoder: &Encode) - -> ilc_base::Result<()> { - for e in decoder.decode(&ctx, input) { - try!(encoder.encode(&ctx, output, &try!(e))); - } - Ok(()) - } -} - /// Last-seen of nicks pub mod seen { use ilc_base::{self, Context, Decode, Encode, Event}; -- cgit v1.2.3