1use super::*;
2
3#[derive(Debug, PartialEq, Clone)]
4pub struct ModulePath {
5 pub path: Vec<String>,
6 pub spaced: bool,
7}
8
9impl TryFrom<&[&str]> for ModulePath {
10 type Error = ();
11
12 fn try_from(path: &[&str]) -> Result<Self, Self::Error> {
13 let spaced = path.len() > 1;
14
15 let path = if path.len() == 1 {
16 let first = path[0];
17
18 if first.starts_with(':') || first.ends_with(':') || first.contains(":::") {
19 return Err(());
20 }
21
22 first
23 .split("::")
24 .map(str::to_string)
25 .collect::<Vec<String>>()
26 } else {
27 path.iter().map(|s| (*s).to_string()).collect()
28 };
29
30 for name in &path {
31 if name.is_empty() {
32 return Err(());
33 }
34
35 for (i, c) in name.chars().enumerate() {
36 if i == 0 {
37 if !Lexer::is_identifier_start(c) {
38 return Err(());
39 }
40 } else if !Lexer::is_identifier_continue(c) {
41 return Err(());
42 }
43 }
44 }
45
46 Ok(Self { path, spaced })
47 }
48}
49
50impl Display for ModulePath {
51 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
52 for (i, name) in self.path.iter().enumerate() {
53 if i > 0 {
54 if self.spaced {
55 write!(f, " ")?;
56 } else {
57 write!(f, "::")?;
58 }
59 }
60 write!(f, "{name}")?;
61 }
62 Ok(())
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn try_from_ok() {
72 #[track_caller]
73 fn case(path: &[&str], expected: &[&str], display: &str) {
74 let actual = ModulePath::try_from(path).unwrap();
75 assert_eq!(actual.path, expected);
76 assert_eq!(actual.to_string(), display);
77 }
78
79 case(&[], &[], "");
80 case(&["foo"], &["foo"], "foo");
81 case(&["foo0"], &["foo0"], "foo0");
82 case(&["foo", "bar"], &["foo", "bar"], "foo bar");
83 case(&["foo::bar"], &["foo", "bar"], "foo::bar");
84 }
85
86 #[test]
87 fn try_from_err() {
88 #[track_caller]
89 fn case(path: &[&str]) {
90 assert!(ModulePath::try_from(path).is_err());
91 }
92
93 case(&[":foo"]);
94 case(&["foo:"]);
95 case(&["foo:::bar"]);
96 case(&["0foo"]);
97 case(&["f$oo"]);
98 case(&[""]);
99 }
100}