forked from argotorg/fe
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstdlib.rs
More file actions
195 lines (166 loc) · 6.28 KB
/
Copy pathstdlib.rs
File metadata and controls
195 lines (166 loc) · 6.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
use std::fs;
use camino::{Utf8Path, Utf8PathBuf};
use rust_embed::Embed;
use url::Url;
use crate::{
InputDb,
ingot::{Ingot, IngotBaseUrl},
};
// Use the canonical single-slash form for custom-scheme base URLs.
// `Url::join` normalizes children under `builtin-core:/...`, so lookups must
// use the same base form or builtin ingots appear empty.
pub static BUILTIN_CORE_BASE_URL: &str = "builtin-core:/";
pub static BUILTIN_STD_BASE_URL: &str = "builtin-std:/";
fn is_library_file(path: &Utf8Path) -> bool {
matches!(path.file_name(), Some("fe.toml")) || matches!(path.extension(), Some("fe"))
}
fn initialize_builtin<E: Embed>(db: &mut dyn InputDb, base_url: &str) {
let base = Url::parse(base_url).unwrap();
// Ensure deterministic file insertion order across platforms/builds.
// This matters because downstream iteration over workspace tries is depth-first.
let mut paths = E::iter()
.map(|path| Utf8PathBuf::from(path.to_string()))
.collect::<Vec<_>>();
paths.sort();
for path in paths {
if !is_library_file(&path) {
continue;
}
let contents = String::from_utf8(
E::get(path.as_str())
.unwrap_or_else(|| panic!("missing embedded builtin `{path}`"))
.data
.into_owned(),
)
.unwrap_or_else(|_| panic!("embedded builtin `{path}` must be UTF-8"));
base.touch(db, path, contents.into());
}
}
fn load_library_dir(db: &mut dyn InputDb, base_url: &str, root: &Utf8Path) -> Result<(), String> {
let base = Url::parse(base_url).map_err(|_| "invalid base url".to_string())?;
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
let entries = fs::read_dir(dir.as_std_path())
.map_err(|err| format!("Failed to read {}: {err}", dir))?
.map(|entry| {
let entry = entry.map_err(|err| format!("Failed to read entry: {err}"))?;
let path = Utf8PathBuf::from_path_buf(entry.path())
.map_err(|_| "Library path is not UTF-8".to_string())?;
let file_type = entry
.file_type()
.map_err(|err| format!("Failed to read file type: {err}"))?;
Ok((path, file_type))
})
.collect::<Result<Vec<_>, String>>()?;
// Ensure deterministic traversal order across platforms/filesystems.
let mut entries = entries;
entries.sort_by(|(lhs, _), (rhs, _)| lhs.cmp(rhs));
for (path, file_type) in entries {
if file_type.is_dir() {
stack.push(path);
continue;
}
if !is_library_file(&path) {
continue;
}
let relative = path
.strip_prefix(root)
.map_err(|_| "Library path escaped root".to_string())?;
let url = base
.join(relative.as_str())
.map_err(|_| "Failed to join library path".to_string())?;
let content = fs::read_to_string(path.as_std_path())
.map_err(|err| format!("Failed to read {}: {err}", path))?;
db.workspace().update(db, url, content);
}
}
Ok(())
}
pub fn load_library_from_path(db: &mut dyn InputDb, library_root: &Utf8Path) -> Result<(), String> {
let core_root = library_root.join("core");
let std_root = library_root.join("std");
// Clear embedded builtins first so stale files from the embedded version
// don't leak into the index when the on-disk version has fewer files.
clear_library(db, BUILTIN_CORE_BASE_URL);
clear_library(db, BUILTIN_STD_BASE_URL);
load_library_dir(db, BUILTIN_CORE_BASE_URL, &core_root)?;
load_library_dir(db, BUILTIN_STD_BASE_URL, &std_root)?;
Ok(())
}
fn clear_library(db: &mut dyn InputDb, base_url: &str) {
let base = Url::parse(base_url).unwrap();
let workspace = db.workspace();
let urls: Vec<Url> = workspace
.items_at_base(db, base)
.iter()
.map(|(url, _)| url.clone())
.collect();
for url in urls {
workspace.remove(db, &url);
}
}
#[derive(Embed)]
#[folder = "../../ingots/core"]
pub struct Core;
pub trait HasBuiltinCore: InputDb {
fn initialize_builtin_core(&mut self);
fn builtin_core(&self) -> Ingot<'_>;
}
impl<T: InputDb> HasBuiltinCore for T {
fn initialize_builtin_core(&mut self) {
initialize_builtin::<Core>(self, BUILTIN_CORE_BASE_URL);
}
fn builtin_core(&self) -> Ingot<'_> {
let core = self
.workspace()
.containing_ingot(self, Url::parse(BUILTIN_CORE_BASE_URL).unwrap());
core.expect("Built-in core ingot failed to initialize")
}
}
#[derive(Embed)]
#[folder = "../../ingots/std"]
pub struct Std;
pub trait HasBuiltinStd: InputDb {
fn initialize_builtin_std(&mut self);
fn builtin_std(&self) -> Ingot<'_>;
}
impl<T: InputDb> HasBuiltinStd for T {
fn initialize_builtin_std(&mut self) {
initialize_builtin::<Std>(self, BUILTIN_STD_BASE_URL);
}
fn builtin_std(&self) -> Ingot<'_> {
let std = self
.workspace()
.containing_ingot(self, Url::parse(BUILTIN_STD_BASE_URL).unwrap());
std.expect("Built-in std ingot failed to initialize")
}
}
#[cfg(test)]
mod tests {
use camino::Utf8Path;
use super::{HasBuiltinCore, HasBuiltinStd, is_library_file};
use crate::define_input_db;
define_input_db!(TestDb);
#[test]
fn library_loader_filters_non_fe_files() {
assert!(is_library_file(Utf8Path::new("fe.toml")));
assert!(is_library_file(Utf8Path::new("src/lib.fe")));
assert!(!is_library_file(Utf8Path::new(".DS_Store")));
assert!(!is_library_file(Utf8Path::new("src/lib.rs")));
assert!(!is_library_file(Utf8Path::new("README.md")));
}
#[test]
fn builtin_ingots_are_indexed_under_their_lookup_urls() {
let db = TestDb::default();
let core = db.builtin_core();
let std = db.builtin_std();
assert!(
core.files(&db).iter().next().is_some(),
"builtin core ingot should contain indexed files"
);
assert!(
std.files(&db).iter().next().is_some(),
"builtin std ingot should contain indexed files"
);
}
}