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
use chrono::{prelude::Local, NaiveDateTime};
use std::{fmt, str::FromStr, string::ParseError};
use vader_sentiment::SentimentIntensityAnalyzer;

use crate::Sentiment;

#[derive(Clone, Debug)]
pub struct Entry {
    pub text: String,
    pub timestamp: NaiveDateTime,
    sentiment: Sentiment,
    dt_format: String,
}

impl Entry {
    pub fn new(text: String, dt_fmt: &str) -> Entry {
        let dt_format = dt_fmt.to_string();
        let score = Entry::calculate_sentiment(&text);
        let sentiment = Sentiment::new(score);
        Entry {
            text,
            timestamp: Local::now().naive_local(),
            sentiment,
            dt_format,
        }
    }

    pub fn replace_text(&mut self, text: &str) {
        self.text.clear();
        self.text.push_str(text);
    }

    fn calculate_sentiment(text: &str) -> f64 {
        // TODO: Use pos/neg/neu as colour space coordinates
        let analyzer = SentimentIntensityAnalyzer::new();
        let scores = analyzer.polarity_scores(text);

        *scores.get("compound").unwrap()
    }
}

impl FromStr for Entry {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let e: Vec<&str> = s.split("---").collect();
        // TODO: Date format still hardcoded here
        // Use str::split_once when available
        // Or use regex
        let header: Vec<&str> = e[0].trim().split('\n').collect();
        let compound: f64 = header[1].split('≅').collect::<Vec<&str>>()[0][5..]
            .trim()
            .parse()
            .unwrap();
        Ok(Entry {
            text: e[1].trim().into(),
            timestamp: NaiveDateTime::parse_from_str(
                header[0].split_at(4).1,
                "%A %e %B, %Y - %H:%M",
            )
            .unwrap(),
            dt_format: "%A %e %B, %Y - %H:%M".to_string(),
            sentiment: { Sentiment::new(compound) },
        })
    }
}

impl fmt::Display for Entry {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "### {}\n#### {}\n---\n\n{}\n\n¶\n",
            self.timestamp.format(&self.dt_format),
            self.sentiment,
            self.text
        )
    }
}

#[cfg(test)]
mod test_entry {
    use super::*;

    #[test]
    fn test_entry_text() {
        let e = Entry::new("Testing this entry".into(), "%A %e %B, %Y - %H:%M");
        assert_eq!(e.text, "Testing this entry");
    }

    #[test]
    fn test_sentiment_positive() {
        let e = Entry::new(
            "This is amazing and should have a really high and awesome compound sentiment!".into(),
            "%A %e %B, %Y - %H:%M",
        );
        assert_eq!(e.sentiment.compound, 0.86732358124633);
    }

    #[test]
    fn test_sentiment_negative() {
        let e = Entry::new(
            "This is awful and should have a really low and terrible compound sentiment!".into(),
            "%A %e %B, %Y - %H:%M",
        );
        assert_eq!(e.sentiment.compound, -0.8157728811846393);
    }

    #[test]
    fn test_replace_text() {
        let mut e = Entry::new(
            "Going to replace some words in this.".into(),
            "%A %e %B, %Y - %H:%M",
        );
        e.replace_text("Replaced some words.");
        assert_eq!(e.text, "Replaced some words.");
    }
}