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:
Charles Dixon 2022-03-16 18:17:06 +00:00 committed by GitHub
parent 0bd8664f33
commit 1a16b9a2c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 696 additions and 621 deletions

2
Cargo.lock generated
View File

@ -2196,6 +2196,8 @@ dependencies = [
name = "nu-cli"
version = "0.59.1"
dependencies = [
"crossterm",
"crossterm_winapi",
"is_executable",
"log",
"miette",

View File

@ -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"]

View File

@ -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 = []

View File

@ -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!());
}

View 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);
}
}
}
}
}

View File

@ -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!());
}

View File

@ -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;

View 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!());
}

View File

@ -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) => {

View File

@ -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(),
},
},
}
}

View File

@ -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);

View File

@ -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,

View File

@ -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(),
},
},
}
}