mirror of
https://github.com/nushell/nushell.git
synced 2024-11-23 00:43:33 +01:00
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:
parent
285f65ba34
commit
8ee619954d
@ -14,7 +14,7 @@ pub use cp::Cp;
|
||||
pub use ls::Ls;
|
||||
pub use mkdir::Mkdir;
|
||||
pub use mv::Mv;
|
||||
pub use open::Open;
|
||||
pub use open::{BufferedReader, Open};
|
||||
pub use rm::Rm;
|
||||
pub use save::Save;
|
||||
pub use touch::Touch;
|
||||
|
@ -184,6 +184,12 @@ pub struct BufferedReader<R: Read> {
|
||||
input: BufReader<R>,
|
||||
}
|
||||
|
||||
impl<R: Read> BufferedReader<R> {
|
||||
pub fn new(input: BufReader<R>) -> Self {
|
||||
Self { input }
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Iterator for BufferedReader<R> {
|
||||
type Item = Result<Vec<u8>, ShellError>;
|
||||
|
||||
|
@ -186,6 +186,10 @@ pub fn get_documentation(
|
||||
long_desc.push('\n');
|
||||
}
|
||||
|
||||
if !sig.named.is_empty() {
|
||||
long_desc.push_str(&get_flags_section(sig))
|
||||
}
|
||||
|
||||
if !sig.required_positional.is_empty()
|
||||
|| !sig.optional_positional.is_empty()
|
||||
|| sig.rest_positional.is_some()
|
||||
@ -205,13 +209,11 @@ pub fn get_documentation(
|
||||
long_desc.push_str(&format!(" ...args: {}\n", rest_positional.desc));
|
||||
}
|
||||
}
|
||||
if !sig.named.is_empty() {
|
||||
long_desc.push_str(&get_flags_section(sig))
|
||||
}
|
||||
|
||||
if !examples.is_empty() {
|
||||
long_desc.push_str("\nExamples:");
|
||||
}
|
||||
|
||||
for example in examples {
|
||||
long_desc.push('\n');
|
||||
long_desc.push_str(" ");
|
||||
@ -222,7 +224,7 @@ pub fn get_documentation(
|
||||
} else if let Some(highlighter) = engine_state.find_decl(b"nu-highlight") {
|
||||
let decl = engine_state.get_decl(highlighter);
|
||||
|
||||
if let Ok(output) = decl.run(
|
||||
match decl.run(
|
||||
engine_state,
|
||||
stack,
|
||||
&Call::new(),
|
||||
@ -232,16 +234,23 @@ pub fn get_documentation(
|
||||
}
|
||||
.into_pipeline_data(),
|
||||
) {
|
||||
let result = output.into_value(Span { start: 0, end: 0 });
|
||||
match result.as_string() {
|
||||
Ok(s) => {
|
||||
long_desc.push_str(&format!("\n > {}\n", s));
|
||||
}
|
||||
_ => {
|
||||
long_desc.push_str(&format!("\n > {}\n", example.example));
|
||||
Ok(output) => {
|
||||
let result = output.into_value(Span { start: 0, end: 0 });
|
||||
match result.as_string() {
|
||||
Ok(s) => {
|
||||
long_desc.push_str(&format!("\n > {}\n", s));
|
||||
}
|
||||
_ => {
|
||||
long_desc.push_str(&format!("\n > {}\n", example.example));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
long_desc.push_str(&format!("\n > {}\n", example.example));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
long_desc.push_str(&format!("\n > {}\n", example.example));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,9 +12,7 @@ pub use flatten::{
|
||||
};
|
||||
pub use lex::{lex, Token, TokenContents};
|
||||
pub use lite_parse::{lite_parse, LiteBlock};
|
||||
pub use parse_keywords::{
|
||||
parse_alias, parse_def, parse_def_predecl, parse_let, parse_module, parse_use,
|
||||
};
|
||||
|
||||
pub use parser::{find_captures_in_expr, parse, trim_quotes, Import};
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::Value;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Example {
|
||||
pub example: &'static str,
|
||||
pub description: &'static str,
|
||||
|
@ -279,6 +279,14 @@ impl Signature {
|
||||
one_liner.push_str(&self.name);
|
||||
one_liner.push(' ');
|
||||
|
||||
// Note: the call signature needs flags first because on the nu commandline,
|
||||
// flags will precede the script file name. Flags for internal commands can come
|
||||
// either before or after (or around) positional parameters, so there isn't a strong
|
||||
// preference, so we default to the more constrained example.
|
||||
if self.named.len() > 1 {
|
||||
one_liner.push_str("{flags} ");
|
||||
}
|
||||
|
||||
for positional in &self.required_positional {
|
||||
one_liner.push_str(&get_positional_short_name(positional, true));
|
||||
}
|
||||
@ -286,18 +294,14 @@ impl Signature {
|
||||
one_liner.push_str(&get_positional_short_name(positional, false));
|
||||
}
|
||||
|
||||
if self.rest_positional.is_some() {
|
||||
one_liner.push_str("...args ");
|
||||
if let Some(rest) = &self.rest_positional {
|
||||
one_liner.push_str(&format!("...{}", get_positional_short_name(rest, false)));
|
||||
}
|
||||
|
||||
// if !self.subcommands.is_empty() {
|
||||
// one_liner.push_str("<subcommand> ");
|
||||
// }
|
||||
|
||||
if !self.named.is_empty() {
|
||||
one_liner.push_str("{flags} ");
|
||||
}
|
||||
|
||||
one_liner
|
||||
}
|
||||
|
||||
|
123
src/eval_file.rs
123
src/eval_file.rs
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
219
src/main.rs
219
src/main.rs
@ -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,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user