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
use std::fmt::{Display, Formatter};

/// The hostname used when the hostname
/// cannot be retrieved.
const UNKNOWN_HOSTNAME: &str = "?";

/// The author or creator of the message.
///
/// The base author will contain the hostname of the OS that sent to message.
/// This helps identify which machine the message came from. If this is not available then '?' will
/// be used.
///
/// A good author should allow the user to quickly and easily identify where the message
/// originates from.
/// To make a good author, think, could you quickly and easily stop the source of the messages just
/// based on the author?
///
/// Filtering should not be performed based on an Author, rather you should filter instead
/// based on [`Level`] and [`Component`]
///
/// [`Level`]: crate::message::Level
/// [`Component`]: crate::message::component::Component
#[derive(Debug, Clone, PartialEq)]
pub struct Author {
    parts: Vec<String>,
}

impl Author {
    /// Parses an Author from the given string.
    ///
    /// ```rust
    /// // A good Author for a message originating from the db_checker program
    /// // which is invoked by user's crontab.
    /// use rnotifylib::message::author::Author;
    ///
    /// let author = Author::parse("user/cron/db_checker".to_owned());
    /// ```
    pub fn parse(parts: String) -> Author {
        let mut new = Self::base();
        new.extend(parts);
        new
    }

    /// Provides the base Author, of either the hostname.
    /// If the hostname cannot be found, uses [`base_incognito`](Self::base_incognito)
    pub fn base() -> Author {
        let base = match hostname::get() {
            Ok(hostname) => {
                hostname.to_string_lossy().to_string()
            }
            Err(_) => {
                return Self::base_incognito();
            }
        };

        Author {
            parts: vec![base],
        }
    }

    /// Creates an Author with an unknown hostname.
    /// You should use [`base`](Self::base) whenever possible, but if you do not want to expose
    /// the hostname of the sender, you can use this method.
    pub fn base_incognito() -> Author {
        Author {
            parts: vec![UNKNOWN_HOSTNAME.to_string()]
        }
    }

    /// Parses an author, using an unknown hostname
    /// You should use [`parse`](Self::parse) whenever possible, but if you do not want to expose
    /// the hostname of the sender, you can use this method.
    pub fn parse_incognito(s: String) -> Author {
        let mut base = Self::base_incognito();
        base.extend(s);
        base
    }

    /// Adds more information to the author of this
    /// ```rust
    /// use rnotifylib::message::author::Author;
    ///
    /// // Normally you should use Author::base(), but we want predictable output for this test.
    /// let mut author = Author::base_incognito();
    /// author.extend("user".to_owned());
    /// author.extend("cron".to_owned());
    /// author.extend("db_checker".to_owned());
    ///
    /// assert_eq!(author.to_string(), "?/user/cron/db_checker");
    pub fn extend(&mut self, parts: String) {
        let vec: Vec<String> = parts.split("/").into_iter()
            .filter(|s| !s.is_empty())
            .map(|s| s.to_owned())
            .collect();

        self.parts.extend(vec);
    }
}

impl Display for Author {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.parts.join("/"))
    }
}