mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 08:36:10 +02:00
Add virtual path abstraction layer (#9245)
This commit is contained in:
@ -7,6 +7,7 @@ mod lite_parser;
|
||||
mod parse_keywords;
|
||||
mod parse_patterns;
|
||||
mod parser;
|
||||
mod parser_path;
|
||||
mod type_check;
|
||||
|
||||
pub use deparse::{escape_for_script_arg, escape_quote_string};
|
||||
@ -17,6 +18,7 @@ pub use known_external::KnownExternal;
|
||||
pub use lex::{lex, lex_signature, Token, TokenContents};
|
||||
pub use lite_parser::{lite_parse, LiteBlock, LiteElement};
|
||||
pub use parse_keywords::*;
|
||||
pub use parser_path::*;
|
||||
|
||||
pub use parser::{
|
||||
is_math_expression_like, parse, parse_block, parse_expression, parse_external_call,
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::parser_path::ParserPath;
|
||||
use itertools::Itertools;
|
||||
use log::trace;
|
||||
use nu_path::canonicalize_with;
|
||||
@ -1724,14 +1725,14 @@ pub fn parse_module_block(
|
||||
|
||||
fn parse_module_file(
|
||||
working_set: &mut StateWorkingSet,
|
||||
path: PathBuf,
|
||||
path: ParserPath,
|
||||
path_span: Span,
|
||||
name_override: Option<String>,
|
||||
) -> Option<ModuleId> {
|
||||
if let Some(i) = working_set
|
||||
.parsed_module_files
|
||||
.iter()
|
||||
.rposition(|p| p == &path)
|
||||
.rposition(|p| p == path.path())
|
||||
{
|
||||
let mut files: Vec<String> = working_set
|
||||
.parsed_module_files
|
||||
@ -1740,7 +1741,7 @@ fn parse_module_file(
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.collect();
|
||||
|
||||
files.push(path.to_string_lossy().to_string());
|
||||
files.push(path.path().to_string_lossy().to_string());
|
||||
|
||||
let msg = files.join("\nuses ");
|
||||
|
||||
@ -1757,14 +1758,14 @@ fn parse_module_file(
|
||||
return None;
|
||||
};
|
||||
|
||||
let contents = if let Ok(contents) = std::fs::read(&path) {
|
||||
let contents = if let Some(contents) = path.read(working_set) {
|
||||
contents
|
||||
} else {
|
||||
working_set.error(ParseError::ModuleNotFound(path_span));
|
||||
return None;
|
||||
};
|
||||
|
||||
let file_id = working_set.add_file(path.to_string_lossy().to_string(), &contents);
|
||||
let file_id = working_set.add_file(path.path().to_string_lossy().to_string(), &contents);
|
||||
let new_span = working_set.get_span_for_file(file_id);
|
||||
|
||||
if let Some(module_id) = working_set.find_module_by_span(new_span) {
|
||||
@ -1773,17 +1774,13 @@ fn parse_module_file(
|
||||
|
||||
// Change the currently parsed directory
|
||||
let prev_currently_parsed_cwd = if let Some(parent) = path.parent() {
|
||||
let prev = working_set.currently_parsed_cwd.clone();
|
||||
|
||||
working_set.currently_parsed_cwd = Some(parent.into());
|
||||
|
||||
prev
|
||||
working_set.currently_parsed_cwd.replace(parent.into())
|
||||
} else {
|
||||
working_set.currently_parsed_cwd.clone()
|
||||
};
|
||||
|
||||
// Add the file to the stack of parsed module files
|
||||
working_set.parsed_module_files.push(path);
|
||||
working_set.parsed_module_files.push(path.path_buf());
|
||||
|
||||
// Parse the module
|
||||
let (block, module, module_comments) =
|
||||
@ -1824,91 +1821,87 @@ pub fn parse_module_file_or_dir(
|
||||
};
|
||||
|
||||
if module_path.is_dir() {
|
||||
if let Ok(dir_contents) = std::fs::read_dir(&module_path) {
|
||||
let module_name = if let Some(stem) = module_path.file_stem() {
|
||||
stem.to_string_lossy().to_string()
|
||||
} else {
|
||||
working_set.error(ParseError::ModuleNotFound(path_span));
|
||||
return None;
|
||||
};
|
||||
let Some(dir_contents) = module_path.read_dir() else {
|
||||
working_set.error(ParseError::ModuleNotFound(path_span));
|
||||
return None;
|
||||
};
|
||||
|
||||
let mod_nu_path = module_path.join("mod.nu");
|
||||
|
||||
if !(mod_nu_path.exists() && mod_nu_path.is_file()) {
|
||||
working_set.error(ParseError::ModuleMissingModNuFile(path_span));
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut paths = vec![];
|
||||
|
||||
for entry in dir_contents.flatten() {
|
||||
let entry_path = entry.path();
|
||||
|
||||
if (entry_path.is_file()
|
||||
&& entry_path.extension() == Some(OsStr::new("nu"))
|
||||
&& entry_path.file_stem() != Some(OsStr::new("mod")))
|
||||
|| (entry_path.is_dir() && entry_path.join("mod.nu").exists())
|
||||
{
|
||||
if entry_path.file_stem() == Some(OsStr::new(&module_name)) {
|
||||
working_set.error(ParseError::InvalidModuleFileName(
|
||||
module_path.to_string_lossy().to_string(),
|
||||
module_name,
|
||||
path_span,
|
||||
));
|
||||
return None;
|
||||
}
|
||||
|
||||
paths.push(entry_path);
|
||||
}
|
||||
}
|
||||
|
||||
paths.sort();
|
||||
|
||||
// working_set.enter_scope();
|
||||
|
||||
let mut submodules = vec![];
|
||||
|
||||
for p in paths {
|
||||
if let Some(submodule_id) = parse_module_file_or_dir(
|
||||
working_set,
|
||||
p.to_string_lossy().as_bytes(),
|
||||
path_span,
|
||||
None,
|
||||
) {
|
||||
let submodule_name = working_set.get_module(submodule_id).name();
|
||||
submodules.push((submodule_name, submodule_id));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(module_id) = parse_module_file(
|
||||
working_set,
|
||||
mod_nu_path,
|
||||
path_span,
|
||||
name_override.or(Some(module_name)),
|
||||
) {
|
||||
let mut module = working_set.get_module(module_id).clone();
|
||||
|
||||
for (submodule_name, submodule_id) in submodules {
|
||||
module.add_submodule(submodule_name, submodule_id);
|
||||
}
|
||||
|
||||
let module_name = String::from_utf8_lossy(&module.name).to_string();
|
||||
|
||||
let module_comments =
|
||||
if let Some(comments) = working_set.get_module_comments(module_id) {
|
||||
comments.to_vec()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let new_module_id = working_set.add_module(&module_name, module, module_comments);
|
||||
|
||||
Some(new_module_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let module_name = if let Some(stem) = module_path.file_stem() {
|
||||
stem.to_string_lossy().to_string()
|
||||
} else {
|
||||
working_set.error(ParseError::ModuleNotFound(path_span));
|
||||
return None;
|
||||
};
|
||||
|
||||
let mod_nu_path = module_path.clone().join("mod.nu");
|
||||
|
||||
if !(mod_nu_path.exists() && mod_nu_path.is_file()) {
|
||||
working_set.error(ParseError::ModuleMissingModNuFile(path_span));
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut paths = vec![];
|
||||
|
||||
for entry_path in dir_contents {
|
||||
if (entry_path.is_file()
|
||||
&& entry_path.extension() == Some(OsStr::new("nu"))
|
||||
&& entry_path.file_stem() != Some(OsStr::new("mod")))
|
||||
|| (entry_path.is_dir() && entry_path.clone().join("mod.nu").exists())
|
||||
{
|
||||
if entry_path.file_stem() == Some(OsStr::new(&module_name)) {
|
||||
working_set.error(ParseError::InvalidModuleFileName(
|
||||
module_path.path().to_string_lossy().to_string(),
|
||||
module_name,
|
||||
path_span,
|
||||
));
|
||||
return None;
|
||||
}
|
||||
|
||||
paths.push(entry_path);
|
||||
}
|
||||
}
|
||||
|
||||
paths.sort();
|
||||
|
||||
let mut submodules = vec![];
|
||||
|
||||
for p in paths {
|
||||
if let Some(submodule_id) = parse_module_file_or_dir(
|
||||
working_set,
|
||||
p.path().to_string_lossy().as_bytes(),
|
||||
path_span,
|
||||
None,
|
||||
) {
|
||||
let submodule_name = working_set.get_module(submodule_id).name();
|
||||
submodules.push((submodule_name, submodule_id));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(module_id) = parse_module_file(
|
||||
working_set,
|
||||
mod_nu_path,
|
||||
path_span,
|
||||
name_override.or(Some(module_name)),
|
||||
) {
|
||||
let mut module = working_set.get_module(module_id).clone();
|
||||
|
||||
for (submodule_name, submodule_id) in submodules {
|
||||
module.add_submodule(submodule_name, submodule_id);
|
||||
}
|
||||
|
||||
let module_name = String::from_utf8_lossy(&module.name).to_string();
|
||||
|
||||
let module_comments = if let Some(comments) = working_set.get_module_comments(module_id)
|
||||
{
|
||||
comments.to_vec()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let new_module_id = working_set.add_module(&module_name, module, module_comments);
|
||||
|
||||
Some(new_module_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if module_path.is_file() {
|
||||
@ -2017,19 +2010,13 @@ pub fn parse_module(
|
||||
}]);
|
||||
|
||||
if spans.len() == split_id + 1 {
|
||||
let cwd = working_set.get_cwd();
|
||||
|
||||
if let Some(module_path) =
|
||||
find_in_dirs(&module_name_or_path, working_set, &cwd, LIB_DIRS_VAR)
|
||||
{
|
||||
let path_str = module_path.to_string_lossy().to_string();
|
||||
let maybe_module_id = parse_module_file_or_dir(
|
||||
working_set,
|
||||
path_str.as_bytes(),
|
||||
module_name_or_path_span,
|
||||
None,
|
||||
);
|
||||
return (pipeline, maybe_module_id);
|
||||
if let Some(module_id) = parse_module_file_or_dir(
|
||||
working_set,
|
||||
module_name_or_path.as_bytes(),
|
||||
module_name_or_path_span,
|
||||
None,
|
||||
) {
|
||||
return (pipeline, Some(module_id));
|
||||
} else {
|
||||
working_set.error(ParseError::ModuleNotFound(module_name_or_path_span));
|
||||
return (pipeline, None);
|
||||
@ -3043,14 +3030,10 @@ pub fn parse_source(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeli
|
||||
};
|
||||
|
||||
if let Some(path) = find_in_dirs(&filename, working_set, &cwd, LIB_DIRS_VAR) {
|
||||
if let Ok(contents) = std::fs::read(&path) {
|
||||
if let Some(contents) = path.read(working_set) {
|
||||
// Change currently parsed directory
|
||||
let prev_currently_parsed_cwd = if let Some(parent) = path.parent() {
|
||||
let prev = working_set.currently_parsed_cwd.clone();
|
||||
|
||||
working_set.currently_parsed_cwd = Some(parent.into());
|
||||
|
||||
prev
|
||||
working_set.currently_parsed_cwd.replace(parent.into())
|
||||
} else {
|
||||
working_set.currently_parsed_cwd.clone()
|
||||
};
|
||||
@ -3059,7 +3042,7 @@ pub fn parse_source(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeli
|
||||
// working set, if it was a successful parse.
|
||||
let block = parse(
|
||||
working_set,
|
||||
Some(&path.to_string_lossy()),
|
||||
Some(&path.path().to_string_lossy()),
|
||||
&contents,
|
||||
scoped,
|
||||
);
|
||||
@ -3300,6 +3283,7 @@ pub fn parse_register(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipe
|
||||
nu_engine::env::env_to_strings(working_set.permanent_state, &stack).unwrap_or_default();
|
||||
let error = match signature {
|
||||
Some(signature) => arguments.and_then(|(path, path_span)| {
|
||||
let path = path.path_buf();
|
||||
// restrict plugin file name starts with `nu_plugin_`
|
||||
let valid_plugin_name = path
|
||||
.file_name()
|
||||
@ -3320,13 +3304,14 @@ pub fn parse_register(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipe
|
||||
}
|
||||
}),
|
||||
None => arguments.and_then(|(path, path_span)| {
|
||||
let path = path.path_buf();
|
||||
// restrict plugin file name starts with `nu_plugin_`
|
||||
let valid_plugin_name = path
|
||||
.file_name()
|
||||
.map(|s| s.to_string_lossy().starts_with("nu_plugin_"));
|
||||
|
||||
if let Some(true) = valid_plugin_name {
|
||||
get_signature(path.as_path(), &shell, ¤t_envs)
|
||||
get_signature(&path, &shell, ¤t_envs)
|
||||
.map_err(|err| {
|
||||
ParseError::LabeledError(
|
||||
"Error getting signatures".into(),
|
||||
@ -3393,28 +3378,52 @@ pub fn find_in_dirs(
|
||||
working_set: &StateWorkingSet,
|
||||
cwd: &str,
|
||||
dirs_var_name: &str,
|
||||
) -> Option<PathBuf> {
|
||||
) -> Option<ParserPath> {
|
||||
pub fn find_in_dirs_with_id(
|
||||
filename: &str,
|
||||
working_set: &StateWorkingSet,
|
||||
cwd: &str,
|
||||
dirs_var_name: &str,
|
||||
) -> Option<PathBuf> {
|
||||
) -> Option<ParserPath> {
|
||||
// Choose whether to use file-relative or PWD-relative path
|
||||
let actual_cwd = if let Some(currently_parsed_cwd) = &working_set.currently_parsed_cwd {
|
||||
currently_parsed_cwd.as_path()
|
||||
} else {
|
||||
Path::new(cwd)
|
||||
};
|
||||
if let Ok(p) = canonicalize_with(filename, actual_cwd) {
|
||||
return Some(p);
|
||||
|
||||
// Try if we have an existing virtual path
|
||||
if let Some(virtual_path) = working_set.find_virtual_path(filename) {
|
||||
return Some(ParserPath::from_virtual_path(
|
||||
working_set,
|
||||
filename,
|
||||
virtual_path,
|
||||
));
|
||||
} else {
|
||||
let abs_virtual_filename = actual_cwd.join(filename);
|
||||
let abs_virtual_filename = abs_virtual_filename.to_string_lossy();
|
||||
|
||||
if let Some(virtual_path) = working_set.find_virtual_path(&abs_virtual_filename) {
|
||||
return Some(ParserPath::from_virtual_path(
|
||||
working_set,
|
||||
&abs_virtual_filename,
|
||||
virtual_path,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Try if we have an existing physical path
|
||||
if let Ok(p) = canonicalize_with(filename, actual_cwd) {
|
||||
return Some(ParserPath::RealPath(p));
|
||||
}
|
||||
|
||||
// Early-exit if path is non-existent absolute path
|
||||
let path = Path::new(filename);
|
||||
if !path.is_relative() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Look up relative path from NU_LIB_DIRS
|
||||
working_set
|
||||
.find_constant(find_dirs_var(working_set, dirs_var_name)?)?
|
||||
.as_list()
|
||||
@ -3427,9 +3436,11 @@ pub fn find_in_dirs(
|
||||
})
|
||||
.find(Option::is_some)
|
||||
.flatten()
|
||||
.map(ParserPath::RealPath)
|
||||
}
|
||||
|
||||
// TODO: remove (see #8310)
|
||||
// Same as find_in_dirs_with_id but using $env.NU_LIB_DIRS instead of constant
|
||||
pub fn find_in_dirs_old(
|
||||
filename: &str,
|
||||
working_set: &StateWorkingSet,
|
||||
@ -3475,8 +3486,9 @@ pub fn find_in_dirs(
|
||||
}
|
||||
}
|
||||
|
||||
find_in_dirs_with_id(filename, working_set, cwd, dirs_var_name)
|
||||
.or_else(|| find_in_dirs_old(filename, working_set, cwd, dirs_var_name))
|
||||
find_in_dirs_with_id(filename, working_set, cwd, dirs_var_name).or_else(|| {
|
||||
find_in_dirs_old(filename, working_set, cwd, dirs_var_name).map(ParserPath::RealPath)
|
||||
})
|
||||
}
|
||||
|
||||
fn detect_params_in_name(
|
||||
|
134
crates/nu-parser/src/parser_path.rs
Normal file
134
crates/nu-parser/src/parser_path.rs
Normal file
@ -0,0 +1,134 @@
|
||||
use nu_protocol::engine::{StateWorkingSet, VirtualPath};
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// An abstraction over a PathBuf that can have virtual paths (files and directories). Virtual
|
||||
/// paths always exist and represent a way to ship Nushell code inside the binary without requiring
|
||||
/// paths to be present in the file system.
|
||||
///
|
||||
/// Created from VirtualPath found in the engine state.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum ParserPath {
|
||||
RealPath(PathBuf),
|
||||
VirtualFile(PathBuf, usize),
|
||||
VirtualDir(PathBuf, Vec<ParserPath>),
|
||||
}
|
||||
|
||||
impl ParserPath {
|
||||
pub fn is_dir(&self) -> bool {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => p.is_dir(),
|
||||
ParserPath::VirtualFile(..) => false,
|
||||
ParserPath::VirtualDir(..) => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_file(&self) -> bool {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => p.is_file(),
|
||||
ParserPath::VirtualFile(..) => true,
|
||||
ParserPath::VirtualDir(..) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exists(&self) -> bool {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => p.exists(),
|
||||
ParserPath::VirtualFile(..) => true,
|
||||
ParserPath::VirtualDir(..) => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &Path {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => p,
|
||||
ParserPath::VirtualFile(p, _) => p,
|
||||
ParserPath::VirtualDir(p, _) => p,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path_buf(self) -> PathBuf {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => p,
|
||||
ParserPath::VirtualFile(p, _) => p,
|
||||
ParserPath::VirtualDir(p, _) => p,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<&Path> {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => p.parent(),
|
||||
ParserPath::VirtualFile(p, _) => p.parent(),
|
||||
ParserPath::VirtualDir(p, _) => p.parent(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_dir(&self) -> Option<Vec<ParserPath>> {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => p.read_dir().ok().map(|read_dir| {
|
||||
read_dir
|
||||
.flatten()
|
||||
.map(|dir_entry| ParserPath::RealPath(dir_entry.path()))
|
||||
.collect()
|
||||
}),
|
||||
ParserPath::VirtualFile(..) => None,
|
||||
ParserPath::VirtualDir(_, files) => Some(files.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_stem(&self) -> Option<&OsStr> {
|
||||
self.path().file_stem()
|
||||
}
|
||||
|
||||
pub fn extension(&self) -> Option<&OsStr> {
|
||||
self.path().extension()
|
||||
}
|
||||
|
||||
pub fn join(self, path: impl AsRef<Path>) -> ParserPath {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => ParserPath::RealPath(p.join(path)),
|
||||
ParserPath::VirtualFile(p, file_id) => ParserPath::VirtualFile(p.join(path), file_id),
|
||||
ParserPath::VirtualDir(p, entries) => {
|
||||
let new_p = p.join(path);
|
||||
let mut pp = ParserPath::RealPath(new_p.clone());
|
||||
for entry in entries {
|
||||
if new_p == entry.path() {
|
||||
pp = entry.clone();
|
||||
}
|
||||
}
|
||||
pp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read<'a>(&'a self, working_set: &'a StateWorkingSet) -> Option<Vec<u8>> {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => std::fs::read(p).ok(),
|
||||
ParserPath::VirtualFile(_, file_id) => working_set
|
||||
.get_contents_of_file(*file_id)
|
||||
.map(|bytes| bytes.to_vec()),
|
||||
|
||||
ParserPath::VirtualDir(..) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_virtual_path(
|
||||
working_set: &StateWorkingSet,
|
||||
name: &str,
|
||||
virtual_path: &VirtualPath,
|
||||
) -> Self {
|
||||
match virtual_path {
|
||||
VirtualPath::File(file_id) => ParserPath::VirtualFile(PathBuf::from(name), *file_id),
|
||||
VirtualPath::Dir(entries) => ParserPath::VirtualDir(
|
||||
PathBuf::from(name),
|
||||
entries
|
||||
.iter()
|
||||
.map(|virtual_path_id| {
|
||||
let (virt_name, virt_path) = working_set.get_virtual_path(*virtual_path_id);
|
||||
ParserPath::from_virtual_path(working_set, virt_name, virt_path)
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user