Skip to main content

xs/nu/
vfs.rs

1use std::collections::HashMap;
2
3use nu_protocol::engine::{EngineState, StateWorkingSet, VirtualPath};
4
5use crate::store::Store;
6
7/// Load modules from a topic->hash map into the engine's VFS.
8///
9/// Each entry with topic `X.Y.Z.nu` is registered as:
10///   X/Y/Z/mod.nu
11///
12/// This allows scripts to write:
13///   use X/Y/Z
14pub fn load_modules(
15    engine_state: &mut EngineState,
16    store: &Store,
17    modules: &HashMap<String, ssri::Integrity>,
18) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
19    for (topic, hash) in modules {
20        let name = match topic.strip_suffix(".nu") {
21            Some(n) if !n.is_empty() => n,
22            _ => continue,
23        };
24        let content_bytes = store.cas_read_sync(hash)?;
25        let content = String::from_utf8(content_bytes)?;
26        register_module(engine_state, name, &content)?;
27    }
28    Ok(())
29}
30
31/// Register a single module by name and content into the engine's VFS.
32///
33/// testmod becomes:
34///   testmod/mod.nu  (virtual file)
35///   testmod          (virtual dir containing mod.nu)
36///
37/// discord.api becomes:
38///   discord/api/mod.nu
39///   discord/api      (virtual dir containing mod.nu)
40///   discord           (virtual dir containing api/)
41///
42/// Usage: `use testmod` imports module named "testmod"
43fn register_module(
44    engine_state: &mut EngineState,
45    name: &str,
46    content: &str,
47) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
48    let module_path = name.replace('.', "/");
49
50    let mut working_set = StateWorkingSet::new(engine_state);
51
52    // Register <module_path>/mod.nu as a virtual file
53    let virt_file_name = format!("{module_path}/mod.nu");
54    let file_id = working_set.add_file(virt_file_name.clone(), content.as_bytes());
55    let virt_file_id = working_set.add_virtual_path(virt_file_name, VirtualPath::File(file_id));
56
57    // Build directory chain from leaf to root:
58    // <module_path> -> contains mod.nu
59    // <parent>      -> contains <child>/
60    // ...
61    let segments: Vec<&str> = module_path.split('/').collect();
62    let mut child_id = virt_file_id;
63
64    for depth in (0..segments.len()).rev() {
65        let dir_path = if depth == 0 {
66            segments[0].to_string()
67        } else {
68            segments[..=depth].join("/")
69        };
70        child_id = working_set.add_virtual_path(dir_path, VirtualPath::Dir(vec![child_id]));
71    }
72
73    engine_state.merge_delta(working_set.render())?;
74
75    tracing::debug!("Registered VFS module: {}", module_path);
76
77    Ok(())
78}