cross-stream 0.13.1

An event stream store for personal, local-first use, specializing in event sourcing.
Documentation
use std::collections::HashMap;

use nu_protocol::engine::{EngineState, StateWorkingSet, VirtualPath};

use crate::store::Store;

/// Load modules from a name->hash map into the engine's VFS.
///
/// Each entry with name `X.Y.Z` is registered as:
///   X/Y/Z/mod.nu
///
/// This allows scripts to write:
///   use X/Y/Z
///
/// The map keys are the module names (after `xs.module.` was stripped by
/// `Store::nu_modules_at`), not the full topic.
pub fn load_modules(
    engine_state: &mut EngineState,
    store: &Store,
    modules: &HashMap<String, ssri::Integrity>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    for (name, hash) in modules {
        if name.is_empty() {
            continue;
        }
        let content_bytes = store.cas_read_sync(hash)?;
        let content = String::from_utf8(content_bytes)?;
        register_module(engine_state, name, &content)?;
    }
    Ok(())
}

/// Register a single module by name and content into the engine's VFS.
///
/// testmod becomes:
///   testmod/mod.nu  (virtual file)
///   testmod          (virtual dir containing mod.nu)
///
/// discord.api becomes:
///   discord/api/mod.nu
///   discord/api      (virtual dir containing mod.nu)
///   discord           (virtual dir containing api/)
///
/// Usage: `use testmod` imports module named "testmod"
fn register_module(
    engine_state: &mut EngineState,
    name: &str,
    content: &str,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let module_path = name.replace('.', "/");

    let mut working_set = StateWorkingSet::new(engine_state);

    // Register <module_path>/mod.nu as a virtual file
    let virt_file_name = format!("{module_path}/mod.nu");
    let file_id = working_set.add_file(&virt_file_name, content.as_bytes());
    let virt_file_id = working_set.add_virtual_path(virt_file_name, VirtualPath::File(file_id));

    // Build directory chain from leaf to root:
    // <module_path> -> contains mod.nu
    // <parent>      -> contains <child>/
    // ...
    let segments: Vec<&str> = module_path.split('/').collect();
    let mut child_id = virt_file_id;

    for depth in (0..segments.len()).rev() {
        let dir_path = if depth == 0 {
            segments[0].to_string()
        } else {
            segments[..=depth].join("/")
        };
        child_id = working_set.add_virtual_path(dir_path, VirtualPath::Dir(vec![child_id]));
    }

    engine_state.merge_delta(working_set.render())?;

    tracing::debug!("Registered VFS module: {}", module_path);

    Ok(())
}