aboutsummaryrefslogtreecommitdiff
path: root/ops/src/stats.rs
blob: 7e37a8081c18e435c3b5c96b4c66aef5d0f06400 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Per-nick word/line statistics
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 {
    fn serialize<S>(&self, s: &mut S) -> Result<(), S::Error>
        where S: Serializer
    {
        struct Visitor<'a>(&'a Stats);
        impl<'a> MapVisitor for Visitor<'a> {
            fn visit<S>(&mut self, s: &mut S) -> Result<Option<()>, S::Error>
                where S: Serializer
            {
                try!(s.serialize_struct_elt("freqs", &self.0.freqs));
                try!(s.serialize_struct_elt("week", &self.0.week));
                Ok(None)
            }

            fn len(&self) -> Option<usize> {
                Some(1)
            }
        }
        s.serialize_struct("Stats", Visitor(self))
    }
}

pub struct NickStat {
    pub lines: u32,
    pub alpha_lines: u32,
    pub words: u32,
}

impl Serialize for NickStat {
    fn serialize<S>(&self, s: &mut S) -> Result<(), S::Error>
        where S: Serializer
    {
        struct Visitor<'a>(&'a NickStat);
        impl<'a> MapVisitor for Visitor<'a> {
            fn visit<S>(&mut self, s: &mut S) -> Result<Option<()>, S::Error>
                where S: Serializer
            {
                try!(s.serialize_struct_elt("lines", self.0.lines));
                try!(s.serialize_struct_elt("alpha_lines", self.0.alpha_lines));
                try!(s.serialize_struct_elt("words", self.0.words));
                Ok(None)
            }

            fn len(&self) -> Option<usize> {
                Some(3)
            }
        }

        s.serialize_struct("NickStat", Visitor(self))
    }
}

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(s: &str) -> &str {
    if s.is_empty() {
        return s;
    }
    match s.as_bytes()[0] {
        b'~' | b'&' | b'@' | b'%' | b'+' => &s[1..],
        _ => s,
    }
    .trim_right_matches('_')
}

/// 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, .. }, 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();
                    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);
                    freqs.insert(nick.to_owned(),
                                 NickStat {
                                     lines: 1,
                                     alpha_lines: if alpha { 1 } else { 0 },
                                     words: words,
                                 });
                }
            }
            _ => (),
        }
    }

    Ok(Stats {
        freqs: freqs,
        week: week,
    })
}