aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authorTill Höppner2016-02-03 03:38:32 +0100
committerTill Höppner2016-02-03 03:38:32 +0100
commitcadd814c28b03205c8277530ef09bffcdba44ec6 (patch)
tree0314a1ee2e87f4f3eedde42b5fe685610b98d1db /src/app
parentc4acc192d9e6b023334156706b9974006652d6d6 (diff)
downloadilc-cadd814c28b03205c8277530ef09bffcdba44ec6.tar.gz
ilc-cadd814c28b03205c8277530ef09bffcdba44ec6.tar.xz
ilc-cadd814c28b03205c8277530ef09bffcdba44ec6.zip
Docopt -> clap, main.rs -> src/app
Modularise the old main function, switch to clap for easier addition of CLI arguments
Diffstat (limited to 'src/app')
-rw-r--r--src/app/freq.rs77
-rw-r--r--src/app/mod.rs226
2 files changed, 303 insertions, 0 deletions
diff --git a/src/app/freq.rs b/src/app/freq.rs
new file mode 100644
index 0000000..9446b50
--- /dev/null
+++ b/src/app/freq.rs
@@ -0,0 +1,77 @@
+use clap::ArgMatches;
+
+use std::collections::HashMap;
+
+use ilc::event::{ Event, Type };
+
+use super::*;
+
+struct Person {
+ lines: u32,
+ alpha_lines: u32,
+ words: u32
+}
+
+fn words_alpha(s: &str) -> (u32, bool) {
+ let mut alpha = false;
+ let mut words = 0;
+ for w in s.split_whitespace() {
+ if !w.is_empty() {
+ words += 1;
+ if w.chars().any(char::is_alphabetic) { alpha = true }
+ }
+ }
+ (words, alpha)
+}
+
+fn strip_nick_prefix(s: &str) -> &str {
+ if s.is_empty() { return s }
+ match s.as_bytes()[0] {
+ b'~' | b'&' | b'@' | b'%' | b'+' => &s[1..],
+ _ => s
+ }
+}
+
+pub fn freq(args: &ArgMatches) {
+ let env = Environment(args);
+ let (context, mut decoder, mut input, mut output) = (env.context(), env.decoder(), env.input(), env.output());
+
+ let mut stats: HashMap<String, Person> = HashMap::new();
+
+ for e in decoder.decode(&context, &mut input) {
+ let m = match e {
+ Ok(m) => m,
+ Err(err) => error(Box::new(err))
+ };
+
+ match m {
+ Event { ty: Type::Msg { ref from, ref content, .. }, .. } => {
+ let nick = strip_nick_prefix(from);
+ if stats.contains_key(nick) {
+ let p: &mut Person = stats.get_mut(nick).unwrap();
+ let (words, alpha) = words_alpha(content);
+ p.lines += 1;
+ if alpha { p.alpha_lines += 1 }
+ p.words += words;
+ } else {
+ let (words, alpha) = words_alpha(content);
+ stats.insert(nick.to_owned(), Person {
+ lines: 1,
+ alpha_lines: if alpha { 1 } else { 0 },
+ words: words
+ });
+ }
+ },
+ _ => ()
+ }
+ }
+
+ let mut stats: Vec<(String, Person)> = stats.into_iter().collect();
+ stats.sort_by(|&(_, ref a), &(_, ref b)| b.words.cmp(&a.words));
+
+ for &(ref name, ref stat) in stats.iter() {
+ let _ = write!(&mut output,
+ "{}:\n\tTotal lines: {}\n\tLines without alphabetic characters: {}\n\tTotal words: {}\n\tWords per line: {}\n",
+ name, stat.lines, stat.lines - stat.alpha_lines, stat.words, stat.words as f32 / stat.lines as f32);
+ }
+}
diff --git a/src/app/mod.rs b/src/app/mod.rs
new file mode 100644
index 0000000..50117e7
--- /dev/null
+++ b/src/app/mod.rs
@@ -0,0 +1,226 @@
+use clap::ArgMatches;
+
+use chrono::offset::fixed::FixedOffset;
+use chrono::naive::date::NaiveDate;
+
+use glob::glob;
+
+use std::process;
+use std::str::FromStr;
+use std::path::PathBuf;
+use std::io::{ self, Write, BufWriter, BufRead, BufReader };
+use std::fs::File;
+use std::error::Error;
+
+use ilc::context::Context;
+use ilc::format::{ self, Encode, Decode };
+
+use ::chain;
+
+pub mod freq;
+
+pub fn error(e: Box<Error>) -> ! {
+ let _ = writeln!(&mut io::stderr(), "Error: {}", e);
+ let mut e = e.cause();
+ while let Some(err) = e {
+ let _ = writeln!(&mut io::stderr(), "\t{}", err);
+ e = err.cause();
+ }
+ process::exit(1)
+}
+
+pub fn die(s: &str) -> ! {
+ let _ = writeln!(&mut io::stderr(), "Aborting: {}", s);
+ process::exit(1)
+}
+
+pub fn force_decoder(s: Option<&str>) -> Box<Decode> {
+ let inf = match s {
+ Some(s) => s,
+ None => die("You didn't specify the input format")
+ };
+ match format::decoder(&inf) {
+ Some(d) => d,
+ None => die(&format!("The format `{}` is unknown to me", inf))
+ }
+}
+
+pub fn force_encoder<'a>(s: Option<&str>) -> Box<Encode> {
+ let outf = match s {
+ Some(s) => s,
+ None => die("You didn't specify the output format")
+ };
+ match format::encoder(&outf) {
+ Some(e) => e,
+ None => die(&format!("The format `{}` is unknown to me", outf))
+ }
+}
+
+pub fn build_context(args: &ArgMatches) -> Context {
+ Context {
+ timezone: FixedOffset::west(args.value_of("timezone").and_then(|s| s.parse().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()
+ }
+}
+
+pub fn build_input(args: &ArgMatches) -> Box<BufRead> {
+ let input_files = args.values_of("input_files");
+ if input_files.map(|files| files.count() > 0).unwrap_or(false) {
+ let input_files: Vec<PathBuf> = if let Some(iter) = args.values_of("input_files") {
+ iter.flat_map(|p| {
+ match glob(p) {
+ Ok(paths) => paths,
+ Err(e) => die(&format!("{}", e.msg))
+ }
+ }).filter_map(Result::ok).collect()
+ } else { Vec::new() };
+
+ /*if args.flag_infer_date {
+ if input_files.len() > 1 { die("Too many input files, can't infer date") }
+ if let Some(date) = input_files.iter().next()
+ .map(PathBuf::as_path)
+ .and_then(Path::file_stem)
+ .and_then(OsStr::to_str)
+ .and_then(|s: &str| NaiveDate::from_str(s).ok()) {
+ context.override_date = Some(date);
+ }
+ }*/
+
+ Box::new(BufReader::new(chain::Chain::new(input_files.iter().map(|p| File::open(p).unwrap()).collect())))
+ } else {
+ Box::new(BufReader::new(io::stdin()))
+ }
+}
+
+pub fn build_output(args: &ArgMatches) -> Box<Write> {
+ if let Some(out) = args.value_of("output_file") {
+ match File::create(out) {
+ Ok(f) => Box::new(BufWriter::new(f)),
+ Err(e) => error(Box::new(e))
+ }
+ } else {
+ Box::new(BufWriter::new(io::stdout()))
+ }
+}
+
+pub struct Environment<'a>(pub &'a ArgMatches<'a>);
+
+impl<'a> Environment<'a> {
+ pub fn context(&self) -> Context { build_context(self.0) }
+ pub fn input(&self) -> Box<BufRead> { build_input(self.0) }
+ pub fn output(&self) -> Box<Write> { build_output(self.0) }
+ pub fn decoder(&self) -> Box<Decode> { force_decoder(self.0.value_of("input_format")) }
+ pub fn encoder(&self) -> Box<Encode> { force_encoder(self.0.value_of("output_format")) }
+}
+
+pub mod parse {
+ use clap::ArgMatches;
+ use super::*;
+ pub fn parse(args: &ArgMatches) {
+ let env = Environment(args);
+ let (context, mut decoder, mut input) = (env.context(), env.decoder(), env.input());
+ for e in decoder.decode(&context, &mut input) {
+ match e {
+ Err(e) => { println!("Foo!"); error(Box::new(e)) },
+ _ => ()
+ }
+ }
+ }
+}
+
+pub mod convert {
+ use clap::ArgMatches;
+ use super::*;
+ pub fn convert(args: &ArgMatches) {
+ let env = Environment(args);
+ let (context, mut decoder, mut input, encoder, mut output) =
+ (env.context(), env.decoder(), env.input(), env.encoder(), env.output());
+
+ for e in decoder.decode(&context, &mut input) {
+ match e {
+ Ok(e) => { let _ = encoder.encode(&context, &mut output, &e); },
+ Err(e) => error(Box::new(e))
+ }
+ }
+ }
+}
+
+pub mod seen {
+ use clap::ArgMatches;
+ use ilc::event::Event;
+ use ilc::format::{ self, Encode };
+ use super::*;
+ pub fn seen(args: &ArgMatches) {
+ let env = Environment(args);
+ let (context, mut decoder, mut input, mut output) = (env.context(), env.decoder(), env.input(), env.output());
+
+ let nick = args.value_of("nick").expect("Required argument <nick> not present");
+
+ let mut last: Option<Event> = None;
+ for e in decoder.decode(&context, &mut input) {
+ let m = match e {
+ Ok(m) => m,
+ Err(err) => error(Box::new(err))
+ };
+
+ if m.ty.involves(nick)
+ && last.as_ref().map_or(true, |last| m.time.as_timestamp() > last.time.as_timestamp()) { last = Some(m) }
+ }
+ let encoder = format::weechat3::Weechat3;
+ if let Some(ref m) = last {
+ let _ = encoder.encode(&context, &mut output, m);
+ }
+ }
+}
+
+pub mod sort {
+ use clap::ArgMatches;
+ use ilc::event::Event;
+ use super::*;
+ pub fn sort(args: &ArgMatches) {
+ let env = Environment(args);
+ let (context, mut decoder, mut input, encoder, mut output) =
+ (env.context(), env.decoder(), env.input(), env.encoder(), env.output());
+
+ let mut events: Vec<Event> = decoder.decode(&context, &mut input)
+ .flat_map(Result::ok)
+ .collect();
+
+ events.sort_by(|a, b| a.time.cmp(&b.time));
+ for e in events {
+ let _ = encoder.encode(&context, &mut output, &e);
+ }
+ }
+}
+
+pub mod dedup {
+ use clap::ArgMatches;
+ use ilc::event::NoTimeHash;
+ use ::ageset::AgeSet;
+ use super::*;
+ pub fn dedup(args: &ArgMatches) {
+ let env = Environment(args);
+ let (context, mut decoder, mut input, encoder, mut output) =
+ (env.context(), env.decoder(), env.input(), env.encoder(), env.output());
+
+ let mut backlog = AgeSet::new();
+
+ for e in decoder.decode(&context, &mut input) {
+ if let Ok(e) = e {
+ let newest_event = e.clone();
+ backlog.prune(move |a: &NoTimeHash| {
+ let age = newest_event.time.as_timestamp() - a.0.time.as_timestamp();
+ age > 5000
+ });
+ // write `e` if it's a new event
+ let n = NoTimeHash(e);
+ if !backlog.contains(&n) {
+ let _ = encoder.encode(&context, &mut output, &n.0);
+ backlog.push(n);
+ }
+ }
+ }
+
+ }
+}