diff options
-rw-r--r-- | cli/src/lib.rs | 54 | ||||
-rw-r--r-- | ops/Cargo.toml | 1 | ||||
-rw-r--r-- | ops/src/lib.rs | 1 | ||||
-rw-r--r-- | ops/src/stats.rs | 26 | ||||
-rw-r--r-- | src/lambda.rs | 18 | ||||
-rw-r--r-- | src/lazy.rs | 74 | ||||
-rw-r--r-- | warning.rs | 11 |
7 files changed, 64 insertions, 121 deletions
diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 8eacea4..eb2fb43 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -52,15 +52,17 @@ pub fn main(cli: Cli) { let args = App::new("ilc") .version(&version[..]) .setting(AppSettings::GlobalVersion) + .setting(AppSettings::AllowLeadingHyphen) + .setting(AppSettings::UnifiedHelpMessage) .setting(AppSettings::VersionlessSubcommands) .setting(AppSettings::ArgRequiredElseHelp) .author("Till Höppner <till@hoeppner.ws>") .about("A converter and statistics utility for IRC log files") - .arg(Arg::with_name("timezone") - .help("UTC offset in the direction of the western hemisphere") + .arg(Arg::with_name("time") + .help("Timestamp offset, in seconds") .global(true) .takes_value(true) - .long("timezone") + .long("timeoffset") .short("t")) .arg(Arg::with_name("date") .help("Override the date for this log, ISO 8601, YYYY-MM-DD") @@ -101,6 +103,7 @@ pub fn main(cli: Cli) { .global(true) .takes_value(true) .multiple(true) + .number_of_values(1) .long("input") .short("i")) .arg(Arg::with_name("output_file") @@ -114,24 +117,32 @@ pub fn main(cli: Cli) { .takes_value(false) .long("notice")) .subcommand(SubCommand::with_name("parse") - .about("Parse the input, checking the format")) + .about("Parse the input, checking the format") + .setting(AppSettings::AllowLeadingHyphen)) .subcommand(SubCommand::with_name("convert") - .about("Convert from a source to a target format")) + .about("Convert from a source to a target format") + .setting(AppSettings::AllowLeadingHyphen)) .subcommand(SubCommand::with_name("stats") - .about("Analyse the activity of users by certain metrics")) + .about("Analyse the activity of users by certain metrics") + .setting(AppSettings::AllowLeadingHyphen)) .subcommand(SubCommand::with_name("seen") .about("Print the last line a nick was active") + .setting(AppSettings::AllowLeadingHyphen) .arg(Arg::with_name("nick") .help("The nick you're looking for") .takes_value(true) .required(true) .index(1))) - .subcommand(SubCommand::with_name("sort").about("Sorts a log by time")) + .subcommand(SubCommand::with_name("sort") + .about("Sorts a log by time") + .setting(AppSettings::AllowLeadingHyphen)) .subcommand(SubCommand::with_name("dedup") - .about("Removes duplicate log entries in close proximity")) + .about("Removes duplicate log entries in close proximity") + .setting(AppSettings::AllowLeadingHyphen)) .subcommand(SubCommand::with_name("merge") .about("Merges the input logs. This has to keep everything \ - in memory")) + in memory") + .setting(AppSettings::AllowLeadingHyphen)) .get_matches(); if args.is_present("notice") { @@ -223,6 +234,21 @@ pub fn die(s: &str) -> ! { process::exit(1) } +macro_rules! error { + ($code: expr, $fmt:expr) => {{ + use std::io::Write; + let err = std::io::stderr(); + let _ = writeln!(&mut err.lock(), $fmt); + std::process::exit($code); + }}; + ($code: expr, $fmt:expr, $($arg:tt)*) => {{ + use std::io::Write; + let err = std::io::stderr(); + let _ = writeln!(&mut err.lock(), $fmt, $($arg)*); + std::process::exit($code); + }}; +} + pub fn decoder(format: &str) -> Option<Box<Decode>> { match format { "energymech" | "em" => Some(Box::new(Energymech)), @@ -252,7 +278,7 @@ pub fn force_decoder(s: Option<&str>) -> Box<Decode> { }; match decoder(&inf) { Some(d) => d, - None => die(&format!("The format `{}` is unknown to me", inf)), + None => error!(2, "The format `{}` is unknown to me", inf), } } @@ -263,7 +289,7 @@ pub fn force_encoder<'a>(s: Option<&str>) -> Box<Encode> { }; match encoder(&outf) { Some(e) => e, - None => die(&format!("The format `{}` is unknown to me", outf)), + None => error!(2, "The format `{}` is unknown to me", outf), } } @@ -306,8 +332,8 @@ impl<'a> Environment<'a> { pub fn build_context(args: &ArgMatches) -> Context { let mut context = Context { - timezone: FixedOffset::west(args.value_of("timezone") - .and_then(|s| s.parse().ok()) + timezone: FixedOffset::west(args.value_of("time") + .and_then(|s| s.parse::<i32>().ok()) .unwrap_or(0)), override_date: args.value_of("date").and_then(|d| NaiveDate::from_str(&d).ok()), channel: args.value_of("channel").map(str::to_owned).clone(), @@ -325,7 +351,7 @@ pub fn build_context(args: &ArgMatches) -> Context { context.override_date = Some(date); } } - _n => die("Too many input files, can't infer date"), + n => error!(3, "Too many input files ({}), can't infer date", n), } } context diff --git a/ops/Cargo.toml b/ops/Cargo.toml index f4bf082..6789aa2 100644 --- a/ops/Cargo.toml +++ b/ops/Cargo.toml @@ -9,6 +9,7 @@ authors = ["Till Höppner <till@hoeppner.ws>"] [dependencies] log = "0.3.5" +chrono = "0.2.19" 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 d5aa003..2ebe51f 100644 --- a/ops/src/lib.rs +++ b/ops/src/lib.rs @@ -3,6 +3,7 @@ extern crate log; extern crate blist; extern crate bit_set; extern crate serde; +extern crate chrono; extern crate ilc_base; mod ageset; diff --git a/ops/src/stats.rs b/ops/src/stats.rs index 49f4068..7e37a80 100644 --- a/ops/src/stats.rs +++ b/ops/src/stats.rs @@ -1,15 +1,21 @@ //! Per-nick word/line statistics - -use ilc_base::{self, Context, Decode, Event}; +use ilc_base::{self, Context, Decode, Event, Time}; use ilc_base::event::Type; use std::collections::HashMap; use std::io::BufRead; +use chrono::{Datelike, NaiveDateTime, Timelike}; + use serde::ser::{MapVisitor, Serialize, Serializer}; +pub type Day = [u32; 24]; +/// Weeks start on mondays. +pub type Week = [Day; 7]; + pub struct Stats { pub freqs: HashMap<String, NickStat>, + pub week: Week, } impl Serialize for Stats { @@ -22,6 +28,7 @@ impl Serialize for Stats { where S: Serializer { try!(s.serialize_struct_elt("freqs", &self.0.freqs)); + try!(s.serialize_struct_elt("week", &self.0.week)); Ok(None) } @@ -91,11 +98,19 @@ fn strip_nick(s: &str) -> &str { /// Return all active nicks, with lines, words and words per lines counted. pub fn stats(ctx: &Context, input: &mut BufRead, decoder: &mut Decode) -> ilc_base::Result<Stats> { let mut freqs: HashMap<String, NickStat> = HashMap::new(); + let mut week: Week = [[0; 24]; 7]; for e in decoder.decode(&ctx, input) { let m = try!(e); match m { - Event { ty: Type::Msg { ref from, ref content, .. }, .. } => { + Event { ty: Type::Msg { ref from, ref content, .. }, ref time, .. } => { + if let &Time::Timestamp(stamp) = time { + let date = NaiveDateTime::from_timestamp(stamp, 0); + let dow = date.weekday().num_days_from_monday() as usize; + let hour = date.hour() as usize; + week[dow][hour] += 1; + } + let nick = strip_nick(from); if freqs.contains_key(nick) { let p: &mut NickStat = freqs.get_mut(nick).unwrap(); @@ -119,5 +134,8 @@ pub fn stats(ctx: &Context, input: &mut BufRead, decoder: &mut Decode) -> ilc_ba } } - Ok(Stats { freqs: freqs }) + Ok(Stats { + freqs: freqs, + week: week, + }) } diff --git a/src/lambda.rs b/src/lambda.rs deleted file mode 100644 index cf7b1ec..0000000 --- a/src/lambda.rs +++ /dev/null @@ -1,18 +0,0 @@ -// I'm only a little sorry for this. - -// Inline definition of anonymous functions. Examples: -// l!(42;) -// l!(i32 := 42) -// l!(i: i32 := i + 42) -// l!(i: i32, j: i32 -> i32 := i + j) -#[macro_export] -macro_rules! l { - ($body: expr) => ({ fn f() { $body } f }); - ($res: ty := $body: expr) => ({ fn f() -> $res { $body } f }); - ($($n: ident: $t: ty),+ := $body: expr) => ({ - fn f($($n: $t),+) { $body } f - }); - ($($n: ident: $t: ty),+ -> $res: ty := $body: expr) => ({ - fn f($($n: $t),+) -> $res { $body } f - }) -} diff --git a/src/lazy.rs b/src/lazy.rs deleted file mode 100644 index b4f801b..0000000 --- a/src/lazy.rs +++ /dev/null @@ -1,74 +0,0 @@ -#![macro_escape] - -use std::ptr; -use std::cell::UnsafeCell; -use std::ops::Deref; -use std::boxed::FnBox; - -pub enum State<V> { - Evaluated(V), - Evaluating, - Unevaluated(Box<FnBox() -> V>) -} - -pub struct Lazy<V> { - state: UnsafeCell<State<V>>, -} - -impl<V> Lazy<V> { - pub fn new(f: Box<FnBox() -> V>) -> Lazy<V> { - Lazy { state: UnsafeCell::new(State::Unevaluated(f)) } - } - - pub fn ready(v: V) -> Lazy<V> { - Lazy { state: UnsafeCell::new(State::Evaluated(v)) } - } - - pub fn force(&mut self) { - self.value(); - } - - pub fn unwrap(self) -> V { - unsafe { - match self.state.into_inner() { - State::Unevaluated(f) => f(), - State::Evaluating => panic!("Illegal state, can't call unwrap during evaluation"), - State::Evaluated(v) => v - } - } - } - - pub fn value(&self) -> &V { - unsafe { - let state = self.state.get(); - match *state { - State::Evaluated(ref v) => v, - State::Evaluating => panic!("Illegal state, can't call value during evaluation"), - State::Unevaluated(_) => { - if let State::Unevaluated(f) = ptr::replace(state, State::Evaluating) { - ptr::replace(state, State::Evaluated(f())); - } - if let State::Evaluated(ref v) = *state { return v } - unreachable!() - } - } - } - } -} - -impl<V> Deref for Lazy<V> { - type Target = V; - fn deref(&self) -> &V { self.value() } -} - -#[macro_export] -macro_rules! lazy { - (@as_expr $val: expr) => { $val }; - ($($val: tt)*) => { Lazy::new(Box::new(move || lazy![@as_expr { $($val)* }])) }; -} - -#[macro_export] -macro_rules! eager { - (@as_expr $val: expr) => { $val }; - ($($val: tt)*) => { Lazy::<_>::ready(eager![@as_expr { $($val)* }]) }; -} diff --git a/warning.rs b/warning.rs deleted file mode 100644 index a71bc3f..0000000 --- a/warning.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::iter; - -fn main() { - let mut v = (0..20) - .map(|_| iter::empty::<()>()) - .collect::<Vec<_>>(); - - for i in 1..8 { - v.remove(i + 1); - } -} |