Move script to nu engine (#3092)

* Move run_script to engine

* Add which dep and feature to engine

* Change unwrap to expect

* Add wasm specification

* Remove which from default, add specification correctly

* Add nu-platform-specifics

* Move is_external_cmd to platform_specifics

* Add is_external_cmd to host and use it instead of nu_platform directly

* Clean up if else logic in is_external_cmd

* Bump nu-platform-specifics version

* Pass context to print_err

* Commit cargo.lock

* Move print functions to own module inside nu-engine

* Hypocratic change to run windows-nightly again

* Add import for Ordering

* Move printing of error to host

* Move platform specific which functionality to basic host

* Allow no use of cmd_name

* Fix windows compile issue
This commit is contained in:
Leonhard Kipp
2021-03-12 06:20:54 +01:00
committed by GitHub
parent 86a89404be
commit 6cf8df8685
18 changed files with 576 additions and 559 deletions

View File

@ -20,7 +20,6 @@ pub(crate) mod chart;
pub(crate) mod classified;
#[cfg(feature = "clipboard-cli")]
pub(crate) mod clip;
pub mod command;
pub(crate) mod compact;
pub(crate) mod config;
pub(crate) mod constants;

View File

@ -28,7 +28,7 @@ pub(crate) async fn run_external_command(
) -> Result<InputStream, ShellError> {
trace!(target: "nu::run::external", "-> {}", command.name);
if !did_find_command(&command.name) {
if !context.host.lock().is_external_cmd(&command.name) {
return Err(ShellError::labeled_error(
"Command not found",
"command not found",
@ -443,35 +443,6 @@ fn spawn(
}
}
pub fn did_find_command(#[allow(unused)] name: &str) -> bool {
#[cfg(not(feature = "which"))]
{
// we can't perform this check, so just assume it can be found
true
}
#[cfg(all(feature = "which", unix))]
{
which::which(name).is_ok()
}
#[cfg(all(feature = "which", windows))]
{
if which::which(name).is_ok() {
true
} else {
// Reference: https://ss64.com/nt/syntax-internal.html
let cmd_builtins = [
"assoc", "break", "color", "copy", "date", "del", "dir", "dpath", "echo", "erase",
"for", "ftype", "md", "mkdir", "mklink", "move", "path", "ren", "rename", "rd",
"rmdir", "start", "time", "title", "type", "ver", "verify", "vol",
];
cmd_builtins.contains(&name)
}
}
}
fn expand_tilde<SI: ?Sized, P, HD>(input: &SI, home_dir: HD) -> std::borrow::Cow<str>
where
SI: AsRef<str>,

View File

@ -1,21 +0,0 @@
use crate::prelude::*;
use nu_engine::Command;
use nu_errors::ShellError;
use parking_lot::Mutex;
use std::sync::atomic::AtomicBool;
pub struct RunnableContext {
pub input: InputStream,
pub shell_manager: ShellManager,
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub scope: Scope,
pub name: Tag,
}
impl RunnableContext {
pub fn get_command(&self, name: &str) -> Option<Command> {
self.scope.get_command(name)
}
}

View File

@ -147,7 +147,7 @@ async fn maybe_autocd_dir<'a>(
|| (cmd.args.is_empty()
&& PathBuf::from(name).is_dir()
&& dunce::canonicalize(name).is_ok()
&& !crate::commands::classified::external::did_find_command(&name))
&& !ctx.host.lock().is_external_cmd(&name))
{
Some(name)
} else {

View File

@ -1,5 +1,5 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_engine::{script, WholeStreamCommand};
use nu_errors::ShellError;
use nu_parser::expand_path;
@ -50,7 +50,7 @@ pub async fn source(args: CommandArgs) -> Result<OutputStream, ShellError> {
let contents = std::fs::read_to_string(expand_path(&filename.item).into_owned());
match contents {
Ok(contents) => {
let result = crate::script::run_script_standalone(contents, true, &ctx, false).await;
let result = script::run_script_standalone(contents, true, &ctx, false).await;
if let Err(err) = result {
ctx.error(err.into());

View File

@ -7,7 +7,9 @@ use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
use nu_table::{draw_table, Alignment, StyledString, TextStyle};
use std::collections::HashMap;
use std::sync::atomic::Ordering;
use std::time::Instant;
#[cfg(feature = "table-pager")]
use {
futures::future::join,

View File

@ -8,15 +8,11 @@ extern crate indexmap;
mod prelude;
pub mod commands;
mod futures;
pub mod maybe_print_errors;
pub mod script;
pub mod utils;
#[cfg(test)]
mod examples;
pub use crate::maybe_print_errors::maybe_print_errors;
pub use nu_data::config;
pub use nu_data::dict::TaggedListBuilder;
pub use nu_data::primitive;

View File

@ -1,17 +0,0 @@
use nu_engine::EvaluationContext;
use nu_source::Text;
pub fn maybe_print_errors(context: &EvaluationContext, source: Text) -> bool {
let errors = context.current_errors.clone();
let mut errors = errors.lock();
if errors.len() > 0 {
let error = errors[0].clone();
*errors = vec![];
crate::script::print_err(error, &source, context);
true
} else {
false
}
}

View File

@ -46,7 +46,6 @@ macro_rules! trace_out_stream {
}};
}
pub(crate) use crate::commands::command::RunnableContext;
pub(crate) use async_trait::async_trait;
pub(crate) use bigdecimal::BigDecimal;
pub(crate) use futures::{Stream, StreamExt};
@ -58,11 +57,12 @@ pub(crate) use nu_engine::EvaluationContext;
pub(crate) use nu_engine::Example;
pub(crate) use nu_engine::Host;
pub(crate) use nu_engine::RawCommandArgs;
pub(crate) use nu_engine::RunnableContext;
pub(crate) use nu_engine::ShellManager;
pub(crate) use nu_engine::{get_full_help, CommandArgs, Scope, WholeStreamCommand};
pub(crate) use nu_parser::ParserScope;
pub(crate) use nu_protocol::{out, row};
pub(crate) use nu_source::{AnchorLocation, PrettyDebug, Span, SpannedItem, Tag, TaggedItem, Text};
pub(crate) use nu_source::{AnchorLocation, PrettyDebug, Span, SpannedItem, Tag, TaggedItem};
pub(crate) use nu_stream::ToInputStream;
pub(crate) use nu_stream::{InputStream, Interruptible, OutputStream};
pub(crate) use nu_value_ext::ValueExt;
@ -71,7 +71,7 @@ pub(crate) use num_traits::cast::ToPrimitive;
pub(crate) use serde::Deserialize;
pub(crate) use std::collections::VecDeque;
pub(crate) use std::future::Future;
pub(crate) use std::sync::atomic::{AtomicBool, Ordering};
pub(crate) use std::sync::atomic::AtomicBool;
pub(crate) use std::sync::Arc;
#[allow(clippy::wrong_self_convention)]

View File

@ -1,272 +0,0 @@
use crate::maybe_print_errors;
use crate::prelude::*;
use futures_codec::FramedRead;
use nu_engine::path::canonicalize;
use nu_engine::run_block;
use nu_engine::EvaluationContext;
use nu_engine::{MaybeTextCodec, StringOrBinary};
use nu_errors::ShellError;
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
use nu_stream::ToInputStream;
use log::{debug, trace};
use std::error::Error;
use std::iter::Iterator;
use std::path::Path;
#[derive(Debug)]
pub enum LineResult {
Success(String),
Error(String, ShellError),
Break,
CtrlC,
CtrlD,
ClearHistory,
}
fn chomp_newline(s: &str) -> &str {
if let Some(s) = s.strip_suffix('\n') {
s
} else {
s
}
}
pub fn print_err(err: ShellError, source: &Text, ctx: &EvaluationContext) {
if let Some(diag) = err.into_diagnostic() {
let source = source.to_string();
let mut files = codespan_reporting::files::SimpleFiles::new();
files.add("shell", source);
let writer = ctx.host.lock().err_termcolor();
let config = codespan_reporting::term::Config::default();
let _ = std::panic::catch_unwind(move || {
let _ = codespan_reporting::term::emit(&mut writer.lock(), &config, &files, &diag);
});
}
}
/// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline
pub async fn process_script(
script_text: &str,
ctx: &EvaluationContext,
redirect_stdin: bool,
span_offset: usize,
cli_mode: bool,
) -> LineResult {
if script_text.trim() == "" {
LineResult::Success(script_text.to_string())
} else {
let line = chomp_newline(script_text);
let (block, err) = nu_parser::parse(&line, span_offset, &ctx.scope);
debug!("{:#?}", block);
//println!("{:#?}", pipeline);
if let Some(failure) = err {
return LineResult::Error(line.to_string(), failure.into());
}
// There's a special case to check before we process the pipeline:
// If we're giving a path by itself
// ...and it's not a command in the path
// ...and it doesn't have any arguments
// ...and we're in the CLI
// ...then change to this directory
if cli_mode
&& block.block.len() == 1
&& block.block[0].pipelines.len() == 1
&& block.block[0].pipelines[0].list.len() == 1
{
if let ClassifiedCommand::Internal(InternalCommand {
ref name, ref args, ..
}) = block.block[0].pipelines[0].list[0]
{
let internal_name = name;
let name = args
.positional
.as_ref()
.and_then(|positionals| {
positionals.get(0).map(|e| {
if let Expression::Literal(Literal::String(ref s)) = e.expr {
&s
} else {
""
}
})
})
.unwrap_or("");
if internal_name == "run_external"
&& args
.positional
.as_ref()
.map(|ref v| v.len() == 1)
.unwrap_or(true)
&& args
.named
.as_ref()
.map(NamedArguments::is_empty)
.unwrap_or(true)
&& canonicalize(ctx.shell_manager.path(), name).is_ok()
&& Path::new(&name).is_dir()
&& !crate::commands::classified::external::did_find_command(&name)
{
// Here we work differently if we're in Windows because of the expected Windows behavior
#[cfg(windows)]
{
if name.ends_with(':') {
// This looks like a drive shortcut. We need to a) switch drives and b) go back to the previous directory we were viewing on that drive
// But first, we need to save where we are now
let current_path = ctx.shell_manager.path();
let split_path: Vec<_> = current_path.split(':').collect();
if split_path.len() > 1 {
ctx.windows_drives_previous_cwd
.lock()
.insert(split_path[0].to_string(), current_path);
}
let name = name.to_uppercase();
let new_drive: Vec<_> = name.split(':').collect();
if let Some(val) =
ctx.windows_drives_previous_cwd.lock().get(new_drive[0])
{
ctx.shell_manager.set_path(val.to_string());
return LineResult::Success(line.to_string());
} else {
ctx.shell_manager
.set_path(format!("{}\\", name.to_string()));
return LineResult::Success(line.to_string());
}
} else {
ctx.shell_manager.set_path(name.to_string());
return LineResult::Success(line.to_string());
}
}
#[cfg(not(windows))]
{
ctx.shell_manager.set_path(name.to_string());
return LineResult::Success(line.to_string());
}
}
}
}
let input_stream = if redirect_stdin {
let file = futures::io::AllowStdIo::new(std::io::stdin());
let stream = FramedRead::new(file, MaybeTextCodec::default()).map(|line| {
if let Ok(line) = line {
let primitive = match line {
StringOrBinary::String(s) => Primitive::String(s),
StringOrBinary::Binary(b) => Primitive::Binary(b.into_iter().collect()),
};
Ok(Value {
value: UntaggedValue::Primitive(primitive),
tag: Tag::unknown(),
})
} else {
panic!("Internal error: could not read lines of text from stdin")
}
});
stream.to_input_stream()
} else {
InputStream::empty()
};
trace!("{:#?}", block);
let env = ctx.get_env();
ctx.scope.add_env_to_base(env);
let result = run_block(&block, ctx, input_stream).await;
match result {
Ok(input) => {
// Running a pipeline gives us back a stream that we can then
// work through. At the top level, we just want to pull on the
// values to compute them.
use futures::stream::TryStreamExt;
let context = RunnableContext {
input,
shell_manager: ctx.shell_manager.clone(),
host: ctx.host.clone(),
ctrl_c: ctx.ctrl_c.clone(),
current_errors: ctx.current_errors.clone(),
scope: ctx.scope.clone(),
name: Tag::unknown(),
};
if let Ok(mut output_stream) =
crate::commands::autoview::command::autoview(context).await
{
loop {
match output_stream.try_next().await {
Ok(Some(ReturnSuccess::Value(Value {
value: UntaggedValue::Error(e),
..
}))) => return LineResult::Error(line.to_string(), e),
Ok(Some(_item)) => {
if ctx.ctrl_c.load(Ordering::SeqCst) {
break;
}
}
Ok(None) => break,
Err(e) => return LineResult::Error(line.to_string(), e),
}
}
}
LineResult::Success(line.to_string())
}
Err(err) => LineResult::Error(line.to_string(), err),
}
}
}
pub async fn run_script_standalone(
script_text: String,
redirect_stdin: bool,
context: &EvaluationContext,
exit_on_error: bool,
) -> Result<(), Box<dyn Error>> {
let line = process_script(&script_text, context, redirect_stdin, 0, false).await;
match line {
LineResult::Success(line) => {
let error_code = {
let errors = context.current_errors.clone();
let errors = errors.lock();
if errors.len() > 0 {
1
} else {
0
}
};
maybe_print_errors(&context, Text::from(line));
if error_code != 0 && exit_on_error {
std::process::exit(error_code);
}
}
LineResult::Error(line, err) => {
print_err(err, &Text::from(line.clone()), &context);
maybe_print_errors(&context, Text::from(line));
if exit_on_error {
std::process::exit(1);
}
}
_ => {}
}
Ok(())
}