1#[macro_export]
16macro_rules! template {
17 ($path:literal) => {
18 include!(concat!(env!("OUT_DIR"), "/fusor_templates/", $path, ".rs"));
19 };
20}
21
22#[macro_export]
32macro_rules! bindings {
33 ($name:ident) => {
34 const __FUSOR_BINDINGS_ORIGIN: (&str, &str) = (file!(), env!("CARGO_MANIFEST_DIR"));
35 include!(concat!(
36 env!("OUT_DIR"),
37 "/fusor_",
38 stringify!($name),
39 ".rs"
40 ));
41 };
42}
43
44#[doc(hidden)]
47pub const fn source_matches(origin: (&str, &str), expected: &str) -> bool {
48 let file = native_path(origin.0.as_bytes());
49 let manifest = native_path(origin.1.as_bytes());
50 let expected = PathParts(b"", native_path(expected.as_bytes()));
51 if same_path(PathParts(b"", file), expected) {
52 return true;
53 }
54 let mut end = manifest.len();
58 loop {
59 if same_path(PathParts(manifest.split_at(end).0, file), expected) {
60 return true;
61 }
62 if end == 0 {
63 return false;
64 }
65 end -= 1;
66 while end > 0 && !separator(manifest[end]) {
67 end -= 1;
68 }
69 }
70}
71
72const fn native_path(path: &[u8]) -> &[u8] {
75 if path.len() >= 4
76 && separator(path[0])
77 && separator(path[1])
78 && path[2] == b'?'
79 && separator(path[3])
80 {
81 let path = path.split_at(4).1;
82 if path.len() >= 4
83 && path[0] == b'U'
84 && path[1] == b'N'
85 && path[2] == b'C'
86 && separator(path[3])
87 {
88 return path.split_at(4).1;
89 }
90 return path;
91 }
92 path
93}
94
95const fn separator(byte: u8) -> bool {
96 byte == b'/' || byte == b'\\'
97}
98
99#[derive(Clone, Copy)]
100struct PathParts<'a>(&'a [u8], &'a [u8]);
101impl PathParts<'_> {
102 const fn len(self) -> usize {
103 self.0.len() + 1 + self.1.len()
104 }
105 const fn at(self, index: usize) -> u8 {
106 if index < self.0.len() {
107 self.0[index]
108 } else if index == self.0.len() {
109 b'/'
110 } else {
111 self.1[index - self.0.len() - 1]
112 }
113 }
114}
115
116const fn previous(path: PathParts<'_>, mut end: usize) -> Option<(usize, usize)> {
117 let mut parents = 0;
118 while end > 0 {
119 while end > 0 && separator(path.at(end - 1)) {
120 end -= 1;
121 }
122 if end == 0 {
123 break;
124 }
125 let mut start = end;
126 while start > 0 && !separator(path.at(start - 1)) {
127 start -= 1;
128 }
129 if end - start == 2 && path.at(start) == b'.' && path.at(start + 1) == b'.' {
130 parents += 1;
131 } else if end - start == 1 && path.at(start) == b'.' {
132 } else if parents > 0 {
133 parents -= 1;
134 } else {
135 return Some((start, end));
136 }
137 end = start;
138 }
139 None
140}
141
142const fn same_path(left: PathParts<'_>, right: PathParts<'_>) -> bool {
143 let mut a = left.len();
144 let mut b = right.len();
145 loop {
146 match (previous(left, a), previous(right, b)) {
147 (None, None) => return true,
148 (Some((start_a, end_a)), Some((start_b, end_b))) => {
149 if end_a - start_a != end_b - start_b {
150 return false;
151 }
152 let mut index = 0;
153 while index < end_a - start_a {
154 if left.at(start_a + index) != right.at(start_b + index) {
155 return false;
156 }
157 index += 1;
158 }
159 a = start_a;
160 b = start_b;
161 }
162 _ => return false,
163 }
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::source_matches;
170
171 #[test]
172 fn source_identity_handles_cargo_and_native_module_paths() {
173 for (origin, expected) in [
174 (("src/app.rs", "/app"), "/app/src/app.rs"),
175 (("/app/src/app.rs", "/app"), "/app/src/app.rs"),
176 (
177 ("examples/app/src/app.rs", "/workspace/examples/app"),
178 "/workspace/examples/app/web/../src/app.rs",
179 ),
180 (("src/../web/page.rs", "/app"), "/app/web/page.rs"),
181 (("src\\app.rs", "C:\\app"), "C:/app/src/app.rs"),
182 (("src\\app.rs", "C:\\app"), "\\\\?\\C:\\app\\src\\app.rs"),
183 (
184 ("src\\app.rs", "\\\\server\\app"),
185 "\\\\?\\UNC\\server\\app\\src\\app.rs",
186 ),
187 (
188 ("./src/nested/../../web/page.rs", "/app"),
189 "/app/web/page.rs",
190 ),
191 ] {
192 assert!(source_matches(origin, expected));
193 assert!(!source_matches(origin, "/another/page.rs"));
194 assert!(!source_matches(origin, "/app/other/src/app.rs"));
195 }
196 }
197}