aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTill Höppner2016-03-10 15:19:45 +0100
committerTill Höppner2016-03-10 15:19:45 +0100
commit1423ecbd30f243637ea75a0aac3f0d19b0b8d18e (patch)
tree10f4296d08afc0b5bc15b50eb597b969b9b483b2
parentc7f8c7e985173e504191b95286f53e0927db2d78 (diff)
downloadilc-1423ecbd30f243637ea75a0aac3f0d19b0b8d18e.tar.gz
ilc-1423ecbd30f243637ea75a0aac3f0d19b0b8d18e.tar.xz
ilc-1423ecbd30f243637ea75a0aac3f0d19b0b8d18e.zip
Various CLI improvements related to timezonesv0.3.1.2
-rw-r--r--cli/src/lib.rs54
-rw-r--r--ops/Cargo.toml1
-rw-r--r--ops/src/lib.rs1
-rw-r--r--ops/src/stats.rs26
4 files changed, 64 insertions, 18 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,
+ })
}