Add virtual path abstraction layer (#9245)

This commit is contained in:
Jakub Žádník
2023-05-23 23:48:50 +03:00
committed by GitHub
parent db4b26c1ac
commit 74724dee80
19 changed files with 500 additions and 323 deletions

View File

@ -1,126 +1,136 @@
use nu_parser::{parse, parse_module_block};
use nu_protocol::report_error;
use nu_protocol::{engine::StateWorkingSet, Module, ShellError, Span};
use std::path::PathBuf;
fn add_file(
working_set: &mut StateWorkingSet,
name: &String,
content: &[u8],
) -> (Module, Vec<Span>) {
let file_id = working_set.add_file(name.clone(), content);
let new_span = working_set.get_span_for_file(file_id);
use nu_engine::{env::current_dir, eval_block};
use nu_parser::parse;
use nu_protocol::engine::{Stack, StateWorkingSet, VirtualPath};
use nu_protocol::{report_error, PipelineData};
let (_, module, comments) = parse_module_block(working_set, new_span, name.as_bytes());
if let Some(err) = working_set.parse_errors.first() {
report_error(working_set, err);
}
parse(working_set, Some(name), content, true);
if let Some(err) = working_set.parse_errors.first() {
report_error(working_set, err);
}
(module, comments)
}
fn load_prelude(working_set: &mut StateWorkingSet, prelude: Vec<(&str, &str)>, module: &Module) {
let mut decls = Vec::new();
let mut errs = Vec::new();
for (name, search_name) in prelude {
if let Some(id) = module.decls.get(&search_name.as_bytes().to_vec()) {
let decl = (name.as_bytes().to_vec(), id.to_owned());
decls.push(decl);
} else {
errs.push(ShellError::GenericError(
format!("could not load `{}` from `std`.", search_name),
String::new(),
None,
None,
Vec::new(),
));
}
}
if !errs.is_empty() {
report_error(
working_set,
&ShellError::GenericError(
"Unable to load the prelude of the standard library.".into(),
String::new(),
None,
Some("this is a bug: please file an issue in the [issue tracker](https://github.com/nushell/nushell/issues/new/choose)".to_string()),
errs,
),
);
}
working_set.use_decls(decls);
}
// Virtual std directory unlikely to appear in user's file system
const NU_STDLIB_VIRTUAL_DIR: &str = "NU_STDLIB_VIRTUAL_DIR";
pub fn load_standard_library(
engine_state: &mut nu_protocol::engine::EngineState,
) -> Result<(), miette::ErrReport> {
let delta = {
let name = "std".to_string();
let content = include_str!("../lib/mod.nu");
// these modules are loaded in the order they appear in this list
#[rustfmt::skip]
let submodules = vec![
// helper modules that could be used in other parts of the library
("log", include_str!("../lib/log.nu")),
// the rest of the library
("dirs", include_str!("../lib/dirs.nu")),
("iter", include_str!("../lib/iter.nu")),
("help", include_str!("../lib/help.nu")),
("testing", include_str!("../lib/testing.nu")),
("xml", include_str!("../lib/xml.nu")),
("dt", include_str!("../lib/dt.nu")),
];
// Define commands to be preloaded into the default (top level, unprefixed) namespace.
// User can invoke these without having to `use std` beforehand.
// Entries are: (name to add to default namespace, path under std to find implementation)
//
// Conventionally, for a command implemented as `std foo`, the name added
// is either `std foo` or bare `foo`, not some arbitrary rename.
#[rustfmt::skip]
let prelude = vec![
("std help", "help"),
("std help commands", "help commands"),
("std help aliases", "help aliases"),
("std help modules", "help modules"),
("std help externs", "help externs"),
("std help operators", "help operators"),
("enter", "dirs enter"),
("shells", "dirs shells"),
("g", "dirs g"),
("n", "dirs n"),
("p", "dirs p"),
("dexit", "dirs dexit"),
let (block, delta) = {
let mut std_files = vec![
(
PathBuf::from(NU_STDLIB_VIRTUAL_DIR)
.join("std")
.join("mod.nu"),
include_str!("../std/mod.nu"),
),
(
PathBuf::from(NU_STDLIB_VIRTUAL_DIR)
.join("std")
.join("dirs.nu"),
include_str!("../std/dirs.nu"),
),
(
PathBuf::from(NU_STDLIB_VIRTUAL_DIR)
.join("std")
.join("dt.nu"),
include_str!("../std/dt.nu"),
),
(
PathBuf::from(NU_STDLIB_VIRTUAL_DIR)
.join("std")
.join("help.nu"),
include_str!("../std/help.nu"),
),
(
PathBuf::from(NU_STDLIB_VIRTUAL_DIR)
.join("std")
.join("iter.nu"),
include_str!("../std/iter.nu"),
),
(
PathBuf::from(NU_STDLIB_VIRTUAL_DIR)
.join("std")
.join("log.nu"),
include_str!("../std/log.nu"),
),
(
PathBuf::from(NU_STDLIB_VIRTUAL_DIR)
.join("std")
.join("testing.nu"),
include_str!("../std/testing.nu"),
),
(
PathBuf::from(NU_STDLIB_VIRTUAL_DIR)
.join("std")
.join("xml.nu"),
include_str!("../std/xml.nu"),
),
];
let mut working_set = StateWorkingSet::new(engine_state);
let mut std_virt_paths = vec![];
for (name, content) in submodules {
let (module, comments) =
add_file(&mut working_set, &name.to_string(), content.as_bytes());
working_set.add_module(name, module, comments);
for (name, content) in std_files.drain(..) {
let file_id =
working_set.add_file(name.to_string_lossy().to_string(), content.as_bytes());
let virtual_file_id = working_set.add_virtual_path(
name.to_string_lossy().to_string(),
VirtualPath::File(file_id),
);
std_virt_paths.push(virtual_file_id);
}
let (module, comments) = add_file(&mut working_set, &name, content.as_bytes());
load_prelude(&mut working_set, prelude, &module);
working_set.add_module(&name, module, comments);
// Using full virtual path to avoid potential conflicts with user having 'std' directory
// in their working directory.
let std_dir = PathBuf::from(NU_STDLIB_VIRTUAL_DIR)
.join("std")
.to_string_lossy()
.to_string();
let source = format!(
r#"
# Define the `std` module
module {std_dir}
working_set.render()
# Prelude
use std dirs [ enter, shells, g, n, p, dexit ]
"#
);
let _ = working_set.add_virtual_path(std_dir, VirtualPath::Dir(std_virt_paths));
// Change the currently parsed directory
let prev_currently_parsed_cwd = working_set.currently_parsed_cwd.clone();
working_set.currently_parsed_cwd = Some(PathBuf::from(NU_STDLIB_VIRTUAL_DIR));
let block = parse(
&mut working_set,
Some("loading stdlib"),
source.as_bytes(),
false,
);
if let Some(err) = working_set.parse_errors.first() {
report_error(&working_set, err);
}
// Restore the currently parsed directory back
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
(block, working_set.render())
};
engine_state.merge_delta(delta)?;
// We need to evaluate the module in order to run the `export-env` blocks.
let mut stack = Stack::new();
let pipeline_data = PipelineData::Empty;
eval_block(
engine_state,
&mut stack,
&block,
pipeline_data,
false,
false,
)?;
let cwd = current_dir(engine_state, &stack)?;
engine_state.merge_env(&mut stack, cwd)?;
Ok(())
}