1use std::path::{Path, PathBuf};
10
11#[derive(Debug, Clone, PartialEq)]
13pub enum ImportType {
14 Relative,
16
17 Absolute,
19
20 Url,
22
23 Package,
25}
26
27#[derive(Debug, Clone)]
29pub struct ResolvedImport {
30 pub original_path: String,
32
33 pub resolved_path: String,
35
36 pub import_type: ImportType,
38}
39
40pub struct Resolver {
42 base_dir: PathBuf,
44
45 package_paths: Vec<PathBuf>,
47}
48
49impl Resolver {
50 pub fn new<P: AsRef<Path>>(base_dir: P) -> Self {
52 Self {
53 base_dir: base_dir.as_ref().to_path_buf(),
54 package_paths: vec![
55 PathBuf::from("node_modules"),
56 PathBuf::from("tcss_modules"),
57 ],
58 }
59 }
60
61 pub fn add_package_path<P: AsRef<Path>>(&mut self, path: P) {
63 self.package_paths.push(path.as_ref().to_path_buf());
64 }
65
66 pub fn resolve(&self, import_path: &str) -> Result<ResolvedImport, String> {
68 let import_type = self.detect_import_type(import_path);
69
70 let resolved_path = match import_type {
71 ImportType::Relative => self.resolve_relative(import_path)?,
72 ImportType::Absolute => self.resolve_absolute(import_path)?,
73 ImportType::Url => import_path.to_string(),
74 ImportType::Package => self.resolve_package(import_path)?,
75 };
76
77 Ok(ResolvedImport {
78 original_path: import_path.to_string(),
79 resolved_path,
80 import_type,
81 })
82 }
83
84 fn detect_import_type(&self, path: &str) -> ImportType {
86 if path.starts_with("http://") || path.starts_with("https://") {
87 ImportType::Url
88 } else if path.starts_with("./") || path.starts_with("../") {
89 ImportType::Relative
90 } else if path.starts_with('/') {
91 ImportType::Absolute
92 } else {
93 ImportType::Package
94 }
95 }
96
97 fn resolve_relative(&self, path: &str) -> Result<String, String> {
99 let full_path = self.base_dir.join(path);
100
101 let normalized = self.normalize_path(&full_path)?;
103
104 if !normalized.exists() {
106 return Err(format!("Import file not found: {}", path));
107 }
108
109 normalized
110 .to_str()
111 .ok_or_else(|| format!("Invalid path: {}", path))
112 .map(|s| s.to_string())
113 }
114
115 fn resolve_absolute(&self, path: &str) -> Result<String, String> {
117 let full_path = PathBuf::from(path);
118
119 if !full_path.exists() {
120 return Err(format!("Import file not found: {}", path));
121 }
122
123 full_path
124 .to_str()
125 .ok_or_else(|| format!("Invalid path: {}", path))
126 .map(|s| s.to_string())
127 }
128
129 fn resolve_package(&self, path: &str) -> Result<String, String> {
131 let (package_name, file_path) = if let Some(slash_pos) = path.find('/') {
133 (&path[..slash_pos], Some(&path[slash_pos + 1..]))
134 } else {
135 (path, None)
136 };
137
138 for pkg_dir in &self.package_paths {
140 let mut package_path = pkg_dir.join(package_name);
141
142 if let Some(file) = file_path {
144 package_path = package_path.join(file);
145 } else {
146 let index_path = package_path.join("index.tcss");
148 if index_path.exists() {
149 package_path = index_path;
150 }
151 }
152
153 if package_path.exists() {
154 return package_path
155 .to_str()
156 .ok_or_else(|| format!("Invalid package path: {}", path))
157 .map(|s| s.to_string());
158 }
159 }
160
161 Err(format!("Package not found: {}", path))
162 }
163
164 fn normalize_path(&self, path: &Path) -> Result<PathBuf, String> {
166 path.canonicalize()
167 .map_err(|e| format!("Failed to normalize path: {}", e))
168 }
169
170 pub fn set_base_dir<P: AsRef<Path>>(&mut self, base_dir: P) {
172 self.base_dir = base_dir.as_ref().to_path_buf();
173 }
174
175 pub fn base_dir(&self) -> &Path {
177 &self.base_dir
178 }
179}
180
181impl Default for Resolver {
182 fn default() -> Self {
183 Self::new(".")
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_detect_import_type() {
193 let resolver = Resolver::new(".");
194
195 assert_eq!(resolver.detect_import_type("./file.tcss"), ImportType::Relative);
196 assert_eq!(resolver.detect_import_type("../file.tcss"), ImportType::Relative);
197 assert_eq!(resolver.detect_import_type("/abs/path.tcss"), ImportType::Absolute);
198 assert_eq!(resolver.detect_import_type("https://example.com/file.tcss"), ImportType::Url);
199 assert_eq!(resolver.detect_import_type("@tcss/core"), ImportType::Package);
200 assert_eq!(resolver.detect_import_type("package-name"), ImportType::Package);
201 }
202
203 #[test]
204 fn test_add_package_path() {
205 let mut resolver = Resolver::new(".");
206 resolver.add_package_path("custom_modules");
207
208 assert_eq!(resolver.package_paths.len(), 3);
209 }
210}
211