forked from extern/nushell
Move repl loop and command/script execution to nu_cli (#4846)
* Refactor usage of is_perf_true to be a parameter passed around * Move repl loop and command/script execution to nu_cli * Move config setup out of nu_cli * Update config_files.rs * Update main.rs Co-authored-by: JT <547158+jntrnr@users.noreply.github.com>
This commit is contained in:
parent
0bd8664f33
commit
1a16b9a2c4
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -2196,6 +2196,8 @@ dependencies = [
|
|||||||
name = "nu-cli"
|
name = "nu-cli"
|
||||||
version = "0.59.1"
|
version = "0.59.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"crossterm",
|
||||||
|
"crossterm_winapi",
|
||||||
"is_executable",
|
"is_executable",
|
||||||
"log",
|
"log",
|
||||||
"miette",
|
"miette",
|
||||||
|
@ -69,7 +69,7 @@ itertools = "0.10.3"
|
|||||||
embed-resource = "1"
|
embed-resource = "1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = ["nu-plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
||||||
default = ["plugin", "which", "zip-support", "trash-support"]
|
default = ["plugin", "which", "zip-support", "trash-support"]
|
||||||
stable = ["default"]
|
stable = ["default"]
|
||||||
extra = ["default", "dataframe"]
|
extra = ["default", "dataframe"]
|
||||||
|
@ -13,9 +13,14 @@ nu-ansi-term = "0.42.0"
|
|||||||
|
|
||||||
nu-color-config = { path = "../nu-color-config" }
|
nu-color-config = { path = "../nu-color-config" }
|
||||||
|
|
||||||
|
crossterm = "0.23.0"
|
||||||
|
crossterm_winapi = "0.9.0"
|
||||||
miette = { version = "4.1.0", features = ["fancy"] }
|
miette = { version = "4.1.0", features = ["fancy"] }
|
||||||
thiserror = "1.0.29"
|
thiserror = "1.0.29"
|
||||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||||
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
is_executable = "1.0.1"
|
is_executable = "1.0.1"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
plugin = []
|
||||||
|
@ -1,24 +1,23 @@
|
|||||||
use crate::is_perf_true;
|
use crate::util::report_error;
|
||||||
use crate::utils::{gather_parent_env_vars, report_error};
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use miette::Result;
|
use miette::Result;
|
||||||
use nu_engine::{convert_env_values, eval_block};
|
use nu_engine::{convert_env_values, eval_block};
|
||||||
use nu_parser::{parse, trim_quotes};
|
use nu_parser::{parse, trim_quotes};
|
||||||
|
use nu_protocol::engine::Stack;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateDelta, StateWorkingSet},
|
engine::{EngineState, StateDelta, StateWorkingSet},
|
||||||
Config, PipelineData, Span, Spanned, Value, CONFIG_VARIABLE_ID,
|
Config, PipelineData, Spanned,
|
||||||
};
|
};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub(crate) fn evaluate(
|
pub fn evaluate_commands(
|
||||||
commands: &Spanned<String>,
|
commands: &Spanned<String>,
|
||||||
init_cwd: &Path,
|
init_cwd: &Path,
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
|
is_perf_true: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// First, set up env vars as strings only
|
|
||||||
gather_parent_env_vars(engine_state);
|
|
||||||
|
|
||||||
// Run a command (or commands) given to us by the user
|
// Run a command (or commands) given to us by the user
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
@ -47,18 +46,6 @@ pub(crate) fn evaluate(
|
|||||||
report_error(&working_set, &err);
|
report_error(&working_set, &err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut stack = nu_protocol::engine::Stack::new();
|
|
||||||
|
|
||||||
// Set up our initial config to start from
|
|
||||||
stack.vars.insert(
|
|
||||||
CONFIG_VARIABLE_ID,
|
|
||||||
Value::Record {
|
|
||||||
cols: vec![],
|
|
||||||
vals: vec![],
|
|
||||||
span: Span { start: 0, end: 0 },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let config = match stack.get_config() {
|
let config = match stack.get_config() {
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -70,13 +57,13 @@ pub(crate) fn evaluate(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Make a note of the exceptions we see for externals that look like math expressions
|
// Make a note of the exceptions we see for externals that look like math expressions
|
||||||
let exceptions = crate::utils::external_exceptions(engine_state, &stack);
|
let exceptions = crate::util::external_exceptions(engine_state, stack);
|
||||||
engine_state.external_exceptions = exceptions;
|
engine_state.external_exceptions = exceptions;
|
||||||
|
|
||||||
// Merge the delta in case env vars changed in the config
|
// Merge the delta in case env vars changed in the config
|
||||||
match nu_engine::env::current_dir(engine_state, &stack) {
|
match nu_engine::env::current_dir(engine_state, stack) {
|
||||||
Ok(cwd) => {
|
Ok(cwd) => {
|
||||||
if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(&mut stack), cwd) {
|
if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(stack), cwd) {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &e);
|
report_error(&working_set, &e);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
@ -90,15 +77,15 @@ pub(crate) fn evaluate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
if let Some(e) = convert_env_values(engine_state, &stack) {
|
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &e);
|
report_error(&working_set, &e);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
match eval_block(engine_state, &mut stack, &block, input, false, false) {
|
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||||
Ok(pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
crate::eval_file::print_table_or_error(engine_state, &mut stack, pipeline_data, &config)
|
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &config)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
@ -108,7 +95,7 @@ pub(crate) fn evaluate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
|
|
84
crates/nu-cli/src/config_files.rs
Normal file
84
crates/nu-cli/src/config_files.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
use crate::util::{eval_source, report_error};
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
use log::info;
|
||||||
|
use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet};
|
||||||
|
use nu_protocol::{PipelineData, Span};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
const PLUGIN_FILE: &str = "plugin.nu";
|
||||||
|
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
pub fn read_plugin_file(
|
||||||
|
engine_state: &mut EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
storage_path: &str,
|
||||||
|
is_perf_true: bool,
|
||||||
|
) {
|
||||||
|
// Reading signatures from signature file
|
||||||
|
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
||||||
|
add_plugin_file(engine_state, storage_path);
|
||||||
|
|
||||||
|
let plugin_path = engine_state.plugin_signatures.clone();
|
||||||
|
if let Some(plugin_path) = plugin_path {
|
||||||
|
let plugin_filename = plugin_path.to_string_lossy().to_owned();
|
||||||
|
|
||||||
|
if let Ok(contents) = std::fs::read(&plugin_path) {
|
||||||
|
eval_source(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
&contents,
|
||||||
|
&plugin_filename,
|
||||||
|
PipelineData::new(Span::new(0, 0)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_perf_true {
|
||||||
|
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
pub fn add_plugin_file(engine_state: &mut EngineState, storage_path: &str) {
|
||||||
|
if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||||
|
// Path to store plugins signatures
|
||||||
|
plugin_path.push(storage_path);
|
||||||
|
plugin_path.push(PLUGIN_FILE);
|
||||||
|
engine_state.plugin_signatures = Some(plugin_path.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_config_contents(
|
||||||
|
config_path: PathBuf,
|
||||||
|
engine_state: &mut EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
) {
|
||||||
|
if config_path.exists() & config_path.is_file() {
|
||||||
|
let config_filename = config_path.to_string_lossy().to_owned();
|
||||||
|
|
||||||
|
if let Ok(contents) = std::fs::read(&config_path) {
|
||||||
|
eval_source(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
&contents,
|
||||||
|
&config_filename,
|
||||||
|
PipelineData::new(Span::new(0, 0)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Merge the delta in case env vars changed in the config
|
||||||
|
match nu_engine::env::current_dir(engine_state, stack) {
|
||||||
|
Ok(cwd) => {
|
||||||
|
if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(stack), cwd) {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(&working_set, &e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(&working_set, &e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
use crate::is_perf_true;
|
use crate::util::{eval_source, report_error};
|
||||||
use crate::utils::{eval_source, gather_parent_env_vars, report_error};
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
@ -8,41 +7,28 @@ use nu_parser::parse;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Config, PipelineData, Span, Value, CONFIG_VARIABLE_ID,
|
Config, PipelineData, Span, Value,
|
||||||
};
|
};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
/// Main function used when a file path is found as argument for nu
|
/// Main function used when a file path is found as argument for nu
|
||||||
pub(crate) fn evaluate(
|
pub fn evaluate_file(
|
||||||
path: String,
|
path: String,
|
||||||
args: &[String],
|
args: &[String],
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
|
is_perf_true: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// First, set up env vars as strings only
|
|
||||||
gather_parent_env_vars(engine_state);
|
|
||||||
|
|
||||||
let mut stack = nu_protocol::engine::Stack::new();
|
|
||||||
|
|
||||||
// Set up our initial config to start from
|
|
||||||
stack.vars.insert(
|
|
||||||
CONFIG_VARIABLE_ID,
|
|
||||||
Value::Record {
|
|
||||||
cols: vec![],
|
|
||||||
vals: vec![],
|
|
||||||
span: Span { start: 0, end: 0 },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
if let Some(e) = convert_env_values(engine_state, &stack) {
|
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &e);
|
report_error(&working_set, &e);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a note of the exceptions we see for externals that look like math expressions
|
// Make a note of the exceptions we see for externals that look like math expressions
|
||||||
let exceptions = crate::utils::external_exceptions(engine_state, &stack);
|
let exceptions = crate::util::external_exceptions(engine_state, stack);
|
||||||
engine_state.external_exceptions = exceptions;
|
engine_state.external_exceptions = exceptions;
|
||||||
|
|
||||||
let file = std::fs::read(&path).into_diagnostic()?;
|
let file = std::fs::read(&path).into_diagnostic()?;
|
||||||
@ -57,27 +43,21 @@ pub(crate) fn evaluate(
|
|||||||
|
|
||||||
if !eval_source(
|
if !eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
&mut stack,
|
stack,
|
||||||
&file,
|
&file,
|
||||||
&path,
|
&path,
|
||||||
PipelineData::new(Span::new(0, 0)),
|
PipelineData::new(Span::new(0, 0)),
|
||||||
) {
|
) {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
if !eval_source(
|
if !eval_source(engine_state, stack, args.as_bytes(), "<commandline>", input) {
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
args.as_bytes(),
|
|
||||||
"<commandline>",
|
|
||||||
input,
|
|
||||||
) {
|
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
} else if !eval_source(engine_state, &mut stack, &file, &path, input) {
|
} else if !eval_source(engine_state, stack, &file, &path, input) {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
|
|
@ -1,17 +1,33 @@
|
|||||||
|
mod commands;
|
||||||
mod completions;
|
mod completions;
|
||||||
|
mod config_files;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
mod eval_file;
|
||||||
mod nu_highlight;
|
mod nu_highlight;
|
||||||
mod print;
|
mod print;
|
||||||
mod prompt;
|
mod prompt;
|
||||||
|
mod prompt_update;
|
||||||
|
mod reedline_config;
|
||||||
|
mod repl;
|
||||||
mod syntax_highlight;
|
mod syntax_highlight;
|
||||||
mod util;
|
mod util;
|
||||||
mod validation;
|
mod validation;
|
||||||
|
|
||||||
|
pub use commands::evaluate_commands;
|
||||||
pub use completions::NuCompleter;
|
pub use completions::NuCompleter;
|
||||||
|
pub use config_files::eval_config_contents;
|
||||||
pub use errors::CliError;
|
pub use errors::CliError;
|
||||||
|
pub use eval_file::evaluate_file;
|
||||||
pub use nu_highlight::NuHighlight;
|
pub use nu_highlight::NuHighlight;
|
||||||
pub use print::Print;
|
pub use print::Print;
|
||||||
pub use prompt::NushellPrompt;
|
pub use prompt::NushellPrompt;
|
||||||
|
pub use repl::evaluate_repl;
|
||||||
pub use syntax_highlight::NuHighlighter;
|
pub use syntax_highlight::NuHighlighter;
|
||||||
pub use util::print_pipeline_data;
|
pub use util::print_pipeline_data;
|
||||||
|
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error};
|
||||||
pub use validation::NuValidator;
|
pub use validation::NuValidator;
|
||||||
|
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
pub use config_files::add_plugin_file;
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
pub use config_files::read_plugin_file;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{is_perf_true, utils::report_error};
|
use crate::util::report_error;
|
||||||
|
use crate::NushellPrompt;
|
||||||
use log::info;
|
use log::info;
|
||||||
use nu_cli::NushellPrompt;
|
|
||||||
use nu_engine::eval_subexpression;
|
use nu_engine::eval_subexpression;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
@ -21,6 +21,7 @@ pub(crate) fn get_prompt_indicators(
|
|||||||
config: &Config,
|
config: &Config,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
|
is_perf_true: bool,
|
||||||
) -> (String, String, String, String) {
|
) -> (String, String, String, String) {
|
||||||
let prompt_indicator = match stack.get_env_var(engine_state, PROMPT_INDICATOR) {
|
let prompt_indicator = match stack.get_env_var(engine_state, PROMPT_INDICATOR) {
|
||||||
Some(pi) => pi.into_string("", config),
|
Some(pi) => pi.into_string("", config),
|
||||||
@ -42,7 +43,7 @@ pub(crate) fn get_prompt_indicators(
|
|||||||
None => "::: ".to_string(),
|
None => "::: ".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!(
|
info!(
|
||||||
"get_prompt_indicators {}:{}:{}",
|
"get_prompt_indicators {}:{}:{}",
|
||||||
file!(),
|
file!(),
|
||||||
@ -64,6 +65,7 @@ fn get_prompt_string(
|
|||||||
config: &Config,
|
config: &Config,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
|
is_perf_true: bool,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
stack
|
stack
|
||||||
.get_env_var(engine_state, prompt)
|
.get_env_var(engine_state, prompt)
|
||||||
@ -82,7 +84,7 @@ fn get_prompt_string(
|
|||||||
block,
|
block,
|
||||||
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
||||||
);
|
);
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!(
|
info!(
|
||||||
"get_prompt_string (block) {}:{}:{}",
|
"get_prompt_string (block) {}:{}:{}",
|
||||||
file!(),
|
file!(),
|
||||||
@ -111,7 +113,7 @@ fn get_prompt_string(
|
|||||||
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
||||||
)
|
)
|
||||||
.ok();
|
.ok();
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!(
|
info!(
|
||||||
"get_prompt_string (string) {}:{}:{}",
|
"get_prompt_string (string) {}:{}:{}",
|
||||||
file!(),
|
file!(),
|
||||||
@ -149,6 +151,7 @@ pub(crate) fn update_prompt<'prompt>(
|
|||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
nu_prompt: &'prompt mut NushellPrompt,
|
nu_prompt: &'prompt mut NushellPrompt,
|
||||||
|
is_perf_true: bool,
|
||||||
) -> &'prompt dyn Prompt {
|
) -> &'prompt dyn Prompt {
|
||||||
// get the other indicators
|
// get the other indicators
|
||||||
let (
|
let (
|
||||||
@ -156,21 +159,33 @@ pub(crate) fn update_prompt<'prompt>(
|
|||||||
prompt_vi_insert_string,
|
prompt_vi_insert_string,
|
||||||
prompt_vi_normal_string,
|
prompt_vi_normal_string,
|
||||||
prompt_multiline_string,
|
prompt_multiline_string,
|
||||||
) = get_prompt_indicators(config, engine_state, stack);
|
) = get_prompt_indicators(config, engine_state, stack, is_perf_true);
|
||||||
|
|
||||||
let mut stack = stack.clone();
|
let mut stack = stack.clone();
|
||||||
|
|
||||||
// apply the other indicators
|
// apply the other indicators
|
||||||
nu_prompt.update_all_prompt_strings(
|
nu_prompt.update_all_prompt_strings(
|
||||||
get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack),
|
get_prompt_string(
|
||||||
get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack),
|
PROMPT_COMMAND,
|
||||||
|
config,
|
||||||
|
engine_state,
|
||||||
|
&mut stack,
|
||||||
|
is_perf_true,
|
||||||
|
),
|
||||||
|
get_prompt_string(
|
||||||
|
PROMPT_COMMAND_RIGHT,
|
||||||
|
config,
|
||||||
|
engine_state,
|
||||||
|
&mut stack,
|
||||||
|
is_perf_true,
|
||||||
|
),
|
||||||
prompt_indicator_string,
|
prompt_indicator_string,
|
||||||
prompt_multiline_string,
|
prompt_multiline_string,
|
||||||
(prompt_vi_insert_string, prompt_vi_normal_string),
|
(prompt_vi_insert_string, prompt_vi_normal_string),
|
||||||
);
|
);
|
||||||
|
|
||||||
let ret_val = nu_prompt as &dyn Prompt;
|
let ret_val = nu_prompt as &dyn Prompt;
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
info!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
|
|
@ -1,28 +1,31 @@
|
|||||||
use crate::is_perf_true;
|
|
||||||
use crate::reedline_config::{add_completion_menu, add_history_menu};
|
use crate::reedline_config::{add_completion_menu, add_history_menu};
|
||||||
use crate::{config_files, prompt_update, reedline_config};
|
use crate::{prompt_update, reedline_config};
|
||||||
use crate::{
|
use crate::{
|
||||||
reedline_config::KeybindingsMode,
|
reedline_config::KeybindingsMode,
|
||||||
utils::{eval_source, gather_parent_env_vars, report_error},
|
util::{eval_source, report_error},
|
||||||
};
|
};
|
||||||
|
use crate::{NuCompleter, NuHighlighter, NuValidator, NushellPrompt};
|
||||||
use log::info;
|
use log::info;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_cli::{NuCompleter, NuHighlighter, NuValidator, NushellPrompt};
|
|
||||||
use nu_color_config::get_color_config;
|
use nu_color_config::get_color_config;
|
||||||
use nu_engine::convert_env_values;
|
use nu_engine::convert_env_values;
|
||||||
use nu_parser::lex;
|
use nu_parser::lex;
|
||||||
|
use nu_protocol::engine::Stack;
|
||||||
|
use nu_protocol::PipelineData;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, StateWorkingSet},
|
||||||
Config, ShellError, Span, Value, CONFIG_VARIABLE_ID,
|
Config, ShellError, Span, Value, CONFIG_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
use nu_protocol::{PipelineData, Spanned};
|
|
||||||
use reedline::{DefaultHinter, Emacs, Vi};
|
use reedline::{DefaultHinter, Emacs, Vi};
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::{sync::atomic::Ordering, time::Instant};
|
use std::{sync::atomic::Ordering, time::Instant};
|
||||||
|
|
||||||
pub(crate) fn evaluate(
|
pub fn evaluate_repl(
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
config_file: Option<Spanned<String>>,
|
stack: &mut Stack,
|
||||||
|
history_path: Option<PathBuf>,
|
||||||
|
is_perf_true: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// use crate::logger::{configure, logger};
|
// use crate::logger::{configure, logger};
|
||||||
use reedline::{FileBackedHistory, Reedline, Signal};
|
use reedline::{FileBackedHistory, Reedline, Signal};
|
||||||
@ -30,34 +33,34 @@ pub(crate) fn evaluate(
|
|||||||
let mut entry_num = 0;
|
let mut entry_num = 0;
|
||||||
|
|
||||||
let mut nu_prompt = NushellPrompt::new();
|
let mut nu_prompt = NushellPrompt::new();
|
||||||
let mut stack = nu_protocol::engine::Stack::new();
|
// let mut stack = nu_protocol::engine::Stack::new();
|
||||||
|
|
||||||
// First, set up env vars as strings only
|
// First, set up env vars as strings only
|
||||||
gather_parent_env_vars(engine_state);
|
// gather_parent_env_vars(engine_state);
|
||||||
|
|
||||||
// Set up our initial config to start from
|
// Set up our initial config to start from
|
||||||
stack.vars.insert(
|
// stack.vars.insert(
|
||||||
CONFIG_VARIABLE_ID,
|
// CONFIG_VARIABLE_ID,
|
||||||
Value::Record {
|
// Value::Record {
|
||||||
cols: vec![],
|
// cols: vec![],
|
||||||
vals: vec![],
|
// vals: vec![],
|
||||||
span: Span::new(0, 0),
|
// span: Span::new(0, 0),
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
// #[cfg(feature = "plugin")]
|
||||||
config_files::read_plugin_file(engine_state, &mut stack);
|
// config_files::read_plugin_file(engine_state, &mut stack, is_perf_true);
|
||||||
|
//
|
||||||
if is_perf_true() {
|
// if is_perf_true {
|
||||||
info!("read_config_file {}:{}:{}", file!(), line!(), column!());
|
// info!("read_config_file {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
config_files::read_config_file(engine_state, &mut stack, config_file);
|
// config_files::read_config_file(engine_state, &mut stack, config_file, is_perf_true);
|
||||||
let history_path = config_files::create_history_path();
|
// let history_path = config_files::create_history_path();
|
||||||
|
|
||||||
// logger(|builder| {
|
// logger(|builder| {
|
||||||
// configure(&config.log_level, builder)?;
|
// configure(&config.log_level, builder)?;
|
||||||
@ -67,7 +70,7 @@ pub(crate) fn evaluate(
|
|||||||
// Ok(())
|
// Ok(())
|
||||||
// })?;
|
// })?;
|
||||||
|
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!(
|
info!(
|
||||||
"translate environment vars {}:{}:{}",
|
"translate environment vars {}:{}:{}",
|
||||||
file!(),
|
file!(),
|
||||||
@ -77,13 +80,13 @@ pub(crate) fn evaluate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
if let Some(e) = convert_env_values(engine_state, &stack) {
|
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &e);
|
report_error(&working_set, &e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a note of the exceptions we see for externals that look like math expressions
|
// Make a note of the exceptions we see for externals that look like math expressions
|
||||||
let exceptions = crate::utils::external_exceptions(engine_state, &stack);
|
let exceptions = crate::util::external_exceptions(engine_state, stack);
|
||||||
engine_state.external_exceptions = exceptions;
|
engine_state.external_exceptions = exceptions;
|
||||||
|
|
||||||
// seed env vars
|
// seed env vars
|
||||||
@ -104,7 +107,7 @@ pub(crate) fn evaluate(
|
|||||||
);
|
);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!(
|
info!(
|
||||||
"load config each loop {}:{}:{}",
|
"load config each loop {}:{}:{}",
|
||||||
file!(),
|
file!(),
|
||||||
@ -128,7 +131,7 @@ pub(crate) fn evaluate(
|
|||||||
ctrlc.store(false, Ordering::SeqCst);
|
ctrlc.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!("setup line editor {}:{}:{}", file!(), line!(), column!());
|
info!("setup line editor {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,14 +153,14 @@ pub(crate) fn evaluate(
|
|||||||
.with_partial_completions(config.partial_completions)
|
.with_partial_completions(config.partial_completions)
|
||||||
.with_ansi_colors(config.use_ansi_coloring);
|
.with_ansi_colors(config.use_ansi_coloring);
|
||||||
|
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
|
|
||||||
line_editor = add_completion_menu(line_editor, &config);
|
line_editor = add_completion_menu(line_editor, &config);
|
||||||
line_editor = add_history_menu(line_editor, &config);
|
line_editor = add_history_menu(line_editor, &config);
|
||||||
|
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
//FIXME: if config.use_ansi_coloring is false then we should
|
//FIXME: if config.use_ansi_coloring is false then we should
|
||||||
@ -165,7 +168,7 @@ pub(crate) fn evaluate(
|
|||||||
|
|
||||||
let color_hm = get_color_config(&config);
|
let color_hm = get_color_config(&config);
|
||||||
|
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!(
|
info!(
|
||||||
"setup history and hinter {}:{}:{}",
|
"setup history and hinter {}:{}:{}",
|
||||||
file!(),
|
file!(),
|
||||||
@ -196,7 +199,7 @@ pub(crate) fn evaluate(
|
|||||||
line_editor
|
line_editor
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!("setup keybindings {}:{}:{}", file!(), line!(), column!());
|
info!("setup keybindings {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,15 +225,21 @@ pub(crate) fn evaluate(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!("prompt_update {}:{}:{}", file!(), line!(), column!());
|
info!("prompt_update {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
|
|
||||||
let prompt = prompt_update::update_prompt(&config, engine_state, &stack, &mut nu_prompt);
|
let prompt = prompt_update::update_prompt(
|
||||||
|
&config,
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
&mut nu_prompt,
|
||||||
|
is_perf_true,
|
||||||
|
);
|
||||||
|
|
||||||
entry_num += 1;
|
entry_num += 1;
|
||||||
|
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!(
|
info!(
|
||||||
"finished setup, starting repl {}:{}:{}",
|
"finished setup, starting repl {}:{}:{}",
|
||||||
file!(),
|
file!(),
|
||||||
@ -245,7 +254,7 @@ pub(crate) fn evaluate(
|
|||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
||||||
// Check if this is a single call to a directory, if so auto-cd
|
// Check if this is a single call to a directory, if so auto-cd
|
||||||
let cwd = nu_engine::env::current_dir_str(engine_state, &stack)?;
|
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
|
||||||
let path = nu_path::expand_path_with(&s, &cwd);
|
let path = nu_path::expand_path_with(&s, &cwd);
|
||||||
|
|
||||||
let orig = s.clone();
|
let orig = s.clone();
|
||||||
@ -308,7 +317,7 @@ pub(crate) fn evaluate(
|
|||||||
|
|
||||||
eval_source(
|
eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
&mut stack,
|
stack,
|
||||||
s.as_bytes(),
|
s.as_bytes(),
|
||||||
&format!("entry #{}", entry_num),
|
&format!("entry #{}", entry_num),
|
||||||
PipelineData::new(Span::new(0, 0)),
|
PipelineData::new(Span::new(0, 0)),
|
||||||
@ -331,7 +340,7 @@ pub(crate) fn evaluate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make a note of the exceptions we see for externals that look like math expressions
|
// Make a note of the exceptions we see for externals that look like math expressions
|
||||||
let exceptions = crate::utils::external_exceptions(engine_state, &stack);
|
let exceptions = crate::util::external_exceptions(engine_state, stack);
|
||||||
engine_state.external_exceptions = exceptions;
|
engine_state.external_exceptions = exceptions;
|
||||||
}
|
}
|
||||||
Ok(Signal::CtrlC) => {
|
Ok(Signal::CtrlC) => {
|
@ -1,5 +1,11 @@
|
|||||||
|
use log::trace;
|
||||||
|
use nu_engine::eval_block;
|
||||||
|
use nu_parser::{lex, parse, trim_quotes, Token, TokenContents};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::CliError;
|
||||||
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
@ -84,3 +90,419 @@ pub fn print_pipeline_data(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will collect environment variables from std::env and adds them to a stack.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
pub 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('\'') {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some('\'')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some('\'')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(
|
||||||
|
&working_set,
|
||||||
|
&ShellError::LabeledError(
|
||||||
|
format!("Environment variable was not captured: {}", env_str),
|
||||||
|
msg.into(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
(cn, cv)
|
||||||
|
} else {
|
||||||
|
// environment variable with its name or value containing both ' and " is ignored
|
||||||
|
report_capture_error(
|
||||||
|
engine_state,
|
||||||
|
&format!("{}={}", name, val),
|
||||||
|
"Name or value should not contain both ' and \" at the same time.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
fake_env_file.push(c_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(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(
|
||||||
|
"Host Environment Variables".to_string(),
|
||||||
|
fake_env_file.as_bytes().to_vec(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (tokens, _) = lex(fake_env_file.as_bytes(), span_offset, &[], &[], true);
|
||||||
|
|
||||||
|
for token in tokens {
|
||||||
|
if let Token {
|
||||||
|
contents: TokenContents::Item,
|
||||||
|
span: full_span,
|
||||||
|
} = token
|
||||||
|
{
|
||||||
|
let contents = engine_state.get_span_contents(&full_span);
|
||||||
|
let (parts, _) = lex(contents, full_span.start, &[], &[b'='], true);
|
||||||
|
|
||||||
|
let name = if let Some(Token {
|
||||||
|
contents: TokenContents::Item,
|
||||||
|
span,
|
||||||
|
}) = parts.get(0)
|
||||||
|
{
|
||||||
|
let bytes = engine_state.get_span_contents(span);
|
||||||
|
|
||||||
|
if bytes.len() < 2 {
|
||||||
|
report_capture_error(
|
||||||
|
engine_state,
|
||||||
|
&String::from_utf8_lossy(contents),
|
||||||
|
"Got empty name.",
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = trim_quotes(bytes);
|
||||||
|
String::from_utf8_lossy(bytes).to_string()
|
||||||
|
} else {
|
||||||
|
report_capture_error(
|
||||||
|
engine_state,
|
||||||
|
&String::from_utf8_lossy(contents),
|
||||||
|
"Got empty name.",
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = if let Some(Token {
|
||||||
|
contents: TokenContents::Item,
|
||||||
|
span,
|
||||||
|
}) = parts.get(2)
|
||||||
|
{
|
||||||
|
let bytes = engine_state.get_span_contents(span);
|
||||||
|
|
||||||
|
if bytes.len() < 2 {
|
||||||
|
report_capture_error(
|
||||||
|
engine_state,
|
||||||
|
&String::from_utf8_lossy(contents),
|
||||||
|
"Got empty value.",
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = trim_quotes(bytes);
|
||||||
|
|
||||||
|
Value::String {
|
||||||
|
val: String::from_utf8_lossy(bytes).to_string(),
|
||||||
|
span: *span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
report_capture_error(
|
||||||
|
engine_state,
|
||||||
|
&String::from_utf8_lossy(contents),
|
||||||
|
"Got empty value.",
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// stack.add_env_var(name, value);
|
||||||
|
engine_state.env_vars.insert(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_source(
|
||||||
|
engine_state: &mut EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
source: &[u8],
|
||||||
|
fname: &str,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> bool {
|
||||||
|
trace!("eval_source");
|
||||||
|
|
||||||
|
let (block, delta) = {
|
||||||
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
let (output, err) = parse(
|
||||||
|
&mut working_set,
|
||||||
|
Some(fname), // format!("entry #{}", entry_num)
|
||||||
|
source,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
if let Some(err) = err {
|
||||||
|
report_error(&working_set, &err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
(output, working_set.render())
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||||
|
Ok(mut pipeline_data) => {
|
||||||
|
if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data {
|
||||||
|
if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) {
|
||||||
|
stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code);
|
||||||
|
} else {
|
||||||
|
stack.add_env_var(
|
||||||
|
"LAST_EXIT_CODE".to_string(),
|
||||||
|
Value::Int {
|
||||||
|
val: 0,
|
||||||
|
span: Span { start: 0, end: 0 },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stack.add_env_var(
|
||||||
|
"LAST_EXIT_CODE".to_string(),
|
||||||
|
Value::Int {
|
||||||
|
val: 0,
|
||||||
|
span: Span { start: 0, end: 0 },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(err) = print_pipeline_data(pipeline_data, engine_state, stack) {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
report_error(&working_set, &err);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
let _ = enable_vt_processing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
stack.add_env_var(
|
||||||
|
"LAST_EXIT_CODE".to_string(),
|
||||||
|
Value::Int {
|
||||||
|
val: 1,
|
||||||
|
span: Span { start: 0, end: 0 },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
report_error(&working_set, &err);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn seems_like_number(bytes: &[u8]) -> bool {
|
||||||
|
if bytes.is_empty() {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
let b = bytes[0];
|
||||||
|
|
||||||
|
b == b'0'
|
||||||
|
|| b == b'1'
|
||||||
|
|| b == b'2'
|
||||||
|
|| b == b'3'
|
||||||
|
|| b == b'4'
|
||||||
|
|| b == b'5'
|
||||||
|
|| b == b'6'
|
||||||
|
|| b == b'7'
|
||||||
|
|| b == b'8'
|
||||||
|
|| b == b'9'
|
||||||
|
|| b == b'('
|
||||||
|
|| b == b'{'
|
||||||
|
|| b == b'['
|
||||||
|
|| b == b'$'
|
||||||
|
|| b == b'"'
|
||||||
|
|| b == b'\''
|
||||||
|
|| b == b'-'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds externals that have names that look like math expressions
|
||||||
|
pub fn external_exceptions(engine_state: &EngineState, stack: &Stack) -> Vec<Vec<u8>> {
|
||||||
|
let mut executables = vec![];
|
||||||
|
|
||||||
|
if let Some(path) = stack.get_env_var(engine_state, "PATH") {
|
||||||
|
match path {
|
||||||
|
Value::List { vals, .. } => {
|
||||||
|
for val in vals {
|
||||||
|
let path = val.as_string();
|
||||||
|
|
||||||
|
if let Ok(path) = path {
|
||||||
|
if let Ok(mut contents) = std::fs::read_dir(path) {
|
||||||
|
while let Some(Ok(item)) = contents.next() {
|
||||||
|
if is_executable::is_executable(&item.path()) {
|
||||||
|
if let Ok(name) = item.file_name().into_string() {
|
||||||
|
if seems_like_number(name.as_bytes()) {
|
||||||
|
let name = name.as_bytes().to_vec();
|
||||||
|
executables.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(name) = item.path().file_stem() {
|
||||||
|
let name = name.to_string_lossy();
|
||||||
|
if seems_like_number(name.as_bytes()) {
|
||||||
|
let name = name.as_bytes().to_vec();
|
||||||
|
executables.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::String { val, .. } => {
|
||||||
|
for path in std::env::split_paths(&val) {
|
||||||
|
let path = path.to_string_lossy().to_string();
|
||||||
|
|
||||||
|
if let Ok(mut contents) = std::fs::read_dir(path) {
|
||||||
|
while let Some(Ok(item)) = contents.next() {
|
||||||
|
if is_executable::is_executable(&item.path()) {
|
||||||
|
if let Ok(name) = item.file_name().into_string() {
|
||||||
|
if seems_like_number(name.as_bytes()) {
|
||||||
|
let name = name.as_bytes().to_vec();
|
||||||
|
executables.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(name) = item.path().file_stem() {
|
||||||
|
let name = name.to_string_lossy();
|
||||||
|
if seems_like_number(name.as_bytes()) {
|
||||||
|
let name = name.as_bytes().to_vec();
|
||||||
|
executables.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executables
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn enable_vt_processing() -> Result<(), ShellError> {
|
||||||
|
use crossterm_winapi::{ConsoleMode, Handle};
|
||||||
|
|
||||||
|
pub const ENABLE_PROCESSED_OUTPUT: u32 = 0x0001;
|
||||||
|
pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
|
||||||
|
// let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||||
|
|
||||||
|
let console_mode = ConsoleMode::from(Handle::current_out_handle()?);
|
||||||
|
let old_mode = console_mode.mode()?;
|
||||||
|
|
||||||
|
// researching odd ansi behavior in windows terminal repo revealed that
|
||||||
|
// enable_processed_output and enable_virtual_terminal_processing should be used
|
||||||
|
// also, instead of checking old_mode & mask, just set the mode already
|
||||||
|
|
||||||
|
// if old_mode & mask == 0 {
|
||||||
|
console_mode
|
||||||
|
.set_mode(old_mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)?;
|
||||||
|
// }
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn report_error(
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||||
|
) {
|
||||||
|
eprintln!("Error: {:?}", CliError(error, working_set));
|
||||||
|
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
let _ = enable_vt_processing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_init_cwd() -> PathBuf {
|
||||||
|
match std::env::current_dir() {
|
||||||
|
Ok(cwd) => cwd,
|
||||||
|
Err(_) => match std::env::var("PWD") {
|
||||||
|
Ok(cwd) => PathBuf::from(cwd),
|
||||||
|
Err(_) => match nu_path::home_dir() {
|
||||||
|
Some(cwd) => cwd,
|
||||||
|
None => PathBuf::new(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,60 +1,22 @@
|
|||||||
use crate::is_perf_true;
|
|
||||||
use crate::utils::{eval_source, report_error};
|
|
||||||
use log::info;
|
use log::info;
|
||||||
|
use nu_cli::{eval_config_contents, eval_source, report_error};
|
||||||
use nu_parser::ParseError;
|
use nu_parser::ParseError;
|
||||||
use nu_path::canonicalize_with;
|
use nu_path::canonicalize_with;
|
||||||
use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{PipelineData, Span, Spanned};
|
use nu_protocol::{PipelineData, Span, Spanned};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
const NUSHELL_FOLDER: &str = "nushell";
|
pub(crate) const NUSHELL_FOLDER: &str = "nushell";
|
||||||
const CONFIG_FILE: &str = "config.nu";
|
const CONFIG_FILE: &str = "config.nu";
|
||||||
const HISTORY_FILE: &str = "history.txt";
|
const HISTORY_FILE: &str = "history.txt";
|
||||||
#[cfg(feature = "plugin")]
|
|
||||||
const PLUGIN_FILE: &str = "plugin.nu";
|
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
|
||||||
pub(crate) fn read_plugin_file(engine_state: &mut EngineState, stack: &mut Stack) {
|
|
||||||
// Reading signatures from signature file
|
|
||||||
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
|
||||||
add_plugin_file(engine_state);
|
|
||||||
|
|
||||||
let plugin_path = engine_state.plugin_signatures.clone();
|
|
||||||
if let Some(plugin_path) = plugin_path {
|
|
||||||
let plugin_filename = plugin_path.to_string_lossy().to_owned();
|
|
||||||
|
|
||||||
if let Ok(contents) = std::fs::read(&plugin_path) {
|
|
||||||
eval_source(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
&contents,
|
|
||||||
&plugin_filename,
|
|
||||||
PipelineData::new(Span::new(0, 0)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_perf_true() {
|
|
||||||
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
|
||||||
pub(crate) fn add_plugin_file(engine_state: &mut EngineState) {
|
|
||||||
if let Some(mut plugin_path) = nu_path::config_dir() {
|
|
||||||
// Path to store plugins signatures
|
|
||||||
plugin_path.push(NUSHELL_FOLDER);
|
|
||||||
plugin_path.push(PLUGIN_FILE);
|
|
||||||
engine_state.plugin_signatures = Some(plugin_path.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn read_config_file(
|
pub(crate) fn read_config_file(
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
config_file: Option<Spanned<String>>,
|
config_file: Option<Spanned<String>>,
|
||||||
|
is_perf_true: bool,
|
||||||
) {
|
) {
|
||||||
// Load config startup file
|
// Load config startup file
|
||||||
if let Some(file) = config_file {
|
if let Some(file) = config_file {
|
||||||
@ -118,41 +80,11 @@ pub(crate) fn read_config_file(
|
|||||||
eval_config_contents(config_path, engine_state, stack);
|
eval_config_contents(config_path, engine_state, stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_perf_true() {
|
if is_perf_true {
|
||||||
info!("read_config_file {}:{}:{}", file!(), line!(), column!());
|
info!("read_config_file {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_config_contents(config_path: PathBuf, engine_state: &mut EngineState, stack: &mut Stack) {
|
|
||||||
if config_path.exists() & config_path.is_file() {
|
|
||||||
let config_filename = config_path.to_string_lossy().to_owned();
|
|
||||||
|
|
||||||
if let Ok(contents) = std::fs::read(&config_path) {
|
|
||||||
eval_source(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
&contents,
|
|
||||||
&config_filename,
|
|
||||||
PipelineData::new(Span::new(0, 0)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Merge the delta in case env vars changed in the config
|
|
||||||
match nu_engine::env::current_dir(engine_state, stack) {
|
|
||||||
Ok(cwd) => {
|
|
||||||
if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(stack), cwd) {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(&working_set, &e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(&working_set, &e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn create_history_path() -> Option<PathBuf> {
|
pub(crate) fn create_history_path() -> Option<PathBuf> {
|
||||||
nu_path::config_dir().and_then(|mut history_path| {
|
nu_path::config_dir().and_then(|mut history_path| {
|
||||||
history_path.push(NUSHELL_FOLDER);
|
history_path.push(NUSHELL_FOLDER);
|
||||||
|
76
src/main.rs
76
src/main.rs
@ -1,18 +1,22 @@
|
|||||||
mod commands;
|
|
||||||
mod config_files;
|
mod config_files;
|
||||||
mod eval_file;
|
|
||||||
mod logger;
|
mod logger;
|
||||||
mod prompt_update;
|
|
||||||
mod reedline_config;
|
|
||||||
mod repl;
|
|
||||||
mod test_bins;
|
mod test_bins;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
mod utils;
|
|
||||||
|
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
use crate::config_files::NUSHELL_FOLDER;
|
||||||
use crate::logger::{configure, logger};
|
use crate::logger::{configure, logger};
|
||||||
use log::info;
|
use log::info;
|
||||||
use miette::Result;
|
use miette::Result;
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
use nu_cli::add_plugin_file;
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
use nu_cli::read_plugin_file;
|
||||||
|
use nu_cli::{
|
||||||
|
evaluate_commands, evaluate_file, evaluate_repl, gather_parent_env_vars, get_init_cwd,
|
||||||
|
report_error,
|
||||||
|
};
|
||||||
use nu_command::{create_default_context, BufferedReader};
|
use nu_command::{create_default_context, BufferedReader};
|
||||||
use nu_engine::{get_full_help, CallExt};
|
use nu_engine::{get_full_help, CallExt};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
@ -31,7 +35,6 @@ use std::{
|
|||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use utils::report_error;
|
|
||||||
|
|
||||||
thread_local! { static IS_PERF: RefCell<bool> = RefCell::new(false) }
|
thread_local! { static IS_PERF: RefCell<bool> = RefCell::new(false) }
|
||||||
|
|
||||||
@ -44,7 +47,7 @@ fn main() -> Result<()> {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Get initial current working directory.
|
// Get initial current working directory.
|
||||||
let init_cwd = utils::get_init_cwd();
|
let init_cwd = get_init_cwd();
|
||||||
let mut engine_state = create_default_context(&init_cwd);
|
let mut engine_state = create_default_context(&init_cwd);
|
||||||
|
|
||||||
// Custom additions
|
// Custom additions
|
||||||
@ -186,11 +189,31 @@ fn main() -> Result<()> {
|
|||||||
info!("redirect_stdin {}:{}:{}", file!(), line!(), column!());
|
info!("redirect_stdin {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First, set up env vars as strings only
|
||||||
|
gather_parent_env_vars(&mut engine_state);
|
||||||
|
let mut stack = nu_protocol::engine::Stack::new();
|
||||||
|
|
||||||
|
stack.vars.insert(
|
||||||
|
CONFIG_VARIABLE_ID,
|
||||||
|
Value::Record {
|
||||||
|
cols: vec![],
|
||||||
|
vals: vec![],
|
||||||
|
span: Span::new(0, 0),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(commands) = &binary_args.commands {
|
if let Some(commands) = &binary_args.commands {
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
config_files::add_plugin_file(&mut engine_state);
|
add_plugin_file(&mut engine_state, NUSHELL_FOLDER);
|
||||||
|
|
||||||
let ret_val = commands::evaluate(commands, &init_cwd, &mut engine_state, input);
|
let ret_val = evaluate_commands(
|
||||||
|
commands,
|
||||||
|
&init_cwd,
|
||||||
|
&mut engine_state,
|
||||||
|
&mut stack,
|
||||||
|
input,
|
||||||
|
is_perf_true(),
|
||||||
|
);
|
||||||
if is_perf_true() {
|
if is_perf_true() {
|
||||||
info!("-c command execution {}:{}:{}", file!(), line!(), column!());
|
info!("-c command execution {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
@ -198,17 +221,27 @@ fn main() -> Result<()> {
|
|||||||
ret_val
|
ret_val
|
||||||
} else if !script_name.is_empty() && binary_args.interactive_shell.is_none() {
|
} else if !script_name.is_empty() && binary_args.interactive_shell.is_none() {
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
config_files::add_plugin_file(&mut engine_state);
|
add_plugin_file(&mut engine_state, NUSHELL_FOLDER);
|
||||||
|
|
||||||
let ret_val =
|
let ret_val = evaluate_file(
|
||||||
eval_file::evaluate(script_name, &args_to_script, &mut engine_state, input);
|
script_name,
|
||||||
|
&args_to_script,
|
||||||
|
&mut engine_state,
|
||||||
|
&mut stack,
|
||||||
|
input,
|
||||||
|
is_perf_true(),
|
||||||
|
);
|
||||||
if is_perf_true() {
|
if is_perf_true() {
|
||||||
info!("eval_file execution {}:{}:{}", file!(), line!(), column!());
|
info!("eval_file execution {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
|
|
||||||
ret_val
|
ret_val
|
||||||
} else {
|
} else {
|
||||||
let ret_val = repl::evaluate(&mut engine_state, binary_args.config_file);
|
setup_config(&mut engine_state, &mut stack, binary_args.config_file);
|
||||||
|
let history_path = config_files::create_history_path();
|
||||||
|
|
||||||
|
let ret_val =
|
||||||
|
evaluate_repl(&mut engine_state, &mut stack, history_path, is_perf_true());
|
||||||
if is_perf_true() {
|
if is_perf_true() {
|
||||||
info!("repl eval {}:{}:{}", file!(), line!(), column!());
|
info!("repl eval {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
@ -220,6 +253,21 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_config(
|
||||||
|
engine_state: &mut EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
config_file: Option<Spanned<String>>,
|
||||||
|
) {
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
read_plugin_file(engine_state, stack, NUSHELL_FOLDER, is_perf_true());
|
||||||
|
|
||||||
|
if is_perf_true() {
|
||||||
|
info!("read_config_file {}:{}:{}", file!(), line!(), column!());
|
||||||
|
}
|
||||||
|
|
||||||
|
config_files::read_config_file(engine_state, stack, config_file, is_perf_true());
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_commandline_args(
|
fn parse_commandline_args(
|
||||||
commandline_args: &str,
|
commandline_args: &str,
|
||||||
init_cwd: &Path,
|
init_cwd: &Path,
|
||||||
|
425
src/utils.rs
425
src/utils.rs
@ -1,425 +0,0 @@
|
|||||||
use log::trace;
|
|
||||||
use nu_cli::{print_pipeline_data, CliError};
|
|
||||||
use nu_engine::eval_block;
|
|
||||||
use nu_parser::{lex, parse, trim_quotes, Token, TokenContents};
|
|
||||||
use nu_protocol::{
|
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
|
||||||
PipelineData, ShellError, Span, Value,
|
|
||||||
};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
// This will collect environment variables from std::env and adds them to a stack.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
pub(crate) 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('\'') {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some('\'')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Some('\'')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(
|
|
||||||
&working_set,
|
|
||||||
&ShellError::LabeledError(
|
|
||||||
format!("Environment variable was not captured: {}", env_str),
|
|
||||||
msg.into(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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)) {
|
|
||||||
(cn, cv)
|
|
||||||
} else {
|
|
||||||
// environment variable with its name or value containing both ' and " is ignored
|
|
||||||
report_capture_error(
|
|
||||||
engine_state,
|
|
||||||
&format!("{}={}", name, val),
|
|
||||||
"Name or value should not contain both ' and \" at the same time.",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
fake_env_file.push(c_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(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(
|
|
||||||
"Host Environment Variables".to_string(),
|
|
||||||
fake_env_file.as_bytes().to_vec(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let (tokens, _) = lex(fake_env_file.as_bytes(), span_offset, &[], &[], true);
|
|
||||||
|
|
||||||
for token in tokens {
|
|
||||||
if let Token {
|
|
||||||
contents: TokenContents::Item,
|
|
||||||
span: full_span,
|
|
||||||
} = token
|
|
||||||
{
|
|
||||||
let contents = engine_state.get_span_contents(&full_span);
|
|
||||||
let (parts, _) = lex(contents, full_span.start, &[], &[b'='], true);
|
|
||||||
|
|
||||||
let name = if let Some(Token {
|
|
||||||
contents: TokenContents::Item,
|
|
||||||
span,
|
|
||||||
}) = parts.get(0)
|
|
||||||
{
|
|
||||||
let bytes = engine_state.get_span_contents(span);
|
|
||||||
|
|
||||||
if bytes.len() < 2 {
|
|
||||||
report_capture_error(
|
|
||||||
engine_state,
|
|
||||||
&String::from_utf8_lossy(contents),
|
|
||||||
"Got empty name.",
|
|
||||||
);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bytes = trim_quotes(bytes);
|
|
||||||
String::from_utf8_lossy(bytes).to_string()
|
|
||||||
} else {
|
|
||||||
report_capture_error(
|
|
||||||
engine_state,
|
|
||||||
&String::from_utf8_lossy(contents),
|
|
||||||
"Got empty name.",
|
|
||||||
);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = if let Some(Token {
|
|
||||||
contents: TokenContents::Item,
|
|
||||||
span,
|
|
||||||
}) = parts.get(2)
|
|
||||||
{
|
|
||||||
let bytes = engine_state.get_span_contents(span);
|
|
||||||
|
|
||||||
if bytes.len() < 2 {
|
|
||||||
report_capture_error(
|
|
||||||
engine_state,
|
|
||||||
&String::from_utf8_lossy(contents),
|
|
||||||
"Got empty value.",
|
|
||||||
);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bytes = trim_quotes(bytes);
|
|
||||||
|
|
||||||
Value::String {
|
|
||||||
val: String::from_utf8_lossy(bytes).to_string(),
|
|
||||||
span: *span,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
report_capture_error(
|
|
||||||
engine_state,
|
|
||||||
&String::from_utf8_lossy(contents),
|
|
||||||
"Got empty value.",
|
|
||||||
);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
// stack.add_env_var(name, value);
|
|
||||||
engine_state.env_vars.insert(name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn eval_source(
|
|
||||||
engine_state: &mut EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
source: &[u8],
|
|
||||||
fname: &str,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> bool {
|
|
||||||
trace!("eval_source");
|
|
||||||
|
|
||||||
let (block, delta) = {
|
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
|
||||||
let (output, err) = parse(
|
|
||||||
&mut working_set,
|
|
||||||
Some(fname), // format!("entry #{}", entry_num)
|
|
||||||
source,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
if let Some(err) = err {
|
|
||||||
report_error(&working_set, &err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
(output, working_set.render())
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
|
||||||
Ok(mut pipeline_data) => {
|
|
||||||
if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data {
|
|
||||||
if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) {
|
|
||||||
stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code);
|
|
||||||
} else {
|
|
||||||
stack.add_env_var(
|
|
||||||
"LAST_EXIT_CODE".to_string(),
|
|
||||||
Value::Int {
|
|
||||||
val: 0,
|
|
||||||
span: Span { start: 0, end: 0 },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stack.add_env_var(
|
|
||||||
"LAST_EXIT_CODE".to_string(),
|
|
||||||
Value::Int {
|
|
||||||
val: 0,
|
|
||||||
span: Span { start: 0, end: 0 },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(err) = print_pipeline_data(pipeline_data, engine_state, stack) {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
|
|
||||||
report_error(&working_set, &err);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
let _ = enable_vt_processing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
stack.add_env_var(
|
|
||||||
"LAST_EXIT_CODE".to_string(),
|
|
||||||
Value::Int {
|
|
||||||
val: 1,
|
|
||||||
span: Span { start: 0, end: 0 },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
|
|
||||||
report_error(&working_set, &err);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn seems_like_number(bytes: &[u8]) -> bool {
|
|
||||||
if bytes.is_empty() {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
let b = bytes[0];
|
|
||||||
|
|
||||||
b == b'0'
|
|
||||||
|| b == b'1'
|
|
||||||
|| b == b'2'
|
|
||||||
|| b == b'3'
|
|
||||||
|| b == b'4'
|
|
||||||
|| b == b'5'
|
|
||||||
|| b == b'6'
|
|
||||||
|| b == b'7'
|
|
||||||
|| b == b'8'
|
|
||||||
|| b == b'9'
|
|
||||||
|| b == b'('
|
|
||||||
|| b == b'{'
|
|
||||||
|| b == b'['
|
|
||||||
|| b == b'$'
|
|
||||||
|| b == b'"'
|
|
||||||
|| b == b'\''
|
|
||||||
|| b == b'-'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds externals that have names that look like math expressions
|
|
||||||
pub fn external_exceptions(engine_state: &EngineState, stack: &Stack) -> Vec<Vec<u8>> {
|
|
||||||
let mut executables = vec![];
|
|
||||||
|
|
||||||
if let Some(path) = stack.get_env_var(engine_state, "PATH") {
|
|
||||||
match path {
|
|
||||||
Value::List { vals, .. } => {
|
|
||||||
for val in vals {
|
|
||||||
let path = val.as_string();
|
|
||||||
|
|
||||||
if let Ok(path) = path {
|
|
||||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
|
||||||
while let Some(Ok(item)) = contents.next() {
|
|
||||||
if is_executable::is_executable(&item.path()) {
|
|
||||||
if let Ok(name) = item.file_name().into_string() {
|
|
||||||
if seems_like_number(name.as_bytes()) {
|
|
||||||
let name = name.as_bytes().to_vec();
|
|
||||||
executables.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(name) = item.path().file_stem() {
|
|
||||||
let name = name.to_string_lossy();
|
|
||||||
if seems_like_number(name.as_bytes()) {
|
|
||||||
let name = name.as_bytes().to_vec();
|
|
||||||
executables.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::String { val, .. } => {
|
|
||||||
for path in std::env::split_paths(&val) {
|
|
||||||
let path = path.to_string_lossy().to_string();
|
|
||||||
|
|
||||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
|
||||||
while let Some(Ok(item)) = contents.next() {
|
|
||||||
if is_executable::is_executable(&item.path()) {
|
|
||||||
if let Ok(name) = item.file_name().into_string() {
|
|
||||||
if seems_like_number(name.as_bytes()) {
|
|
||||||
let name = name.as_bytes().to_vec();
|
|
||||||
executables.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(name) = item.path().file_stem() {
|
|
||||||
let name = name.to_string_lossy();
|
|
||||||
if seems_like_number(name.as_bytes()) {
|
|
||||||
let name = name.as_bytes().to_vec();
|
|
||||||
executables.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
executables
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn enable_vt_processing() -> Result<(), ShellError> {
|
|
||||||
use crossterm_winapi::{ConsoleMode, Handle};
|
|
||||||
|
|
||||||
pub const ENABLE_PROCESSED_OUTPUT: u32 = 0x0001;
|
|
||||||
pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
|
|
||||||
// let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
|
||||||
|
|
||||||
let console_mode = ConsoleMode::from(Handle::current_out_handle()?);
|
|
||||||
let old_mode = console_mode.mode()?;
|
|
||||||
|
|
||||||
// researching odd ansi behavior in windows terminal repo revealed that
|
|
||||||
// enable_processed_output and enable_virtual_terminal_processing should be used
|
|
||||||
// also, instead of checking old_mode & mask, just set the mode already
|
|
||||||
|
|
||||||
// if old_mode & mask == 0 {
|
|
||||||
console_mode
|
|
||||||
.set_mode(old_mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)?;
|
|
||||||
// }
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report_error(
|
|
||||||
working_set: &StateWorkingSet,
|
|
||||||
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
|
||||||
) {
|
|
||||||
eprintln!("Error: {:?}", CliError(error, working_set));
|
|
||||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
let _ = enable_vt_processing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_init_cwd() -> PathBuf {
|
|
||||||
match std::env::current_dir() {
|
|
||||||
Ok(cwd) => cwd,
|
|
||||||
Err(_) => match std::env::var("PWD") {
|
|
||||||
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