kcl_lib/execution/
typed_path.rs

1//! A typed path type so that in wasm we can track if its a windows or unix path.
2//! On non-wasm platforms, this is just a std::path::PathBuf.
3
4#[derive(Clone, Debug, PartialEq, Eq, Hash)]
5pub struct TypedPath(
6    #[cfg(target_arch = "wasm32")] pub typed_path::TypedPathBuf,
7    #[cfg(not(target_arch = "wasm32"))] pub std::path::PathBuf,
8);
9
10impl std::fmt::Display for TypedPath {
11    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12        #[cfg(target_arch = "wasm32")]
13        {
14            self.0.to_path().display().fmt(f)
15        }
16        #[cfg(not(target_arch = "wasm32"))]
17        {
18            self.0.display().fmt(f)
19        }
20    }
21}
22
23impl Default for TypedPath {
24    fn default() -> Self {
25        #[cfg(target_arch = "wasm32")]
26        {
27            TypedPath(typed_path::TypedPath::derive("").to_path_buf())
28        }
29        #[cfg(not(target_arch = "wasm32"))]
30        {
31            TypedPath(std::path::PathBuf::new())
32        }
33    }
34}
35
36impl From<&String> for TypedPath {
37    fn from(path: &String) -> Self {
38        TypedPath::new(path)
39    }
40}
41
42impl From<&str> for TypedPath {
43    fn from(path: &str) -> Self {
44        TypedPath::new(path)
45    }
46}
47
48impl TypedPath {
49    pub fn new(path: &str) -> Self {
50        #[cfg(target_arch = "wasm32")]
51        {
52            TypedPath(typed_path::TypedPath::derive(path).to_path_buf())
53        }
54        #[cfg(not(target_arch = "wasm32"))]
55        {
56            TypedPath(normalise_import(path))
57        }
58    }
59
60    pub fn extension(&self) -> Option<&str> {
61        #[cfg(target_arch = "wasm32")]
62        {
63            self.0
64                .extension()
65                .map(|s| std::str::from_utf8(s).map(|s| s.trim_start_matches('.')).unwrap_or(""))
66                .filter(|s| !s.is_empty())
67        }
68        #[cfg(not(target_arch = "wasm32"))]
69        {
70            self.0.extension().and_then(|s| s.to_str())
71        }
72    }
73
74    pub fn join(&self, path: &str) -> Self {
75        #[cfg(target_arch = "wasm32")]
76        {
77            TypedPath(self.0.join(path))
78        }
79        #[cfg(not(target_arch = "wasm32"))]
80        {
81            TypedPath(self.0.join(path))
82        }
83    }
84
85    pub fn join_typed(&self, path: &TypedPath) -> Self {
86        #[cfg(target_arch = "wasm32")]
87        {
88            TypedPath(self.0.join(path.0.to_path()))
89        }
90        #[cfg(not(target_arch = "wasm32"))]
91        {
92            TypedPath(self.0.join(&path.0))
93        }
94    }
95
96    pub fn parent(&self) -> Option<Self> {
97        #[cfg(target_arch = "wasm32")]
98        {
99            self.0.parent().map(|p| TypedPath(p.to_path_buf()))
100        }
101        #[cfg(not(target_arch = "wasm32"))]
102        {
103            self.0.parent().map(|p| TypedPath(p.to_path_buf()))
104        }
105    }
106
107    pub fn to_string_lossy(&self) -> String {
108        #[cfg(target_arch = "wasm32")]
109        {
110            self.0.to_path().to_string_lossy().to_string()
111        }
112        #[cfg(not(target_arch = "wasm32"))]
113        {
114            self.0.to_string_lossy().to_string()
115        }
116    }
117
118    pub fn display(&self) -> String {
119        #[cfg(target_arch = "wasm32")]
120        {
121            self.0.to_path().display().to_string()
122        }
123        #[cfg(not(target_arch = "wasm32"))]
124        {
125            self.0.display().to_string()
126        }
127    }
128
129    pub fn file_name(&self) -> Option<String> {
130        #[cfg(target_arch = "wasm32")]
131        {
132            self.0
133                .file_name()
134                .map(|s| std::str::from_utf8(s).unwrap_or(""))
135                .filter(|s| !s.is_empty())
136                .map(|s| s.to_string())
137        }
138        #[cfg(not(target_arch = "wasm32"))]
139        {
140            self.0.file_name().and_then(|s| s.to_str()).map(|s| s.to_string())
141        }
142    }
143}
144
145impl serde::Serialize for TypedPath {
146    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
147    where
148        S: serde::Serializer,
149    {
150        #[cfg(target_arch = "wasm32")]
151        {
152            self.0.to_str().serialize(serializer)
153        }
154        #[cfg(not(target_arch = "wasm32"))]
155        {
156            self.0.serialize(serializer)
157        }
158    }
159}
160
161impl<'de> serde::de::Deserialize<'de> for TypedPath {
162    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
163    where
164        D: serde::Deserializer<'de>,
165    {
166        #[cfg(target_arch = "wasm32")]
167        {
168            let path: String = serde::Deserialize::deserialize(deserializer)?;
169            Ok(TypedPath(typed_path::TypedPath::derive(&path).to_path_buf()))
170        }
171        #[cfg(not(target_arch = "wasm32"))]
172        {
173            let path: std::path::PathBuf = serde::Deserialize::deserialize(deserializer)?;
174            Ok(TypedPath(path))
175        }
176    }
177}
178
179impl ts_rs::TS for TypedPath {
180    type WithoutGenerics = Self;
181
182    fn name() -> String {
183        "string".to_string()
184    }
185
186    fn decl() -> String {
187        std::path::PathBuf::decl()
188    }
189
190    fn decl_concrete() -> String {
191        std::path::PathBuf::decl_concrete()
192    }
193
194    fn inline() -> String {
195        std::path::PathBuf::inline()
196    }
197
198    fn inline_flattened() -> String {
199        std::path::PathBuf::inline_flattened()
200    }
201
202    fn output_path() -> Option<&'static std::path::Path> {
203        std::path::PathBuf::output_path()
204    }
205}
206
207impl schemars::JsonSchema for TypedPath {
208    fn schema_name() -> String {
209        "TypedPath".to_owned()
210    }
211
212    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
213        // TODO: Actually generate a reasonable schema.
214        gen.subschema_for::<std::path::PathBuf>()
215    }
216}
217
218/// Turn `nested\foo\bar\main.kcl` or `nested/foo/bar/main.kcl`
219/// into a PathBuf that works on the host OS.
220///
221/// * Does **not** touch `..` or symlinks – call `canonicalize()` if you need that.
222/// * Returns an owned `PathBuf` only when normalisation was required.
223fn normalise_import<S: AsRef<str>>(raw: S) -> std::path::PathBuf {
224    let s = raw.as_ref();
225    // On Unix we need to swap `\` → `/`.  On Windows we leave it alone.
226    // (Windows happily consumes `/`)
227    if cfg!(unix) && s.contains('\\') {
228        std::path::PathBuf::from(s.replace('\\', "/"))
229    } else {
230        std::path::Path::new(s).to_path_buf()
231    }
232}