mirror of
https://github.com/nushell/nushell.git
synced 2024-11-07 17:14:23 +01:00
Use only $nu.env.PWD for getting the current directory (#587)
* Use only $nu.env.PWD for getting current directory Because setting and reading to/from std::env changes the global state shich is problematic if we call `cd` from multiple threads (e.g., in a `par-each` block). With this change, when engine-q starts, it will either inherit existing PWD env var, or create a new one from `std::env::current_dir()`. Otherwise, everything that needs the current directory will get it from `$nu.env.PWD`. Each spawned external command will get its current directory per-process which should be thread-safe. One thing left to do is to patch nu-path for this as well since it uses `std::env::current_dir()` in its expansions. * Rename nu-path functions *_with is not *_relative which should be more descriptive and frees "with" for use in a followup commit. * Clone stack every each iter; Fix some commands Cloning the stack each iteration of `each` makes sure we're not reusing PWD between iterations. Some fixes in commands to make them use the new PWD. * Post-rebase cleanup, fmt, clippy * Change back _relative to _with in nu-path funcs Didn't use the idea I had for the new "_with". * Remove leftover current_dir from rebase * Add cwd sync at merge_delta() This makes sure the parser and completer always have up-to-date cwd. * Always pass absolute path to glob in ls * Do not allow PWD a relative path; Allow recovery Makes it possible to recover PWD by proceeding with the REPL cycle. * Clone stack in each also for byte/string stream * (WIP) Start moving env variables to engine state * (WIP) Move env vars to engine state (ugly) Quick and dirty code. * (WIP) Remove unused mut and args; Fmt * (WIP) Fix dataframe tests * (WIP) Fix missing args after rebase * (WIP) Clone only env vars, not the whole stack * (WIP) Add env var clone to `for` loop as well * Minor edits * Refactor merge_delta() to include stack merging. Less error-prone than doing it manually. * Clone env for each `update` command iteration * Mark env var hidden only when found in eng. state * Fix clippt warnings * Add TODO about env var reading * Do not clone empty environment in loops * Remove extra cwd collection * Split current_dir() into str and path; Fix autocd * Make completions respect PWD env var
This commit is contained in:
parent
8f6843c600
commit
74dcd91cc3
@ -104,9 +104,18 @@ impl NuCompleter {
|
||||
| nu_parser::FlatShape::String => {
|
||||
let prefix = working_set.get_span_contents(flat.0);
|
||||
let results = working_set.find_commands_by_prefix(prefix);
|
||||
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD")
|
||||
{
|
||||
match d.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
let prefix = String::from_utf8_lossy(prefix).to_string();
|
||||
let results2 = file_path_completion(flat.0, &prefix)
|
||||
let results2 = file_path_completion(flat.0, &prefix, &cwd)
|
||||
.into_iter()
|
||||
.map(move |x| {
|
||||
(
|
||||
@ -137,8 +146,17 @@ impl NuCompleter {
|
||||
| nu_parser::FlatShape::ExternalArg => {
|
||||
let prefix = working_set.get_span_contents(flat.0);
|
||||
let prefix = String::from_utf8_lossy(prefix).to_string();
|
||||
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD")
|
||||
{
|
||||
match d.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
let results = file_path_completion(flat.0, &prefix);
|
||||
let results = file_path_completion(flat.0, &prefix, &cwd);
|
||||
|
||||
return results
|
||||
.into_iter()
|
||||
@ -212,6 +230,7 @@ impl Completer for NuCompleter {
|
||||
fn file_path_completion(
|
||||
span: nu_protocol::Span,
|
||||
partial: &str,
|
||||
cwd: &str,
|
||||
) -> Vec<(nu_protocol::Span, String)> {
|
||||
use std::path::{is_separator, Path};
|
||||
|
||||
@ -238,7 +257,7 @@ fn file_path_completion(
|
||||
(base, rest)
|
||||
};
|
||||
|
||||
let base_dir = nu_path::expand_path(&base_dir_name);
|
||||
let base_dir = nu_path::expand_path_with(&base_dir_name, cwd);
|
||||
// This check is here as base_dir.read_dir() with base_dir == "" will open the current dir
|
||||
// which we don't want in this case (if we did, base_dir would already be ".")
|
||||
if base_dir == Path::new("") {
|
||||
|
@ -71,12 +71,16 @@ impl Command for For {
|
||||
let engine_state = engine_state.clone();
|
||||
let block = engine_state.get_block(block_id).clone();
|
||||
let mut stack = stack.collect_captures(&block.captures);
|
||||
let orig_env_vars = stack.env_vars.clone();
|
||||
let orig_env_hidden = stack.env_hidden.clone();
|
||||
|
||||
match values {
|
||||
Value::List { vals, .. } => Ok(vals
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(move |(idx, x)| {
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
stack.add_var(
|
||||
var_id,
|
||||
if numbered {
|
||||
@ -107,6 +111,8 @@ impl Command for For {
|
||||
.into_range_iter()?
|
||||
.enumerate()
|
||||
.map(move |(idx, x)| {
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
stack.add_var(
|
||||
var_id,
|
||||
if numbered {
|
||||
|
@ -98,12 +98,12 @@ impl Command for Hide {
|
||||
return Err(ShellError::NonUtf8(import_pattern.span()));
|
||||
};
|
||||
|
||||
if stack.remove_env_var(&name).is_none() {
|
||||
if stack.remove_env_var(engine_state, &name).is_none() {
|
||||
return Err(ShellError::NotFound(call.positional[0].span));
|
||||
}
|
||||
}
|
||||
} else if !import_pattern.hidden.contains(&import_pattern.head.name)
|
||||
&& stack.remove_env_var(&head_name_str).is_none()
|
||||
&& stack.remove_env_var(engine_state, &head_name_str).is_none()
|
||||
{
|
||||
return Err(ShellError::NotFound(call.positional[0].span));
|
||||
}
|
||||
|
@ -32,7 +32,8 @@ pub fn test_dataframe(cmds: Vec<Box<dyn Command + 'static>>) {
|
||||
working_set.render()
|
||||
};
|
||||
|
||||
let _ = engine_state.merge_delta(delta);
|
||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||
let _ = engine_state.merge_delta(delta, None, &cwd);
|
||||
|
||||
for example in examples {
|
||||
// Skip tests that don't have results to compare to
|
||||
@ -52,7 +53,7 @@ pub fn test_dataframe(cmds: Vec<Box<dyn Command + 'static>>) {
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
let _ = engine_state.merge_delta(delta);
|
||||
let _ = engine_state.merge_delta(delta, None, &cwd);
|
||||
|
||||
let mut stack = Stack::new();
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub fn create_default_context() -> EngineState {
|
||||
pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
||||
let mut engine_state = EngineState::new();
|
||||
|
||||
let delta = {
|
||||
@ -306,7 +308,7 @@ pub fn create_default_context() -> EngineState {
|
||||
working_set.render()
|
||||
};
|
||||
|
||||
let _ = engine_state.merge_delta(delta);
|
||||
let _ = engine_state.merge_delta(delta, None, &cwd);
|
||||
|
||||
engine_state
|
||||
}
|
||||
|
3
crates/nu-command/src/env/env_command.rs
vendored
3
crates/nu-command/src/env/env_command.rs
vendored
@ -29,7 +29,8 @@ impl Command for Env {
|
||||
let span = call.head;
|
||||
let config = stack.get_config().unwrap_or_default();
|
||||
|
||||
let mut env_vars: Vec<(String, Value)> = stack.get_env_vars().into_iter().collect();
|
||||
let mut env_vars: Vec<(String, Value)> =
|
||||
stack.get_env_vars(engine_state).into_iter().collect();
|
||||
env_vars.sort_by(|(name1, _), (name2, _)| name1.cmp(name2));
|
||||
|
||||
let mut values = vec![];
|
||||
|
@ -47,7 +47,8 @@ pub fn test_examples(cmd: impl Command + 'static) {
|
||||
working_set.render()
|
||||
};
|
||||
|
||||
let _ = engine_state.merge_delta(delta);
|
||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||
let _ = engine_state.merge_delta(delta, None, &cwd);
|
||||
|
||||
for example in examples {
|
||||
// Skip tests that don't have results to compare to
|
||||
@ -67,10 +68,19 @@ pub fn test_examples(cmd: impl Command + 'static) {
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
let _ = engine_state.merge_delta(delta);
|
||||
let _ = engine_state.merge_delta(delta, None, &cwd);
|
||||
|
||||
let mut stack = Stack::new();
|
||||
|
||||
// Set up PWD
|
||||
stack.add_env_var(
|
||||
"PWD".to_string(),
|
||||
Value::String {
|
||||
val: cwd.to_string_lossy().to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
);
|
||||
|
||||
// Set up our initial config to start from
|
||||
stack.vars.insert(
|
||||
CONFIG_VARIABLE_ID,
|
||||
|
@ -1,3 +1,4 @@
|
||||
use nu_engine::env::current_dir_str;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
@ -32,7 +33,10 @@ impl Command for Cd {
|
||||
|
||||
let (path, span) = match path_val {
|
||||
Some(v) => {
|
||||
let path = nu_path::expand_path(v.as_string()?);
|
||||
let path = nu_path::canonicalize_with(
|
||||
v.as_string()?,
|
||||
current_dir_str(engine_state, stack)?,
|
||||
)?;
|
||||
(path.to_string_lossy().to_string(), v.span()?)
|
||||
}
|
||||
None => {
|
||||
@ -40,7 +44,6 @@ impl Command for Cd {
|
||||
(path.to_string_lossy().to_string(), call.head)
|
||||
}
|
||||
};
|
||||
let _ = std::env::set_current_dir(&path);
|
||||
|
||||
//FIXME: this only changes the current scope, but instead this environment variable
|
||||
//should probably be a block that loads the information from the state in the overlay
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::env::current_dir;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::util::get_interactive_confirmation;
|
||||
use nu_engine::env::current_dir;
|
||||
use nu_engine::CallExt;
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::ast::Call;
|
||||
@ -49,7 +49,7 @@ impl Command for Cp {
|
||||
let interactive = call.has_flag("interactive");
|
||||
let force = call.has_flag("force");
|
||||
|
||||
let path = current_dir()?;
|
||||
let path = current_dir(engine_state, stack)?;
|
||||
let source = path.join(source.as_str());
|
||||
let destination = path.join(destination.as_str());
|
||||
|
||||
@ -135,7 +135,7 @@ impl Command for Cp {
|
||||
|
||||
for entry in sources.into_iter().flatten() {
|
||||
let mut sources = FileStructure::new();
|
||||
sources.walk_decorate(&entry)?;
|
||||
sources.walk_decorate(&entry, engine_state, stack)?;
|
||||
|
||||
if entry.is_file() {
|
||||
let sources = sources.paths_applying_with(|(source_file, _depth_level)| {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use nu_engine::env::current_dir;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
@ -10,6 +11,7 @@ use nu_protocol::{
|
||||
use std::io::ErrorKind;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Ls;
|
||||
@ -63,10 +65,17 @@ impl Command for Ls {
|
||||
|
||||
let call_span = call.head;
|
||||
|
||||
let pattern = if let Some(mut result) =
|
||||
let (pattern, prefix) = if let Some(result) =
|
||||
call.opt::<Spanned<String>>(engine_state, stack, 0)?
|
||||
{
|
||||
let path = std::path::Path::new(&result.item);
|
||||
let path = PathBuf::from(&result.item);
|
||||
|
||||
let (mut path, prefix) = if path.is_relative() {
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
(cwd.join(path), Some(cwd))
|
||||
} else {
|
||||
(path, None)
|
||||
};
|
||||
|
||||
if path.is_dir() {
|
||||
if permission_denied(&path) {
|
||||
@ -92,16 +101,14 @@ impl Command for Ls {
|
||||
}
|
||||
|
||||
if path.is_dir() {
|
||||
if !result.item.ends_with(std::path::MAIN_SEPARATOR) {
|
||||
result.item.push(std::path::MAIN_SEPARATOR);
|
||||
}
|
||||
result.item.push('*');
|
||||
path = path.join("*");
|
||||
}
|
||||
}
|
||||
|
||||
result.item
|
||||
(path.to_string_lossy().to_string(), prefix)
|
||||
} else {
|
||||
"*".into()
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
(cwd.join("*").to_string_lossy().to_string(), Some(cwd))
|
||||
};
|
||||
|
||||
let glob = glob::glob(&pattern).map_err(|err| {
|
||||
@ -144,11 +151,34 @@ impl Command for Ls {
|
||||
return None;
|
||||
}
|
||||
|
||||
let entry =
|
||||
dir_entry_dict(&path, metadata.as_ref(), call_span, long, short_names);
|
||||
let display_name = if short_names {
|
||||
path.file_name().and_then(|s| s.to_str())
|
||||
} else if let Some(pre) = &prefix {
|
||||
match path.strip_prefix(pre) {
|
||||
Ok(stripped) => stripped.to_str(),
|
||||
Err(_) => path.to_str(),
|
||||
}
|
||||
} else {
|
||||
path.to_str()
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
ShellError::SpannedLabeledError(
|
||||
format!("Invalid file name: {:}", path.to_string_lossy()),
|
||||
"invalid file name".into(),
|
||||
call_span,
|
||||
)
|
||||
});
|
||||
|
||||
match entry {
|
||||
Ok(value) => Some(value),
|
||||
match display_name {
|
||||
Ok(name) => {
|
||||
let entry =
|
||||
dir_entry_dict(&path, name, metadata.as_ref(), call_span, long);
|
||||
|
||||
match entry {
|
||||
Ok(value) => Some(value),
|
||||
Err(err) => Some(Value::Error { error: err }),
|
||||
}
|
||||
}
|
||||
Err(err) => Some(Value::Error { error: err }),
|
||||
}
|
||||
}
|
||||
@ -213,7 +243,7 @@ fn path_contains_hidden_folder(path: &Path, folders: &[PathBuf]) -> bool {
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
|
||||
pub fn get_file_type(md: &std::fs::Metadata) -> &str {
|
||||
let ft = md.file_type();
|
||||
@ -243,31 +273,18 @@ pub fn get_file_type(md: &std::fs::Metadata) -> &str {
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn dir_entry_dict(
|
||||
filename: &std::path::Path,
|
||||
filename: &std::path::Path, // absolute path
|
||||
display_name: &str, // gile name to be displayed
|
||||
metadata: Option<&std::fs::Metadata>,
|
||||
span: Span,
|
||||
long: bool,
|
||||
short_name: bool,
|
||||
) -> Result<Value, ShellError> {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
let name = if short_name {
|
||||
filename.file_name().and_then(|s| s.to_str())
|
||||
} else {
|
||||
filename.to_str()
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
ShellError::SpannedLabeledError(
|
||||
format!("Invalid file name: {:}", filename.to_string_lossy()),
|
||||
"invalid file name".into(),
|
||||
span,
|
||||
)
|
||||
})?;
|
||||
|
||||
cols.push("name".into());
|
||||
vals.push(Value::String {
|
||||
val: name.to_string(),
|
||||
val: display_name.to_string(),
|
||||
span,
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::env::current_dir;
|
||||
|
||||
use nu_engine::env::current_dir;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
@ -39,7 +39,7 @@ impl Command for Mkdir {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let path = current_dir()?;
|
||||
let path = current_dir(engine_state, stack)?;
|
||||
let mut directories = call
|
||||
.rest::<String>(engine_state, stack, 0)?
|
||||
.into_iter()
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::env::current_dir;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
|
||||
use super::util::get_interactive_confirmation;
|
||||
use nu_engine::env::current_dir;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
@ -50,7 +50,7 @@ impl Command for Mv {
|
||||
let interactive = call.has_flag("interactive");
|
||||
let force = call.has_flag("force");
|
||||
|
||||
let path: PathBuf = current_dir()?;
|
||||
let path = current_dir(engine_state, stack)?;
|
||||
let source = path.join(spanned_source.item.as_str());
|
||||
let destination = path.join(destination.as_str());
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
use std::env::current_dir;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::prelude::FileTypeExt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::util::get_interactive_confirmation;
|
||||
|
||||
use nu_engine::env::current_dir;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
@ -86,7 +86,7 @@ fn rm(
|
||||
));
|
||||
}
|
||||
|
||||
let current_path = current_dir()?;
|
||||
let current_path = current_dir(engine_state, stack)?;
|
||||
let mut paths = call
|
||||
.rest::<String>(engine_state, stack, 0)?
|
||||
.into_iter()
|
||||
|
@ -1,6 +1,8 @@
|
||||
use std::fs::OpenOptions;
|
||||
|
||||
use nu_engine::env::current_dir_str;
|
||||
use nu_engine::CallExt;
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape};
|
||||
@ -39,7 +41,8 @@ impl Command for Touch {
|
||||
let rest: Vec<String> = call.rest(engine_state, stack, 1)?;
|
||||
|
||||
for (index, item) in vec![target].into_iter().chain(rest).enumerate() {
|
||||
match OpenOptions::new().write(true).create(true).open(&item) {
|
||||
let path = expand_path_with(&item, current_dir_str(engine_state, stack)?);
|
||||
match OpenOptions::new().write(true).create(true).open(&path) {
|
||||
Ok(_) => continue,
|
||||
Err(err) => {
|
||||
return Err(ShellError::CreateNotPossible(
|
||||
|
@ -1,6 +1,8 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use nu_engine::env::current_dir_str;
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::ShellError;
|
||||
|
||||
use dialoguer::Input;
|
||||
@ -39,16 +41,27 @@ impl FileStructure {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn walk_decorate(&mut self, start_path: &Path) -> Result<(), ShellError> {
|
||||
pub fn walk_decorate(
|
||||
&mut self,
|
||||
start_path: &Path,
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
) -> Result<(), ShellError> {
|
||||
self.resources = Vec::<Resource>::new();
|
||||
self.build(start_path, 0)?;
|
||||
self.build(start_path, 0, engine_state, stack)?;
|
||||
self.resources.sort();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build(&mut self, src: &Path, lvl: usize) -> Result<(), ShellError> {
|
||||
let source = canonicalize_with(src, std::env::current_dir()?)?;
|
||||
fn build(
|
||||
&mut self,
|
||||
src: &Path,
|
||||
lvl: usize,
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
) -> Result<(), ShellError> {
|
||||
let source = canonicalize_with(src, current_dir_str(engine_state, stack)?)?;
|
||||
|
||||
if source.is_dir() {
|
||||
for entry in std::fs::read_dir(src)? {
|
||||
@ -56,7 +69,7 @@ impl FileStructure {
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_dir() {
|
||||
self.build(&path, lvl + 1)?;
|
||||
self.build(&path, lvl + 1, engine_state, stack)?;
|
||||
}
|
||||
|
||||
self.resources.push(Resource {
|
||||
|
@ -71,6 +71,8 @@ impl Command for Each {
|
||||
let engine_state = engine_state.clone();
|
||||
let block = engine_state.get_block(block_id).clone();
|
||||
let mut stack = stack.collect_captures(&block.captures);
|
||||
let orig_env_vars = stack.env_vars.clone();
|
||||
let orig_env_hidden = stack.env_hidden.clone();
|
||||
let span = call.head;
|
||||
|
||||
match input {
|
||||
@ -80,6 +82,8 @@ impl Command for Each {
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(move |(idx, x)| {
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
if numbered {
|
||||
@ -113,6 +117,8 @@ impl Command for Each {
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(move |(idx, x)| {
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
let x = match x {
|
||||
Ok(x) => Value::Binary { val: x, span },
|
||||
Err(err) => return Value::Error { error: err },
|
||||
@ -151,6 +157,8 @@ impl Command for Each {
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(move |(idx, x)| {
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
let x = match x {
|
||||
Ok(x) => Value::String { val: x, span },
|
||||
Err(err) => return Value::Error { error: err },
|
||||
@ -192,7 +200,7 @@ impl Command for Each {
|
||||
for (col, val) in cols.into_iter().zip(vals.into_iter()) {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let mut stack = stack.clone();
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
|
@ -74,9 +74,13 @@ fn update(
|
||||
let block = engine_state.get_block(block_id).clone();
|
||||
|
||||
let mut stack = stack.collect_captures(&block.captures);
|
||||
let orig_env_vars = stack.env_vars.clone();
|
||||
let orig_env_hidden = stack.env_hidden.clone();
|
||||
|
||||
input.map(
|
||||
move |mut input| {
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, input.clone())
|
||||
|
@ -1,7 +1,8 @@
|
||||
use std::path::Path;
|
||||
|
||||
use nu_engine::env::current_dir_str;
|
||||
use nu_engine::CallExt;
|
||||
use nu_path::{canonicalize, expand_path};
|
||||
use nu_path::{canonicalize_with, expand_path};
|
||||
use nu_protocol::{engine::Command, Example, ShellError, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
use super::PathSubcommandArguments;
|
||||
@ -9,6 +10,7 @@ use super::PathSubcommandArguments;
|
||||
struct Arguments {
|
||||
strict: bool,
|
||||
columns: Option<Vec<String>>,
|
||||
cwd: String,
|
||||
}
|
||||
|
||||
impl PathSubcommandArguments for Arguments {
|
||||
@ -55,6 +57,7 @@ impl Command for SubCommand {
|
||||
let args = Arguments {
|
||||
strict: call.has_flag("strict"),
|
||||
columns: call.get_flag(engine_state, stack, "columns")?,
|
||||
cwd: current_dir_str(engine_state, stack)?,
|
||||
};
|
||||
|
||||
input.map(
|
||||
@ -107,7 +110,7 @@ impl Command for SubCommand {
|
||||
}
|
||||
|
||||
fn expand(path: &Path, span: Span, args: &Arguments) -> Value {
|
||||
if let Ok(p) = canonicalize(path) {
|
||||
if let Ok(p) = canonicalize_with(path, &args.cwd) {
|
||||
Value::string(p.to_string_lossy(), span)
|
||||
} else if args.strict {
|
||||
Value::Error {
|
||||
|
@ -1,5 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::process::{Command as CommandSys, Stdio};
|
||||
use std::sync::atomic::Ordering;
|
||||
@ -113,9 +112,19 @@ impl<'call> ExternalCommand<'call> {
|
||||
|
||||
// TODO. We don't have a way to know the current directory
|
||||
// This should be information from the EvaluationContex or EngineState
|
||||
let path = env::current_dir()?;
|
||||
|
||||
process.current_dir(path);
|
||||
if let Some(d) = self.env_vars.get("PWD") {
|
||||
process.current_dir(d);
|
||||
} else {
|
||||
return Err(ShellError::SpannedLabeledErrorHelp(
|
||||
"Current directory not found".to_string(),
|
||||
"did not find PWD environment variable".to_string(),
|
||||
head,
|
||||
concat!(
|
||||
"The environment variable 'PWD' was not found. ",
|
||||
"It is required to define the current directory when running an external command."
|
||||
).to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
process.envs(&self.env_vars);
|
||||
|
||||
|
@ -62,7 +62,7 @@ prints out the list properly."#
|
||||
let color_param: bool = call.has_flag("color");
|
||||
let separator_param: Option<String> = call.get_flag(engine_state, stack, "separator")?;
|
||||
let config = stack.get_config().unwrap_or_default();
|
||||
let env_str = match stack.get_env_var("LS_COLORS") {
|
||||
let env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
|
||||
Some(v) => Some(env_to_string("LS_COLORS", v, engine_state, stack, &config)?),
|
||||
None => None,
|
||||
};
|
||||
|
@ -112,7 +112,7 @@ impl Command for Table {
|
||||
let config = config.clone();
|
||||
let ctrlc = ctrlc.clone();
|
||||
|
||||
let ls_colors = match stack.get_env_var("LS_COLORS") {
|
||||
let ls_colors = match stack.get_env_var(engine_state, "LS_COLORS") {
|
||||
Some(v) => LsColors::from_string(&env_to_string(
|
||||
"LS_COLORS",
|
||||
v,
|
||||
|
@ -1,4 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::{Config, PipelineData, ShellError, Value};
|
||||
@ -17,69 +18,62 @@ const ENV_SEP: &str = ":";
|
||||
/// skip errors. This function is called in the main() so we want to keep running, we cannot just
|
||||
/// exit.
|
||||
pub fn convert_env_values(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
engine_state: &mut EngineState,
|
||||
stack: &Stack,
|
||||
config: &Config,
|
||||
) -> Option<ShellError> {
|
||||
let mut new_env_vars = vec![];
|
||||
let mut error = None;
|
||||
|
||||
for scope in &stack.env_vars {
|
||||
let mut new_scope = HashMap::new();
|
||||
let mut new_scope = HashMap::new();
|
||||
|
||||
for (name, val) in scope {
|
||||
if let Some(env_conv) = config.env_conversions.get(name) {
|
||||
if let Some((block_id, from_span)) = env_conv.from_string {
|
||||
let val_span = match val.span() {
|
||||
Ok(sp) => sp,
|
||||
Err(e) => {
|
||||
error = error.or(Some(e));
|
||||
continue;
|
||||
for (name, val) in &engine_state.env_vars {
|
||||
if let Some(env_conv) = config.env_conversions.get(name) {
|
||||
if let Some((block_id, from_span)) = env_conv.from_string {
|
||||
let val_span = match val.span() {
|
||||
Ok(sp) => sp,
|
||||
Err(e) => {
|
||||
error = error.or(Some(e));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
let mut stack = stack.collect_captures(&block.captures);
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, val.clone());
|
||||
}
|
||||
|
||||
let result =
|
||||
eval_block(engine_state, &mut stack, block, PipelineData::new(val_span));
|
||||
|
||||
match result {
|
||||
Ok(data) => {
|
||||
let val = data.into_value(val_span);
|
||||
new_scope.insert(name.to_string(), val);
|
||||
}
|
||||
};
|
||||
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
let mut stack = stack.collect_captures(&block.captures);
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, val.clone());
|
||||
}
|
||||
|
||||
let result = eval_block(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
block,
|
||||
PipelineData::new(val_span),
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(data) => {
|
||||
let val = data.into_value(val_span);
|
||||
new_scope.insert(name.to_string(), val);
|
||||
}
|
||||
Err(e) => error = error.or(Some(e)),
|
||||
}
|
||||
} else {
|
||||
error = error.or_else(|| {
|
||||
Some(ShellError::MissingParameter(
|
||||
"block input".into(),
|
||||
from_span,
|
||||
))
|
||||
});
|
||||
Err(e) => error = error.or(Some(e)),
|
||||
}
|
||||
} else {
|
||||
new_scope.insert(name.to_string(), val.clone());
|
||||
error = error.or_else(|| {
|
||||
Some(ShellError::MissingParameter(
|
||||
"block input".into(),
|
||||
from_span,
|
||||
))
|
||||
});
|
||||
}
|
||||
} else {
|
||||
new_scope.insert(name.to_string(), val.clone());
|
||||
}
|
||||
} else {
|
||||
new_scope.insert(name.to_string(), val.clone());
|
||||
}
|
||||
|
||||
new_env_vars.push(new_scope);
|
||||
}
|
||||
|
||||
stack.env_vars = new_env_vars;
|
||||
for (k, v) in new_scope {
|
||||
engine_state.env_vars.insert(k, v);
|
||||
}
|
||||
|
||||
error
|
||||
}
|
||||
@ -89,7 +83,7 @@ pub fn env_to_string(
|
||||
env_name: &str,
|
||||
value: Value,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
stack: &Stack,
|
||||
config: &Config,
|
||||
) -> Result<String, ShellError> {
|
||||
if let Some(env_conv) = config.env_conversions.get(env_name) {
|
||||
@ -128,10 +122,10 @@ pub fn env_to_string(
|
||||
/// Translate all environment variables from Values to Strings
|
||||
pub fn env_to_strings(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
stack: &Stack,
|
||||
config: &Config,
|
||||
) -> Result<HashMap<String, String>, ShellError> {
|
||||
let env_vars = stack.get_env_vars();
|
||||
let env_vars = stack.get_env_vars(engine_state);
|
||||
let mut env_vars_str = HashMap::new();
|
||||
for (env_name, val) in env_vars {
|
||||
let val_str = env_to_string(&env_name, val, engine_state, stack, config)?;
|
||||
@ -140,3 +134,33 @@ pub fn env_to_strings(
|
||||
|
||||
Ok(env_vars_str)
|
||||
}
|
||||
|
||||
/// Shorthand for env_to_string() for PWD with custom error
|
||||
pub fn current_dir_str(engine_state: &EngineState, stack: &Stack) -> Result<String, ShellError> {
|
||||
let config = stack.get_config()?;
|
||||
if let Some(pwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
match env_to_string("PWD", pwd, engine_state, stack, &config) {
|
||||
Ok(cwd) => {
|
||||
if Path::new(&cwd).is_absolute() {
|
||||
Ok(cwd)
|
||||
} else {
|
||||
Err(ShellError::LabeledError(
|
||||
"Invalid current directory".to_string(),
|
||||
format!("The 'PWD' environment variable must be set to an absolute path. Found: '{}'", cwd)
|
||||
))
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::LabeledError(
|
||||
"Current directory not found".to_string(),
|
||||
"The environment variable 'PWD' was not found. It is required to define the current directory.".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls current_dir_str() and returns the current directory as a PathBuf
|
||||
pub fn current_dir(engine_state: &EngineState, stack: &Stack) -> Result<PathBuf, ShellError> {
|
||||
current_dir_str(engine_state, stack).map(PathBuf::from)
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use nu_protocol::{
|
||||
Spanned, Type, Unit, Value, VarId, ENV_VARIABLE_ID,
|
||||
};
|
||||
|
||||
use crate::get_full_help;
|
||||
use crate::{current_dir_str, get_full_help};
|
||||
|
||||
pub fn eval_operator(op: &Expression) -> Result<Operator, ShellError> {
|
||||
match op {
|
||||
@ -575,15 +575,9 @@ pub fn eval_variable(
|
||||
|
||||
// since the env var PWD doesn't exist on all platforms
|
||||
// lets just get the current directory
|
||||
if let Ok(current_dir) = std::env::current_dir() {
|
||||
if let Some(cwd) = current_dir.to_str() {
|
||||
output_cols.push("cwd".into());
|
||||
output_vals.push(Value::String {
|
||||
val: cwd.into(),
|
||||
span,
|
||||
})
|
||||
}
|
||||
}
|
||||
let cwd = current_dir_str(engine_state, stack)?;
|
||||
output_cols.push("cwd".into());
|
||||
output_vals.push(Value::String { val: cwd, span });
|
||||
|
||||
if let Some(home_path) = nu_path::home_dir() {
|
||||
if let Some(home_path_str) = home_path.to_str() {
|
||||
@ -886,7 +880,7 @@ pub fn eval_variable(
|
||||
span,
|
||||
})
|
||||
} else if var_id == ENV_VARIABLE_ID {
|
||||
let env_vars = stack.get_env_vars();
|
||||
let env_vars = stack.get_env_vars(engine_state);
|
||||
let env_columns = env_vars.keys();
|
||||
let env_values = env_vars.values();
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
mod call_ext;
|
||||
pub mod column;
|
||||
mod documentation;
|
||||
mod env;
|
||||
pub mod env;
|
||||
mod eval;
|
||||
|
||||
pub use call_ext::CallExt;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn expand_tilde_with(path: impl AsRef<Path>, home: Option<PathBuf>) -> PathBuf {
|
||||
fn expand_tilde_with_home(path: impl AsRef<Path>, home: Option<PathBuf>) -> PathBuf {
|
||||
let path = path.as_ref();
|
||||
|
||||
if !path.starts_with("~") {
|
||||
@ -27,7 +27,7 @@ fn expand_tilde_with(path: impl AsRef<Path>, home: Option<PathBuf>) -> PathBuf {
|
||||
/// Expand tilde ("~") into a home directory if it is the first path component
|
||||
pub fn expand_tilde(path: impl AsRef<Path>) -> PathBuf {
|
||||
// TODO: Extend this to work with "~user" style of home paths
|
||||
expand_tilde_with(path, dirs_next::home_dir())
|
||||
expand_tilde_with_home(path, dirs_next::home_dir())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -37,17 +37,17 @@ mod tests {
|
||||
fn check_expanded(s: &str) {
|
||||
let home = Path::new("/home");
|
||||
let buf = Some(PathBuf::from(home));
|
||||
assert!(expand_tilde_with(Path::new(s), buf).starts_with(&home));
|
||||
assert!(expand_tilde_with_home(Path::new(s), buf).starts_with(&home));
|
||||
|
||||
// Tests the special case in expand_tilde for "/" as home
|
||||
let home = Path::new("/");
|
||||
let buf = Some(PathBuf::from(home));
|
||||
assert!(!expand_tilde_with(Path::new(s), buf).starts_with("//"));
|
||||
assert!(!expand_tilde_with_home(Path::new(s), buf).starts_with("//"));
|
||||
}
|
||||
|
||||
fn check_not_expanded(s: &str) {
|
||||
let home = PathBuf::from("/home");
|
||||
let expanded = expand_tilde_with(Path::new(s), Some(home));
|
||||
let expanded = expand_tilde_with_home(Path::new(s), Some(home));
|
||||
assert!(expanded == Path::new(s));
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::Command;
|
||||
use super::{Command, Stack};
|
||||
use crate::{
|
||||
ast::Block, BlockId, DeclId, Example, Overlay, OverlayId, ShellError, Signature, Span, Type,
|
||||
VarId,
|
||||
@ -9,6 +9,10 @@ use std::{
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
use crate::Value;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
use std::path::PathBuf;
|
||||
|
||||
@ -140,6 +144,7 @@ pub struct EngineState {
|
||||
overlays: im::Vector<Overlay>,
|
||||
pub scope: im::Vector<ScopeFrame>,
|
||||
pub ctrlc: Option<Arc<AtomicBool>>,
|
||||
pub env_vars: im::HashMap<String, Value>,
|
||||
#[cfg(feature = "plugin")]
|
||||
pub plugin_signatures: Option<PathBuf>,
|
||||
}
|
||||
@ -167,6 +172,7 @@ impl EngineState {
|
||||
overlays: im::vector![],
|
||||
scope: im::vector![ScopeFrame::new()],
|
||||
ctrlc: None,
|
||||
env_vars: im::HashMap::new(),
|
||||
#[cfg(feature = "plugin")]
|
||||
plugin_signatures: None,
|
||||
}
|
||||
@ -179,7 +185,12 @@ impl EngineState {
|
||||
///
|
||||
/// When we want to preserve what the parser has created, we can take its output (the `StateDelta`) and
|
||||
/// use this function to merge it into the global state.
|
||||
pub fn merge_delta(&mut self, mut delta: StateDelta) -> Result<(), ShellError> {
|
||||
pub fn merge_delta(
|
||||
&mut self,
|
||||
mut delta: StateDelta,
|
||||
stack: Option<&mut Stack>,
|
||||
cwd: impl AsRef<Path>,
|
||||
) -> Result<(), ShellError> {
|
||||
// Take the mutable reference and extend the permanent state from the working set
|
||||
self.files.extend(delta.files);
|
||||
self.file_contents.extend(delta.file_contents);
|
||||
@ -216,6 +227,16 @@ impl EngineState {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(stack) = stack {
|
||||
for mut env_scope in stack.env_vars.drain(..) {
|
||||
for (k, v) in env_scope.drain() {
|
||||
self.env_vars.insert(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::env::set_current_dir(cwd)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1209,7 +1230,8 @@ mod engine_state_tests {
|
||||
working_set.render()
|
||||
};
|
||||
|
||||
engine_state.merge_delta(delta)?;
|
||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||
engine_state.merge_delta(delta, None, &cwd)?;
|
||||
|
||||
assert_eq!(engine_state.num_files(), 2);
|
||||
assert_eq!(&engine_state.files[0].0, "test.nu");
|
||||
|
@ -1,5 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::engine::EngineState;
|
||||
use crate::{Config, ShellError, Value, VarId, CONFIG_VARIABLE_ID};
|
||||
|
||||
/// A runtime value stack used during evaluation
|
||||
@ -25,6 +26,9 @@ pub struct Stack {
|
||||
pub vars: HashMap<VarId, Value>,
|
||||
/// Environment variables arranged as a stack to be able to recover values from parent scopes
|
||||
pub env_vars: Vec<HashMap<String, Value>>,
|
||||
/// Tells which environment variables from engine state are hidden. We don't need to track the
|
||||
/// env vars in the stack since we can just delete them.
|
||||
pub env_hidden: HashSet<String>,
|
||||
}
|
||||
|
||||
impl Default for Stack {
|
||||
@ -38,6 +42,17 @@ impl Stack {
|
||||
Stack {
|
||||
vars: HashMap::new(),
|
||||
env_vars: vec![],
|
||||
env_hidden: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_env(&mut self, env_vars: &[HashMap<String, Value>], env_hidden: &HashSet<String>) {
|
||||
if env_vars.iter().any(|scope| !scope.is_empty()) {
|
||||
self.env_vars = env_vars.to_owned();
|
||||
}
|
||||
|
||||
if !env_hidden.is_empty() {
|
||||
self.env_hidden = env_hidden.clone();
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,6 +69,9 @@ impl Stack {
|
||||
}
|
||||
|
||||
pub fn add_env_var(&mut self, var: String, value: Value) {
|
||||
// if the env var was hidden, let's activate it again
|
||||
self.env_hidden.remove(&var);
|
||||
|
||||
if let Some(scope) = self.env_vars.last_mut() {
|
||||
scope.insert(var, value);
|
||||
} else {
|
||||
@ -85,8 +103,15 @@ impl Stack {
|
||||
}
|
||||
|
||||
/// Flatten the env var scope frames into one frame
|
||||
pub fn get_env_vars(&self) -> HashMap<String, Value> {
|
||||
let mut result = HashMap::new();
|
||||
pub fn get_env_vars(&self, engine_state: &EngineState) -> HashMap<String, Value> {
|
||||
// TODO: We're collecting im::HashMap to HashMap here. It might make sense to make these
|
||||
// the same data structure.
|
||||
let mut result: HashMap<String, Value> = engine_state
|
||||
.env_vars
|
||||
.iter()
|
||||
.filter(|(k, _)| !self.env_hidden.contains(*k))
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect();
|
||||
|
||||
for scope in &self.env_vars {
|
||||
result.extend(scope.clone());
|
||||
@ -95,24 +120,37 @@ impl Stack {
|
||||
result
|
||||
}
|
||||
|
||||
pub fn get_env_var(&self, name: &str) -> Option<Value> {
|
||||
pub fn get_env_var(&self, engine_state: &EngineState, name: &str) -> Option<Value> {
|
||||
for scope in self.env_vars.iter().rev() {
|
||||
if let Some(v) = scope.get(name) {
|
||||
return Some(v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
if self.env_hidden.contains(name) {
|
||||
None
|
||||
} else {
|
||||
engine_state.env_vars.get(name).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_env_var(&mut self, name: &str) -> Option<Value> {
|
||||
pub fn remove_env_var(&mut self, engine_state: &EngineState, name: &str) -> Option<Value> {
|
||||
for scope in self.env_vars.iter_mut().rev() {
|
||||
if let Some(v) = scope.remove(name) {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
if self.env_hidden.contains(name) {
|
||||
// the environment variable is already hidden
|
||||
None
|
||||
} else if let Some(val) = engine_state.env_vars.get(name) {
|
||||
// the environment variable was found in the engine state => mark it as hidden
|
||||
self.env_hidden.insert(name.to_string());
|
||||
Some(val.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config(&self) -> Result<Config, ShellError> {
|
||||
|
@ -96,12 +96,9 @@ pub enum ShellError {
|
||||
VariableNotFoundAtRuntime(#[label = "variable not found"] Span),
|
||||
|
||||
#[error("Environment variable not found")]
|
||||
#[diagnostic(code(nu::shell::variable_not_found), url(docsrs))]
|
||||
#[diagnostic(code(nu::shell::env_variable_not_found), url(docsrs))]
|
||||
EnvVarNotFoundAtRuntime(#[label = "environment variable not found"] Span),
|
||||
|
||||
// #[error("Environment variable is not a string")]
|
||||
// #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))]
|
||||
// EnvVarNotAString(#[label = "does not evaluate to a string"] Span),
|
||||
#[error("Not found.")]
|
||||
#[diagnostic(code(nu::parser::not_found), url(docsrs))]
|
||||
NotFound(#[label = "did not find anything under this name"] Span),
|
||||
@ -185,7 +182,7 @@ pub enum ShellError {
|
||||
PluginFailedToDecode(String),
|
||||
|
||||
#[error("I/O error")]
|
||||
#[diagnostic(code(nu::shell::io_error), url(docsrs))]
|
||||
#[diagnostic(code(nu::shell::io_error), url(docsrs), help("{0}"))]
|
||||
IOError(String),
|
||||
|
||||
#[error("Directory not found")]
|
||||
|
@ -65,6 +65,9 @@ impl GStat {
|
||||
}
|
||||
|
||||
// This path has to exist
|
||||
// TODO: If the path is relative, it will be expanded using `std::env::current_dir` and not
|
||||
// the "PWD" environment variable. We would need a way to read the engine's environment
|
||||
// variables here.
|
||||
if !std::path::Path::new(&a_path.item).exists() {
|
||||
return Err(LabeledError {
|
||||
label: "error with path".to_string(),
|
||||
|
122
src/main.rs
122
src/main.rs
@ -20,6 +20,7 @@ use nu_protocol::{
|
||||
use reedline::{Completer, CompletionActionHandler, DefaultHinter, LineBuffer, Prompt, Vi};
|
||||
use std::{
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
@ -97,7 +98,9 @@ fn main() -> Result<()> {
|
||||
miette_hook(x);
|
||||
}));
|
||||
|
||||
let mut engine_state = create_default_context();
|
||||
// Get initial current working directory.
|
||||
let init_cwd = get_init_cwd();
|
||||
let mut engine_state = create_default_context(&init_cwd);
|
||||
|
||||
// TODO: make this conditional in the future
|
||||
// Ctrl-c protection section
|
||||
@ -129,7 +132,7 @@ fn main() -> Result<()> {
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
if let Err(err) = engine_state.merge_delta(delta, None, &init_cwd) {
|
||||
let working_set = StateWorkingSet::new(&engine_state);
|
||||
report_error(&working_set, &err);
|
||||
}
|
||||
@ -137,7 +140,7 @@ fn main() -> Result<()> {
|
||||
let mut stack = nu_protocol::engine::Stack::new();
|
||||
|
||||
// First, set up env vars as strings only
|
||||
gather_parent_env_vars(&mut engine_state, &mut stack);
|
||||
gather_parent_env_vars(&mut engine_state);
|
||||
|
||||
// Set up our initial config to start from
|
||||
stack.vars.insert(
|
||||
@ -160,7 +163,7 @@ fn main() -> Result<()> {
|
||||
};
|
||||
|
||||
// Translate environment variables from Strings to Values
|
||||
if let Some(e) = convert_env_values(&engine_state, &mut stack, &config) {
|
||||
if let Some(e) = convert_env_values(&mut engine_state, &stack, &config) {
|
||||
let working_set = StateWorkingSet::new(&engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
@ -204,7 +207,9 @@ fn main() -> Result<()> {
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
let cwd = nu_engine::env::current_dir_str(&engine_state, &stack)?;
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta, Some(&mut stack), &cwd) {
|
||||
let working_set = StateWorkingSet::new(&engine_state);
|
||||
report_error(&working_set, &err);
|
||||
}
|
||||
@ -255,7 +260,7 @@ fn main() -> Result<()> {
|
||||
let mut stack = nu_protocol::engine::Stack::new();
|
||||
|
||||
// First, set up env vars as strings only
|
||||
gather_parent_env_vars(&mut engine_state, &mut stack);
|
||||
gather_parent_env_vars(&mut engine_state);
|
||||
|
||||
// Set up our initial config to start from
|
||||
stack.vars.insert(
|
||||
@ -331,7 +336,7 @@ fn main() -> Result<()> {
|
||||
})?;
|
||||
|
||||
// Translate environment variables from Strings to Values
|
||||
if let Some(e) = convert_env_values(&engine_state, &mut stack, &config) {
|
||||
if let Some(e) = convert_env_values(&mut engine_state, &stack, &config) {
|
||||
let working_set = StateWorkingSet::new(&engine_state);
|
||||
report_error(&working_set, &e);
|
||||
}
|
||||
@ -429,7 +434,8 @@ fn main() -> Result<()> {
|
||||
Ok(Signal::Success(s)) => {
|
||||
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
||||
// Check if this is a single call to a directory, if so auto-cd
|
||||
let path = nu_path::expand_path(&s);
|
||||
let cwd = nu_engine::env::current_dir_str(&engine_state, &stack)?;
|
||||
let path = nu_path::expand_path_with(&s, &cwd);
|
||||
let orig = s.clone();
|
||||
|
||||
if (orig.starts_with('.')
|
||||
@ -440,8 +446,6 @@ fn main() -> Result<()> {
|
||||
&& tokens.0.len() == 1
|
||||
{
|
||||
// We have an auto-cd
|
||||
let _ = std::env::set_current_dir(&path);
|
||||
|
||||
//FIXME: this only changes the current scope, but instead this environment variable
|
||||
//should probably be a block that loads the information from the state in the overlay
|
||||
stack.add_env_var(
|
||||
@ -493,7 +497,8 @@ fn main() -> Result<()> {
|
||||
// In order to ensure the values have spans, it first creates a dummy file, writes the collected
|
||||
// env vars into it (in a "NAME"="value" format, quite similar to the output of the Unix 'env'
|
||||
// tool), then uses the file to get the spans. The file stays in memory, no filesystem IO is done.
|
||||
fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) {
|
||||
fn gather_parent_env_vars(engine_state: &mut EngineState) {
|
||||
// Some helper functions
|
||||
fn get_surround_char(s: &str) -> Option<char> {
|
||||
if s.contains('"') {
|
||||
if s.contains('\'') {
|
||||
@ -517,10 +522,14 @@ fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) {
|
||||
);
|
||||
}
|
||||
|
||||
let mut fake_env_file = String::new();
|
||||
for (name, val) in std::env::vars() {
|
||||
fn put_env_to_fake_file(
|
||||
name: &str,
|
||||
val: &str,
|
||||
fake_env_file: &mut String,
|
||||
engine_state: &EngineState,
|
||||
) {
|
||||
let (c_name, c_val) =
|
||||
if let (Some(cn), Some(cv)) = (get_surround_char(&name), get_surround_char(&val)) {
|
||||
if let (Some(cn), Some(cv)) = (get_surround_char(name), get_surround_char(val)) {
|
||||
(cn, cv)
|
||||
} else {
|
||||
// environment variable with its name or value containing both ' and " is ignored
|
||||
@ -529,19 +538,53 @@ fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) {
|
||||
&format!("{}={}", name, val),
|
||||
"Name or value should not contain both ' and \" at the same time.",
|
||||
);
|
||||
continue;
|
||||
return;
|
||||
};
|
||||
|
||||
fake_env_file.push(c_name);
|
||||
fake_env_file.push_str(&name);
|
||||
fake_env_file.push_str(name);
|
||||
fake_env_file.push(c_name);
|
||||
fake_env_file.push('=');
|
||||
fake_env_file.push(c_val);
|
||||
fake_env_file.push_str(&val);
|
||||
fake_env_file.push_str(val);
|
||||
fake_env_file.push(c_val);
|
||||
fake_env_file.push('\n');
|
||||
}
|
||||
|
||||
let mut fake_env_file = String::new();
|
||||
|
||||
// Make sure we always have PWD
|
||||
if std::env::var("PWD").is_err() {
|
||||
match std::env::current_dir() {
|
||||
Ok(cwd) => {
|
||||
put_env_to_fake_file(
|
||||
"PWD",
|
||||
&cwd.to_string_lossy(),
|
||||
&mut fake_env_file,
|
||||
engine_state,
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
// Could not capture current working directory
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::LabeledError(
|
||||
"Current directory not found".to_string(),
|
||||
format!("Retrieving current directory failed: {:?}", e),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write all the env vars into a fake file
|
||||
for (name, val) in std::env::vars() {
|
||||
put_env_to_fake_file(&name, &val, &mut fake_env_file, engine_state);
|
||||
}
|
||||
|
||||
// Lex the fake file, assign spans to all environment variables and add them
|
||||
// to stack
|
||||
let span_offset = engine_state.next_span_start();
|
||||
|
||||
engine_state.add_file(
|
||||
@ -622,7 +665,8 @@ fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) {
|
||||
continue;
|
||||
};
|
||||
|
||||
stack.add_env_var(name, value);
|
||||
// stack.add_env_var(name, value);
|
||||
engine_state.env_vars.insert(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -714,23 +758,27 @@ fn print_pipeline_data(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_prompt_indicators(config: &Config, stack: &Stack) -> (String, String, String, String) {
|
||||
let prompt_indicator = match stack.get_env_var(PROMPT_INDICATOR) {
|
||||
fn get_prompt_indicators(
|
||||
config: &Config,
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
) -> (String, String, String, String) {
|
||||
let prompt_indicator = match stack.get_env_var(engine_state, PROMPT_INDICATOR) {
|
||||
Some(pi) => pi.into_string("", config),
|
||||
None => "〉".to_string(),
|
||||
};
|
||||
|
||||
let prompt_vi_insert = match stack.get_env_var(PROMPT_INDICATOR_VI_INSERT) {
|
||||
let prompt_vi_insert = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_INSERT) {
|
||||
Some(pvii) => pvii.into_string("", config),
|
||||
None => ": ".to_string(),
|
||||
};
|
||||
|
||||
let prompt_vi_visual = match stack.get_env_var(PROMPT_INDICATOR_VI_VISUAL) {
|
||||
let prompt_vi_visual = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_VISUAL) {
|
||||
Some(pviv) => pviv.into_string("", config),
|
||||
None => "v ".to_string(),
|
||||
};
|
||||
|
||||
let prompt_multiline = match stack.get_env_var(PROMPT_MULTILINE_INDICATOR) {
|
||||
let prompt_multiline = match stack.get_env_var(engine_state, PROMPT_MULTILINE_INDICATOR) {
|
||||
Some(pm) => pm.into_string("", config),
|
||||
None => "::: ".to_string(),
|
||||
};
|
||||
@ -755,9 +803,9 @@ fn update_prompt<'prompt>(
|
||||
prompt_vi_insert_string,
|
||||
prompt_vi_visual_string,
|
||||
prompt_multiline_string,
|
||||
) = get_prompt_indicators(config, stack);
|
||||
) = get_prompt_indicators(config, engine_state, stack);
|
||||
|
||||
let prompt_command_block_id = match stack.get_env_var(PROMPT_COMMAND) {
|
||||
let prompt_command_block_id = match stack.get_env_var(engine_state, PROMPT_COMMAND) {
|
||||
Some(v) => match v.as_block() {
|
||||
Ok(b) => b,
|
||||
Err(_) => {
|
||||
@ -859,7 +907,16 @@ fn eval_source(
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
let cwd = match nu_engine::env::current_dir_str(engine_state, stack) {
|
||||
Ok(p) => PathBuf::from(p),
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
get_init_cwd()
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta, Some(stack), &cwd) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
}
|
||||
@ -929,3 +986,16 @@ pub fn report_error(
|
||||
let _ = enable_vt_processing();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_init_cwd() -> PathBuf {
|
||||
match std::env::current_dir() {
|
||||
Ok(cwd) => cwd,
|
||||
Err(_) => match std::env::var("PWD".to_string()) {
|
||||
Ok(cwd) => PathBuf::from(cwd),
|
||||
Err(_) => match nu_path::home_dir() {
|
||||
Some(cwd) => cwd,
|
||||
None => PathBuf::new(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user