Make Nu bootstrap itself from main. (#3619)

We've relied on `clap` for building our cli app bootstrapping that figures out the positionals, flags, and other convenient facilities. Nu has been capable of solving this problem for quite some time. Given this and much more reasons (including the build time caused by `clap`) we start here working with our own.
This commit is contained in:
Andrés N. Robalino 2021-06-15 17:43:25 -05:00 committed by GitHub
parent ec96e85d04
commit 7c7e5112ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 852 additions and 272 deletions

10
Cargo.lock generated
View File

@ -1626,9 +1626,9 @@ dependencies = [
[[package]]
name = "env_logger"
version = "0.8.3"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f"
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
dependencies = [
"log 0.4.14",
"regex 1.5.4",
@ -3226,13 +3226,11 @@ dependencies = [
name = "nu"
version = "0.32.1"
dependencies = [
"clap",
"ctrlc",
"dunce",
"futures 0.3.15",
"hamcrest2",
"itertools",
"log 0.4.14",
"nu-cli",
"nu-command",
"nu-data",
@ -3263,7 +3261,6 @@ dependencies = [
"nu_plugin_to_sqlite",
"nu_plugin_tree",
"nu_plugin_xpath",
"pretty_env_logger",
"serial_test",
]
@ -3340,6 +3337,7 @@ dependencies = [
"num-traits 0.2.14",
"parking_lot 0.11.1",
"pin-utils",
"pretty_env_logger",
"ptree",
"query_interface",
"quick-xml 0.21.0",
@ -4860,7 +4858,7 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
dependencies = [
"env_logger 0.8.3",
"env_logger 0.8.4",
"log 0.4.14",
"rand 0.8.3",
]

View File

@ -50,12 +50,9 @@ nu_plugin_tree = { version = "0.32.1", path = "./crates/nu_plugin_tree", optiona
nu_plugin_xpath = { version = "0.32.1", path = "./crates/nu_plugin_xpath", optional = true }
# Required to bootstrap the main binary
clap = "2.33.3"
ctrlc = { version = "3.1.7", optional = true }
futures = { version = "0.3.12", features = ["compat", "io-compat"] }
itertools = "0.10.0"
log = "0.4.14"
pretty_env_logger = "0.4.0"
[dev-dependencies]
nu-test-support = { version = "0.32.1", path = "./crates/nu-test-support" }

View File

@ -62,6 +62,7 @@ indexmap = { version = "1.6.1", features = ["serde-1"] }
itertools = "0.10.0"
lazy_static = "1.*"
log = "0.4.14"
pretty_env_logger = "0.4.0"
meval = "0.2.0"
num-bigint = { version = "0.3.1", features = ["serde"] }
num-format = { version = "0.4.0", features = ["with-num-bigint"] }

465
crates/nu-cli/src/app.rs Normal file
View File

@ -0,0 +1,465 @@
mod logger;
mod options;
mod options_parser;
pub use options::{CliOptions, NuScript, Options};
use options_parser::{NuParser, OptionsParser};
use nu_command::{commands::nu::Nu, utils::test_bins as binaries};
use nu_engine::get_full_help;
use nu_errors::ShellError;
use nu_protocol::hir::{Call, Expression, SpannedExpression};
use nu_protocol::{Primitive, UntaggedValue};
use nu_source::{Span, Tag};
pub struct App {
parser: Box<dyn OptionsParser>,
pub options: Options,
}
impl App {
pub fn new(parser: Box<dyn OptionsParser>, options: Options) -> Self {
Self { parser, options }
}
pub fn run(args: &[String]) -> Result<(), ShellError> {
let nu = Box::new(NuParser::new());
let options = Options::default();
let ui = App::new(nu, options);
ui.main(args)
}
pub fn main(&self, argv: &[String]) -> Result<(), ShellError> {
let argv = quote_positionals(argv).join(" ");
if let Err(cause) = self.parse(&argv) {
self.parser
.context()
.host()
.lock()
.print_err(cause, &nu_source::Text::from(argv));
std::process::exit(1);
}
if self.help() {
let ctx = self.parser.context();
let autoview_cmd = ctx
.get_command("autoview")
.expect("could not find autoview command");
if let Ok(output_stream) = ctx.run_command(
autoview_cmd,
Tag::unknown(),
Call::new(
Box::new(SpannedExpression::new(
Expression::string("autoview".to_string()),
Span::unknown(),
)),
Span::unknown(),
),
nu_stream::OutputStream::one(
UntaggedValue::string(get_full_help(&Nu, &ctx.scope))
.into_value(nu_source::Tag::unknown()),
),
) {
for _ in output_stream {}
}
std::process::exit(0);
}
if let Some(bin) = self.testbin() {
match bin.as_deref() {
Ok("echo_env") => binaries::echo_env(),
Ok("cococo") => binaries::cococo(),
Ok("meow") => binaries::meow(),
Ok("iecho") => binaries::iecho(),
Ok("fail") => binaries::fail(),
Ok("nonu") => binaries::nonu(),
Ok("chop") => binaries::chop(),
Ok("repeater") => binaries::repeater(),
_ => unreachable!(),
}
return Ok(());
}
let mut opts = CliOptions::new();
opts.config = self.config().map(std::ffi::OsString::from);
opts.stdin = self.takes_stdin();
opts.save_history = self.save_history();
use logger::{configure, debug_filters, logger, trace_filters};
logger(|builder| {
configure(&self, builder)?;
trace_filters(&self, builder)?;
debug_filters(&self, builder)?;
Ok(())
})?;
if let Some(commands) = self.commands() {
let commands = commands?;
let script = NuScript::code(&commands)?;
opts.scripts = vec![script];
let context = crate::create_default_context(false)?;
return crate::run_script_file(context, opts);
}
if let Some(scripts) = self.scripts() {
opts.scripts = scripts
.into_iter()
.filter_map(Result::ok)
.map(|path| {
let path = std::ffi::OsString::from(path);
NuScript::source_file(path.as_os_str())
})
.filter_map(Result::ok)
.collect();
let context = crate::create_default_context(false)?;
return crate::run_script_file(context, opts);
}
let context = crate::create_default_context(true)?;
if !self.skip_plugins() {
let _ = crate::register_plugins(&context);
}
#[cfg(feature = "rustyline-support")]
{
crate::cli(context, opts)?;
}
#[cfg(not(feature = "rustyline-support"))]
{
println!("Nushell needs the 'rustyline-support' feature for CLI support");
}
Ok(())
}
pub fn commands(&self) -> Option<Result<String, ShellError>> {
self.options.get("commands").map(|v| match v.value {
UntaggedValue::Error(err) => Err(err),
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
})
}
pub fn help(&self) -> bool {
let help_asked = self
.options
.get("args")
.map(|v| {
v.table_entries().next().map(|v| {
if let Ok(value) = v.as_string() {
value == "help"
} else {
false
}
})
})
.flatten()
.unwrap_or(false);
if help_asked {
self.options.shift();
return true;
}
false
}
pub fn scripts(&self) -> Option<Vec<Result<String, ShellError>>> {
self.options.get("args").map(|v| {
v.table_entries()
.map(|v| match &v.value {
UntaggedValue::Error(err) => Err(err.clone()),
UntaggedValue::Primitive(Primitive::FilePath(path)) => {
Ok(path.display().to_string())
}
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name.clone()),
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
})
.collect()
})
}
pub fn takes_stdin(&self) -> bool {
self.options
.get("stdin")
.map(|v| matches!(v.as_bool(), Ok(true)))
.unwrap_or(false)
}
pub fn config(&self) -> Option<String> {
self.options
.get("config-file")
.map(|v| v.as_string().expect("not a string"))
}
pub fn develop(&self) -> Option<Vec<Result<String, ShellError>>> {
self.options.get("develop").map(|v| {
let mut values = vec![];
match v.value {
UntaggedValue::Error(err) => values.push(Err(err)),
UntaggedValue::Primitive(Primitive::String(filters)) => {
values.extend(filters.split(',').map(|filter| Ok(filter.to_string())));
}
_ => values.push(Err(ShellError::untagged_runtime_error(
"Unsupported option",
))),
};
values
})
}
pub fn debug(&self) -> Option<Vec<Result<String, ShellError>>> {
self.options.get("debug").map(|v| {
let mut values = vec![];
match v.value {
UntaggedValue::Error(err) => values.push(Err(err)),
UntaggedValue::Primitive(Primitive::String(filters)) => {
values.extend(filters.split(',').map(|filter| Ok(filter.to_string())));
}
_ => values.push(Err(ShellError::untagged_runtime_error(
"Unsupported option",
))),
};
values
})
}
pub fn loglevel(&self) -> Option<Result<String, ShellError>> {
self.options.get("loglevel").map(|v| match v.value {
UntaggedValue::Error(err) => Err(err),
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
})
}
pub fn testbin(&self) -> Option<Result<String, ShellError>> {
self.options.get("testbin").map(|v| match v.value {
UntaggedValue::Error(err) => Err(err),
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
})
}
pub fn skip_plugins(&self) -> bool {
self.options
.get("skip-plugins")
.map(|v| matches!(v.as_bool(), Ok(true)))
.unwrap_or(false)
}
pub fn save_history(&self) -> bool {
self.options
.get("no-history")
.map(|v| !matches!(v.as_bool(), Ok(true)))
.unwrap_or(true)
}
pub fn parse(&self, args: &str) -> Result<(), ShellError> {
self.parser.parse(&args).map(|options| {
self.options.swap(&options);
})
}
}
fn quote_positionals(parameters: &[String]) -> Vec<String> {
parameters
.iter()
.cloned()
.map(|arg| {
if arg.contains(' ') {
format!("\"{}\"", arg)
} else {
arg
}
})
.collect::<Vec<_>>()
}
#[cfg(test)]
mod tests {
use super::*;
fn cli_app() -> App {
let parser = Box::new(NuParser::new());
let options = Options::default();
App::new(parser, options)
}
#[test]
fn default_options() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu")?;
assert_eq!(ui.help(), false);
assert_eq!(ui.takes_stdin(), false);
assert_eq!(ui.save_history(), true);
assert_eq!(ui.skip_plugins(), false);
assert_eq!(ui.config(), None);
assert_eq!(ui.loglevel(), None);
assert_eq!(ui.debug(), None);
assert_eq!(ui.develop(), None);
assert_eq!(ui.testbin(), None);
assert_eq!(ui.commands(), None);
assert_eq!(ui.scripts(), None);
Ok(())
}
#[test]
fn reports_errors_on_unsupported_flags() -> Result<(), ShellError> {
let ui = cli_app();
assert!(ui.parse("nu --coonfig-file /path/to/config.toml").is_err());
assert!(ui.config().is_none());
Ok(())
}
#[test]
fn configures_debug_trace_level_with_filters() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu --develop=cli,parser")?;
assert_eq!(ui.develop().unwrap()[0], Ok("cli".to_string()));
assert_eq!(ui.develop().unwrap()[1], Ok("parser".to_string()));
Ok(())
}
#[test]
fn configures_debug_level_with_filters() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu --debug=cli,run")?;
assert_eq!(ui.debug().unwrap()[0], Ok("cli".to_string()));
assert_eq!(ui.debug().unwrap()[1], Ok("run".to_string()));
Ok(())
}
#[test]
fn can_use_loglevels() -> Result<(), ShellError> {
for level in &["error", "warn", "info", "debug", "trace"] {
let ui = cli_app();
let args = format!("nu --loglevel={}", *level);
ui.parse(&args)?;
assert_eq!(ui.loglevel().unwrap(), Ok(level.to_string()));
let ui = cli_app();
let args = format!("nu -l {}", *level);
ui.parse(&args)?;
assert_eq!(ui.loglevel().unwrap(), Ok(level.to_string()));
}
let ui = cli_app();
ui.parse("nu --loglevel=nada")?;
assert_eq!(
ui.loglevel().unwrap(),
Err(ShellError::untagged_runtime_error("nada is not supported."))
);
Ok(())
}
#[test]
fn can_be_passed_nu_scripts() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu code.nu bootstrap.nu")?;
assert_eq!(ui.scripts().unwrap()[0], Ok("code.nu".into()));
assert_eq!(ui.scripts().unwrap()[1], Ok("bootstrap.nu".into()));
Ok(())
}
#[test]
fn can_use_test_binaries() -> Result<(), ShellError> {
for binarie_name in &[
"echo_env", "cococo", "iecho", "fail", "nonu", "chop", "repeater", "meow",
] {
let ui = cli_app();
let args = format!("nu --testbin={}", *binarie_name);
ui.parse(&args)?;
assert_eq!(ui.testbin().unwrap(), Ok(binarie_name.to_string()));
}
let ui = cli_app();
ui.parse("nu --testbin=andres")?;
assert_eq!(
ui.testbin().unwrap(),
Err(ShellError::untagged_runtime_error(
"andres is not supported."
))
);
Ok(())
}
#[test]
fn has_help() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu help")?;
assert_eq!(ui.help(), true);
Ok(())
}
#[test]
fn can_take_stdin() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu --stdin")?;
assert_eq!(ui.takes_stdin(), true);
Ok(())
}
#[test]
fn can_opt_to_avoid_saving_history() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu --no-history")?;
assert_eq!(ui.save_history(), false);
Ok(())
}
#[test]
fn can_opt_to_skip_plugins() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu --skip-plugins")?;
assert_eq!(ui.skip_plugins(), true);
Ok(())
}
#[test]
fn understands_commands_need_to_be_run() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu -c \"ls | get name\"")?;
assert_eq!(ui.commands().unwrap(), Ok(String::from("ls | get name")));
let ui = cli_app();
ui.parse("nu -c \"echo 'hola'\"")?;
assert_eq!(ui.commands().unwrap(), Ok(String::from("echo 'hola'")));
Ok(())
}
#[test]
fn knows_custom_configurations() -> Result<(), ShellError> {
let ui = cli_app();
ui.parse("nu --config-file /path/to/config.toml")?;
assert_eq!(ui.config().unwrap(), String::from("/path/to/config.toml"));
Ok(())
}
}

View File

@ -0,0 +1,52 @@
use super::App;
use log::LevelFilter;
use nu_errors::ShellError;
use pretty_env_logger::env_logger::Builder;
pub fn logger(f: impl FnOnce(&mut Builder) -> Result<(), ShellError>) -> Result<(), ShellError> {
let mut builder = pretty_env_logger::formatted_builder();
f(&mut builder)?;
let _ = builder.try_init();
Ok(())
}
pub fn configure(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
if let Some(level) = app.loglevel() {
let level = match level.as_deref() {
Ok("error") => LevelFilter::Error,
Ok("warn") => LevelFilter::Warn,
Ok("info") => LevelFilter::Info,
Ok("debug") => LevelFilter::Debug,
Ok("trace") => LevelFilter::Trace,
Ok(_) | Err(_) => LevelFilter::Warn,
};
logger.filter_module("nu", level);
};
if let Ok(s) = std::env::var("RUST_LOG") {
logger.parse_filters(&s);
}
Ok(())
}
pub fn trace_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
if let Some(filters) = app.develop() {
filters.into_iter().filter_map(Result::ok).for_each(|name| {
logger.filter_module(&name, LevelFilter::Trace);
})
}
Ok(())
}
pub fn debug_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
if let Some(filters) = app.debug() {
filters.into_iter().filter_map(Result::ok).for_each(|name| {
logger.filter_module(&name, LevelFilter::Debug);
})
}
Ok(())
}

View File

@ -0,0 +1,100 @@
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
use std::cell::RefCell;
use std::ffi::{OsStr, OsString};
#[derive(Debug)]
pub struct CliOptions {
pub config: Option<OsString>,
pub stdin: bool,
pub scripts: Vec<NuScript>,
pub save_history: bool,
}
impl Default for CliOptions {
fn default() -> Self {
Self::new()
}
}
impl CliOptions {
pub fn new() -> Self {
Self {
config: None,
stdin: false,
scripts: vec![],
save_history: true,
}
}
}
#[derive(Debug)]
pub struct Options {
inner: RefCell<IndexMap<String, Value>>,
}
impl Options {
pub fn default() -> Self {
Self {
inner: RefCell::new(IndexMap::default()),
}
}
pub fn get(&self, key: &str) -> Option<Value> {
self.inner.borrow().get(key).map(Clone::clone)
}
pub fn put(&self, key: &str, value: Value) {
self.inner.borrow_mut().insert(key.into(), value);
}
pub fn shift(&self) {
if let Some(Value {
value: UntaggedValue::Table(ref mut args),
..
}) = self.inner.borrow_mut().get_mut("args")
{
args.remove(0);
}
}
pub fn swap(&self, other: &Options) {
self.inner.swap(&other.inner);
}
}
#[derive(Debug)]
pub struct NuScript {
pub filepath: Option<OsString>,
pub contents: String,
}
impl NuScript {
pub fn code(content: &str) -> Result<Self, ShellError> {
Ok(Self {
filepath: None,
contents: content.to_string(),
})
}
pub fn get_code(&self) -> &str {
&self.contents
}
pub fn source_file(path: &OsStr) -> Result<Self, ShellError> {
use std::fs::File;
use std::io::Read;
let path = path.to_os_string();
let mut file = File::open(&path)?;
let mut buffer = String::new();
file.read_to_string(&mut buffer)?;
Ok(Self {
filepath: Some(path),
contents: buffer,
})
}
}

View File

@ -0,0 +1,140 @@
use super::Options;
use nu_command::commands::nu::{self, Nu};
use nu_command::commands::Autoview;
use nu_engine::{whole_stream_command, EvaluationContext};
use nu_errors::ShellError;
use nu_protocol::hir::{ClassifiedCommand, InternalCommand, NamedValue};
use nu_protocol::UntaggedValue;
use nu_source::Tag;
pub struct NuParser {
context: EvaluationContext,
}
pub trait OptionsParser {
fn parse(&self, input: &str) -> Result<Options, ShellError>;
fn context(&self) -> &EvaluationContext;
}
impl NuParser {
pub fn new() -> Self {
let context = EvaluationContext::basic();
context.add_commands(vec![
whole_stream_command(Nu {}),
whole_stream_command(Autoview {}),
]);
Self { context }
}
}
impl OptionsParser for NuParser {
fn context(&self) -> &EvaluationContext {
&self.context
}
fn parse(&self, input: &str) -> Result<Options, ShellError> {
let options = Options::default();
let (lite_result, _err) = nu_parser::lex(input, 0);
let (lite_result, _err) = nu_parser::parse_block(lite_result);
let (parsed, err) = nu_parser::classify_block(&lite_result, &self.context.scope);
if let Some(reason) = err {
return Err(reason.into());
}
match parsed.block[0].pipelines[0].list[0] {
ClassifiedCommand::Internal(InternalCommand { ref args, .. }) => {
if let Some(ref params) = args.named {
params.iter().for_each(|(k, v)| {
let value = match v {
NamedValue::AbsentSwitch => {
Some(UntaggedValue::from(false).into_untagged_value())
}
NamedValue::PresentSwitch(span) => {
Some(UntaggedValue::from(true).into_value(Tag::from(span)))
}
NamedValue::AbsentValue => None,
NamedValue::Value(span, exprs) => {
let value = nu_engine::evaluate_baseline_expr(exprs, &self.context)
.expect("value");
Some(value.value.into_value(Tag::from(span)))
}
};
let value =
value
.map(|v| match k.as_ref() {
"testbin" => {
if let Ok(name) = v.as_string() {
if nu::testbins().iter().any(|n| name == *n) {
Some(v)
} else {
Some(
UntaggedValue::Error(
ShellError::untagged_runtime_error(
format!("{} is not supported.", name),
),
)
.into_value(v.tag),
)
}
} else {
Some(v)
}
}
"loglevel" => {
if let Ok(name) = v.as_string() {
if nu::loglevels().iter().any(|n| name == *n) {
Some(v)
} else {
Some(
UntaggedValue::Error(
ShellError::untagged_runtime_error(
format!("{} is not supported.", name),
),
)
.into_value(v.tag),
)
}
} else {
Some(v)
}
}
_ => Some(v),
})
.flatten();
if let Some(value) = value {
options.put(&k, value);
}
});
}
let mut positional_args = vec![];
if let Some(positional) = &args.positional {
for pos in positional {
let result = nu_engine::evaluate_baseline_expr(pos, &self.context)?;
positional_args.push(result);
}
}
if !positional_args.is_empty() {
options.put(
"args",
UntaggedValue::Table(positional_args).into_untagged_value(),
);
}
}
ClassifiedCommand::Error(ref reason) => {
return Err(reason.clone().into());
}
_ => return Err(ShellError::untagged_runtime_error("unrecognized command")),
}
Ok(options)
}
}

View File

@ -15,7 +15,6 @@ use crate::line_editor::{
use nu_data::config;
use nu_source::{Tag, Text};
use nu_stream::InputStream;
use std::ffi::{OsStr, OsString};
#[allow(unused_imports)]
use std::sync::atomic::Ordering;
@ -31,69 +30,6 @@ use std::error::Error;
use std::iter::Iterator;
use std::path::PathBuf;
pub struct Options {
pub config: Option<OsString>,
pub stdin: bool,
pub scripts: Vec<NuScript>,
pub save_history: bool,
}
impl Default for Options {
fn default() -> Self {
Self::new()
}
}
impl Options {
pub fn new() -> Self {
Self {
config: None,
stdin: false,
scripts: vec![],
save_history: true,
}
}
}
pub struct NuScript {
pub filepath: Option<OsString>,
pub contents: String,
}
impl NuScript {
pub fn code<'a>(content: impl Iterator<Item = &'a str>) -> Result<Self, ShellError> {
let text = content
.map(|x| x.to_string())
.collect::<Vec<String>>()
.join("\n");
Ok(Self {
filepath: None,
contents: text,
})
}
pub fn get_code(&self) -> &str {
&self.contents
}
pub fn source_file(path: &OsStr) -> Result<Self, ShellError> {
use std::fs::File;
use std::io::Read;
let path = path.to_os_string();
let mut file = File::open(&path)?;
let mut buffer = String::new();
file.read_to_string(&mut buffer)?;
Ok(Self {
filepath: Some(path),
contents: buffer,
})
}
}
pub fn search_paths() -> Vec<std::path::PathBuf> {
use std::env;
@ -123,7 +59,10 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
search_paths
}
pub fn run_script_file(context: EvaluationContext, options: Options) -> Result<(), Box<dyn Error>> {
pub fn run_script_file(
context: EvaluationContext,
options: super::app::CliOptions,
) -> Result<(), ShellError> {
if let Some(cfg) = options.config {
load_cfg_as_global_cfg(&context, PathBuf::from(cfg));
} else {
@ -144,7 +83,10 @@ pub fn run_script_file(context: EvaluationContext, options: Options) -> Result<(
}
#[cfg(feature = "rustyline-support")]
pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn Error>> {
pub fn cli(
context: EvaluationContext,
options: super::app::CliOptions,
) -> Result<(), Box<dyn Error>> {
let _ = configure_ctrl_c(&context);
// start time for running startup scripts (this metric includes loading of the cfg, but w/e)

View File

@ -9,6 +9,7 @@ extern crate quickcheck;
#[macro_use(quickcheck)]
extern crate quickcheck_macros;
mod app;
mod cli;
#[cfg(feature = "rustyline-support")]
mod completion;
@ -22,8 +23,8 @@ pub mod types;
#[cfg(feature = "rustyline-support")]
pub use crate::cli::cli;
pub use crate::app::App;
pub use crate::cli::{parse_and_eval, register_plugins, run_script_file};
pub use crate::cli::{NuScript, Options};
pub use nu_command::commands::default_context::create_default_context;
pub use nu_data::config;

View File

@ -86,7 +86,7 @@ pub(crate) mod mkdir;
pub(crate) mod move_;
pub(crate) mod next;
pub(crate) mod nth;
pub(crate) mod nu;
pub mod nu;
pub(crate) mod open;
pub(crate) mod parse;
pub(crate) mod path;
@ -140,7 +140,7 @@ pub(crate) mod which_;
pub(crate) mod with_env;
pub(crate) mod wrap;
pub(crate) use autoview::Autoview;
pub use autoview::Autoview;
pub(crate) use cd::Cd;
pub(crate) use alias::Alias;

View File

@ -0,0 +1,60 @@
use nu_engine::WholeStreamCommand;
use nu_protocol::{Signature, SyntaxShape};
pub struct Command;
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"nu"
}
fn signature(&self) -> Signature {
Signature::build("nu")
.switch("stdin", "stdin", None)
.switch("skip-plugins", "do not load plugins", None)
.switch("no-history", "don't save history", None)
.named("commands", SyntaxShape::String, "Nu commands", Some('c'))
.named(
"testbin",
SyntaxShape::String,
"BIN: echo_env, cococo, iecho, fail, nonu, chop, repeater, meow",
None,
)
.named("develop", SyntaxShape::String, "trace mode", None)
.named("debug", SyntaxShape::String, "debug mode", None)
.named(
"loglevel",
SyntaxShape::String,
"LEVEL: error, warn, info, debug, trace",
Some('l'),
)
.named(
"config-file",
SyntaxShape::FilePath,
"custom configuration source file",
None,
)
.optional("script", SyntaxShape::FilePath, "The Nu script to run")
.rest(SyntaxShape::String, "Left overs...")
}
fn usage(&self) -> &str {
"Nu"
}
}
pub fn testbins() -> Vec<String> {
vec![
"echo_env", "cococo", "iecho", "fail", "nonu", "chop", "repeater", "meow",
]
.into_iter()
.map(String::from)
.collect()
}
pub fn loglevels() -> Vec<String> {
vec!["error", "warn", "info", "debug", "trace"]
.into_iter()
.map(String::from)
.collect()
}

View File

@ -1,3 +1,6 @@
pub mod command;
mod plugin;
pub use command::Command as Nu;
pub use command::{loglevels, testbins};
pub use plugin::SubCommand as NuPlugin;

View File

@ -52,7 +52,7 @@ pub fn source(args: CommandArgs) -> Result<ActionStream, ShellError> {
let result = script::run_script_standalone(contents, true, &ctx, false);
if let Err(err) = result {
ctx.error(err.into());
ctx.error(err);
}
Ok(ActionStream::empty())
}

View File

@ -153,8 +153,7 @@ impl EvaluationContext {
}
}
#[allow(unused)]
pub(crate) fn get_command(&self, name: &str) -> Option<Command> {
pub fn get_command(&self, name: &str) -> Option<Command> {
self.scope.get_command(name)
}
@ -162,7 +161,7 @@ impl EvaluationContext {
self.scope.has_command(name)
}
pub(crate) fn run_command(
pub fn run_command(
&self,
command: Command,
name_tag: Tag,

View File

@ -265,11 +265,11 @@ pub fn run_script_standalone(
redirect_stdin: bool,
context: &EvaluationContext,
exit_on_error: bool,
) -> Result<(), Box<dyn Error>> {
) -> Result<(), ShellError> {
context
.shell_manager()
.enter_script_mode()
.map_err(Box::new)?;
.map_err(ShellError::from)?;
let line = process_script(&script_text, context, redirect_stdin, 0, false);
match line {

View File

@ -1,191 +1,13 @@
use clap::{App, Arg};
use log::LevelFilter;
use nu_cli::{create_default_context, NuScript, Options};
use nu_command::utils::test_bins as binaries;
use std::error::Error;
use nu_cli::App as CliApp;
use nu_errors::ShellError;
fn main() -> Result<(), Box<dyn Error>> {
let mut options = Options::new();
fn main() -> Result<(), ShellError> {
let mut argv = vec![String::from("nu")];
argv.extend(positionals());
let matches = App::new("nushell")
.version(clap::crate_version!())
.arg(
Arg::with_name("config-file")
.long("config-file")
.help("custom configuration source file")
.hidden(true)
.takes_value(true),
)
.arg(
Arg::with_name("no-history")
.hidden(true)
.long("no-history")
.multiple(false)
.takes_value(false),
)
.arg(
Arg::with_name("loglevel")
.short("l")
.long("loglevel")
.value_name("LEVEL")
.possible_values(&["error", "warn", "info", "debug", "trace"])
.takes_value(true),
)
.arg(
Arg::with_name("skip-plugins")
.hidden(true)
.long("skip-plugins")
.multiple(false)
.takes_value(false),
)
.arg(
Arg::with_name("testbin")
.hidden(true)
.long("testbin")
.value_name("TESTBIN")
.possible_values(&[
"echo_env", "cococo", "iecho", "fail", "nonu", "chop", "repeater", "meow",
])
.takes_value(true),
)
.arg(
Arg::with_name("commands")
.short("c")
.long("commands")
.multiple(false)
.takes_value(true),
)
.arg(
Arg::with_name("develop")
.long("develop")
.multiple(true)
.takes_value(true),
)
.arg(
Arg::with_name("debug")
.long("debug")
.multiple(true)
.takes_value(true),
)
.arg(
Arg::with_name("stdin")
.long("stdin")
.multiple(false)
.takes_value(false),
)
.arg(
Arg::with_name("script")
.help("the nu script to run")
.index(1),
)
.arg(
Arg::with_name("args")
.help("positional args (used by --testbin)")
.index(2)
.multiple(true),
)
.get_matches();
if let Some(bin) = matches.value_of("testbin") {
match bin {
"echo_env" => binaries::echo_env(),
"cococo" => binaries::cococo(),
"meow" => binaries::meow(),
"iecho" => binaries::iecho(),
"fail" => binaries::fail(),
"nonu" => binaries::nonu(),
"chop" => binaries::chop(),
"repeater" => binaries::repeater(),
_ => unreachable!(),
}
return Ok(());
}
options.config = matches
.value_of("config-file")
.map(std::ffi::OsString::from);
options.stdin = matches.is_present("stdin");
options.save_history = !matches.is_present("no-history");
let loglevel = match matches.value_of("loglevel") {
None => LevelFilter::Warn,
Some("error") => LevelFilter::Error,
Some("warn") => LevelFilter::Warn,
Some("info") => LevelFilter::Info,
Some("debug") => LevelFilter::Debug,
Some("trace") => LevelFilter::Trace,
_ => unreachable!(),
};
let mut builder = pretty_env_logger::formatted_builder();
if let Ok(s) = std::env::var("RUST_LOG") {
builder.parse_filters(&s);
}
builder.filter_module("nu", loglevel);
match matches.values_of("develop") {
None => {}
Some(values) => {
for item in values {
builder.filter_module(&format!("nu::{}", item), LevelFilter::Trace);
}
}
}
match matches.values_of("debug") {
None => {}
Some(values) => {
for item in values {
builder.filter_module(&format!("nu::{}", item), LevelFilter::Debug);
}
}
}
builder.try_init()?;
match matches.values_of("commands") {
None => {}
Some(values) => {
options.scripts = vec![NuScript::code(values)?];
let context = create_default_context(false)?;
nu_cli::run_script_file(context, options)?;
return Ok(());
}
}
match matches.value_of("script") {
Some(filepath) => {
let filepath = std::ffi::OsString::from(filepath);
options.scripts = vec![NuScript::source_file(filepath.as_os_str())?];
let context = create_default_context(false)?;
nu_cli::run_script_file(context, options)?;
return Ok(());
}
None => {
let context = create_default_context(true)?;
if !matches.is_present("skip-plugins") {
let _ = nu_cli::register_plugins(&context);
}
#[cfg(feature = "rustyline-support")]
{
nu_cli::cli(context, options)?;
}
#[cfg(not(feature = "rustyline-support"))]
{
println!("Nushell needs the 'rustyline-support' feature for CLI support");
}
}
}
Ok(())
CliApp::run(&argv)
}
fn positionals() -> Vec<String> {
std::env::args().skip(1).collect::<Vec<_>>()
}