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
extern crate notify_rust;

use std::collections::HashMap;
use notify_rust::Notification;

const BREAK_TAG: &'static str = "!break!";

const VALID_TAGS: [&'static str; 11] = ["status", "url", "file", "artist", "album", "discnumber", "tracknumber", "title", "date", "duration", BREAK_TAG];

pub type Metadata = HashMap<String, String>;

pub fn print_usage() {
    println!("You must set cmus to call this script as notifier");
}

pub fn parse(cmus_data: String) -> Metadata {
    let mut result = Metadata::new();
    let mut split_data: Vec<&str> = cmus_data.split(" ").collect();
    split_data.push(BREAK_TAG);
    let mut last_tag_found: Option<&str> = None;
    let mut value_collector: Vec<&str> = vec![];
    for part in split_data.iter() {
        if VALID_TAGS.contains(part) {
            if value_collector.len() > 0 {
                if let Some(tag) = last_tag_found {
                    result.insert(tag.to_string(), value_collector.join(" "));
                    value_collector.clear();
                }
            }
            last_tag_found = Some(part);
            continue;
        }
        value_collector.push(part);
    }
    result
}

pub fn format_notification_body(m: &Metadata) -> String {
    let mut notification_body = match m.get("title") {
        Some(t) => t.to_string(),
        None => "Unknown".to_string()
    };

    if let Some(artist) = m.get("artist") {
        notification_body = format!("{} by {}", notification_body, artist);
        if let Some(album) = m.get("album") {
            notification_body = format!("{}, {}", notification_body, album);
        }
    }

    notification_body
}

pub trait Notifier {
    fn send(&self, summary: String, content: String);
}

pub struct DbusNotifier { }

impl Notifier for DbusNotifier {
    fn send(&self, summary: String, content: String) {
        Notification::new()
            .summary(&summary)
            .body(&content)
            .show().unwrap();
    }
}

pub fn run<T>(n: &T, cmus_data: String)
    where T: Notifier {
    let metadata = parse(cmus_data);
    let notification_body = format_notification_body(&metadata);
    n.send("Cmustify - Current song".to_string(), notification_body);
}


#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::RefCell;

    #[test]
    fn parse_cmus_data_correctly() {
        let cmus_data = "artist Todd album Reno title super song".to_string();
        let result = parse(cmus_data);

        assert_eq!(result.get("artist").unwrap(), "Todd");
        assert_eq!(result.get("album").unwrap(), "Reno");
        assert_eq!(result.get("title").unwrap(), "super song");
    }

    #[test]
    fn format_notification_body_correctly() {
        let mut metadata = Metadata::new();
        metadata.insert("title".to_string(), "Super title".to_string());
        metadata.insert("artist".to_string(), "Todd".to_string());

        let notification_body = format_notification_body(&metadata);

        assert!(notification_body.contains("Super title"));
        assert!(notification_body.contains("Todd"));
    }

    #[test]
    fn run_send_notification() {
        struct NotifyMock {
            called: RefCell<bool>
        }

        impl Notifier for NotifyMock {
            fn send(&self, _summary: String, _content: String) {
                *self.called.borrow_mut() = true;
            }
        }

        let notifier_mock = NotifyMock { called: RefCell::new(false) };
        let notification_body = "Hola".to_string();
        run(&notifier_mock, notification_body);

        assert!(*notifier_mock.called.borrow())
    }
}