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

@ -1,5 +1,7 @@
use crate::Host;
use nu_errors::ShellError;
use nu_protocol::{errln, outln};
use nu_source::Text;
use std::ffi::OsString;
#[derive(Debug)]
@ -20,6 +22,21 @@ impl Host for BasicHost {
}
}
fn print_err(&mut self, err: ShellError, source: &Text) {
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 = termcolor::StandardStream::stderr(termcolor::ColorChoice::Auto);
let config = codespan_reporting::term::Config::default();
let _ = std::panic::catch_unwind(move || {
let _ = codespan_reporting::term::emit(&mut writer.lock(), &config, &files, &diag);
});
}
}
#[allow(unused_variables)]
fn vars(&mut self) -> Vec<(String, String)> {
#[cfg(not(target_arch = "wasm32"))]
@ -61,14 +78,6 @@ impl Host for BasicHost {
}
}
fn out_termcolor(&self) -> termcolor::StandardStream {
termcolor::StandardStream::stdout(termcolor::ColorChoice::Auto)
}
fn err_termcolor(&self) -> termcolor::StandardStream {
termcolor::StandardStream::stderr(termcolor::ColorChoice::Auto)
}
fn width(&self) -> usize {
let (mut term_width, _) = term_size::dimensions().unwrap_or((80, 20));
term_width -= 1;
@ -79,4 +88,33 @@ impl Host for BasicHost {
let (_, term_height) = term_size::dimensions().unwrap_or((80, 20));
term_height
}
fn is_external_cmd(&self, #[allow(unused)] cmd_name: &str) -> bool {
#[cfg(any(target_arch = "wasm32", not(feature = "which")))]
{
true
}
#[cfg(all(unix, feature = "which"))]
{
which::which(cmd_name).is_ok()
}
#[cfg(all(windows, feature = "which"))]
{
if which::which(cmd_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(&cmd_name)
}
}
}
}

View File

@ -1,13 +1,15 @@
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_source::Text;
use std::ffi::OsString;
use std::fmt::Debug;
pub trait Host: Debug + Send {
fn out_termcolor(&self) -> termcolor::StandardStream;
fn err_termcolor(&self) -> termcolor::StandardStream;
use super::basic_host::BasicHost;
pub trait Host: Debug + Send {
fn stdout(&mut self, out: &str);
fn stderr(&mut self, out: &str);
fn print_err(&mut self, err: ShellError, source: &Text);
fn vars(&mut self) -> Vec<(String, String)>;
fn env_get(&mut self, key: OsString) -> Option<OsString>;
@ -16,6 +18,8 @@ pub trait Host: Debug + Send {
fn width(&self) -> usize;
fn height(&self) -> usize;
fn is_external_cmd(&self, cmd_name: &str) -> bool;
}
impl Host for Box<dyn Host> {
@ -27,6 +31,10 @@ impl Host for Box<dyn Host> {
(**self).stderr(out)
}
fn print_err(&mut self, err: ShellError, source: &Text) {
(**self).print_err(err, source)
}
fn vars(&mut self) -> Vec<(String, String)> {
(**self).vars()
}
@ -43,14 +51,6 @@ impl Host for Box<dyn Host> {
(**self).env_rm(key)
}
fn out_termcolor(&self) -> termcolor::StandardStream {
(**self).out_termcolor()
}
fn err_termcolor(&self) -> termcolor::StandardStream {
(**self).err_termcolor()
}
fn width(&self) -> usize {
(**self).width()
}
@ -58,6 +58,10 @@ impl Host for Box<dyn Host> {
fn height(&self) -> usize {
(**self).height()
}
fn is_external_cmd(&self, name: &str) -> bool {
(**self).is_external_cmd(name)
}
}
#[derive(Debug)]
@ -90,6 +94,10 @@ impl Host for FakeHost {
self.line_written = out.to_string();
}
fn print_err(&mut self, err: ShellError, source: &Text) {
BasicHost {}.print_err(err, source);
}
fn vars(&mut self) -> Vec<(String, String)> {
self.env_vars
.iter()
@ -118,14 +126,6 @@ impl Host for FakeHost {
.shift_remove(&key.into_string().expect("Couldn't convert to string."));
}
fn out_termcolor(&self) -> termcolor::StandardStream {
termcolor::StandardStream::stdout(termcolor::ColorChoice::Auto)
}
fn err_termcolor(&self) -> termcolor::StandardStream {
termcolor::StandardStream::stderr(termcolor::ColorChoice::Auto)
}
fn width(&self) -> usize {
1
}
@ -133,4 +133,8 @@ impl Host for FakeHost {
fn height(&self) -> usize {
1
}
fn is_external_cmd(&self, _: &str) -> bool {
true
}
}

View File

@ -12,6 +12,9 @@ pub mod filesystem;
mod history_path;
mod maybe_text_codec;
pub mod plugin;
pub mod print;
mod runnable_context;
pub mod script;
pub mod shell;
mod whole_stream_command;
@ -35,6 +38,7 @@ pub use crate::filesystem::filesystem_shell::FilesystemShell;
pub use crate::filesystem::path;
pub use crate::history_path::history_path;
pub use crate::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
pub use crate::runnable_context::RunnableContext;
pub use crate::shell::help_shell::{command_dict, HelpShell};
pub use crate::shell::painter::Painter;
pub use crate::shell::palette::{DefaultPalette, Palette};

View File

@ -0,0 +1,18 @@
use nu_source::Text;
use crate::EvaluationContext;
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![];
context.host.lock().print_err(error, &source);
true
} else {
false
}
}

View File

@ -0,0 +1,22 @@
use crate::{Command, Host, Scope, ShellManager};
use nu_errors::ShellError;
use nu_source::Tag;
use nu_stream::InputStream;
use parking_lot::Mutex;
use std::sync::{atomic::AtomicBool, Arc};
pub struct RunnableContext {
pub input: InputStream,
pub shell_manager: ShellManager,
pub host: Arc<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

@ -0,0 +1,268 @@
use crate::run_block;
use crate::{path::canonicalize, print::maybe_print_errors};
use crate::{MaybeTextCodec, StringOrBinary};
use futures::StreamExt;
use futures_codec::FramedRead;
use nu_errors::ShellError;
use nu_protocol::hir::{
Call, ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments,
SpannedExpression,
};
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
use nu_stream::{InputStream, ToInputStream};
use crate::EvaluationContext;
use log::{debug, trace};
use nu_source::{Span, Tag, Text};
use std::iter::Iterator;
use std::path::Path;
use std::{error::Error, sync::atomic::Ordering};
#[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
}
}
/// 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()
&& !ctx.host.lock().is_external_cmd(&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 autoview_cmd = ctx
.get_command("autoview")
.expect("Could not find autoview command");
if let Ok(mut output_stream) = ctx
.run_command(
autoview_cmd,
Tag::unknown(),
Call::new(
Box::new(SpannedExpression::new(
Expression::string("autoview".to_string()),
Span::unknown(),
)),
Span::unknown(),
),
input,
)
.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) => {
context
.host
.lock()
.print_err(err, &Text::from(line.clone()));
maybe_print_errors(&context, Text::from(line));
if exit_on_error {
std::process::exit(1);
}
}
_ => {}
}
Ok(())
}