Skip to content

Commit 1423ecb

Browse files
committed
Various CLI improvements related to timezones
1 parent c7f8c7e commit 1423ecb

File tree

4 files changed

+64
-18
lines changed

4 files changed

+64
-18
lines changed

cli/src/lib.rs

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,17 @@ pub fn main(cli: Cli) {
5252
let args = App::new("ilc")
5353
.version(&version[..])
5454
.setting(AppSettings::GlobalVersion)
55+
.setting(AppSettings::AllowLeadingHyphen)
56+
.setting(AppSettings::UnifiedHelpMessage)
5557
.setting(AppSettings::VersionlessSubcommands)
5658
.setting(AppSettings::ArgRequiredElseHelp)
5759
.author("Till Höppner <till@hoeppner.ws>")
5860
.about("A converter and statistics utility for IRC log files")
59-
.arg(Arg::with_name("timezone")
60-
.help("UTC offset in the direction of the western hemisphere")
61+
.arg(Arg::with_name("time")
62+
.help("Timestamp offset, in seconds")
6163
.global(true)
6264
.takes_value(true)
63-
.long("timezone")
65+
.long("timeoffset")
6466
.short("t"))
6567
.arg(Arg::with_name("date")
6668
.help("Override the date for this log, ISO 8601, YYYY-MM-DD")
@@ -101,6 +103,7 @@ pub fn main(cli: Cli) {
101103
.global(true)
102104
.takes_value(true)
103105
.multiple(true)
106+
.number_of_values(1)
104107
.long("input")
105108
.short("i"))
106109
.arg(Arg::with_name("output_file")
@@ -114,24 +117,32 @@ pub fn main(cli: Cli) {
114117
.takes_value(false)
115118
.long("notice"))
116119
.subcommand(SubCommand::with_name("parse")
117-
.about("Parse the input, checking the format"))
120+
.about("Parse the input, checking the format")
121+
.setting(AppSettings::AllowLeadingHyphen))
118122
.subcommand(SubCommand::with_name("convert")
119-
.about("Convert from a source to a target format"))
123+
.about("Convert from a source to a target format")
124+
.setting(AppSettings::AllowLeadingHyphen))
120125
.subcommand(SubCommand::with_name("stats")
121-
.about("Analyse the activity of users by certain metrics"))
126+
.about("Analyse the activity of users by certain metrics")
127+
.setting(AppSettings::AllowLeadingHyphen))
122128
.subcommand(SubCommand::with_name("seen")
123129
.about("Print the last line a nick was active")
130+
.setting(AppSettings::AllowLeadingHyphen)
124131
.arg(Arg::with_name("nick")
125132
.help("The nick you're looking for")
126133
.takes_value(true)
127134
.required(true)
128135
.index(1)))
129-
.subcommand(SubCommand::with_name("sort").about("Sorts a log by time"))
136+
.subcommand(SubCommand::with_name("sort")
137+
.about("Sorts a log by time")
138+
.setting(AppSettings::AllowLeadingHyphen))
130139
.subcommand(SubCommand::with_name("dedup")
131-
.about("Removes duplicate log entries in close proximity"))
140+
.about("Removes duplicate log entries in close proximity")
141+
.setting(AppSettings::AllowLeadingHyphen))
132142
.subcommand(SubCommand::with_name("merge")
133143
.about("Merges the input logs. This has to keep everything \
134-
in memory"))
144+
in memory")
145+
.setting(AppSettings::AllowLeadingHyphen))
135146
.get_matches();
136147

137148
if args.is_present("notice") {
@@ -223,6 +234,21 @@ pub fn die(s: &str) -> ! {
223234
process::exit(1)
224235
}
225236

237+
macro_rules! error {
238+
($code: expr, $fmt:expr) => {{
239+
use std::io::Write;
240+
let err = std::io::stderr();
241+
let _ = writeln!(&mut err.lock(), $fmt);
242+
std::process::exit($code);
243+
}};
244+
($code: expr, $fmt:expr, $($arg:tt)*) => {{
245+
use std::io::Write;
246+
let err = std::io::stderr();
247+
let _ = writeln!(&mut err.lock(), $fmt, $($arg)*);
248+
std::process::exit($code);
249+
}};
250+
}
251+
226252
pub fn decoder(format: &str) -> Option<Box<Decode>> {
227253
match format {
228254
"energymech" | "em" => Some(Box::new(Energymech)),
@@ -252,7 +278,7 @@ pub fn force_decoder(s: Option<&str>) -> Box<Decode> {
252278
};
253279
match decoder(&inf) {
254280
Some(d) => d,
255-
None => die(&format!("The format `{}` is unknown to me", inf)),
281+
None => error!(2, "The format `{}` is unknown to me", inf),
256282
}
257283
}
258284

@@ -263,7 +289,7 @@ pub fn force_encoder<'a>(s: Option<&str>) -> Box<Encode> {
263289
};
264290
match encoder(&outf) {
265291
Some(e) => e,
266-
None => die(&format!("The format `{}` is unknown to me", outf)),
292+
None => error!(2, "The format `{}` is unknown to me", outf),
267293
}
268294
}
269295

@@ -306,8 +332,8 @@ impl<'a> Environment<'a> {
306332

307333
pub fn build_context(args: &ArgMatches) -> Context {
308334
let mut context = Context {
309-
timezone: FixedOffset::west(args.value_of("timezone")
310-
.and_then(|s| s.parse().ok())
335+
timezone: FixedOffset::west(args.value_of("time")
336+
.and_then(|s| s.parse::<i32>().ok())
311337
.unwrap_or(0)),
312338
override_date: args.value_of("date").and_then(|d| NaiveDate::from_str(&d).ok()),
313339
channel: args.value_of("channel").map(str::to_owned).clone(),
@@ -325,7 +351,7 @@ pub fn build_context(args: &ArgMatches) -> Context {
325351
context.override_date = Some(date);
326352
}
327353
}
328-
_n => die("Too many input files, can't infer date"),
354+
n => error!(3, "Too many input files ({}), can't infer date", n),
329355
}
330356
}
331357
context

ops/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ authors = ["Till Höppner <till@hoeppner.ws>"]
99

1010
[dependencies]
1111
log = "0.3.5"
12+
chrono = "0.2.19"
1213
ilc-base = "~0.2"
1314
blist = "0.0.4"
1415
bit-set = "0.3.0"

ops/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ extern crate log;
33
extern crate blist;
44
extern crate bit_set;
55
extern crate serde;
6+
extern crate chrono;
67
extern crate ilc_base;
78

89
mod ageset;

ops/src/stats.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
//! Per-nick word/line statistics
2-
3-
use ilc_base::{self, Context, Decode, Event};
2+
use ilc_base::{self, Context, Decode, Event, Time};
43
use ilc_base::event::Type;
54

65
use std::collections::HashMap;
76
use std::io::BufRead;
87

8+
use chrono::{Datelike, NaiveDateTime, Timelike};
9+
910
use serde::ser::{MapVisitor, Serialize, Serializer};
1011

12+
pub type Day = [u32; 24];
13+
/// Weeks start on mondays.
14+
pub type Week = [Day; 7];
15+
1116
pub struct Stats {
1217
pub freqs: HashMap<String, NickStat>,
18+
pub week: Week,
1319
}
1420

1521
impl Serialize for Stats {
@@ -22,6 +28,7 @@ impl Serialize for Stats {
2228
where S: Serializer
2329
{
2430
try!(s.serialize_struct_elt("freqs", &self.0.freqs));
31+
try!(s.serialize_struct_elt("week", &self.0.week));
2532
Ok(None)
2633
}
2734

@@ -91,11 +98,19 @@ fn strip_nick(s: &str) -> &str {
9198
/// Return all active nicks, with lines, words and words per lines counted.
9299
pub fn stats(ctx: &Context, input: &mut BufRead, decoder: &mut Decode) -> ilc_base::Result<Stats> {
93100
let mut freqs: HashMap<String, NickStat> = HashMap::new();
101+
let mut week: Week = [[0; 24]; 7];
94102

95103
for e in decoder.decode(&ctx, input) {
96104
let m = try!(e);
97105
match m {
98-
Event { ty: Type::Msg { ref from, ref content, .. }, .. } => {
106+
Event { ty: Type::Msg { ref from, ref content, .. }, ref time, .. } => {
107+
if let &Time::Timestamp(stamp) = time {
108+
let date = NaiveDateTime::from_timestamp(stamp, 0);
109+
let dow = date.weekday().num_days_from_monday() as usize;
110+
let hour = date.hour() as usize;
111+
week[dow][hour] += 1;
112+
}
113+
99114
let nick = strip_nick(from);
100115
if freqs.contains_key(nick) {
101116
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
119134
}
120135
}
121136

122-
Ok(Stats { freqs: freqs })
137+
Ok(Stats {
138+
freqs: freqs,
139+
week: week,
140+
})
123141
}

0 commit comments

Comments
 (0)