Skip to main content

dada_compiler/
vfs.rs

1use std::path::Path;
2
3use dada_util::Fallible;
4use url::Url;
5
6pub trait VirtualFileSystem: Send + Sync + 'static {
7    /// Loads the contents of the given URL (or fail with a useful error).
8    fn contents(&self, url: &Url) -> Fallible<String>;
9
10    /// True if the given URL exists.
11    fn exists(&self, url: &Url) -> bool;
12
13    /// (Try to) convert a path on the local file system to a URL
14    fn path_url(&self, path: &Path) -> Fallible<Url>;
15
16    /// Return a string for the way we should display `url` to the user
17    fn url_display(&self, url: &Url) -> String;
18}
19
20#[derive(Clone, Debug)]
21pub(crate) struct UrlPath {
22    source_url: Url,
23    paths: Vec<String>,
24}
25
26impl From<Url> for UrlPath {
27    fn from(url: Url) -> Self {
28        let paths = url
29            .path()
30            .split("/")
31            .filter(|s| !s.is_empty())
32            .map(|s| s.to_string())
33            .collect();
34
35        Self {
36            source_url: url,
37            paths,
38        }
39    }
40}
41
42impl UrlPath {
43    pub fn is_empty(&self) -> bool {
44        self.paths.is_empty()
45    }
46
47    /// Removes the final component (if any).
48    /// Result will never be a dada file.
49    pub fn pop(mut self) -> Self {
50        self.paths.pop();
51        self
52    }
53
54    /// Append a component.
55    pub fn push(&mut self, s: &str) {
56        assert!(!self.is_dada_file());
57        self.paths.push(s.to_string());
58    }
59
60    /// True if final component ends in `.dada`
61    pub fn is_dada_file(&self) -> bool {
62        let Some(last) = self.paths.last() else {
63            return false;
64        };
65
66        last.ends_with(".dada")
67    }
68
69    /// True if final component ends in `.dada`
70    pub fn final_module_name(&self) -> &str {
71        assert!(self.is_dada_file());
72
73        let Some(last) = self.paths.last() else {
74            unreachable!()
75        };
76
77        &last[0..last.len() - ".dada".len()]
78    }
79    /// Remove `.dada` suffix from final component.
80    ///
81    /// # Panics
82    ///
83    /// Panics if final component does not end in `.dada`
84    pub fn make_directory(mut self) -> Self {
85        assert!(self.is_dada_file());
86
87        let Some(last) = self.paths.last_mut() else {
88            unreachable!()
89        };
90
91        assert!(last.ends_with(".dada"));
92        last.truncate(last.len() - ".dada".len());
93        self
94    }
95
96    /// Add `.dada` suffix to final component.
97    ///
98    /// # Panics
99    ///
100    /// Panics if final component already has `.dada`
101    pub fn make_dada_file(mut self) -> Self {
102        assert!(!self.is_dada_file());
103
104        let Some(last) = self.paths.last_mut() else {
105            self.paths.push(".dada".to_string());
106            return self;
107        };
108
109        last.push_str(".dada");
110        self
111    }
112
113    /// Create a URL for this path with a `.dada` extension
114    ///
115    /// # Panics
116    ///
117    /// Panics if this path already has a `.dada` extension
118    pub fn dada_url(&self) -> Url {
119        assert!(!self.is_dada_file());
120        let mut path = self.paths.join("/");
121        path.push_str(".dada");
122
123        let mut url = self.source_url.clone();
124        url.set_path(&path);
125
126        url
127    }
128
129    /// Convert this path back into a URL
130    pub fn url(&self) -> Url {
131        let path = self.paths.join("/");
132        let mut url = self.source_url.clone();
133        url.set_path(&path);
134        url
135    }
136}
137
138pub trait ToUrl {
139    fn to_url(&self, vfs: &dyn VirtualFileSystem) -> Fallible<Url>;
140}
141
142impl ToUrl for str {
143    fn to_url(&self, _vfs: &dyn VirtualFileSystem) -> Fallible<Url> {
144        Ok(Url::parse(self)?)
145    }
146}
147
148impl ToUrl for Url {
149    fn to_url(&self, _vfs: &dyn VirtualFileSystem) -> Fallible<Url> {
150        Ok(self.clone())
151    }
152}
153
154impl ToUrl for Path {
155    fn to_url(&self, vfs: &dyn VirtualFileSystem) -> Fallible<Url> {
156        vfs.path_url(self)
157    }
158}