Start support for commandline args to nu itself (#851)

* cmdline args wip

* WIP

* redirect working

* Add help and examples

* Only show flags in signature of more than help
This commit is contained in:
JT
2022-01-26 09:42:39 -05:00
committed by GitHub
parent 285f65ba34
commit 8ee619954d
9 changed files with 307 additions and 97 deletions

View File

@ -13,8 +13,10 @@ use crate::utils::{gather_parent_env_vars, report_error};
/// Main function used when a file path is found as argument for nu
pub(crate) fn evaluate(
path: String,
args: &[String],
init_cwd: PathBuf,
engine_state: &mut EngineState,
input: PipelineData,
) -> Result<()> {
// First, set up env vars as strings only
gather_parent_env_vars(engine_state);
@ -82,84 +84,71 @@ pub(crate) fn evaluate(
std::process::exit(1);
}
match eval_block(
engine_state,
&mut stack,
&block,
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
) {
Ok(pipeline_data) => {
for item in pipeline_data {
if let Value::Error { error } = item {
let working_set = StateWorkingSet::new(engine_state);
// Next, let's check if there are any flags we want to pass to the main function
if args.is_empty() && engine_state.find_decl(b"main").is_none() {
// We don't have a main, so evaluate the whole file
match eval_block(engine_state, &mut stack, &block, input) {
Ok(pipeline_data) => {
for item in pipeline_data {
if let Value::Error { error } = item {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error);
report_error(&working_set, &error);
std::process::exit(1);
}
println!("{}", item.into_string("\n", &config));
}
// Next, let's check if there are any flags we want to pass to the main function
let args: Vec<String> = std::env::args().skip(2).collect();
if args.is_empty() && engine_state.find_decl(b"main").is_none() {
return Ok(());
}
let args = format!("main {}", args.join(" ")).as_bytes().to_vec();
let (block, delta) = {
let mut working_set = StateWorkingSet::new(engine_state);
let (output, err) = parse(&mut working_set, Some("<cmdline>"), &args, false);
if let Some(err) = err {
report_error(&working_set, &err);
std::process::exit(1);
}
(output, working_set.render())
};
let cwd = nu_engine::env::current_dir_str(engine_state, &stack)?;
if let Err(err) = engine_state.merge_delta(delta, Some(&mut stack), &cwd) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
}
match eval_block(
engine_state,
&mut stack,
&block,
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
) {
Ok(pipeline_data) => {
for item in pipeline_data {
if let Value::Error { error } = item {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error);
std::process::exit(1);
}
println!("{}", item.into_string("\n", &config));
std::process::exit(1);
}
println!("{}", item.into_string("\n", &config));
}
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);
}
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
report_error(&working_set, &err);
std::process::exit(1);
}
std::process::exit(1);
}
}
Err(err) => {
} else {
let args = format!("main {}", args.join(" ")).as_bytes().to_vec();
let (block, delta) = {
let mut working_set = StateWorkingSet::new(engine_state);
let (output, err) = parse(&mut working_set, Some("<cmdline>"), &args, false);
if let Some(err) = err {
report_error(&working_set, &err);
std::process::exit(1);
}
(output, working_set.render())
};
let cwd = nu_engine::env::current_dir_str(engine_state, &stack)?;
if let Err(err) = engine_state.merge_delta(delta, Some(&mut stack), &cwd) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
}
std::process::exit(1);
match eval_block(engine_state, &mut stack, &block, input) {
Ok(pipeline_data) => {
for item in pipeline_data {
if let Value::Error { error } = item {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error);
std::process::exit(1);
}
println!("{}", item.into_string("\n", &config));
}
}
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
std::process::exit(1);
}
}
}

View File

@ -10,11 +10,24 @@ mod utils;
mod tests;
use miette::Result;
use nu_command::create_default_context;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
use nu_command::{create_default_context, BufferedReader};
use nu_engine::get_full_help;
use nu_parser::parse;
use nu_protocol::{
ast::{Call, Expr, Expression, Pipeline, Statement},
engine::{Command, EngineState, Stack, StateWorkingSet},
ByteStream, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
Spanned, SyntaxShape, Value, CONFIG_VARIABLE_ID,
};
use std::{
io::BufReader,
path::Path,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use utils::report_error;
fn main() -> Result<()> {
// miette::set_panic_hook();
@ -51,9 +64,199 @@ fn main() -> Result<()> {
engine_state.ctrlc = Some(engine_state_ctrlc);
// End ctrl-c protection section
if let Some(path) = std::env::args().nth(1) {
eval_file::evaluate(path, init_cwd, &mut engine_state)
} else {
repl::evaluate(ctrlc, &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 collect_arg_nushell = false;
for arg in std::env::args().skip(1) {
if !script_name.is_empty() {
args_to_script.push(arg);
} else if collect_arg_nushell {
args_to_nushell.push(arg);
collect_arg_nushell = false;
} else if arg.starts_with('-') {
// Cool, it's a flag
if arg == "-c"
|| arg == "--commands"
|| arg == "--develop"
|| arg == "--debug"
|| arg == "--loglevel"
|| arg == "--config-file"
{
collect_arg_nushell = true;
}
args_to_nushell.push(arg);
} else {
// Our script file
script_name = arg;
}
}
args_to_nushell.insert(0, "nu".into());
let nushell_commandline_args = args_to_nushell.join(" ");
let nushell_config =
parse_commandline_args(&nushell_commandline_args, &init_cwd, &mut engine_state);
match nushell_config {
Ok(nushell_config) => {
if !script_name.is_empty() {
let input = if let Some(redirect_stdin) = &nushell_config.redirect_stdin {
let stdin = std::io::stdin();
let buf_reader = BufReader::new(stdin);
PipelineData::ByteStream(
ByteStream {
stream: Box::new(BufferedReader::new(buf_reader)),
ctrlc: Some(ctrlc),
},
redirect_stdin.span,
None,
)
} else {
PipelineData::new(Span::new(0, 0))
};
eval_file::evaluate(
script_name,
&args_to_script,
init_cwd,
&mut engine_state,
input,
)
} else {
repl::evaluate(ctrlc, &mut engine_state)
}
}
Err(_) => std::process::exit(1),
}
}
fn parse_commandline_args(
commandline_args: &str,
init_cwd: &Path,
engine_state: &mut EngineState,
) -> Result<NushellConfig, 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())
};
let _ = engine_state.merge_delta(delta, None, init_cwd);
let mut stack = Stack::new();
stack.add_var(
CONFIG_VARIABLE_ID,
Value::Record {
cols: vec![],
vals: vec![],
span: Span::new(0, 0),
},
);
// We should have a successful parse now
if let Some(Statement::Pipeline(Pipeline { expressions })) = block.stmts.get(0) {
if let Some(Expression {
expr: Expr::Call(call),
..
}) = expressions.get(0)
{
let redirect_stdin = call.get_named_arg("stdin");
let help = call.has_flag("help");
if help {
let full_help =
get_full_help(&Nu.signature(), &Nu.examples(), engine_state, &mut stack);
print!("{}", full_help);
std::process::exit(1);
}
return Ok(NushellConfig { redirect_stdin });
}
}
// Just give the help and exit if the above fails
let full_help = get_full_help(&Nu.signature(), &Nu.examples(), engine_state, &mut stack);
print!("{}", full_help);
std::process::exit(1);
}
struct NushellConfig {
redirect_stdin: Option<Spanned<String>>,
}
#[derive(Clone)]
struct Nu;
impl Command for Nu {
fn name(&self) -> &str {
"nu"
}
fn signature(&self) -> Signature {
Signature::build("nu")
.desc("The nushell language and shell.")
.switch("stdin", "redirect the stdin", 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)
}
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),
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,
},
]
}
}

View File

@ -317,7 +317,7 @@ pub(crate) fn eval_source(
engine_state,
stack,
&block,
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
PipelineData::new(Span::new(0, 0)),
) {
Ok(pipeline_data) => {
if let Err(err) = print_pipeline_data(pipeline_data, engine_state, stack) {