2022-01-18 09:48:28 +01:00
|
|
|
mod config_files;
|
|
|
|
mod logger;
|
2022-02-09 23:08:16 +01:00
|
|
|
mod test_bins;
|
2021-08-10 20:57:08 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests;
|
|
|
|
|
2022-03-16 19:17:06 +01:00
|
|
|
#[cfg(feature = "plugin")]
|
|
|
|
use crate::config_files::NUSHELL_FOLDER;
|
2022-02-09 23:08:16 +01:00
|
|
|
use crate::logger::{configure, logger};
|
2022-10-21 17:20:21 +02:00
|
|
|
use log::{info, Level};
|
2022-01-18 09:48:28 +01:00
|
|
|
use miette::Result;
|
2022-03-16 19:17:06 +01:00
|
|
|
#[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,
|
2022-07-14 16:09:27 +02:00
|
|
|
report_error, report_error_new,
|
2022-03-16 19:17:06 +01:00
|
|
|
};
|
2022-01-26 15:42:39 +01:00
|
|
|
use nu_command::{create_default_context, BufferedReader};
|
2022-02-11 19:46:36 +01:00
|
|
|
use nu_engine::{get_full_help, CallExt};
|
2022-06-11 10:52:31 +02:00
|
|
|
use nu_parser::{escape_for_script_arg, escape_quote_string, parse};
|
2022-08-22 18:30:09 +02:00
|
|
|
use nu_path::canonicalize_with;
|
2022-01-26 15:42:39 +01:00
|
|
|
use nu_protocol::{
|
2022-11-18 22:46:48 +01:00
|
|
|
ast::{Call, Expr, Expression, PipelineElement},
|
2022-01-26 15:42:39 +01:00
|
|
|
engine::{Command, EngineState, Stack, StateWorkingSet},
|
2022-01-28 19:32:33 +01:00
|
|
|
Category, Example, IntoPipelineData, PipelineData, RawStream, ShellError, Signature, Span,
|
2022-04-19 00:28:01 +02:00
|
|
|
Spanned, SyntaxShape, Value,
|
2022-01-18 09:48:28 +01:00
|
|
|
};
|
2022-05-17 20:28:18 +02:00
|
|
|
use nu_utils::stdout_write_all_and_flush;
|
2022-01-26 15:42:39 +01:00
|
|
|
use std::{
|
2022-05-17 20:28:18 +02:00
|
|
|
io::BufReader,
|
2022-01-26 15:42:39 +01:00
|
|
|
sync::{
|
|
|
|
atomic::{AtomicBool, Ordering},
|
|
|
|
Arc,
|
|
|
|
},
|
|
|
|
};
|
2022-10-21 17:20:21 +02:00
|
|
|
use std::{path::Path, str::FromStr};
|
2022-02-09 23:08:16 +01:00
|
|
|
|
2022-09-29 20:37:48 +02:00
|
|
|
// Inspired by fish's acquire_tty_or_exit
|
|
|
|
#[cfg(unix)]
|
|
|
|
fn take_control(interactive: bool) {
|
|
|
|
use nix::{
|
|
|
|
errno::Errno,
|
|
|
|
sys::signal::{self, SaFlags, SigAction, SigHandler, SigSet, Signal},
|
|
|
|
unistd::{self, Pid},
|
|
|
|
};
|
|
|
|
|
|
|
|
let shell_pgid = unistd::getpgrp();
|
|
|
|
|
2022-11-04 21:06:04 +01:00
|
|
|
match unistd::tcgetpgrp(nix::libc::STDIN_FILENO) {
|
|
|
|
Ok(owner_pgid) if owner_pgid == shell_pgid => {
|
|
|
|
// Common case, nothing to do
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Ok(owner_pgid) if owner_pgid == unistd::getpid() => {
|
|
|
|
// This can apparently happen with sudo: https://github.com/fish-shell/fish-shell/issues/7388
|
|
|
|
let _ = unistd::setpgid(owner_pgid, owner_pgid);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_ => (),
|
2022-09-29 20:37:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reset all signal handlers to default
|
|
|
|
for sig in Signal::iterator() {
|
|
|
|
unsafe {
|
|
|
|
if let Ok(old_act) = signal::sigaction(
|
|
|
|
sig,
|
|
|
|
&SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty()),
|
|
|
|
) {
|
|
|
|
// fish preserves ignored SIGHUP, presumably for nohup support, so let's do the same
|
|
|
|
if sig == Signal::SIGHUP && old_act.handler() == SigHandler::SigIgn {
|
|
|
|
let _ = signal::sigaction(sig, &old_act);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut success = false;
|
|
|
|
for _ in 0..4096 {
|
|
|
|
match unistd::tcgetpgrp(nix::libc::STDIN_FILENO) {
|
|
|
|
Ok(owner_pgid) if owner_pgid == shell_pgid => {
|
|
|
|
success = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Ok(owner_pgid) if owner_pgid == Pid::from_raw(0) => {
|
|
|
|
// Zero basically means something like "not owned" and we can just take it
|
|
|
|
let _ = unistd::tcsetpgrp(nix::libc::STDIN_FILENO, shell_pgid);
|
|
|
|
}
|
|
|
|
Err(Errno::ENOTTY) => {
|
|
|
|
if !interactive {
|
|
|
|
// that's fine
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
eprintln!("ERROR: no TTY for interactive shell");
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
// fish also has other heuristics than "too many attempts" for the orphan check, but they're optional
|
|
|
|
if signal::killpg(Pid::from_raw(-shell_pgid.as_raw()), Signal::SIGTTIN).is_err() {
|
2022-10-17 21:08:25 +02:00
|
|
|
if !interactive {
|
|
|
|
// that's fine
|
|
|
|
return;
|
|
|
|
}
|
2022-09-29 20:37:48 +02:00
|
|
|
eprintln!("ERROR: failed to SIGTTIN ourselves");
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-10-17 21:08:25 +02:00
|
|
|
if !success && interactive {
|
2022-09-29 20:37:48 +02:00
|
|
|
eprintln!("ERROR: failed take control of the terminal, we might be orphaned");
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(unix)]
|
|
|
|
fn acquire_terminal(interactive: bool) {
|
|
|
|
use nix::sys::signal::{signal, SigHandler, Signal};
|
|
|
|
|
|
|
|
if !atty::is(atty::Stream::Stdin) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
take_control(interactive);
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
// SIGINT and SIGQUIT have special handling above
|
|
|
|
signal(Signal::SIGTSTP, SigHandler::SigIgn).expect("signal ignore");
|
|
|
|
signal(Signal::SIGTTIN, SigHandler::SigIgn).expect("signal ignore");
|
|
|
|
signal(Signal::SIGTTOU, SigHandler::SigIgn).expect("signal ignore");
|
|
|
|
// signal::signal(Signal::SIGCHLD, SigHandler::SigIgn).expect("signal ignore"); // needed for std::command's waitpid usage
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(unix))]
|
|
|
|
fn acquire_terminal(_: bool) {}
|
|
|
|
|
2021-09-20 23:37:26 +02:00
|
|
|
fn main() -> Result<()> {
|
2021-11-14 20:25:57 +01:00
|
|
|
// miette::set_panic_hook();
|
2021-10-01 18:39:50 +02:00
|
|
|
let miette_hook = std::panic::take_hook();
|
|
|
|
std::panic::set_hook(Box::new(move |x| {
|
2021-12-04 13:38:21 +01:00
|
|
|
crossterm::terminal::disable_raw_mode().expect("unable to disable raw mode");
|
2021-10-01 18:39:50 +02:00
|
|
|
miette_hook(x);
|
|
|
|
}));
|
2021-09-21 21:37:16 +02:00
|
|
|
|
2022-01-04 23:30:34 +01:00
|
|
|
// Get initial current working directory.
|
2022-03-16 19:17:06 +01:00
|
|
|
let init_cwd = get_init_cwd();
|
2022-07-14 16:09:27 +02:00
|
|
|
let mut engine_state = create_default_context();
|
2021-07-17 08:31:34 +02:00
|
|
|
|
2022-01-24 16:05:19 +01: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));
|
2022-02-18 19:43:34 +01:00
|
|
|
working_set.add_decl(Box::new(nu_cli::Print));
|
2022-01-24 16:05:19 +01:00
|
|
|
|
|
|
|
working_set.render()
|
|
|
|
};
|
2022-07-14 16:09:27 +02:00
|
|
|
|
|
|
|
if let Err(err) = engine_state.merge_delta(delta) {
|
|
|
|
report_error_new(&engine_state, &err);
|
|
|
|
}
|
2022-01-24 16:05:19 +01:00
|
|
|
|
2021-10-28 06:13:10 +02:00
|
|
|
// TODO: make this conditional in the future
|
|
|
|
// Ctrl-c protection section
|
|
|
|
let ctrlc = Arc::new(AtomicBool::new(false));
|
|
|
|
let handler_ctrlc = ctrlc.clone();
|
|
|
|
let engine_state_ctrlc = ctrlc.clone();
|
|
|
|
|
|
|
|
ctrlc::set_handler(move || {
|
|
|
|
handler_ctrlc.store(true, Ordering::SeqCst);
|
|
|
|
})
|
|
|
|
.expect("Error setting Ctrl-C handler");
|
|
|
|
|
|
|
|
engine_state.ctrlc = Some(engine_state_ctrlc);
|
|
|
|
// End ctrl-c protection section
|
|
|
|
|
2022-06-09 14:08:15 +02:00
|
|
|
// SIGQUIT protection section (only works for POSIX system)
|
|
|
|
#[cfg(not(windows))]
|
|
|
|
{
|
|
|
|
use signal_hook::consts::SIGQUIT;
|
|
|
|
let sig_quit = Arc::new(AtomicBool::new(false));
|
|
|
|
signal_hook::flag::register(SIGQUIT, sig_quit.clone()).expect("Error setting SIGQUIT flag");
|
|
|
|
engine_state.set_sig_quit(sig_quit);
|
|
|
|
}
|
|
|
|
// End SIGQUIT protection section
|
|
|
|
|
2022-01-26 15:42:39 +01:00
|
|
|
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
|
2022-07-26 16:41:05 +02:00
|
|
|
let mut args = std::env::args();
|
|
|
|
let argv0 = args.next();
|
|
|
|
|
2022-04-30 20:23:05 +02:00
|
|
|
while let Some(arg) = args.next() {
|
2022-01-26 15:42:39 +01:00
|
|
|
if !script_name.is_empty() {
|
2022-06-11 10:52:31 +02:00
|
|
|
args_to_script.push(escape_for_script_arg(&arg));
|
2022-01-26 15:42:39 +01:00
|
|
|
} else if arg.starts_with('-') {
|
|
|
|
// Cool, it's a flag
|
2022-04-30 20:23:05 +02:00
|
|
|
let flag_value = match arg.as_ref() {
|
2022-08-18 11:25:52 +02:00
|
|
|
"--commands" | "-c" | "--table-mode" | "-m" | "-e" | "--execute" => {
|
2022-05-11 23:15:31 +02:00
|
|
|
args.next().map(|a| escape_quote_string(&a))
|
|
|
|
}
|
2022-04-30 20:23:05 +02:00
|
|
|
"--config" | "--env-config" => args.next().map(|a| escape_quote_string(&a)),
|
2022-07-17 20:29:19 +02:00
|
|
|
#[cfg(feature = "plugin")]
|
|
|
|
"--plugin-config" => args.next().map(|a| escape_quote_string(&a)),
|
2022-08-09 18:44:37 +02:00
|
|
|
"--log-level" | "--log-target" | "--testbin" | "--threads" | "-t" => args.next(),
|
2022-04-30 20:23:05 +02:00
|
|
|
_ => None,
|
|
|
|
};
|
2022-02-19 21:54:43 +01:00
|
|
|
|
2022-01-26 15:42:39 +01:00
|
|
|
args_to_nushell.push(arg);
|
2022-04-30 20:23:05 +02:00
|
|
|
|
|
|
|
if let Some(flag_value) = flag_value {
|
|
|
|
args_to_nushell.push(flag_value);
|
|
|
|
}
|
2022-01-26 15:42:39 +01:00
|
|
|
} else {
|
|
|
|
// Our script file
|
|
|
|
script_name = arg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
args_to_nushell.insert(0, "nu".into());
|
|
|
|
|
2022-07-26 16:41:05 +02:00
|
|
|
if let Some(argv0) = argv0 {
|
|
|
|
if argv0.starts_with('-') {
|
|
|
|
args_to_nushell.push("--login".into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-26 15:42:39 +01:00
|
|
|
let nushell_commandline_args = args_to_nushell.join(" ");
|
|
|
|
|
2022-07-14 16:09:27 +02:00
|
|
|
let parsed_nu_cli_args = parse_commandline_args(&nushell_commandline_args, &mut engine_state);
|
2022-01-26 15:42:39 +01:00
|
|
|
|
2022-08-22 18:30:09 +02:00
|
|
|
if let Ok(ref args) = parsed_nu_cli_args {
|
|
|
|
set_config_path(
|
|
|
|
&mut engine_state,
|
|
|
|
&init_cwd,
|
|
|
|
"config.nu",
|
|
|
|
"config-path",
|
|
|
|
&args.config_file,
|
|
|
|
);
|
|
|
|
|
|
|
|
set_config_path(
|
|
|
|
&mut engine_state,
|
|
|
|
&init_cwd,
|
|
|
|
"env.nu",
|
|
|
|
"env-path",
|
|
|
|
&args.env_file,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-02-09 23:08:16 +01:00
|
|
|
match parsed_nu_cli_args {
|
|
|
|
Ok(binary_args) => {
|
2022-09-29 20:37:48 +02:00
|
|
|
// keep this condition in sync with the branches below
|
|
|
|
acquire_terminal(
|
|
|
|
binary_args.commands.is_none()
|
|
|
|
&& (script_name.is_empty() || binary_args.interactive_shell.is_some()),
|
|
|
|
);
|
|
|
|
|
2022-02-11 19:46:36 +01:00
|
|
|
if let Some(t) = binary_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");
|
|
|
|
}
|
|
|
|
|
2022-10-21 17:20:21 +02:00
|
|
|
if binary_args.log_level.is_some() {
|
|
|
|
let mut level = binary_args
|
2022-02-20 11:08:53 +01:00
|
|
|
.log_level
|
|
|
|
.map(|level| level.item)
|
|
|
|
.unwrap_or_else(|| "info".to_string());
|
|
|
|
|
2022-10-21 17:20:21 +02:00
|
|
|
if Level::from_str(level.as_str()).is_err() {
|
|
|
|
eprintln!("ERROR: log library did not recognize log level '{level}', using default 'info'");
|
|
|
|
level = "info".to_string();
|
|
|
|
}
|
|
|
|
|
2022-08-09 18:44:37 +02:00
|
|
|
let target = binary_args
|
|
|
|
.log_target
|
|
|
|
.map(|target| target.item)
|
|
|
|
.unwrap_or_else(|| "stderr".to_string());
|
|
|
|
|
|
|
|
logger(|builder| configure(level.as_str(), target.as_str(), builder))?;
|
2022-02-09 23:08:16 +01:00
|
|
|
info!("start logging {}:{}:{}", file!(), line!(), column!());
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(testbin) = &binary_args.testbin {
|
2022-02-02 21:59:01 +01:00
|
|
|
// Call out to the correct testbin
|
|
|
|
match testbin.item.as_str() {
|
2022-10-20 14:56:44 +02:00
|
|
|
"echo_env" => test_bins::echo_env(true),
|
|
|
|
"echo_env_stderr" => test_bins::echo_env(false),
|
2022-02-02 21:59:01 +01:00
|
|
|
"cococo" => test_bins::cococo(),
|
|
|
|
"meow" => test_bins::meow(),
|
2022-03-27 04:35:59 +02:00
|
|
|
"meowb" => test_bins::meowb(),
|
|
|
|
"relay" => test_bins::relay(),
|
2022-02-02 21:59:01 +01:00
|
|
|
"iecho" => test_bins::iecho(),
|
|
|
|
"fail" => test_bins::fail(),
|
|
|
|
"nonu" => test_bins::nonu(),
|
|
|
|
"chop" => test_bins::chop(),
|
|
|
|
"repeater" => test_bins::repeater(),
|
2022-07-29 22:42:00 +02:00
|
|
|
"nu_repl" => test_bins::nu_repl(),
|
2022-02-02 21:59:01 +01:00
|
|
|
_ => std::process::exit(1),
|
|
|
|
}
|
|
|
|
std::process::exit(0)
|
|
|
|
}
|
2022-02-09 23:08:16 +01:00
|
|
|
let input = if let Some(redirect_stdin) = &binary_args.redirect_stdin {
|
2022-01-26 18:26:43 +01:00
|
|
|
let stdin = std::io::stdin();
|
|
|
|
let buf_reader = BufReader::new(stdin);
|
|
|
|
|
2022-02-25 20:51:31 +01:00
|
|
|
PipelineData::ExternalStream {
|
2022-03-08 02:17:33 +01:00
|
|
|
stdout: Some(RawStream::new(
|
2022-01-28 21:32:46 +01:00
|
|
|
Box::new(BufferedReader::new(buf_reader)),
|
|
|
|
Some(ctrlc),
|
|
|
|
redirect_stdin.span,
|
2022-03-08 02:17:33 +01:00
|
|
|
)),
|
2022-02-25 20:51:31 +01:00
|
|
|
stderr: None,
|
|
|
|
exit_code: None,
|
|
|
|
span: redirect_stdin.span,
|
|
|
|
metadata: None,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
trim_end_newline: false,
|
2022-02-25 20:51:31 +01:00
|
|
|
}
|
2022-01-26 18:26:43 +01:00
|
|
|
} else {
|
|
|
|
PipelineData::new(Span::new(0, 0))
|
|
|
|
};
|
2022-01-26 15:42:39 +01:00
|
|
|
|
2022-10-21 17:20:21 +02:00
|
|
|
info!("redirect_stdin {}:{}:{}", file!(), line!(), column!());
|
2022-02-09 23:08:16 +01:00
|
|
|
|
2022-03-16 19:17:06 +01:00
|
|
|
// First, set up env vars as strings only
|
2022-06-10 20:01:08 +02:00
|
|
|
gather_parent_env_vars(&mut engine_state, &init_cwd);
|
2022-07-14 16:09:27 +02:00
|
|
|
|
2022-03-16 19:17:06 +01:00
|
|
|
let mut stack = nu_protocol::engine::Stack::new();
|
|
|
|
|
2022-02-09 23:08:16 +01:00
|
|
|
if let Some(commands) = &binary_args.commands {
|
2022-02-26 09:57:51 +01:00
|
|
|
#[cfg(feature = "plugin")]
|
2022-04-06 09:45:26 +02:00
|
|
|
read_plugin_file(
|
|
|
|
&mut engine_state,
|
|
|
|
&mut stack,
|
2022-07-17 20:29:19 +02:00
|
|
|
binary_args.plugin_file,
|
2022-04-06 09:45:26 +02:00
|
|
|
NUSHELL_FOLDER,
|
|
|
|
);
|
2022-07-10 17:12:24 +02:00
|
|
|
|
2022-05-23 14:47:08 +02:00
|
|
|
// only want to load config and env if relative argument is provided.
|
2022-07-10 17:12:24 +02:00
|
|
|
if binary_args.env_file.is_some() {
|
2022-05-23 14:47:08 +02:00
|
|
|
config_files::read_config_file(
|
|
|
|
&mut engine_state,
|
|
|
|
&mut stack,
|
2022-07-10 17:12:24 +02:00
|
|
|
binary_args.env_file,
|
|
|
|
true,
|
2022-07-10 14:16:46 +02:00
|
|
|
);
|
2022-07-14 07:53:13 +02:00
|
|
|
} else {
|
2022-10-21 17:20:21 +02:00
|
|
|
config_files::read_default_env_file(&mut engine_state, &mut stack)
|
2022-07-10 14:16:46 +02:00
|
|
|
}
|
2022-07-10 17:12:24 +02:00
|
|
|
|
|
|
|
if binary_args.config_file.is_some() {
|
2022-07-10 14:16:46 +02:00
|
|
|
config_files::read_config_file(
|
|
|
|
&mut engine_state,
|
|
|
|
&mut stack,
|
2022-07-10 17:12:24 +02:00
|
|
|
binary_args.config_file,
|
|
|
|
false,
|
2022-05-23 14:47:08 +02:00
|
|
|
);
|
|
|
|
}
|
2022-02-26 09:57:51 +01:00
|
|
|
|
2022-03-16 19:17:06 +01:00
|
|
|
let ret_val = evaluate_commands(
|
|
|
|
commands,
|
|
|
|
&mut engine_state,
|
|
|
|
&mut stack,
|
|
|
|
input,
|
2022-05-11 23:15:31 +02:00
|
|
|
binary_args.table_mode,
|
2022-03-16 19:17:06 +01:00
|
|
|
);
|
2022-10-21 17:20:21 +02:00
|
|
|
info!("-c command execution {}:{}:{}", file!(), line!(), column!());
|
2022-06-20 16:05:11 +02:00
|
|
|
match ret_val {
|
|
|
|
Ok(Some(exit_code)) => std::process::exit(exit_code as i32),
|
|
|
|
Ok(None) => Ok(()),
|
|
|
|
Err(e) => Err(e),
|
|
|
|
}
|
2022-02-09 23:08:16 +01:00
|
|
|
} else if !script_name.is_empty() && binary_args.interactive_shell.is_none() {
|
2022-02-26 09:57:51 +01:00
|
|
|
#[cfg(feature = "plugin")]
|
2022-04-06 09:45:26 +02:00
|
|
|
read_plugin_file(
|
|
|
|
&mut engine_state,
|
|
|
|
&mut stack,
|
2022-07-17 20:29:19 +02:00
|
|
|
binary_args.plugin_file,
|
2022-04-06 09:45:26 +02:00
|
|
|
NUSHELL_FOLDER,
|
|
|
|
);
|
2022-07-10 17:12:24 +02:00
|
|
|
|
2022-05-23 14:47:08 +02:00
|
|
|
// only want to load config and env if relative argument is provided.
|
2022-07-10 17:12:24 +02:00
|
|
|
if binary_args.env_file.is_some() {
|
2022-05-23 14:47:08 +02:00
|
|
|
config_files::read_config_file(
|
|
|
|
&mut engine_state,
|
|
|
|
&mut stack,
|
2022-07-10 17:12:24 +02:00
|
|
|
binary_args.env_file,
|
|
|
|
true,
|
2022-07-10 14:16:46 +02:00
|
|
|
);
|
2022-07-14 07:53:13 +02:00
|
|
|
} else {
|
2022-10-21 17:20:21 +02:00
|
|
|
config_files::read_default_env_file(&mut engine_state, &mut stack)
|
2022-07-10 14:16:46 +02:00
|
|
|
}
|
2022-07-10 17:12:24 +02:00
|
|
|
|
|
|
|
if binary_args.config_file.is_some() {
|
2022-07-10 14:16:46 +02:00
|
|
|
config_files::read_config_file(
|
|
|
|
&mut engine_state,
|
|
|
|
&mut stack,
|
2022-07-10 17:12:24 +02:00
|
|
|
binary_args.config_file,
|
|
|
|
false,
|
2022-05-23 14:47:08 +02:00
|
|
|
);
|
|
|
|
}
|
2022-02-26 09:57:51 +01:00
|
|
|
|
2022-03-16 19:17:06 +01:00
|
|
|
let ret_val = evaluate_file(
|
|
|
|
script_name,
|
|
|
|
&args_to_script,
|
|
|
|
&mut engine_state,
|
|
|
|
&mut stack,
|
|
|
|
input,
|
|
|
|
);
|
2022-07-25 00:57:10 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-10-21 17:20:21 +02:00
|
|
|
info!("eval_file execution {}:{}:{}", file!(), line!(), column!());
|
2022-02-09 23:08:16 +01:00
|
|
|
|
|
|
|
ret_val
|
2022-01-26 15:42:39 +01:00
|
|
|
} else {
|
2022-04-06 19:11:51 +02:00
|
|
|
setup_config(
|
|
|
|
&mut engine_state,
|
|
|
|
&mut stack,
|
2022-07-17 20:29:19 +02:00
|
|
|
#[cfg(feature = "plugin")]
|
|
|
|
binary_args.plugin_file,
|
2022-04-06 19:11:51 +02:00
|
|
|
binary_args.config_file,
|
|
|
|
binary_args.env_file,
|
2022-06-06 13:52:37 +02:00
|
|
|
binary_args.login_shell.is_some(),
|
2022-04-06 19:11:51 +02:00
|
|
|
);
|
2022-03-16 19:17:06 +01:00
|
|
|
|
2022-06-14 22:53:33 +02:00
|
|
|
let ret_val = evaluate_repl(
|
|
|
|
&mut engine_state,
|
|
|
|
&mut stack,
|
|
|
|
config_files::NUSHELL_FOLDER,
|
2022-08-18 11:25:52 +02:00
|
|
|
binary_args.execute,
|
2022-06-14 22:53:33 +02:00
|
|
|
);
|
2022-10-21 17:20:21 +02:00
|
|
|
info!("repl eval {}:{}:{}", file!(), line!(), column!());
|
2022-02-09 23:08:16 +01:00
|
|
|
|
|
|
|
ret_val
|
2022-01-26 15:42:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(_) => std::process::exit(1),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-16 19:17:06 +01:00
|
|
|
fn setup_config(
|
|
|
|
engine_state: &mut EngineState,
|
|
|
|
stack: &mut Stack,
|
2022-07-17 20:29:19 +02:00
|
|
|
#[cfg(feature = "plugin")] plugin_file: Option<Spanned<String>>,
|
2022-03-16 19:17:06 +01:00
|
|
|
config_file: Option<Spanned<String>>,
|
2022-04-06 19:11:51 +02:00
|
|
|
env_file: Option<Spanned<String>>,
|
2022-06-06 13:52:37 +02:00
|
|
|
is_login_shell: bool,
|
2022-03-16 19:17:06 +01:00
|
|
|
) {
|
|
|
|
#[cfg(feature = "plugin")]
|
2022-10-21 17:20:21 +02:00
|
|
|
read_plugin_file(engine_state, stack, plugin_file, NUSHELL_FOLDER);
|
2022-03-16 19:17:06 +01:00
|
|
|
|
2022-10-21 17:20:21 +02:00
|
|
|
info!("read_config_file {}:{}:{}", file!(), line!(), column!());
|
|
|
|
|
|
|
|
config_files::read_config_file(engine_state, stack, env_file, true);
|
|
|
|
config_files::read_config_file(engine_state, stack, config_file, false);
|
2022-04-24 22:40:55 +02:00
|
|
|
|
2022-06-06 13:52:37 +02:00
|
|
|
if is_login_shell {
|
2022-10-21 17:20:21 +02:00
|
|
|
config_files::read_loginshell_file(engine_state, stack);
|
2022-06-06 13:52:37 +02:00
|
|
|
}
|
|
|
|
|
2022-04-24 22:40:55 +02:00
|
|
|
// Give a warning if we see `$config` for a few releases
|
|
|
|
{
|
|
|
|
let working_set = StateWorkingSet::new(engine_state);
|
|
|
|
if working_set.find_variable(b"$config").is_some() {
|
|
|
|
println!("warning: use `let-env config = ...` instead of `let config = ...`");
|
|
|
|
}
|
|
|
|
}
|
2022-03-16 19:17:06 +01:00
|
|
|
}
|
|
|
|
|
2022-01-26 15:42:39 +01:00
|
|
|
fn parse_commandline_args(
|
|
|
|
commandline_args: &str,
|
|
|
|
engine_state: &mut EngineState,
|
2022-02-09 23:08:16 +01:00
|
|
|
) -> Result<NushellCliArgs, ShellError> {
|
2022-01-26 15:42:39 +01:00
|
|
|
let (block, delta) = {
|
|
|
|
let mut working_set = StateWorkingSet::new(engine_state);
|
|
|
|
working_set.add_decl(Box::new(Nu));
|
|
|
|
|
2022-03-18 20:03:57 +01:00
|
|
|
let (output, err) = parse(
|
|
|
|
&mut working_set,
|
|
|
|
None,
|
|
|
|
commandline_args.as_bytes(),
|
|
|
|
false,
|
|
|
|
&[],
|
|
|
|
);
|
2022-01-26 15:42:39 +01:00
|
|
|
if let Some(err) = err {
|
|
|
|
report_error(&working_set, &err);
|
|
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
working_set.hide_decl(b"nu");
|
|
|
|
(output, working_set.render())
|
|
|
|
};
|
|
|
|
|
2022-07-14 16:09:27 +02:00
|
|
|
engine_state.merge_delta(delta)?;
|
2022-01-26 15:42:39 +01:00
|
|
|
|
|
|
|
let mut stack = Stack::new();
|
|
|
|
|
|
|
|
// We should have a successful parse now
|
2022-02-15 20:31:14 +01:00
|
|
|
if let Some(pipeline) = block.pipelines.get(0) {
|
2022-11-22 19:26:13 +01:00
|
|
|
if let Some(PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
)) = pipeline.elements.get(0)
|
2022-01-26 15:42:39 +01:00
|
|
|
{
|
|
|
|
let redirect_stdin = call.get_named_arg("stdin");
|
2022-01-26 18:26:43 +01:00
|
|
|
let login_shell = call.get_named_arg("login");
|
|
|
|
let interactive_shell = call.get_named_arg("interactive");
|
|
|
|
let commands: Option<Expression> = call.get_flag_expr("commands");
|
2022-02-02 21:59:01 +01:00
|
|
|
let testbin: Option<Expression> = call.get_flag_expr("testbin");
|
2022-07-17 20:29:19 +02:00
|
|
|
#[cfg(feature = "plugin")]
|
|
|
|
let plugin_file: Option<Expression> = call.get_flag_expr("plugin-config");
|
2022-02-19 21:54:43 +01:00
|
|
|
let config_file: Option<Expression> = call.get_flag_expr("config");
|
2022-04-06 19:11:51 +02:00
|
|
|
let env_file: Option<Expression> = call.get_flag_expr("env-config");
|
2022-02-20 11:08:53 +01:00
|
|
|
let log_level: Option<Expression> = call.get_flag_expr("log-level");
|
2022-08-09 18:44:37 +02:00
|
|
|
let log_target: Option<Expression> = call.get_flag_expr("log-target");
|
2022-08-18 11:25:52 +02:00
|
|
|
let execute: Option<Expression> = call.get_flag_expr("execute");
|
2022-02-11 19:46:36 +01:00
|
|
|
let threads: Option<Value> = call.get_flag(engine_state, &mut stack, "threads")?;
|
2022-05-11 23:15:31 +02:00
|
|
|
let table_mode: Option<Value> =
|
|
|
|
call.get_flag(engine_state, &mut stack, "table-mode")?;
|
2022-01-26 18:26:43 +01:00
|
|
|
|
2022-02-19 21:54:43 +01:00
|
|
|
fn extract_contents(
|
|
|
|
expression: Option<Expression>,
|
2022-05-01 23:49:31 +02:00
|
|
|
) -> Result<Option<Spanned<String>>, ShellError> {
|
|
|
|
if let Some(expr) = expression {
|
|
|
|
let str = expr.as_string();
|
|
|
|
if let Some(str) = str {
|
|
|
|
Ok(Some(Spanned {
|
|
|
|
item: str,
|
|
|
|
span: expr.span,
|
|
|
|
}))
|
|
|
|
} else {
|
|
|
|
Err(ShellError::TypeMismatch("string".into(), expr.span))
|
2022-02-19 21:54:43 +01:00
|
|
|
}
|
2022-05-01 23:49:31 +02:00
|
|
|
} else {
|
|
|
|
Ok(None)
|
|
|
|
}
|
2022-02-19 21:54:43 +01:00
|
|
|
}
|
2022-01-26 15:42:39 +01:00
|
|
|
|
2022-05-01 23:49:31 +02:00
|
|
|
let commands = extract_contents(commands)?;
|
|
|
|
let testbin = extract_contents(testbin)?;
|
2022-07-17 20:29:19 +02:00
|
|
|
#[cfg(feature = "plugin")]
|
|
|
|
let plugin_file = extract_contents(plugin_file)?;
|
2022-05-01 23:49:31 +02:00
|
|
|
let config_file = extract_contents(config_file)?;
|
|
|
|
let env_file = extract_contents(env_file)?;
|
|
|
|
let log_level = extract_contents(log_level)?;
|
2022-08-09 18:44:37 +02:00
|
|
|
let log_target = extract_contents(log_target)?;
|
2022-08-18 11:25:52 +02:00
|
|
|
let execute = extract_contents(execute)?;
|
2022-02-02 21:59:01 +01:00
|
|
|
|
2022-01-26 15:42:39 +01:00
|
|
|
let help = call.has_flag("help");
|
|
|
|
|
|
|
|
if help {
|
2022-11-20 14:22:42 +01:00
|
|
|
let full_help = get_full_help(
|
|
|
|
&Nu.signature(),
|
|
|
|
&Nu.examples(),
|
|
|
|
engine_state,
|
|
|
|
&mut stack,
|
|
|
|
true,
|
|
|
|
);
|
2022-02-02 21:59:01 +01:00
|
|
|
|
2022-05-17 20:28:18 +02:00
|
|
|
let _ = std::panic::catch_unwind(move || stdout_write_all_and_flush(full_help));
|
2022-02-02 21:59:01 +01:00
|
|
|
|
2022-01-26 15:42:39 +01:00
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
2022-02-17 12:02:46 +01:00
|
|
|
if call.has_flag("version") {
|
2022-02-17 13:29:58 +01:00
|
|
|
let version = env!("CARGO_PKG_VERSION").to_string();
|
2022-02-17 12:02:46 +01:00
|
|
|
let _ = std::panic::catch_unwind(move || {
|
2022-05-17 20:28:18 +02:00
|
|
|
stdout_write_all_and_flush(format!("{}\n", version))
|
2022-02-17 12:02:46 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
std::process::exit(0);
|
|
|
|
}
|
|
|
|
|
2022-02-09 23:08:16 +01:00
|
|
|
return Ok(NushellCliArgs {
|
2022-01-26 18:26:43 +01:00
|
|
|
redirect_stdin,
|
|
|
|
login_shell,
|
|
|
|
interactive_shell,
|
|
|
|
commands,
|
2022-02-02 21:59:01 +01:00
|
|
|
testbin,
|
2022-07-17 20:29:19 +02:00
|
|
|
#[cfg(feature = "plugin")]
|
|
|
|
plugin_file,
|
2022-02-19 21:54:43 +01:00
|
|
|
config_file,
|
2022-04-06 19:11:51 +02:00
|
|
|
env_file,
|
2022-02-20 11:08:53 +01:00
|
|
|
log_level,
|
2022-08-09 18:44:37 +02:00
|
|
|
log_target,
|
2022-08-18 11:25:52 +02:00
|
|
|
execute,
|
2022-02-11 19:46:36 +01:00
|
|
|
threads,
|
2022-05-11 23:15:31 +02:00
|
|
|
table_mode,
|
2022-01-26 18:26:43 +01:00
|
|
|
});
|
2022-01-26 15:42:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Just give the help and exit if the above fails
|
2022-11-20 14:22:42 +01:00
|
|
|
let full_help = get_full_help(
|
|
|
|
&Nu.signature(),
|
|
|
|
&Nu.examples(),
|
|
|
|
engine_state,
|
|
|
|
&mut stack,
|
|
|
|
true,
|
|
|
|
);
|
2022-01-26 15:42:39 +01:00
|
|
|
print!("{}", full_help);
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
2022-02-09 23:08:16 +01:00
|
|
|
struct NushellCliArgs {
|
2022-01-26 15:42:39 +01:00
|
|
|
redirect_stdin: Option<Spanned<String>>,
|
2022-01-26 18:26:43 +01:00
|
|
|
login_shell: Option<Spanned<String>>,
|
|
|
|
interactive_shell: Option<Spanned<String>>,
|
|
|
|
commands: Option<Spanned<String>>,
|
2022-02-02 21:59:01 +01:00
|
|
|
testbin: Option<Spanned<String>>,
|
2022-07-17 20:29:19 +02:00
|
|
|
#[cfg(feature = "plugin")]
|
|
|
|
plugin_file: Option<Spanned<String>>,
|
2022-02-19 21:54:43 +01:00
|
|
|
config_file: Option<Spanned<String>>,
|
2022-04-06 19:11:51 +02:00
|
|
|
env_file: Option<Spanned<String>>,
|
2022-02-20 11:08:53 +01:00
|
|
|
log_level: Option<Spanned<String>>,
|
2022-08-09 18:44:37 +02:00
|
|
|
log_target: Option<Spanned<String>>,
|
2022-08-18 11:25:52 +02:00
|
|
|
execute: Option<Spanned<String>>,
|
2022-02-11 19:46:36 +01:00
|
|
|
threads: Option<Value>,
|
2022-05-11 23:15:31 +02:00
|
|
|
table_mode: Option<Value>,
|
2022-01-26 15:42:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct Nu;
|
|
|
|
|
|
|
|
impl Command for Nu {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
"nu"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn signature(&self) -> Signature {
|
2022-07-17 20:29:19 +02:00
|
|
|
let signature = Signature::build("nu")
|
2022-03-27 21:25:30 +02:00
|
|
|
.usage("The nushell language and shell.")
|
2022-01-26 15:42:39 +01:00
|
|
|
.switch("stdin", "redirect the stdin", None)
|
2022-01-26 18:26:43 +01:00
|
|
|
.switch("login", "start as a login shell", Some('l'))
|
|
|
|
.switch("interactive", "start as an interactive shell", Some('i'))
|
2022-02-17 12:02:46 +01:00
|
|
|
.switch("version", "print the version", Some('v'))
|
2022-02-02 21:59:01 +01:00
|
|
|
.named(
|
|
|
|
"testbin",
|
|
|
|
SyntaxShape::String,
|
|
|
|
"run internal test binary",
|
|
|
|
None,
|
|
|
|
)
|
2022-01-26 18:26:43 +01:00
|
|
|
.named(
|
|
|
|
"commands",
|
|
|
|
SyntaxShape::String,
|
|
|
|
"run the given commands and then exit",
|
|
|
|
Some('c'),
|
|
|
|
)
|
2022-02-19 21:54:43 +01:00
|
|
|
.named(
|
|
|
|
"config",
|
|
|
|
SyntaxShape::String,
|
2022-02-20 01:25:07 +01:00
|
|
|
"start with an alternate config file",
|
2022-02-19 21:54:43 +01:00
|
|
|
None,
|
|
|
|
)
|
2022-04-06 19:11:51 +02:00
|
|
|
.named(
|
|
|
|
"env-config",
|
|
|
|
SyntaxShape::String,
|
|
|
|
"start with an alternate environment config file",
|
|
|
|
None,
|
|
|
|
)
|
2022-02-20 11:08:53 +01:00
|
|
|
.named(
|
|
|
|
"log-level",
|
|
|
|
SyntaxShape::String,
|
2022-10-21 17:20:21 +02:00
|
|
|
"log level for diagnostic logs (error, warn, info, debug, trace). Off by default",
|
2022-02-20 11:08:53 +01:00
|
|
|
None,
|
|
|
|
)
|
2022-08-09 18:44:37 +02:00
|
|
|
.named(
|
|
|
|
"log-target",
|
|
|
|
SyntaxShape::String,
|
|
|
|
"set the target for the log to output. stdout, stderr(default), mixed or file",
|
|
|
|
None,
|
|
|
|
)
|
2022-08-18 11:25:52 +02:00
|
|
|
.named(
|
|
|
|
"execute",
|
|
|
|
SyntaxShape::String,
|
|
|
|
"run the given commands and then enter an interactive shell",
|
|
|
|
Some('e'),
|
|
|
|
)
|
2022-02-11 19:46:36 +01:00
|
|
|
.named(
|
|
|
|
"threads",
|
|
|
|
SyntaxShape::Int,
|
|
|
|
"threads to use for parallel commands",
|
|
|
|
Some('t'),
|
|
|
|
)
|
2022-05-11 23:15:31 +02:00
|
|
|
.named(
|
|
|
|
"table-mode",
|
|
|
|
SyntaxShape::String,
|
|
|
|
"the table mode to use. rounded is default.",
|
|
|
|
Some('m'),
|
|
|
|
)
|
2022-03-09 14:04:50 +01:00
|
|
|
.optional(
|
|
|
|
"script file",
|
|
|
|
SyntaxShape::Filepath,
|
|
|
|
"name of the optional script file to run",
|
|
|
|
)
|
2022-01-26 15:42:39 +01:00
|
|
|
.rest(
|
|
|
|
"script args",
|
|
|
|
SyntaxShape::String,
|
|
|
|
"parameters to the script file",
|
|
|
|
)
|
2022-07-17 20:29:19 +02:00
|
|
|
.category(Category::System);
|
|
|
|
|
|
|
|
#[cfg(feature = "plugin")]
|
|
|
|
{
|
|
|
|
signature.named(
|
|
|
|
"plugin-config",
|
|
|
|
SyntaxShape::String,
|
|
|
|
"start with an alternate plugin signature file",
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(feature = "plugin"))]
|
|
|
|
{
|
|
|
|
signature
|
|
|
|
}
|
2022-01-26 15:42:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
|
|
|
"The nushell language and shell."
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run(
|
|
|
|
&self,
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &mut Stack,
|
|
|
|
call: &Call,
|
|
|
|
_input: PipelineData,
|
|
|
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
|
|
|
Ok(Value::String {
|
2022-11-20 14:22:42 +01:00
|
|
|
val: get_full_help(&Nu.signature(), &Nu.examples(), engine_state, stack, true),
|
2022-01-26 15:42:39 +01:00
|
|
|
span: call.head,
|
|
|
|
}
|
|
|
|
.into_pipeline_data())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn examples(&self) -> Vec<nu_protocol::Example> {
|
|
|
|
vec![
|
|
|
|
Example {
|
|
|
|
description: "Run a script",
|
|
|
|
example: "nu myfile.nu",
|
|
|
|
result: None,
|
|
|
|
},
|
|
|
|
Example {
|
|
|
|
description: "Run nushell interactively (as a shell or REPL)",
|
|
|
|
example: "nu",
|
|
|
|
result: None,
|
|
|
|
},
|
|
|
|
]
|
2022-01-04 23:30:34 +01:00
|
|
|
}
|
2019-05-15 18:12:38 +02:00
|
|
|
}
|
2022-02-09 23:08:16 +01:00
|
|
|
|
2022-08-22 18:30:09 +02:00
|
|
|
fn set_config_path(
|
|
|
|
engine_state: &mut EngineState,
|
|
|
|
cwd: &Path,
|
|
|
|
default_config_name: &str,
|
|
|
|
key: &str,
|
|
|
|
config_file: &Option<Spanned<String>>,
|
|
|
|
) {
|
|
|
|
let config_path = match config_file {
|
|
|
|
Some(s) => canonicalize_with(&s.item, cwd).ok(),
|
|
|
|
None => nu_path::config_dir().map(|mut p| {
|
|
|
|
p.push(config_files::NUSHELL_FOLDER);
|
|
|
|
p.push(default_config_name);
|
|
|
|
p
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(path) = config_path {
|
|
|
|
engine_state.set_config_path(key, path);
|
|
|
|
}
|
|
|
|
}
|