nushell/src/main.rs

328 lines
10 KiB
Rust
Raw Normal View History

mod command;
2022-01-18 08:48:28 +00:00
mod config_files;
mod logger;
mod signals;
mod terminal;
mod test_bins;
2021-08-10 18:57:08 +00:00
#[cfg(test)]
mod tests;
#[cfg(feature = "plugin")]
use crate::config_files::NUSHELL_FOLDER;
use crate::{
command::parse_commandline_args,
config_files::{set_config_path, setup_config},
logger::{configure, logger},
terminal::acquire_terminal,
};
use log::{info, Level};
2022-01-18 08:48:28 +00:00
use miette::Result;
#[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_new,
};
use nu_command::create_default_context;
use nu_parser::{escape_for_script_arg, escape_quote_string};
use nu_protocol::{util::BufferedReader, PipelineData, RawStream};
use signals::{ctrlc_protection, sigquit_protection};
use std::{
io::BufReader,
str::FromStr,
sync::{atomic::AtomicBool, Arc},
time::Instant,
};
fn main() -> Result<()> {
let start_time = Instant::now();
let miette_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |x| {
2021-12-04 12:38:21 +00:00
crossterm::terminal::disable_raw_mode().expect("unable to disable raw mode");
miette_hook(x);
}));
2021-09-21 19:37:16 +00:00
Use only $nu.env.PWD for getting the current directory (#587) * Use only $nu.env.PWD for getting current directory Because setting and reading to/from std::env changes the global state shich is problematic if we call `cd` from multiple threads (e.g., in a `par-each` block). With this change, when engine-q starts, it will either inherit existing PWD env var, or create a new one from `std::env::current_dir()`. Otherwise, everything that needs the current directory will get it from `$nu.env.PWD`. Each spawned external command will get its current directory per-process which should be thread-safe. One thing left to do is to patch nu-path for this as well since it uses `std::env::current_dir()` in its expansions. * Rename nu-path functions *_with is not *_relative which should be more descriptive and frees "with" for use in a followup commit. * Clone stack every each iter; Fix some commands Cloning the stack each iteration of `each` makes sure we're not reusing PWD between iterations. Some fixes in commands to make them use the new PWD. * Post-rebase cleanup, fmt, clippy * Change back _relative to _with in nu-path funcs Didn't use the idea I had for the new "_with". * Remove leftover current_dir from rebase * Add cwd sync at merge_delta() This makes sure the parser and completer always have up-to-date cwd. * Always pass absolute path to glob in ls * Do not allow PWD a relative path; Allow recovery Makes it possible to recover PWD by proceeding with the REPL cycle. * Clone stack in each also for byte/string stream * (WIP) Start moving env variables to engine state * (WIP) Move env vars to engine state (ugly) Quick and dirty code. * (WIP) Remove unused mut and args; Fmt * (WIP) Fix dataframe tests * (WIP) Fix missing args after rebase * (WIP) Clone only env vars, not the whole stack * (WIP) Add env var clone to `for` loop as well * Minor edits * Refactor merge_delta() to include stack merging. Less error-prone than doing it manually. * Clone env for each `update` command iteration * Mark env var hidden only when found in eng. state * Fix clippt warnings * Add TODO about env var reading * Do not clone empty environment in loops * Remove extra cwd collection * Split current_dir() into str and path; Fix autocd * Make completions respect PWD env var
2022-01-04 22:30:34 +00:00
// Get initial current working directory.
let init_cwd = get_init_cwd();
let mut engine_state = create_default_context();
2021-07-17 06:31:34 +00:00
// Custom additions
let delta = {
let mut working_set = nu_protocol::engine::StateWorkingSet::new(&engine_state);
working_set.add_decl(Box::new(nu_cli::NuHighlight));
working_set.add_decl(Box::new(nu_cli::Print));
working_set.render()
};
if let Err(err) = engine_state.merge_delta(delta) {
report_error_new(&engine_state, &err);
}
2021-10-28 04:13:10 +00:00
let ctrlc = Arc::new(AtomicBool::new(false));
// TODO: make this conditional in the future
ctrlc_protection(&mut engine_state, &ctrlc);
sigquit_protection(&mut engine_state);
let mut args_to_nushell = vec![];
let mut script_name = String::new();
let mut args_to_script = vec![];
// Would be nice if we had a way to parse this. The first flags we see will be going to nushell
// then it'll be the script name
// then the args to the script
let mut args = std::env::args();
let argv0 = args.next();
while let Some(arg) = args.next() {
if !script_name.is_empty() {
args_to_script.push(escape_for_script_arg(&arg));
} else if arg.starts_with('-') {
// Cool, it's a flag
let flag_value = match arg.as_ref() {
2022-08-18 09:25:52 +00:00
"--commands" | "-c" | "--table-mode" | "-m" | "-e" | "--execute" => {
args.next().map(|a| escape_quote_string(&a))
}
"--config" | "--env-config" => args.next().map(|a| escape_quote_string(&a)),
Add plugin CLI argument (#6064) * Add plugin CLI argument While working on supporting CustomValues in Plugins I stumbled upon the test utilities defined in [nu-test-support][nu-test-support] and thought these will come in handy, but they end up being outdated. They haven't been used or since engine-q's was merged, so they are currently using the old way engine-q handled plugins, where it would just look into a specific folder for plugins and call them without signatures or registration. While fixing that I realized that there is currently no way to tell nushell to load and save signatures into a specific path, and so those integration tests could end up potentially conflicting with each other and with the local plugins the person running them is using. So this adds a new CLI argument to specify where to store and load plugin signatures from I am not super sure of the way I implemented this, mainly I was a bit confused about the distinction between [src/config_files.rs][src/config_files.rs] and [crates/nu-cli/src/config_files.rs][crates/nu-cli/src/config_files.rs]. Should I be moving the plugin loading function from the `nu-cli` one to the root one? [nu-test-support]: https://github.com/nushell/nushell/blob/9d0be7d96fa0e960bcc702a523ca62f0c53b765c/crates/nu-test-support/src/macros.rs#L106 [src/config_files.rs]: https://github.com/nushell/nushell/blob/9d0be7d96fa0e960bcc702a523ca62f0c53b765c/src/config_files.rs [crates/nu-cli/src/config_files.rs]: https://github.com/nushell/nushell/blob/9d0be7d96fa0e960bcc702a523ca62f0c53b765c/crates/nu-cli/src/config_files.rs * Gate new CLI option behind plugin feature * Rename option to plugin-config
2022-07-17 18:29:19 +00:00
#[cfg(feature = "plugin")]
"--plugin-config" => args.next().map(|a| escape_quote_string(&a)),
"--log-level" | "--log-target" | "--testbin" | "--threads" | "-t" => args.next(),
_ => None,
};
args_to_nushell.push(arg);
if let Some(flag_value) = flag_value {
args_to_nushell.push(flag_value);
}
} else {
// Our script file
script_name = arg;
}
}
args_to_nushell.insert(0, "nu".into());
if let Some(argv0) = argv0 {
if argv0.starts_with('-') {
args_to_nushell.push("--login".into());
}
}
let nushell_commandline_args = args_to_nushell.join(" ");
let parsed_nu_cli_args = parse_commandline_args(&nushell_commandline_args, &mut engine_state)
.unwrap_or_else(|_| std::process::exit(1));
set_config_path(
&mut engine_state,
&init_cwd,
"config.nu",
"config-path",
&parsed_nu_cli_args.config_file,
);
set_config_path(
&mut engine_state,
&init_cwd,
"env.nu",
"env-path",
&parsed_nu_cli_args.env_file,
);
// keep this condition in sync with the branches below
acquire_terminal(
parsed_nu_cli_args.commands.is_none()
&& (script_name.is_empty() || parsed_nu_cli_args.interactive_shell.is_some()),
);
if let Some(t) = parsed_nu_cli_args.threads {
// 0 means to let rayon decide how many threads to use
let threads = t.as_i64().unwrap_or(0);
rayon::ThreadPoolBuilder::new()
.num_threads(threads as usize)
.build_global()
.expect("error setting number of threads");
}
if let Some(level) = parsed_nu_cli_args.log_level.map(|level| level.item) {
let level = if Level::from_str(&level).is_ok() {
level
} else {
eprintln!(
"ERROR: log library did not recognize log level '{level}', using default 'info'"
);
"info".to_string()
};
let target = parsed_nu_cli_args
.log_target
.map(|target| target.item)
.unwrap_or_else(|| "stderr".to_string());
logger(|builder| configure(&level, &target, builder))?;
info!("start logging {}:{}:{}", file!(), line!(), column!());
}
if let Some(testbin) = &parsed_nu_cli_args.testbin {
// Call out to the correct testbin
match testbin.item.as_str() {
"echo_env" => test_bins::echo_env(true),
"echo_env_stderr" => test_bins::echo_env(false),
"cococo" => test_bins::cococo(),
"meow" => test_bins::meow(),
"meowb" => test_bins::meowb(),
"relay" => test_bins::relay(),
"iecho" => test_bins::iecho(),
"fail" => test_bins::fail(),
"nonu" => test_bins::nonu(),
"chop" => test_bins::chop(),
"repeater" => test_bins::repeater(),
"nu_repl" => test_bins::nu_repl(),
_ => std::process::exit(1),
}
std::process::exit(0)
}
let input = if let Some(redirect_stdin) = &parsed_nu_cli_args.redirect_stdin {
let stdin = std::io::stdin();
let buf_reader = BufReader::new(stdin);
PipelineData::ExternalStream {
stdout: Some(RawStream::new(
Box::new(BufferedReader::new(buf_reader)),
Some(ctrlc),
redirect_stdin.span,
None,
)),
stderr: None,
exit_code: None,
span: redirect_stdin.span,
metadata: None,
trim_end_newline: false,
}
} else {
PipelineData::empty()
};
info!("redirect_stdin {}:{}:{}", file!(), line!(), column!());
// First, set up env vars as strings only
gather_parent_env_vars(&mut engine_state, &init_cwd);
let mut stack = nu_protocol::engine::Stack::new();
if let Some(commands) = &parsed_nu_cli_args.commands {
#[cfg(feature = "plugin")]
read_plugin_file(
&mut engine_state,
&mut stack,
parsed_nu_cli_args.plugin_file,
NUSHELL_FOLDER,
);
// only want to load config and env if relative argument is provided.
if parsed_nu_cli_args.env_file.is_some() {
config_files::read_config_file(
&mut engine_state,
&mut stack,
parsed_nu_cli_args.env_file,
true,
);
} else {
config_files::read_default_env_file(&mut engine_state, &mut stack)
}
if parsed_nu_cli_args.config_file.is_some() {
config_files::read_config_file(
&mut engine_state,
&mut stack,
parsed_nu_cli_args.config_file,
false,
);
}
let ret_val = evaluate_commands(
commands,
&mut engine_state,
&mut stack,
input,
parsed_nu_cli_args.table_mode,
);
info!("-c command execution {}:{}:{}", file!(), line!(), column!());
match ret_val {
Ok(Some(exit_code)) => std::process::exit(exit_code as i32),
Ok(None) => Ok(()),
Err(e) => Err(e),
}
} else if !script_name.is_empty() && parsed_nu_cli_args.interactive_shell.is_none() {
#[cfg(feature = "plugin")]
read_plugin_file(
&mut engine_state,
&mut stack,
parsed_nu_cli_args.plugin_file,
NUSHELL_FOLDER,
);
2022-07-10 15:12:24 +00:00
// only want to load config and env if relative argument is provided.
if parsed_nu_cli_args.env_file.is_some() {
config_files::read_config_file(
&mut engine_state,
&mut stack,
parsed_nu_cli_args.env_file,
true,
);
} else {
config_files::read_default_env_file(&mut engine_state, &mut stack)
}
if parsed_nu_cli_args.config_file.is_some() {
config_files::read_config_file(
&mut engine_state,
&mut stack,
parsed_nu_cli_args.config_file,
false,
);
}
2022-07-10 15:12:24 +00:00
let ret_val = evaluate_file(
script_name,
&args_to_script,
&mut engine_state,
&mut stack,
input,
);
let last_exit_code = stack.get_env_var(&engine_state, "LAST_EXIT_CODE");
if let Some(last_exit_code) = last_exit_code {
let value = last_exit_code.as_integer();
if let Ok(value) = value {
if value != 0 {
std::process::exit(value as i32);
}
}
}
info!("eval_file execution {}:{}:{}", file!(), line!(), column!());
ret_val
} else {
setup_config(
&mut engine_state,
&mut stack,
#[cfg(feature = "plugin")]
parsed_nu_cli_args.plugin_file,
parsed_nu_cli_args.config_file,
parsed_nu_cli_args.env_file,
parsed_nu_cli_args.login_shell.is_some(),
);
let ret_val = evaluate_repl(
&mut engine_state,
&mut stack,
config_files::NUSHELL_FOLDER,
parsed_nu_cli_args.execute,
start_time,
);
info!("repl eval {}:{}:{}", file!(), line!(), column!());
ret_val
}
}