1use std::{fmt::Display, hash::Hash, path::{Path, PathBuf}, sync::LazyLock};
2use crate::*;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Namepath {
19 pub(crate) full_path: PathBuf,
20 pub(crate) raw: RawNamepath
21}
22
23impl Namepath {
24 pub fn new_module(package_name: &'static str, use_case: UseCase, module_path: &'static str) -> anyhow::Result<Self> {
25 let raw = RawNamepath {
26 kind: TestingKind::Module,
27 use_case,
28 package_name,
29 path: module_path,
30 name: None,
31 };
32
33 let full_path = normalize_path(&raw)?;
34
35 Ok(Self {
36 full_path,
37 raw,
38 })
39 }
40
41 pub fn new_group(package_name: &'static str, use_case: UseCase, path: &'static str) -> anyhow::Result<Self> {
42 let raw = RawNamepath {
43 kind: TestingKind::Group,
44 use_case,
45 package_name,
46 path,
47 name: None,
48 };
49
50 let full_path = normalize_path(&raw)?;
51
52 Ok(Self {
53 full_path,
54 raw,
55 })
56 }
57
58 pub fn new_test(package_name: &'static str, use_case: UseCase, module_path: &'static str, function_name: &'static str) -> anyhow::Result<Self> {
59 let raw = RawNamepath {
60 kind: TestingKind::Test,
61 use_case,
62 package_name,
63 path: module_path,
64 name: Some(function_name),
65 };
66
67 let full_path = normalize_path(&raw)?;
68
69 Ok(Self {
70 full_path,
71 raw,
72 })
73 }
74
75 pub fn path(&self) -> &Path {
78 let mut components = self.full_path.components();
79 components.next();
80 components.as_path()
81 }
82
83 pub fn full_path(&self) -> &Path {
86 &self.full_path
87 }
88
89 pub fn full_path_to_squashed_slug(&self) -> String {
90 self.full_path
91 .to_str().expect("Invalid namepath")
92 .replace("/", "-")
93 }
94
95 pub fn package_name(&self) -> &str {
97 self.full_path.components()
98 .next().expect("Invalid path")
99 .as_os_str().to_str().expect("Invalid path")
100 }
101
102 pub fn kind(&self) -> TestingKind {
104 self.raw.kind
105 }
106
107 pub fn use_case(&self) -> UseCase {
109 self.raw.use_case
110 }
111
112 pub fn name(&self) -> &str {
115 self.full_path.components()
116 .last().expect("Invalid namepath")
117 .as_os_str().to_str().expect("Invalid path")
118 }
119
120 pub fn raw(&self) -> &RawNamepath {
121 &self.raw
122 }
123}
124
125impl Display for Namepath {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 self.path().display().fmt(f)
128 }
129}
130
131impl Hash for Namepath {
132 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
133 self.full_path().hash(state);
134 }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct RawNamepath {
142 pub kind: TestingKind,
143 pub use_case: UseCase,
144 pub package_name: &'static str,
145 pub path: &'static str,
146 pub name: Option<&'static str>
147}
148
149impl Display for RawNamepath {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 write!(f, "{};{};{};{}", self.kind, self.use_case, self.package_name, self.path)?;
152
153 if let Some(name) = &self.name {
154 write!(f, ";{}", name)?;
155 }
156
157 Ok(())
158 }
159}
160
161fn normalize_path(raw: &RawNamepath) -> anyhow::Result<PathBuf> {
166 static REGEX_NAMESPACE_INTEGRATION: LazyLock<regex::Regex> = LazyLock::new(|| {
167 regex::Regex::new(r"^(.+?)(?:::tests)?$").unwrap()
168 });
169
170 static REGEX_NAMESPACE_UNIT: LazyLock<regex::Regex> = LazyLock::new(|| {
171 regex::Regex::new(r"^\w+::(.+?)(?:::tests)?$").unwrap()
172 });
173
174 let path = if raw.kind == TestingKind::Group {
176 &raw.path
177 } else {
178 let captures = match raw.use_case {
179 UseCase::Integration => REGEX_NAMESPACE_INTEGRATION.captures(&raw.path),
180 UseCase::Unit => REGEX_NAMESPACE_UNIT.captures(&raw.path)
181 };
182
183 match captures {
184 Some(captures) => captures.get(1).unwrap().as_str(),
185 None => ""
186 }
187 };
188
189 let full_path = format!("{package}/{use_case}/{path}{slash_name}",
190 package = &raw.package_name,
191 use_case = &raw.use_case,
192 path = &path
193 .replace("::", "/")
194 .replace("_", "-"),
195 slash_name = raw.name.as_ref()
196 .map(|name| format!("/{}", name.replace("_", "-")))
197 .unwrap_or_default()
198 );
199
200 Ok(PathBuf::from(full_path))
201}
202
203#[cfg(test)]
204mod tests {
205 use crate::prelude::*;
206
207 static TESTING: testing::Module = testing::module!(Unit);
210
211 const GROUP_NAME: &'static str = "namepath-group/uno/dos";
212 static GROUP: testing::Group = testing::group!(GROUP_NAME, Unit);
213
214 #[named]
215 #[test]
216 fn test_unit_namepath() {
217 const EXPECTED_PACKAGE_NAME: &'static str = "asmov-common-testing";
218 const EXPECTED_USE_CASE: testing::UseCase = testing::UseCase::Unit;
219
220 const EXPECTED_MODULE_KIND: testing::TestingKind = testing::TestingKind::Module;
221 const EXPECTED_MODULE_FULL_PATH: &'static str = "asmov-common-testing/unit/namepath";
222 const EXPECTED_MODULE_PATH: &'static str = "unit/namepath";
223 const EXPECTED_MODULE_NAME: &'static str = "namepath";
224 const EXPECTED_MODULE_RAW: &'static str = "module;unit;asmov-common-testing;asmov_common_testing::namepath::tests";
225
226 const EXPECTED_GROUP_KIND: testing::TestingKind = testing::TestingKind::Group;
227 const EXPECTED_GROUP_FULL_PATH: &'static str = "asmov-common-testing/unit/namepath-group/uno/dos";
228 const EXPECTED_GROUP_PATH: &'static str = "unit/namepath-group/uno/dos";
229 const EXPECTED_GROUP_NAME: &'static str = "dos";
230 const EXPECTED_GROUP_RAW: &'static str = "group;unit;asmov-common-testing;namepath-group/uno/dos";
231
232 const EXPECTED_TEST_KIND: testing::TestingKind = testing::TestingKind::Test;
233 const EXPECTED_TEST_FULL_PATH: &'static str = "asmov-common-testing/unit/namepath/test-unit-namepath";
234 const EXPECTED_TEST_PATH: &'static str = "unit/namepath/test-unit-namepath";
235 const EXPECTED_TEST_NAME: &'static str = "test-unit-namepath";
236 const EXPECTED_TEST_RAW: &'static str = "test;unit;asmov-common-testing;asmov_common_testing::namepath::tests;test_unit_namepath";
237
238 assert_eq!(EXPECTED_PACKAGE_NAME, TESTING.namepath().package_name());
240 assert_eq!(EXPECTED_MODULE_KIND, TESTING.namepath().kind());
241 assert_eq!(EXPECTED_USE_CASE, TESTING.namepath().use_case());
242 assert_eq!(EXPECTED_MODULE_FULL_PATH, TESTING.namepath().full_path().to_string_lossy());
243 assert_eq!(EXPECTED_MODULE_PATH, TESTING.namepath().path().to_string_lossy());
244 assert_eq!(EXPECTED_MODULE_NAME, TESTING.namepath().name());
245 assert_eq!(EXPECTED_MODULE_RAW, TESTING.namepath().raw().to_string());
246
247 assert_eq!(EXPECTED_PACKAGE_NAME, GROUP.namepath().package_name());
249 assert_eq!(EXPECTED_GROUP_KIND, GROUP.namepath().kind());
250 assert_eq!(EXPECTED_USE_CASE, GROUP.namepath().use_case());
251 assert_eq!(EXPECTED_GROUP_FULL_PATH, GROUP.namepath().full_path().to_string_lossy());
252 assert_eq!(EXPECTED_GROUP_PATH, GROUP.namepath().path().to_string_lossy());
253 assert_eq!(EXPECTED_GROUP_NAME, GROUP.namepath().name());
254 assert_eq!(EXPECTED_GROUP_RAW, GROUP.namepath().raw().to_string());
255
256 let test = testing::test!();
258 assert_eq!(EXPECTED_PACKAGE_NAME, test.namepath().package_name());
259 assert_eq!(EXPECTED_TEST_KIND, test.namepath().kind());
260 assert_eq!(EXPECTED_USE_CASE, test.namepath().use_case());
261 assert_eq!(EXPECTED_TEST_FULL_PATH, test.namepath().full_path().to_string_lossy());
262 assert_eq!(EXPECTED_TEST_PATH, test.namepath().path().to_string_lossy());
263 assert_eq!(EXPECTED_TEST_NAME, test.namepath().name());
264 assert_eq!(EXPECTED_TEST_RAW, test.namepath().raw().to_string());
265 }
266}