1use std::fmt;
4
5#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct SimplePath {
8 segments: Vec<String>,
9}
10
11impl SimplePath {
12 #[must_use]
23 pub fn new<I, S>(segments: I) -> Self
24 where
25 I: IntoIterator<Item = S>,
26 S: Into<String>,
27 {
28 Self {
29 segments: segments.into_iter().map(Into::into).collect(),
30 }
31 }
32
33 #[must_use]
49 pub fn parse(path: &str) -> Self {
50 Self::new(path.split("::").filter(|segment| !segment.is_empty()))
51 }
52
53 #[must_use]
55 #[rustfmt::skip]
56 pub fn segments(&self) -> &[String] { &self.segments }
57
58 #[must_use]
60 #[rustfmt::skip]
61 pub fn last(&self) -> Option<&str> { self.segments.last().map(String::as_str) }
62
63 #[must_use]
65 pub fn matches<I, S>(&self, candidate: I) -> bool
66 where
67 I: IntoIterator<Item = S>,
68 S: AsRef<str>,
69 {
70 let mut candidate_iter = candidate.into_iter();
71
72 for expected in &self.segments {
73 match candidate_iter.next() {
74 Some(candidate_segment) if expected == candidate_segment.as_ref() => continue,
75 _ => return false,
76 }
77 }
78
79 candidate_iter.next().is_none()
80 }
81
82 #[must_use]
84 #[rustfmt::skip]
85 pub fn is_doc(&self) -> bool { self.matches(["doc"]) }
86}
87
88impl From<&str> for SimplePath {
89 fn from(path: &str) -> Self {
90 Self::parse(path)
91 }
92}
93
94impl From<String> for SimplePath {
95 fn from(path: String) -> Self {
96 Self::parse(&path)
97 }
98}
99
100impl fmt::Display for SimplePath {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.write_str(&self.segments.join("::"))
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use rstest::rstest;
110 use std::collections::VecDeque;
111
112 #[rstest]
113 fn filters_empty_segments() {
114 let path = SimplePath::from("::crate::::Item::");
115 assert!(path.matches(["crate", "Item"]));
116 }
117
118 #[rstest]
119 fn matches_segments() {
120 let path = SimplePath::from("crate::module::Item");
121 assert!(path.matches(["crate", "module", "Item"]));
122 assert!(!path.matches(["crate", "module", "Other"]));
123 }
124
125 #[rstest]
126 fn last_returns_final_segment() {
127 let populated = SimplePath::from("crate::module::Item");
128 assert_eq!(populated.last(), Some("Item"));
129
130 let empty = SimplePath::new(Vec::<String>::new());
131 assert_eq!(empty.last(), None);
132 }
133
134 #[rstest]
135 fn is_doc_identifies_doc_segments() {
136 assert!(SimplePath::from("doc").is_doc());
137 assert!(!SimplePath::from("allow").is_doc());
138 }
139
140 #[rstest]
141 fn display_formats_with_separators() {
142 let path = SimplePath::from("crate::module::Item");
143 assert_eq!(path.to_string(), "crate::module::Item");
144 }
145
146 #[rstest]
147 fn from_string_parses_owned_values() {
148 let owned = String::from("test::path");
149 let path = SimplePath::from(owned);
150 assert!(path.matches(["test", "path"]));
151 }
152
153 #[rstest]
154 fn new_accepts_varied_iterators() {
155 let from_vec = SimplePath::new(vec!["a", "b"]);
156 let from_array = SimplePath::new(["a", "b"]);
157 let from_owned = SimplePath::new(vec![String::from("a"), String::from("b")]);
158
159 assert!(from_vec.matches(["a", "b"]));
160 assert!(from_array.matches(["a", "b"]));
161 assert!(from_owned.matches(["a", "b"]));
162 }
163
164 #[rstest]
165 fn new_accepts_iterator_inputs_beyond_vectors() {
166 let deque_path = SimplePath::new(VecDeque::from(["module", "Item"]));
167 assert!(deque_path.matches(["module", "Item"]));
168
169 let once_path = SimplePath::new(std::iter::once("solo"));
170 assert!(once_path.matches(["solo"]));
171 }
172}