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"
|
||||
version = "0.59.1"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"crossterm_winapi",
|
||||
"is_executable",
|
||||
"log",
|
||||
"miette",
|
||||
|
@ -69,7 +69,7 @@ itertools = "0.10.3"
|
||||
embed-resource = "1"
|
||||
|
||||
[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"]
|
||||
stable = ["default"]
|
||||
extra = ["default", "dataframe"]
|
||||
|
@ -13,9 +13,14 @@ nu-ansi-term = "0.42.0"
|
||||
|
||||
nu-color-config = { path = "../nu-color-config" }
|
||||
|
||||
crossterm = "0.23.0"
|
||||
crossterm_winapi = "0.9.0"
|
||||
miette = { version = "4.1.0", features = ["fancy"] }
|
||||
thiserror = "1.0.29"
|
||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||
|
||||
log = "0.4"
|
||||
is_executable = "1.0.1"
|
||||
|
||||
[features]
|
||||
plugin = []
|
||||
|
@ -1,24 +1,23 @@
|
||||
use crate::is_perf_true;
|
||||
use crate::utils::{gather_parent_env_vars, report_error};
|
||||
use crate::util::report_error;
|
||||
use log::info;
|
||||
use miette::Result;
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::{parse, trim_quotes};
|
||||
use nu_protocol::engine::Stack;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateDelta, StateWorkingSet},
|
||||
Config, PipelineData, Span, Spanned, Value, CONFIG_VARIABLE_ID,
|
||||
Config, PipelineData, Spanned,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
pub(crate) fn evaluate(
|
||||
pub fn evaluate_commands(
|
||||
commands: &Spanned<String>,
|
||||
init_cwd: &Path,
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
input: PipelineData,
|
||||
is_perf_true: bool,
|
||||
) -> 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
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
@ -47,18 +46,6 @@ pub(crate) fn evaluate(
|
||||
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() {
|
||||
Ok(config) => config,
|
||||
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
|
||||
let exceptions = crate::utils::external_exceptions(engine_state, &stack);
|
||||
let exceptions = crate::util::external_exceptions(engine_state, stack);
|
||||
engine_state.external_exceptions = exceptions;
|
||||
|
||||
// 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) => {
|
||||
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);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
@ -90,15 +77,15 @@ pub(crate) fn evaluate(
|
||||
}
|
||||
|
||||
// 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);
|
||||
report_error(&working_set, &e);
|
||||
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) => {
|
||||
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) => {
|
||||
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!());
|
||||
}
|
||||
|
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::utils::{eval_source, gather_parent_env_vars, report_error};
|
||||
use crate::util::{eval_source, report_error};
|
||||
use log::info;
|
||||
use log::trace;
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
@ -8,41 +7,28 @@ use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Config, PipelineData, Span, Value, CONFIG_VARIABLE_ID,
|
||||
Config, PipelineData, Span, Value,
|
||||
};
|
||||
use std::io::Write;
|
||||
|
||||
/// Main function used when a file path is found as argument for nu
|
||||
pub(crate) fn evaluate(
|
||||
pub fn evaluate_file(
|
||||
path: String,
|
||||
args: &[String],
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
input: PipelineData,
|
||||
is_perf_true: bool,
|
||||
) -> 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
|
||||
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);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
let file = std::fs::read(&path).into_diagnostic()?;
|
||||
@ -57,27 +43,21 @@ pub(crate) fn evaluate(
|
||||
|
||||
if !eval_source(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
stack,
|
||||
&file,
|
||||
&path,
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
) {
|
||||
std::process::exit(1);
|
||||
}
|
||||
if !eval_source(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
args.as_bytes(),
|
||||
"<commandline>",
|
||||
input,
|
||||
) {
|
||||
if !eval_source(engine_state, stack, args.as_bytes(), "<commandline>", input) {
|
||||
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);
|
||||
}
|
||||
|
||||
if is_perf_true() {
|
||||
if is_perf_true {
|
||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
@ -1,17 +1,33 @@
|
||||
mod commands;
|
||||
mod completions;
|
||||
mod config_files;
|
||||
mod errors;
|
||||
mod eval_file;
|
||||
mod nu_highlight;
|
||||
mod print;
|
||||
mod prompt;
|
||||
mod prompt_update;
|
||||
mod reedline_config;
|
||||
mod repl;
|
||||
mod syntax_highlight;
|
||||
mod util;
|
||||
mod validation;
|
||||
|
||||
pub use commands::evaluate_commands;
|
||||
pub use completions::NuCompleter;
|
||||
pub use config_files::eval_config_contents;
|
||||
pub use errors::CliError;
|
||||
pub use eval_file::evaluate_file;
|
||||
pub use nu_highlight::NuHighlight;
|
||||
pub use print::Print;
|
||||
pub use prompt::NushellPrompt;
|
||||
pub use repl::evaluate_repl;
|
||||
pub use syntax_highlight::NuHighlighter;
|
||||
pub use util::print_pipeline_data;
|
||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error};
|
||||
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 nu_cli::NushellPrompt;
|
||||
use nu_engine::eval_subexpression;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
@ -21,6 +21,7 @@ pub(crate) fn get_prompt_indicators(
|
||||
config: &Config,
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
is_perf_true: bool,
|
||||
) -> (String, String, String, String) {
|
||||
let prompt_indicator = match stack.get_env_var(engine_state, PROMPT_INDICATOR) {
|
||||
Some(pi) => pi.into_string("", config),
|
||||
@ -42,7 +43,7 @@ pub(crate) fn get_prompt_indicators(
|
||||
None => "::: ".to_string(),
|
||||
};
|
||||
|
||||
if is_perf_true() {
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"get_prompt_indicators {}:{}:{}",
|
||||
file!(),
|
||||
@ -64,6 +65,7 @@ fn get_prompt_string(
|
||||
config: &Config,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
is_perf_true: bool,
|
||||
) -> Option<String> {
|
||||
stack
|
||||
.get_env_var(engine_state, prompt)
|
||||
@ -82,7 +84,7 @@ fn get_prompt_string(
|
||||
block,
|
||||
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!(
|
||||
"get_prompt_string (block) {}:{}:{}",
|
||||
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
|
||||
)
|
||||
.ok();
|
||||
if is_perf_true() {
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"get_prompt_string (string) {}:{}:{}",
|
||||
file!(),
|
||||
@ -149,6 +151,7 @@ pub(crate) fn update_prompt<'prompt>(
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
nu_prompt: &'prompt mut NushellPrompt,
|
||||
is_perf_true: bool,
|
||||
) -> &'prompt dyn Prompt {
|
||||
// get the other indicators
|
||||
let (
|
||||
@ -156,21 +159,33 @@ pub(crate) fn update_prompt<'prompt>(
|
||||
prompt_vi_insert_string,
|
||||
prompt_vi_normal_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();
|
||||
|
||||
// apply the other indicators
|
||||
nu_prompt.update_all_prompt_strings(
|
||||
get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack),
|
||||
get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack),
|
||||
get_prompt_string(
|
||||
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_multiline_string,
|
||||
(prompt_vi_insert_string, prompt_vi_normal_string),
|
||||
);
|
||||
|
||||
let ret_val = nu_prompt as &dyn Prompt;
|
||||
if is_perf_true() {
|
||||
if is_perf_true {
|
||||
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::{config_files, prompt_update, reedline_config};
|
||||
use crate::{prompt_update, reedline_config};
|
||||
use crate::{
|
||||
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::trace;
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_cli::{NuCompleter, NuHighlighter, NuValidator, NushellPrompt};
|
||||
use nu_color_config::get_color_config;
|
||||
use nu_engine::convert_env_values;
|
||||
use nu_parser::lex;
|
||||
use nu_protocol::engine::Stack;
|
||||
use nu_protocol::PipelineData;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
Config, ShellError, Span, Value, CONFIG_VARIABLE_ID,
|
||||
};
|
||||
use nu_protocol::{PipelineData, Spanned};
|
||||
use reedline::{DefaultHinter, Emacs, Vi};
|
||||
use std::path::PathBuf;
|
||||
use std::{sync::atomic::Ordering, time::Instant};
|
||||
|
||||
pub(crate) fn evaluate(
|
||||
pub fn evaluate_repl(
|
||||
engine_state: &mut EngineState,
|
||||
config_file: Option<Spanned<String>>,
|
||||
stack: &mut Stack,
|
||||
history_path: Option<PathBuf>,
|
||||
is_perf_true: bool,
|
||||
) -> Result<()> {
|
||||
// use crate::logger::{configure, logger};
|
||||
use reedline::{FileBackedHistory, Reedline, Signal};
|
||||
@ -30,34 +33,34 @@ pub(crate) fn evaluate(
|
||||
let mut entry_num = 0;
|
||||
|
||||
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
|
||||
gather_parent_env_vars(engine_state);
|
||||
// gather_parent_env_vars(engine_state);
|
||||
|
||||
// Set up our initial config to start from
|
||||
stack.vars.insert(
|
||||
CONFIG_VARIABLE_ID,
|
||||
Value::Record {
|
||||
cols: vec![],
|
||||
vals: vec![],
|
||||
span: Span::new(0, 0),
|
||||
},
|
||||
);
|
||||
// stack.vars.insert(
|
||||
// CONFIG_VARIABLE_ID,
|
||||
// Value::Record {
|
||||
// cols: vec![],
|
||||
// vals: vec![],
|
||||
// span: Span::new(0, 0),
|
||||
// },
|
||||
// );
|
||||
|
||||
if is_perf_true() {
|
||||
if is_perf_true {
|
||||
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
config_files::read_plugin_file(engine_state, &mut stack);
|
||||
|
||||
if is_perf_true() {
|
||||
info!("read_config_file {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
config_files::read_config_file(engine_state, &mut stack, config_file);
|
||||
let history_path = config_files::create_history_path();
|
||||
// #[cfg(feature = "plugin")]
|
||||
// config_files::read_plugin_file(engine_state, &mut stack, is_perf_true);
|
||||
//
|
||||
// if is_perf_true {
|
||||
// info!("read_config_file {}:{}:{}", file!(), line!(), column!());
|
||||
// }
|
||||
//
|
||||
// config_files::read_config_file(engine_state, &mut stack, config_file, is_perf_true);
|
||||
// let history_path = config_files::create_history_path();
|
||||
|
||||
// logger(|builder| {
|
||||
// configure(&config.log_level, builder)?;
|
||||
@ -67,7 +70,7 @@ pub(crate) fn evaluate(
|
||||
// Ok(())
|
||||
// })?;
|
||||
|
||||
if is_perf_true() {
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"translate environment vars {}:{}:{}",
|
||||
file!(),
|
||||
@ -77,13 +80,13 @@ pub(crate) fn evaluate(
|
||||
}
|
||||
|
||||
// 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);
|
||||
report_error(&working_set, &e);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// seed env vars
|
||||
@ -104,7 +107,7 @@ pub(crate) fn evaluate(
|
||||
);
|
||||
|
||||
loop {
|
||||
if is_perf_true() {
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"load config each loop {}:{}:{}",
|
||||
file!(),
|
||||
@ -128,7 +131,7 @@ pub(crate) fn evaluate(
|
||||
ctrlc.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
if is_perf_true() {
|
||||
if is_perf_true {
|
||||
info!("setup line editor {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
@ -150,14 +153,14 @@ pub(crate) fn evaluate(
|
||||
.with_partial_completions(config.partial_completions)
|
||||
.with_ansi_colors(config.use_ansi_coloring);
|
||||
|
||||
if is_perf_true() {
|
||||
if is_perf_true {
|
||||
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
line_editor = add_completion_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!());
|
||||
}
|
||||
//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);
|
||||
|
||||
if is_perf_true() {
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"setup history and hinter {}:{}:{}",
|
||||
file!(),
|
||||
@ -196,7 +199,7 @@ pub(crate) fn evaluate(
|
||||
line_editor
|
||||
};
|
||||
|
||||
if is_perf_true() {
|
||||
if is_perf_true {
|
||||
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!());
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if is_perf_true() {
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"finished setup, starting repl {}:{}:{}",
|
||||
file!(),
|
||||
@ -245,7 +254,7 @@ pub(crate) fn evaluate(
|
||||
let start_time = Instant::now();
|
||||
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
||||
// 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 orig = s.clone();
|
||||
@ -308,7 +317,7 @@ pub(crate) fn evaluate(
|
||||
|
||||
eval_source(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
stack,
|
||||
s.as_bytes(),
|
||||
&format!("entry #{}", entry_num),
|
||||
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
|
||||
let exceptions = crate::utils::external_exceptions(engine_state, &stack);
|
||||
let exceptions = crate::util::external_exceptions(engine_state, stack);
|
||||
engine_state.external_exceptions = exceptions;
|
||||
}
|
||||
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::path::PathBuf;
|
||||
|
||||
use crate::CliError;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack},
|
||||
@ -84,3 +90,419 @@ pub fn print_pipeline_data(
|
||||
|
||||
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 nu_cli::{eval_config_contents, eval_source, report_error};
|
||||
use nu_parser::ParseError;
|
||||
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 std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const NUSHELL_FOLDER: &str = "nushell";
|
||||
pub(crate) const NUSHELL_FOLDER: &str = "nushell";
|
||||
const CONFIG_FILE: &str = "config.nu";
|
||||
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(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
config_file: Option<Spanned<String>>,
|
||||
is_perf_true: bool,
|
||||
) {
|
||||
// Load config startup 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);
|
||||
}
|
||||
|
||||
if is_perf_true() {
|
||||
if is_perf_true {
|
||||
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> {
|
||||
nu_path::config_dir().and_then(|mut history_path| {
|
||||
history_path.push(NUSHELL_FOLDER);
|
||||
|
76
src/main.rs
76
src/main.rs
@ -1,18 +1,22 @@
|
||||
mod commands;
|
||||
mod config_files;
|
||||
mod eval_file;
|
||||
mod logger;
|
||||
mod prompt_update;
|
||||
mod reedline_config;
|
||||
mod repl;
|
||||
mod test_bins;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod utils;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
use crate::config_files::NUSHELL_FOLDER;
|
||||
use crate::logger::{configure, logger};
|
||||
use log::info;
|
||||
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_engine::{get_full_help, CallExt};
|
||||
use nu_parser::parse;
|
||||
@ -31,7 +35,6 @@ use std::{
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use utils::report_error;
|
||||
|
||||
thread_local! { static IS_PERF: RefCell<bool> = RefCell::new(false) }
|
||||
|
||||
@ -44,7 +47,7 @@ fn main() -> Result<()> {
|
||||
}));
|
||||
|
||||
// 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);
|
||||
|
||||
// Custom additions
|
||||
@ -186,11 +189,31 @@ fn main() -> Result<()> {
|
||||
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 {
|
||||
#[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() {
|
||||
info!("-c command execution {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
@ -198,17 +221,27 @@ fn main() -> Result<()> {
|
||||
ret_val
|
||||
} else if !script_name.is_empty() && binary_args.interactive_shell.is_none() {
|
||||
#[cfg(feature = "plugin")]
|
||||
config_files::add_plugin_file(&mut engine_state);
|
||||
add_plugin_file(&mut engine_state, NUSHELL_FOLDER);
|
||||
|
||||
let ret_val =
|
||||
eval_file::evaluate(script_name, &args_to_script, &mut engine_state, input);
|
||||
let ret_val = evaluate_file(
|
||||
script_name,
|
||||
&args_to_script,
|
||||
&mut engine_state,
|
||||
&mut stack,
|
||||
input,
|
||||
is_perf_true(),
|
||||
);
|
||||
if is_perf_true() {
|
||||
info!("eval_file execution {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
ret_val
|
||||
} 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() {
|
||||
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(
|
||||
commandline_args: &str,
|
||||
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