contack/
name.rs

1use std::fmt;
2use std::fmt::Display;
3
4#[cfg(feature = "read_write")]
5use crate::read_write::component::Component;
6
7/// A name
8///
9/// Stored with all the properties required for the
10/// VCard specification.
11///
12/// Name implements `Display` which allows it to
13/// be formatted in the style of: "Prefix Given
14/// Additional(s) Family Suffix." Additional names
15/// will be separated by spaces.
16///
17/// # Examples
18///
19/// Create a contact with the name "John Doe"
20///
21/// ```rust
22/// use contack::{Name, Contact};
23/// 
24/// let contact = Contact::new(Name {
25///    given: vec!["John".to_string()],
26///    family: vec!["Doe".to_string()],
27///    ..Default::default()
28/// });
29/// ```
30#[derive(Debug, Clone, PartialEq, PartialOrd, Default)]
31pub struct Name {
32    /// The given name.
33    ///
34    /// Sometimes called a first name.
35    /// The "John" in "John Doe".
36    pub given: Vec<String>,
37    /// Additional names.
38    ///
39    /// Sometimes are called "middle" names. You
40    /// often have more than one.
41    pub additional: Vec<String>,
42    /// The family name.
43    ///
44    /// Sometimes called "last" names. The "Doe" in "John Doe"
45    pub family: Vec<String>,
46    /// Prefixes.
47    ///
48    /// Often times these are things such as "Ms", "Mr" or "Mx",
49    /// although in some cases they can be more advanced, such as
50    /// "Doctor" or "Seargant".
51    pub prefixes: Vec<String>,
52    /// Suffixes
53    ///
54    /// The opposite of prefixes. An example is "Esq".
55    /// Not that common  in English.
56    pub suffixes: Vec<String>,
57}
58
59impl Name {
60    /// Creates a new `Name`
61    ///
62    /// For most cases it is probably clearer
63    /// to instantiate the name manually for clarity.
64    ///
65    /// # Example
66    ///
67    /// Create a new name for "John Doe"
68    /// 
69    /// ```rust
70    /// use contack::Name;
71    /// 
72    /// let name = Name::new(
73    ///     Some("John".to_string()),
74    ///     Some("Doe".to_string()),
75    ///     None,
76    ///     None,
77    ///     None
78    /// );
79    /// ```
80    #[must_use]
81    pub fn new(
82        given: Option<String>,
83        additional: Option<String>,
84        family: Option<String>,
85        prefixes: Option<String>,
86        suffixes: Option<String>,
87    ) -> Self {
88        Self {
89            given: given.map_or(Vec::new(), |x| vec![x]),
90            additional: additional.map_or(Vec::new(), |x| vec![x]),
91            family: family.map_or(Vec::new(), |x| vec![x]),
92            prefixes: prefixes.map_or(Vec::new(), |x| vec![x]),
93            suffixes: suffixes.map_or(Vec::new(), |x| vec![x]),
94        }
95    }
96
97    /// Converts the name to a single string escaped by spaces.
98    ///
99    /// Each subname is separated by a space, and if
100    /// they contain a space they are prefixed with
101    /// a `\ `. This only exists to be used in the sql
102    /// backend because of backwards compatability.
103    ///
104    /// # Example
105    ///
106    /// Escape someone who has a name with spaces.
107    /// 
108    /// ```rust
109    /// use contack::Name;
110    /// let name = vec!["Mary Rose", "Bee", "a\\"];
111    /// assert_eq!(
112    ///     Some(String::from(r#"Mary\ Rose Bee a\\"#)),
113    ///     Name::escape_with_spaces(name.into_iter())
114    /// );
115    /// ```
116    pub fn escape_with_spaces<'a>(vec: impl Iterator<Item=&'a str>) -> Option<String> {
117        let mut string = String::new();
118        for substring in vec {
119            string.push_str(&substring.replace('\\', "\\\\").replace(' ', r#"\ "#));
120            string.push(' ');
121        }
122        string.pop();
123        if string.is_empty() {
124            None
125        } else {
126            Some(string)
127        }
128    }
129
130    /// Unescapes a name with spaces.
131    ///
132    /// Performs the opposite of [`Name::escape_with_spaces`]
133    ///
134    /// # Example
135    ///
136    /// Unescape someone who has a name with spaces.
137    /// 
138    /// ```rust
139    /// use contack::Name;
140    /// let name = r#"Mary\ Rose Bee a\\"#;
141    /// assert_eq!(vec!["Mary Rose", "Bee", "a\\"], Name::unescape_with_spaces(name));
142    /// ```
143    ///
144    /// # Panics
145    ///
146    /// It doesn't, just clippy thinks it does.
147    #[must_use] pub fn unescape_with_spaces(string: &str) -> Vec<String> {
148        let mut in_escape = false;
149        let mut vec = vec![String::new()];
150        for chr in string.chars() {
151            match chr {
152                '\\' if in_escape => vec.last_mut().unwrap().push('\\'),
153                '\\' if !in_escape => in_escape = true,
154                ' ' if in_escape => vec.last_mut().unwrap().push(' '),
155                ' ' if !in_escape => vec.push(String::new()),
156                other => {
157                    vec.last_mut().unwrap().push(other);
158                    in_escape = false;
159                }
160            }
161        }
162        vec
163    }
164}
165
166impl Display for Name {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        let mut string = String::new();
169        if !self.prefixes.is_empty() {
170            string.push(' ');
171            string.push_str(&self.prefixes.join(" "))
172        }
173        if !self.given.is_empty() {
174            string.push(' ');
175            string.push_str(&self.given.join(" "))
176        }
177        if !self.additional.is_empty() {
178            string.push(' ');
179            string.push_str(&self.additional.join(" "))
180        }
181        if !self.family.is_empty() {
182            string.push(' ');
183            string.push_str(&self.family.join(" "))
184        }
185        if !self.suffixes.is_empty() {
186            string.push(' ');
187            string.push_str(&self.suffixes.join(" "))
188        }
189
190        write!(f, "{}", string.trim())
191    }
192}
193
194/// Allows a name to be translated into a component.
195///
196/// You should never need to call this directly and should
197/// prefer turning a whole contact into a VCard.
198///
199/// # Example
200///
201/// Convert a name into a contact and print it out.
202/// 
203/// 
204/// ```rust
205/// use contack::Name;
206/// use contack::read_write::component::Component;
207/// 
208/// let name = Name {
209///    given: vec!["John".to_string()],
210///    family: vec!["Doe".to_string()],
211///    ..Default::default()
212/// };
213///
214/// let component: Component = name.into();
215///
216/// // NAME:Doe;John;;;;
217/// println!("{}", component);
218/// ```
219#[cfg(feature = "read_write")]
220#[cfg_attr(feature = "dox", doc(cfg(feature = "read_write")))]
221impl From<Name> for Component {
222    fn from(name: Name) -> Self {
223        Self {
224            name: "N".to_string(),
225            values: vec![
226                name.family,
227                name.given,
228                name.additional,
229                name.prefixes,
230                name.suffixes,
231            ],
232            ..Self::default()
233        }
234    }
235}
236
237#[cfg(feature = "read_write")]
238impl From<Component> for Name {
239    fn from(mut comp: Component) -> Self {
240        comp.values.truncate(5);
241        Self {
242            suffixes: comp.values.pop().unwrap_or_default(),
243            prefixes: comp.values.pop().unwrap_or_default(),
244            additional: comp.values.pop().unwrap_or_default(),
245            given: comp.values.pop().unwrap_or_default(),
246            family: comp.values.pop().unwrap_or_default(),
247        }
248    }
249}