mirror of
https://github.com/nushell/nushell
synced 2024-11-16 01:37:57 +00:00
Move all functions of main.rs into modules (#7803)
The affected modules are: - `command.rs` - `config_files.rs` - `terminal.rs`
This commit is contained in:
parent
24aa1f312a
commit
9e4a2ab824
4 changed files with 462 additions and 453 deletions
300
src/command.rs
Normal file
300
src/command.rs
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
use nu_cli::report_error;
|
||||||
|
use nu_engine::{get_full_help, CallExt};
|
||||||
|
use nu_parser::parse;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::{Call, Expr, Expression, PipelineElement},
|
||||||
|
engine::{Command, EngineState, Stack, StateWorkingSet},
|
||||||
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
use nu_utils::stdout_write_all_and_flush;
|
||||||
|
|
||||||
|
pub(crate) fn parse_commandline_args(
|
||||||
|
commandline_args: &str,
|
||||||
|
engine_state: &mut EngineState,
|
||||||
|
) -> Result<NushellCliArgs, ShellError> {
|
||||||
|
let (block, delta) = {
|
||||||
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
working_set.add_decl(Box::new(Nu));
|
||||||
|
|
||||||
|
let (output, err) = parse(
|
||||||
|
&mut working_set,
|
||||||
|
None,
|
||||||
|
commandline_args.as_bytes(),
|
||||||
|
false,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
if let Some(err) = err {
|
||||||
|
report_error(&working_set, &err);
|
||||||
|
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
working_set.hide_decl(b"nu");
|
||||||
|
(output, working_set.render())
|
||||||
|
};
|
||||||
|
|
||||||
|
engine_state.merge_delta(delta)?;
|
||||||
|
|
||||||
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
|
// We should have a successful parse now
|
||||||
|
if let Some(pipeline) = block.pipelines.get(0) {
|
||||||
|
if let Some(PipelineElement::Expression(
|
||||||
|
_,
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
)) = pipeline.elements.get(0)
|
||||||
|
{
|
||||||
|
let redirect_stdin = call.get_named_arg("stdin");
|
||||||
|
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");
|
||||||
|
let testbin: Option<Expression> = call.get_flag_expr("testbin");
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
let plugin_file: Option<Expression> = call.get_flag_expr("plugin-config");
|
||||||
|
let config_file: Option<Expression> = call.get_flag_expr("config");
|
||||||
|
let env_file: Option<Expression> = call.get_flag_expr("env-config");
|
||||||
|
let log_level: Option<Expression> = call.get_flag_expr("log-level");
|
||||||
|
let log_target: Option<Expression> = call.get_flag_expr("log-target");
|
||||||
|
let execute: Option<Expression> = call.get_flag_expr("execute");
|
||||||
|
let threads: Option<Value> = call.get_flag(engine_state, &mut stack, "threads")?;
|
||||||
|
let table_mode: Option<Value> =
|
||||||
|
call.get_flag(engine_state, &mut stack, "table-mode")?;
|
||||||
|
|
||||||
|
fn extract_contents(
|
||||||
|
expression: Option<Expression>,
|
||||||
|
) -> 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))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let commands = extract_contents(commands)?;
|
||||||
|
let testbin = extract_contents(testbin)?;
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
let plugin_file = extract_contents(plugin_file)?;
|
||||||
|
let config_file = extract_contents(config_file)?;
|
||||||
|
let env_file = extract_contents(env_file)?;
|
||||||
|
let log_level = extract_contents(log_level)?;
|
||||||
|
let log_target = extract_contents(log_target)?;
|
||||||
|
let execute = extract_contents(execute)?;
|
||||||
|
|
||||||
|
let help = call.has_flag("help");
|
||||||
|
|
||||||
|
if help {
|
||||||
|
let full_help = get_full_help(
|
||||||
|
&Nu.signature(),
|
||||||
|
&Nu.examples(),
|
||||||
|
engine_state,
|
||||||
|
&mut stack,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = std::panic::catch_unwind(move || stdout_write_all_and_flush(full_help));
|
||||||
|
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if call.has_flag("version") {
|
||||||
|
let version = env!("CARGO_PKG_VERSION").to_string();
|
||||||
|
let _ = std::panic::catch_unwind(move || {
|
||||||
|
stdout_write_all_and_flush(format!("{}\n", version))
|
||||||
|
});
|
||||||
|
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(NushellCliArgs {
|
||||||
|
redirect_stdin,
|
||||||
|
login_shell,
|
||||||
|
interactive_shell,
|
||||||
|
commands,
|
||||||
|
testbin,
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
plugin_file,
|
||||||
|
config_file,
|
||||||
|
env_file,
|
||||||
|
log_level,
|
||||||
|
log_target,
|
||||||
|
execute,
|
||||||
|
threads,
|
||||||
|
table_mode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just give the help and exit if the above fails
|
||||||
|
let full_help = get_full_help(
|
||||||
|
&Nu.signature(),
|
||||||
|
&Nu.examples(),
|
||||||
|
engine_state,
|
||||||
|
&mut stack,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
print!("{}", full_help);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct NushellCliArgs {
|
||||||
|
pub(crate) redirect_stdin: Option<Spanned<String>>,
|
||||||
|
pub(crate) login_shell: Option<Spanned<String>>,
|
||||||
|
pub(crate) interactive_shell: Option<Spanned<String>>,
|
||||||
|
pub(crate) commands: Option<Spanned<String>>,
|
||||||
|
pub(crate) testbin: Option<Spanned<String>>,
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
pub(crate) plugin_file: Option<Spanned<String>>,
|
||||||
|
pub(crate) config_file: Option<Spanned<String>>,
|
||||||
|
pub(crate) env_file: Option<Spanned<String>>,
|
||||||
|
pub(crate) log_level: Option<Spanned<String>>,
|
||||||
|
pub(crate) log_target: Option<Spanned<String>>,
|
||||||
|
pub(crate) execute: Option<Spanned<String>>,
|
||||||
|
pub(crate) threads: Option<Value>,
|
||||||
|
pub(crate) table_mode: Option<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Nu;
|
||||||
|
|
||||||
|
impl Command for Nu {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"nu"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
let mut signature = Signature::build("nu")
|
||||||
|
.usage("The nushell language and shell.")
|
||||||
|
.named(
|
||||||
|
"commands",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"run the given commands and then exit",
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"execute",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"run the given commands and then enter an interactive shell",
|
||||||
|
Some('e'),
|
||||||
|
)
|
||||||
|
.switch("interactive", "start as an interactive shell", Some('i'))
|
||||||
|
.switch("login", "start as a login shell", Some('l'))
|
||||||
|
.named(
|
||||||
|
"table-mode",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the table mode to use. rounded is default.",
|
||||||
|
Some('m'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"threads",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"threads to use for parallel commands",
|
||||||
|
Some('t'),
|
||||||
|
)
|
||||||
|
.switch("version", "print the version", Some('v'))
|
||||||
|
.named(
|
||||||
|
"config",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"start with an alternate config file",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"env-config",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"start with an alternate environment config file",
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
{
|
||||||
|
signature = signature.named(
|
||||||
|
"plugin-config",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"start with an alternate plugin signature file",
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
signature = signature
|
||||||
|
.named(
|
||||||
|
"log-level",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"log level for diagnostic logs (error, warn, info, debug, trace). Off by default",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"log-target",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"set the target for the log to output. stdout, stderr(default), mixed or file",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"stdin",
|
||||||
|
"redirect standard input to a command (with `-c`) or a script file",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"testbin",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"run internal test binary",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.optional(
|
||||||
|
"script file",
|
||||||
|
SyntaxShape::Filepath,
|
||||||
|
"name of the optional script file to run",
|
||||||
|
)
|
||||||
|
.rest(
|
||||||
|
"script args",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"parameters to the script file",
|
||||||
|
)
|
||||||
|
.category(Category::System);
|
||||||
|
|
||||||
|
signature
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
val: get_full_help(&Nu.signature(), &Nu.examples(), engine_state, stack, true),
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
use log::info;
|
use log::info;
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
use nu_cli::read_plugin_file;
|
||||||
use nu_cli::{eval_config_contents, eval_source, report_error};
|
use nu_cli::{eval_config_contents, eval_source, report_error};
|
||||||
use nu_parser::ParseError;
|
use nu_parser::ParseError;
|
||||||
use nu_path::canonicalize_with;
|
use nu_path::canonicalize_with;
|
||||||
|
@ -7,6 +9,7 @@ use nu_protocol::{PipelineData, Spanned};
|
||||||
use nu_utils::{get_default_config, get_default_env};
|
use nu_utils::{get_default_config, get_default_env};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
pub(crate) const NUSHELL_FOLDER: &str = "nushell";
|
pub(crate) const NUSHELL_FOLDER: &str = "nushell";
|
||||||
const CONFIG_FILE: &str = "config.nu";
|
const CONFIG_FILE: &str = "config.nu";
|
||||||
|
@ -190,3 +193,53 @@ fn eval_default_config(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn setup_config(
|
||||||
|
engine_state: &mut EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
#[cfg(feature = "plugin")] plugin_file: Option<Spanned<String>>,
|
||||||
|
config_file: Option<Spanned<String>>,
|
||||||
|
env_file: Option<Spanned<String>>,
|
||||||
|
is_login_shell: bool,
|
||||||
|
) {
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
read_plugin_file(engine_state, stack, plugin_file, NUSHELL_FOLDER);
|
||||||
|
|
||||||
|
info!("read_config_file {}:{}:{}", file!(), line!(), column!());
|
||||||
|
|
||||||
|
read_config_file(engine_state, stack, env_file, true);
|
||||||
|
read_config_file(engine_state, stack, config_file, false);
|
||||||
|
|
||||||
|
if is_login_shell {
|
||||||
|
read_loginshell_file(engine_state, stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = ...`");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) 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(NUSHELL_FOLDER);
|
||||||
|
p.push(default_config_name);
|
||||||
|
p
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(path) = config_path {
|
||||||
|
engine_state.set_config_path(key, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
465
src/main.rs
465
src/main.rs
|
@ -1,138 +1,37 @@
|
||||||
|
mod command;
|
||||||
mod config_files;
|
mod config_files;
|
||||||
mod logger;
|
mod logger;
|
||||||
mod signals;
|
mod signals;
|
||||||
|
mod terminal;
|
||||||
mod test_bins;
|
mod test_bins;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use crate::config_files::NUSHELL_FOLDER;
|
use crate::config_files::NUSHELL_FOLDER;
|
||||||
use crate::logger::{configure, logger};
|
use crate::{
|
||||||
|
command::parse_commandline_args,
|
||||||
|
config_files::{set_config_path, setup_config},
|
||||||
|
logger::{configure, logger},
|
||||||
|
terminal::acquire_terminal,
|
||||||
|
};
|
||||||
use log::{info, Level};
|
use log::{info, Level};
|
||||||
use miette::Result;
|
use miette::Result;
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use nu_cli::read_plugin_file;
|
use nu_cli::read_plugin_file;
|
||||||
use nu_cli::{
|
use nu_cli::{
|
||||||
evaluate_commands, evaluate_file, evaluate_repl, gather_parent_env_vars, get_init_cwd,
|
evaluate_commands, evaluate_file, evaluate_repl, gather_parent_env_vars, get_init_cwd,
|
||||||
report_error, report_error_new,
|
report_error_new,
|
||||||
};
|
};
|
||||||
use nu_command::create_default_context;
|
use nu_command::create_default_context;
|
||||||
use nu_engine::{get_full_help, CallExt};
|
use nu_parser::{escape_for_script_arg, escape_quote_string};
|
||||||
use nu_parser::{escape_for_script_arg, escape_quote_string, parse};
|
use nu_protocol::{util::BufferedReader, PipelineData, RawStream};
|
||||||
use nu_path::canonicalize_with;
|
|
||||||
use nu_protocol::{
|
|
||||||
ast::{Call, Expr, Expression, PipelineElement},
|
|
||||||
engine::{Command, EngineState, Stack, StateWorkingSet},
|
|
||||||
util::BufferedReader,
|
|
||||||
Category, Example, IntoPipelineData, PipelineData, RawStream, ShellError, Signature, Spanned,
|
|
||||||
SyntaxShape, Value,
|
|
||||||
};
|
|
||||||
use nu_utils::stdout_write_all_and_flush;
|
|
||||||
use signals::{ctrlc_protection, sigquit_protection};
|
use signals::{ctrlc_protection, sigquit_protection};
|
||||||
|
use std::str::FromStr;
|
||||||
use std::{
|
use std::{
|
||||||
io::BufReader,
|
io::BufReader,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use std::{path::Path, str::FromStr};
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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() {
|
|
||||||
if !interactive {
|
|
||||||
// that's fine
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
eprintln!("ERROR: failed to SIGTTIN ourselves");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !success && interactive {
|
|
||||||
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) {}
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let miette_hook = std::panic::take_hook();
|
let miette_hook = std::panic::take_hook();
|
||||||
|
@ -423,343 +322,3 @@ fn main() -> Result<()> {
|
||||||
ret_val
|
ret_val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_config(
|
|
||||||
engine_state: &mut EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
#[cfg(feature = "plugin")] plugin_file: Option<Spanned<String>>,
|
|
||||||
config_file: Option<Spanned<String>>,
|
|
||||||
env_file: Option<Spanned<String>>,
|
|
||||||
is_login_shell: bool,
|
|
||||||
) {
|
|
||||||
#[cfg(feature = "plugin")]
|
|
||||||
read_plugin_file(engine_state, stack, plugin_file, NUSHELL_FOLDER);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
if is_login_shell {
|
|
||||||
config_files::read_loginshell_file(engine_state, stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 = ...`");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_commandline_args(
|
|
||||||
commandline_args: &str,
|
|
||||||
engine_state: &mut EngineState,
|
|
||||||
) -> Result<NushellCliArgs, ShellError> {
|
|
||||||
let (block, delta) = {
|
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
|
||||||
working_set.add_decl(Box::new(Nu));
|
|
||||||
|
|
||||||
let (output, err) = parse(
|
|
||||||
&mut working_set,
|
|
||||||
None,
|
|
||||||
commandline_args.as_bytes(),
|
|
||||||
false,
|
|
||||||
&[],
|
|
||||||
);
|
|
||||||
if let Some(err) = err {
|
|
||||||
report_error(&working_set, &err);
|
|
||||||
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
working_set.hide_decl(b"nu");
|
|
||||||
(output, working_set.render())
|
|
||||||
};
|
|
||||||
|
|
||||||
engine_state.merge_delta(delta)?;
|
|
||||||
|
|
||||||
let mut stack = Stack::new();
|
|
||||||
|
|
||||||
// We should have a successful parse now
|
|
||||||
if let Some(pipeline) = block.pipelines.get(0) {
|
|
||||||
if let Some(PipelineElement::Expression(
|
|
||||||
_,
|
|
||||||
Expression {
|
|
||||||
expr: Expr::Call(call),
|
|
||||||
..
|
|
||||||
},
|
|
||||||
)) = pipeline.elements.get(0)
|
|
||||||
{
|
|
||||||
let redirect_stdin = call.get_named_arg("stdin");
|
|
||||||
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");
|
|
||||||
let testbin: Option<Expression> = call.get_flag_expr("testbin");
|
|
||||||
#[cfg(feature = "plugin")]
|
|
||||||
let plugin_file: Option<Expression> = call.get_flag_expr("plugin-config");
|
|
||||||
let config_file: Option<Expression> = call.get_flag_expr("config");
|
|
||||||
let env_file: Option<Expression> = call.get_flag_expr("env-config");
|
|
||||||
let log_level: Option<Expression> = call.get_flag_expr("log-level");
|
|
||||||
let log_target: Option<Expression> = call.get_flag_expr("log-target");
|
|
||||||
let execute: Option<Expression> = call.get_flag_expr("execute");
|
|
||||||
let threads: Option<Value> = call.get_flag(engine_state, &mut stack, "threads")?;
|
|
||||||
let table_mode: Option<Value> =
|
|
||||||
call.get_flag(engine_state, &mut stack, "table-mode")?;
|
|
||||||
|
|
||||||
fn extract_contents(
|
|
||||||
expression: Option<Expression>,
|
|
||||||
) -> 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))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let commands = extract_contents(commands)?;
|
|
||||||
let testbin = extract_contents(testbin)?;
|
|
||||||
#[cfg(feature = "plugin")]
|
|
||||||
let plugin_file = extract_contents(plugin_file)?;
|
|
||||||
let config_file = extract_contents(config_file)?;
|
|
||||||
let env_file = extract_contents(env_file)?;
|
|
||||||
let log_level = extract_contents(log_level)?;
|
|
||||||
let log_target = extract_contents(log_target)?;
|
|
||||||
let execute = extract_contents(execute)?;
|
|
||||||
|
|
||||||
let help = call.has_flag("help");
|
|
||||||
|
|
||||||
if help {
|
|
||||||
let full_help = get_full_help(
|
|
||||||
&Nu.signature(),
|
|
||||||
&Nu.examples(),
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = std::panic::catch_unwind(move || stdout_write_all_and_flush(full_help));
|
|
||||||
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if call.has_flag("version") {
|
|
||||||
let version = env!("CARGO_PKG_VERSION").to_string();
|
|
||||||
let _ = std::panic::catch_unwind(move || {
|
|
||||||
stdout_write_all_and_flush(format!("{}\n", version))
|
|
||||||
});
|
|
||||||
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(NushellCliArgs {
|
|
||||||
redirect_stdin,
|
|
||||||
login_shell,
|
|
||||||
interactive_shell,
|
|
||||||
commands,
|
|
||||||
testbin,
|
|
||||||
#[cfg(feature = "plugin")]
|
|
||||||
plugin_file,
|
|
||||||
config_file,
|
|
||||||
env_file,
|
|
||||||
log_level,
|
|
||||||
log_target,
|
|
||||||
execute,
|
|
||||||
threads,
|
|
||||||
table_mode,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just give the help and exit if the above fails
|
|
||||||
let full_help = get_full_help(
|
|
||||||
&Nu.signature(),
|
|
||||||
&Nu.examples(),
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
print!("{}", full_help);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NushellCliArgs {
|
|
||||||
redirect_stdin: Option<Spanned<String>>,
|
|
||||||
login_shell: Option<Spanned<String>>,
|
|
||||||
interactive_shell: Option<Spanned<String>>,
|
|
||||||
commands: Option<Spanned<String>>,
|
|
||||||
testbin: Option<Spanned<String>>,
|
|
||||||
#[cfg(feature = "plugin")]
|
|
||||||
plugin_file: Option<Spanned<String>>,
|
|
||||||
config_file: Option<Spanned<String>>,
|
|
||||||
env_file: Option<Spanned<String>>,
|
|
||||||
log_level: Option<Spanned<String>>,
|
|
||||||
log_target: Option<Spanned<String>>,
|
|
||||||
execute: Option<Spanned<String>>,
|
|
||||||
threads: Option<Value>,
|
|
||||||
table_mode: Option<Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct Nu;
|
|
||||||
|
|
||||||
impl Command for Nu {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"nu"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
let mut signature = Signature::build("nu")
|
|
||||||
.usage("The nushell language and shell.")
|
|
||||||
.named(
|
|
||||||
"commands",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"run the given commands and then exit",
|
|
||||||
Some('c'),
|
|
||||||
)
|
|
||||||
.named(
|
|
||||||
"execute",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"run the given commands and then enter an interactive shell",
|
|
||||||
Some('e'),
|
|
||||||
)
|
|
||||||
.switch("interactive", "start as an interactive shell", Some('i'))
|
|
||||||
.switch("login", "start as a login shell", Some('l'))
|
|
||||||
.named(
|
|
||||||
"table-mode",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"the table mode to use. rounded is default.",
|
|
||||||
Some('m'),
|
|
||||||
)
|
|
||||||
.named(
|
|
||||||
"threads",
|
|
||||||
SyntaxShape::Int,
|
|
||||||
"threads to use for parallel commands",
|
|
||||||
Some('t'),
|
|
||||||
)
|
|
||||||
.switch("version", "print the version", Some('v'))
|
|
||||||
.named(
|
|
||||||
"config",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"start with an alternate config file",
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.named(
|
|
||||||
"env-config",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"start with an alternate environment config file",
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
|
||||||
{
|
|
||||||
signature = signature.named(
|
|
||||||
"plugin-config",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"start with an alternate plugin signature file",
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
signature = signature
|
|
||||||
.named(
|
|
||||||
"log-level",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"log level for diagnostic logs (error, warn, info, debug, trace). Off by default",
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.named(
|
|
||||||
"log-target",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"set the target for the log to output. stdout, stderr(default), mixed or file",
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.switch(
|
|
||||||
"stdin",
|
|
||||||
"redirect standard input to a command (with `-c`) or a script file",
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.named(
|
|
||||||
"testbin",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"run internal test binary",
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.optional(
|
|
||||||
"script file",
|
|
||||||
SyntaxShape::Filepath,
|
|
||||||
"name of the optional script file to run",
|
|
||||||
)
|
|
||||||
.rest(
|
|
||||||
"script args",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"parameters to the script file",
|
|
||||||
)
|
|
||||||
.category(Category::System);
|
|
||||||
|
|
||||||
signature
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
val: get_full_help(&Nu.signature(), &Nu.examples(), engine_state, stack, true),
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
97
src/terminal.rs
Normal file
97
src/terminal.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub(crate) 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub(crate) fn acquire_terminal(_: bool) {}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
if !interactive {
|
||||||
|
// that's fine
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
eprintln!("ERROR: failed to SIGTTIN ourselves");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !success && interactive {
|
||||||
|
eprintln!("ERROR: failed take control of the terminal, we might be orphaned");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue