artifact_app/user/
name.rs

1/* Copyright (c) 2017 Garrett Berg, vitiral@gmail.com
2 *
3 * Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4 * http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5 * http://opensource.org/licenses/MIT>, at your option. This file may not be
6 * copied, modified, or distributed except according to those terms.
7 */
8//! module for defining logic for parsing and collapsing artifact names
9
10use std::hash::{Hash, Hasher};
11use std::cmp::Ordering;
12
13use dev_prefix::*;
14use types::*;
15
16// Public Trait Methods
17
18impl FromStr for Name {
19    type Err = Error;
20    fn from_str(s: &str) -> Result<Name> {
21        Name::from_string(s.to_string())
22    }
23}
24
25impl Name {
26    pub fn from_string(s: String) -> Result<Name> {
27        let value = s.to_ascii_uppercase();
28        if !NAME_VALID.is_match(&value) {
29            return Err(ErrorKind::InvalidName(s.to_string()).into());
30        }
31        let value: Vec<String> = value.split('-').map(|s| s.to_string()).collect();
32        let ty = _get_type(&value[0], &s)?;
33        Ok(Name {
34            raw: s,
35            value: value,
36            ty: ty,
37        })
38    }
39
40    /// parse name from string and handle errors
41    /// see: SPC-artifact-name.2
42    /// see: SPC-artifact-partof-2
43    pub fn parent(&self) -> Option<Name> {
44        if self.value.len() <= 2 {
45            return None;
46        }
47        let mut value = self.value.clone();
48        value.pop().unwrap();
49        Some(Name {
50            raw: value.join("-"),
51            value: value,
52            ty: self.ty,
53        })
54    }
55
56    /// return whether this is a root name (whether it has no parent)
57    pub fn is_root(&self) -> bool {
58        self.value.len() == 2
59    }
60
61    pub fn parent_rc(&self) -> Option<NameRc> {
62        match self.parent() {
63            Some(p) => Some(Arc::new(p)),
64            None => None,
65        }
66    }
67
68    /// return the artifact this artifact is automatically
69    /// a partof (because of it's name)
70    /// see: SPC-artifact-partof-1
71    pub fn named_partofs(&self) -> Vec<Name> {
72        match self.ty {
73            Type::TST => vec![self._get_named_partof("SPC")],
74            Type::SPC => vec![self._get_named_partof("REQ")],
75            Type::REQ => vec![],
76        }
77    }
78
79    /// CAN PANIC
80    fn _get_named_partof(&self, ty: &str) -> Name {
81        let s = ty.to_string() + self.raw.split_at(3).1;
82        Name::from_str(&s).unwrap()
83    }
84}
85
86#[test]
87fn test_artname_parent() {
88    let name = Name::from_str("REQ-foo-bar-b").unwrap();
89    let parent = name.parent().unwrap();
90    assert_eq!(parent, Name::from_str("REQ-foo-bar").unwrap());
91    let parent = parent.parent().unwrap();
92    assert_eq!(parent, Name::from_str("REQ-foo").unwrap());
93}
94
95impl Default for Name {
96    fn default() -> Name {
97        Name::from_str("REQ-default").unwrap()
98    }
99}
100
101impl fmt::Display for Name {
102    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103        write!(f, "{}", self.raw)
104    }
105}
106
107impl fmt::Debug for Name {
108    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109        write!(f, "{}", self.value.join("-"))
110    }
111}
112
113impl Hash for Name {
114    fn hash<H: Hasher>(&self, state: &mut H) {
115        self.value.hash(state);
116    }
117}
118
119impl PartialEq for Name {
120    fn eq(&self, other: &Name) -> bool {
121        self.value == other.value
122    }
123}
124
125impl Eq for Name {}
126
127impl Ord for Name {
128    fn cmp(&self, other: &Self) -> Ordering {
129        self.value.cmp(&other.value)
130    }
131}
132
133impl PartialOrd for Name {
134    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
135        self.value.partial_cmp(&other.value)
136    }
137}
138
139impl LoadFromStr for NameRc {
140    fn from_str(s: &str) -> Result<NameRc> {
141        Ok(Arc::new(try!(Name::from_str(s))))
142    }
143}
144
145impl LoadFromStr for Names {
146    /// Parse a "names str" and convert into a Set of Names
147    fn from_str(partof_str: &str) -> Result<Names> {
148        let strs = try!(parse_names(&mut partof_str.chars(), false));
149        let mut out = HashSet::new();
150        for s in strs {
151            out.insert(Arc::new(try!(Name::from_str(&s))));
152        }
153        Ok(out)
154    }
155}
156
157// Private Methods
158
159fn _get_type(value: &str, raw: &str) -> Result<Type> {
160    match value {
161        "REQ" => Ok(Type::REQ),
162        "SPC" => Ok(Type::SPC),
163        "TST" => Ok(Type::TST),
164        _ => Err(
165            ErrorKind::InvalidName(format!(
166                "name must start with REQ-, SPC- or TST-: \
167                 {}",
168                raw
169            )).into(),
170        ),
171    }
172}
173
174// Private: Expanding Names. Use `Name::from_str`
175
176/// subfunction to parse names from a names-str recusively
177fn parse_names<I>(raw: &mut I, in_brackets: bool) -> Result<Vec<String>>
178where
179    I: Iterator<Item = char>,
180{
181    // hello-[there, you-[are, great]]
182    // hello-there, hello-you-are, hello-you-great
183    let mut strout = String::new();
184    let mut current = String::new();
185    loop {
186        // SPC-names.1: read one char at a time
187        let c = match raw.next() {
188            Some(c) => c,
189            None => {
190                if in_brackets {
191                    // SPC-names.2: do validation
192                    return Err(
193                        ErrorKind::InvalidName("brackets are not closed".to_string()).into(),
194                    );
195                }
196                break;
197            }
198        };
199        match c {
200            ' ' | '\n' | '\r' => {}
201            // ignore whitespace
202            '[' => {
203                if current == "" {
204                    // SPC-names.2: more validation
205                    let msg = "cannot have '[' after characters ',' or ']'\
206                               or at start of string"
207                        .to_string();
208                    return Err(ErrorKind::InvalidName(msg).into());
209                }
210                // SPC-names.3: recurse for brackets
211                for p in try!(parse_names(raw, true)) {
212                    strout.write_str(&current).unwrap();
213                    strout.write_str(&p).unwrap();
214                    strout.push(',');
215                }
216                current.clear();
217            }
218            ']' => break,
219            ',' => {
220                strout.write_str(&current).unwrap();
221                strout.push(',');
222                current.clear();
223            }
224            _ => current.push(c),
225        }
226    }
227    strout.write_str(&current).unwrap();
228    Ok(
229        strout
230            .split(',')
231            .filter(|s| s != &"")
232            .map(|s| s.to_string())
233            .collect(),
234    )
235}
236
237#[cfg(test)]
238fn do_test_parse(user: &str, expected_collapsed: &[&str]) {
239    let parsed = parse_names(&mut user.chars(), false).unwrap();
240    assert_eq!(parsed, expected_collapsed);
241}
242
243#[test]
244/// #TST-project-partof
245fn test_parse_names() {
246    do_test_parse("hi, ho", &["hi", "ho"]);
247    do_test_parse("hi-[he, ho]", &["hi-he", "hi-ho"]);
248    do_test_parse(
249        "he-[ha-[ha, he], hi, ho], hi-[he, ho]",
250        &["he-ha-ha", "he-ha-he", "he-hi", "he-ho", "hi-he", "hi-ho"],
251    );
252    assert!(parse_names(&mut "[]".chars(), false).is_err());
253    assert!(parse_names(&mut "[hi]".chars(), false).is_err());
254    assert!(parse_names(&mut "hi-[ho, [he]]".chars(), false).is_err());
255    assert!(parse_names(&mut "hi-[ho, he".chars(), false).is_err());
256}
257
258#[test]
259fn test_name() {
260    // valid names
261    for name in vec![
262        "REQ-foo",
263        "REQ-foo-2",
264        "REQ-foo2",
265        "REQ-foo2",
266        "REQ-foo-bar-2_3",
267        "SPC-foo",
268        "TST-foo",
269    ] {
270        assert!(Name::from_str(name).is_ok());
271    }
272    for name in vec!["REQ-foo*", "REQ-foo\n", "REQ-foo-"] {
273        assert!(Name::from_str(name).is_err())
274    }
275    // spaces are invalid
276    assert!(Name::from_str("REQ-foo ").is_err());
277}