nushell/src/cli.rs

553 lines
19 KiB
Rust
Raw Normal View History

2019-06-07 18:30:50 +02:00
use crate::commands::autoview;
2019-06-07 08:34:42 +02:00
use crate::commands::classified::SinkCommand;
2019-05-24 09:29:16 +02:00
use crate::commands::classified::{
2019-05-26 08:54:41 +02:00
ClassifiedCommand, ClassifiedInputStream, ClassifiedPipeline, ExternalCommand, InternalCommand,
StreamNext,
2019-05-24 09:29:16 +02:00
};
2019-07-03 19:37:09 +02:00
use crate::commands::command::sink;
2019-07-04 05:06:43 +02:00
use crate::commands::plugin::JsonRpc;
2019-07-16 09:08:35 +02:00
use crate::commands::plugin::{PluginCommand, PluginSink};
2019-05-23 06:30:43 +02:00
use crate::context::Context;
crate use crate::errors::ShellError;
2019-05-28 08:45:18 +02:00
use crate::evaluate::Scope;
2019-07-03 19:37:09 +02:00
use crate::git::current_branch;
use crate::object::Value;
2019-06-25 06:33:12 +02:00
use crate::parser::parse::span::Spanned;
2019-06-22 05:43:37 +02:00
use crate::parser::registry;
2019-07-03 19:37:09 +02:00
use crate::parser::registry::CommandConfig;
2019-06-23 19:35:43 +02:00
use crate::parser::{Pipeline, PipelineElement, TokenNode};
2019-07-03 19:37:09 +02:00
use crate::prelude::*;
2019-05-23 06:30:43 +02:00
2019-06-22 05:43:37 +02:00
use log::{debug, trace};
2019-07-04 05:06:43 +02:00
use regex::Regex;
2019-05-23 06:30:43 +02:00
use rustyline::error::ReadlineError;
use rustyline::{self, ColorMode, Config, Editor};
2019-07-03 19:37:09 +02:00
use std::env;
2019-05-23 06:30:43 +02:00
use std::error::Error;
2019-07-03 19:37:09 +02:00
use std::io::{BufRead, BufReader, Write};
2019-05-24 09:29:16 +02:00
use std::iter::Iterator;
2019-06-07 02:31:22 +02:00
use std::sync::atomic::{AtomicBool, Ordering};
2019-05-23 06:30:43 +02:00
#[derive(Debug)]
pub enum MaybeOwned<'a, T> {
Owned(T),
Borrowed(&'a T),
}
impl<T> MaybeOwned<'a, T> {
2019-07-04 07:11:56 +02:00
pub fn borrow(&self) -> &T {
2019-05-23 06:30:43 +02:00
match self {
MaybeOwned::Owned(v) => v,
MaybeOwned::Borrowed(v) => v,
}
}
}
2019-07-04 05:06:43 +02:00
fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), ShellError> {
let mut child = std::process::Command::new(path)
2019-07-03 19:37:09 +02:00
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.expect("Failed to spawn child process");
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout);
let request = JsonRpc::new("config", Vec::<Value>::new());
let request_raw = serde_json::to_string(&request).unwrap();
stdin.write(format!("{}\n", request_raw).as_bytes())?;
2019-07-04 07:11:56 +02:00
let path = dunce::canonicalize(path).unwrap();
2019-07-03 19:37:09 +02:00
let mut input = String::new();
match reader.read_line(&mut input) {
Ok(_) => {
let response =
serde_json::from_str::<JsonRpc<Result<CommandConfig, ShellError>>>(&input);
match response {
Ok(jrpc) => match jrpc.params {
Ok(params) => {
2019-07-04 05:06:43 +02:00
let fname = path.to_string_lossy();
2019-07-03 19:37:09 +02:00
if params.is_filter {
let fname = fname.to_string();
2019-07-16 09:08:35 +02:00
let name = params.name.clone();
context.add_commands(vec![Arc::new(PluginCommand::new(
name, fname, params,
))]);
2019-07-03 19:37:09 +02:00
Ok(())
} else if params.is_sink {
let fname = fname.to_string();
2019-07-16 09:08:35 +02:00
let name = params.name.clone();
context.add_sinks(vec![Arc::new(PluginSink::new(name, fname, params))]);
2019-07-03 19:37:09 +02:00
Ok(())
} else {
Ok(())
}
}
Err(e) => Err(e),
},
Err(e) => Err(ShellError::string(format!("Error: {:?}", e))),
}
}
Err(e) => Err(ShellError::string(format!("Error: {:?}", e))),
}
}
2019-07-04 05:06:43 +02:00
fn load_plugins_in_dir(path: &std::path::PathBuf, context: &mut Context) -> Result<(), ShellError> {
let re_bin = Regex::new(r"^nu_plugin_[A-Za-z_]+$").unwrap();
let re_exe = Regex::new(r"^nu_plugin_[A-Za-z_]+\.exe$").unwrap();
match std::fs::read_dir(path) {
Ok(p) => {
for entry in p {
let entry = entry.unwrap();
let filename = entry.file_name();
let f_name = filename.to_string_lossy();
if re_bin.is_match(&f_name) || re_exe.is_match(&f_name) {
let mut load_path = path.clone();
load_path.push(f_name.to_string());
load_plugin(&load_path, context)?;
}
}
}
_ => {}
}
Ok(())
}
2019-07-03 19:37:09 +02:00
fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
match env::var_os("PATH") {
Some(paths) => {
for path in env::split_paths(&paths) {
2019-07-04 05:06:43 +02:00
let _ = load_plugins_in_dir(&path, context);
2019-07-03 19:37:09 +02:00
}
}
None => println!("PATH is not defined in the environment."),
}
// Also use our debug output for now
let mut path = std::path::PathBuf::from(".");
path.push("target");
path.push("debug");
2019-07-04 05:06:43 +02:00
let _ = load_plugins_in_dir(&path, context);
2019-07-03 19:37:09 +02:00
// Also use our release output for now
let mut path = std::path::PathBuf::from(".");
path.push("target");
path.push("release");
let _ = load_plugins_in_dir(&path, context);
2019-07-03 19:37:09 +02:00
Ok(())
}
pub async fn cli() -> Result<(), Box<dyn Error>> {
2019-05-23 06:30:43 +02:00
let mut context = Context::basic()?;
{
use crate::commands::*;
context.add_commands(vec![
2019-07-03 19:37:09 +02:00
command("ps", Box::new(ps::ps)),
command("ls", Box::new(ls::ls)),
command("sysinfo", Box::new(sysinfo::sysinfo)),
command("cd", Box::new(cd::cd)),
command("first", Box::new(first::first)),
command("size", Box::new(size::size)),
command("from-csv", Box::new(from_csv::from_csv)),
2019-07-03 19:37:09 +02:00
command("from-ini", Box::new(from_ini::from_ini)),
command("from-json", Box::new(from_json::from_json)),
command("from-toml", Box::new(from_toml::from_toml)),
command("from-xml", Box::new(from_xml::from_xml)),
command("from-yaml", Box::new(from_yaml::from_yaml)),
command("get", Box::new(get::get)),
command("exit", Box::new(exit::exit)),
command("lines", Box::new(lines::lines)),
command("pick", Box::new(pick::pick)),
command("split-column", Box::new(split_column::split_column)),
command("split-row", Box::new(split_row::split_row)),
command("lines", Box::new(lines::lines)),
command("reject", Box::new(reject::reject)),
command("trim", Box::new(trim::trim)),
command("to-array", Box::new(to_array::to_array)),
2019-07-21 09:08:05 +02:00
command("to-csv", Box::new(to_csv::to_csv)),
2019-07-03 19:37:09 +02:00
command("to-json", Box::new(to_json::to_json)),
command("to-toml", Box::new(to_toml::to_toml)),
2019-07-16 06:03:28 +02:00
command("to-yaml", Box::new(to_yaml::to_yaml)),
2019-07-03 19:37:09 +02:00
command("sort-by", Box::new(sort_by::sort_by)),
2019-07-17 21:51:18 +02:00
Arc::new(Remove),
2019-07-22 04:23:02 +02:00
Arc::new(Copycp),
2019-06-22 05:43:37 +02:00
Arc::new(Open),
2019-05-28 08:45:18 +02:00
Arc::new(Where),
Arc::new(Config),
2019-06-18 02:39:09 +02:00
Arc::new(SkipWhile),
2019-05-23 06:30:43 +02:00
]);
2019-06-07 08:34:42 +02:00
2019-06-07 09:50:26 +02:00
context.add_sinks(vec![
2019-07-03 19:37:09 +02:00
sink("autoview", Box::new(autoview::autoview)),
sink("clip", Box::new(clip::clip)),
sink("save", Box::new(save::save)),
sink("table", Box::new(table::table)),
sink("vtable", Box::new(vtable::vtable)),
2019-06-07 09:50:26 +02:00
]);
2019-05-23 06:30:43 +02:00
}
2019-07-04 05:06:43 +02:00
let _ = load_plugins(&mut context);
2019-05-23 06:30:43 +02:00
2019-05-26 08:54:41 +02:00
let config = Config::builder().color_mode(ColorMode::Forced).build();
let h = crate::shell::Helper::new(context.clone_commands());
let mut rl: Editor<crate::shell::Helper> = Editor::with_config(config);
#[cfg(windows)]
{
let _ = ansi_term::enable_ansi_support();
}
rl.set_helper(Some(h));
2019-06-03 02:03:40 +02:00
let _ = rl.load_history("history.txt");
2019-05-26 08:54:41 +02:00
2019-06-07 02:31:22 +02:00
let ctrl_c = Arc::new(AtomicBool::new(false));
let cc = ctrl_c.clone();
ctrlc::set_handler(move || {
cc.store(true, Ordering::SeqCst);
})
.expect("Error setting Ctrl-C handler");
2019-06-15 20:36:17 +02:00
let mut ctrlcbreak = false;
2019-05-23 06:30:43 +02:00
loop {
2019-06-07 02:31:22 +02:00
if ctrl_c.load(Ordering::SeqCst) {
ctrl_c.store(false, Ordering::SeqCst);
continue;
}
2019-07-16 21:10:25 +02:00
let cwd = {
2019-06-13 23:47:25 +02:00
let env = context.env.lock().unwrap();
2019-07-16 21:10:25 +02:00
env.path().display().to_string()
2019-06-13 23:47:25 +02:00
};
2019-07-16 21:10:25 +02:00
let readline = rl.readline(&format!(
"{}{}> ",
cwd,
match current_branch() {
Some(s) => format!("({})", s),
None => "".to_string(),
}
));
2019-05-23 06:30:43 +02:00
match process_line(readline, &mut context).await {
2019-05-23 06:30:43 +02:00
LineResult::Success(line) => {
rl.add_history_entry(line.clone());
}
2019-06-15 20:36:17 +02:00
LineResult::CtrlC => {
if ctrlcbreak {
std::process::exit(0);
} else {
context
.host
.lock()
.unwrap()
.stdout("CTRL-C pressed (again to quit)");
ctrlcbreak = true;
continue;
}
}
2019-06-18 02:39:09 +02:00
LineResult::Error(mut line, err) => {
rl.add_history_entry(line.clone());
2019-06-03 07:11:21 +02:00
2019-06-24 02:55:31 +02:00
let diag = err.to_diagnostic();
let host = context.host.lock().unwrap();
let writer = host.err_termcolor();
line.push_str(" ");
let files = crate::parser::Files::new(line);
language_reporting::emit(
&mut writer.lock(),
&files,
&diag,
&language_reporting::DefaultConfig,
)
.unwrap();
2019-06-18 02:39:09 +02:00
}
2019-05-23 06:30:43 +02:00
LineResult::Break => {
break;
}
2019-06-24 02:55:31 +02:00
LineResult::FatalError(_, err) => {
2019-05-23 06:30:43 +02:00
context
.host
.lock()
.unwrap()
2019-05-23 06:30:43 +02:00
.stdout(&format!("A surprising fatal error occurred.\n{:?}", err));
}
}
2019-06-15 20:36:17 +02:00
ctrlcbreak = false;
2019-05-23 06:30:43 +02:00
}
rl.save_history("history.txt").unwrap();
Ok(())
}
enum LineResult {
Success(String),
2019-06-08 00:35:07 +02:00
Error(String, ShellError),
2019-06-15 20:36:17 +02:00
CtrlC,
2019-05-23 06:30:43 +02:00
Break,
#[allow(unused)]
2019-06-24 02:55:31 +02:00
FatalError(String, ShellError),
2019-05-23 06:30:43 +02:00
}
2019-05-24 06:34:43 +02:00
impl std::ops::Try for LineResult {
type Ok = Option<String>;
2019-06-24 02:55:31 +02:00
type Error = (String, ShellError);
2019-05-24 06:34:43 +02:00
2019-06-24 02:55:31 +02:00
fn into_result(self) -> Result<Option<String>, (String, ShellError)> {
2019-05-24 06:34:43 +02:00
match self {
LineResult::Success(s) => Ok(Some(s)),
2019-06-24 02:55:31 +02:00
LineResult::Error(string, err) => Err((string, err)),
2019-05-24 06:34:43 +02:00
LineResult::Break => Ok(None),
2019-06-15 20:36:17 +02:00
LineResult::CtrlC => Ok(None),
2019-06-24 02:55:31 +02:00
LineResult::FatalError(string, err) => Err((string, err)),
2019-05-24 06:34:43 +02:00
}
}
2019-06-24 02:55:31 +02:00
fn from_error(v: (String, ShellError)) -> Self {
LineResult::Error(v.0, v.1)
2019-05-24 06:34:43 +02:00
}
fn from_ok(v: Option<String>) -> Self {
match v {
None => LineResult::Break,
Some(v) => LineResult::Success(v),
}
}
}
async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context) -> LineResult {
2019-05-23 06:30:43 +02:00
match &readline {
Ok(line) if line.trim() == "" => LineResult::Success(line.clone()),
Ok(line) => {
2019-06-02 18:28:40 +02:00
let result = match crate::parser::parse(&line) {
2019-05-23 06:30:43 +02:00
Err(err) => {
2019-06-09 19:52:56 +02:00
return LineResult::Error(line.clone(), err);
2019-05-23 06:30:43 +02:00
}
Ok(val) => val,
};
2019-05-26 08:54:41 +02:00
debug!("=== Parsed ===");
debug!("{:#?}", result);
2019-05-24 09:29:16 +02:00
2019-06-24 02:55:31 +02:00
let mut pipeline = classify_pipeline(&result, ctx, &Text::from(line))
.map_err(|err| (line.clone(), err))?;
2019-06-07 08:34:42 +02:00
match pipeline.commands.last() {
Some(ClassifiedCommand::Sink(_)) => {}
2019-06-07 09:54:52 +02:00
Some(ClassifiedCommand::External(_)) => {}
2019-06-07 08:34:42 +02:00
_ => pipeline.commands.push(ClassifiedCommand::Sink(SinkCommand {
2019-07-03 19:37:09 +02:00
command: sink("autoview", Box::new(autoview::autoview)),
2019-06-08 00:35:07 +02:00
name_span: None,
2019-06-22 05:43:37 +02:00
args: registry::Args {
positional: None,
named: None,
2019-06-07 08:34:42 +02:00
},
})),
}
2019-05-24 09:29:16 +02:00
let mut input = ClassifiedInputStream::new();
2019-05-26 08:54:41 +02:00
let mut iter = pipeline.commands.into_iter().peekable();
2019-05-24 09:29:16 +02:00
loop {
let item: Option<ClassifiedCommand> = iter.next();
let next: Option<&ClassifiedCommand> = iter.peek();
input = match (item, next) {
(None, _) => break,
2019-06-04 23:42:31 +02:00
(Some(ClassifiedCommand::Expr(_)), _) => {
2019-06-15 20:36:17 +02:00
return LineResult::Error(
line.clone(),
ShellError::unimplemented("Expression-only commands"),
)
2019-06-04 23:42:31 +02:00
}
(_, Some(ClassifiedCommand::Expr(_))) => {
2019-06-15 20:36:17 +02:00
return LineResult::Error(
line.clone(),
ShellError::unimplemented("Expression-only commands"),
)
2019-06-04 23:42:31 +02:00
}
2019-06-15 20:36:17 +02:00
(Some(ClassifiedCommand::Sink(SinkCommand { name_span, .. })), Some(_)) => {
return LineResult::Error(line.clone(), ShellError::maybe_labeled_error("Commands like table, save, and autoview must come last in the pipeline", "must come last", name_span));
2019-06-07 08:34:42 +02:00
}
(Some(ClassifiedCommand::Sink(left)), None) => {
2019-07-08 18:44:53 +02:00
let input_vec: Vec<Spanned<Value>> = input.objects.into_vec().await;
2019-06-16 01:03:49 +02:00
if let Err(err) = left.run(ctx, input_vec) {
return LineResult::Error(line.clone(), err);
}
2019-06-07 08:34:42 +02:00
break;
}
2019-05-24 09:29:16 +02:00
(
Some(ClassifiedCommand::Internal(left)),
2019-06-07 08:34:42 +02:00
Some(ClassifiedCommand::External(_)),
) => match left.run(ctx, input).await {
Ok(val) => ClassifiedInputStream::from_input_stream(val),
2019-06-09 19:52:56 +02:00
Err(err) => return LineResult::Error(line.clone(), err),
2019-06-07 08:34:42 +02:00
},
2019-06-15 20:36:17 +02:00
(Some(ClassifiedCommand::Internal(left)), Some(_)) => {
match left.run(ctx, input).await {
Ok(val) => ClassifiedInputStream::from_input_stream(val),
Err(err) => return LineResult::Error(line.clone(), err),
}
}
2019-05-24 09:29:16 +02:00
(Some(ClassifiedCommand::Internal(left)), None) => {
match left.run(ctx, input).await {
Ok(val) => ClassifiedInputStream::from_input_stream(val),
2019-06-09 19:52:56 +02:00
Err(err) => return LineResult::Error(line.clone(), err),
2019-05-24 09:29:16 +02:00
}
}
(
Some(ClassifiedCommand::External(left)),
Some(ClassifiedCommand::External(_)),
) => match left.run(ctx, input, StreamNext::External).await {
2019-05-24 09:29:16 +02:00
Ok(val) => val,
2019-06-09 19:52:56 +02:00
Err(err) => return LineResult::Error(line.clone(), err),
2019-05-24 09:29:16 +02:00
},
2019-06-15 20:36:17 +02:00
(Some(ClassifiedCommand::External(left)), Some(_)) => {
match left.run(ctx, input, StreamNext::Internal).await {
Ok(val) => val,
Err(err) => return LineResult::Error(line.clone(), err),
}
}
2019-05-24 09:29:16 +02:00
(Some(ClassifiedCommand::External(left)), None) => {
match left.run(ctx, input, StreamNext::Last).await {
2019-05-24 09:29:16 +02:00
Ok(val) => val,
2019-06-09 19:52:56 +02:00
Err(err) => return LineResult::Error(line.clone(), err),
2019-05-24 09:29:16 +02:00
}
}
}
2019-05-23 06:30:43 +02:00
}
2019-06-09 19:52:56 +02:00
LineResult::Success(line.clone())
2019-05-23 06:30:43 +02:00
}
2019-06-15 20:36:17 +02:00
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
2019-05-23 06:30:43 +02:00
Err(ReadlineError::Eof) => {
println!("CTRL-D");
LineResult::Break
}
Err(err) => {
println!("Error: {:?}", err);
LineResult::Break
}
}
}
2019-05-26 08:54:41 +02:00
fn classify_pipeline(
2019-06-22 05:43:37 +02:00
pipeline: &TokenNode,
2019-05-26 08:54:41 +02:00
context: &Context,
2019-06-22 22:46:16 +02:00
source: &Text,
2019-05-26 08:54:41 +02:00
) -> Result<ClassifiedPipeline, ShellError> {
2019-06-22 05:43:37 +02:00
let pipeline = pipeline.as_pipeline()?;
2019-06-23 19:35:43 +02:00
let Pipeline { parts, .. } = pipeline;
let commands: Result<Vec<_>, ShellError> = parts
2019-05-26 08:54:41 +02:00
.iter()
2019-06-22 22:46:16 +02:00
.map(|item| classify_command(&item, context, &source))
2019-05-26 08:54:41 +02:00
.collect();
Ok(ClassifiedPipeline {
commands: commands?,
})
}
2019-05-23 06:30:43 +02:00
fn classify_command(
2019-06-22 05:43:37 +02:00
command: &PipelineElement,
2019-05-23 06:30:43 +02:00
context: &Context,
2019-06-22 22:46:16 +02:00
source: &Text,
2019-05-23 06:30:43 +02:00
) -> Result<ClassifiedCommand, ShellError> {
2019-06-22 05:43:37 +02:00
let call = command.call();
2019-06-04 23:42:31 +02:00
2019-06-22 05:43:37 +02:00
match call {
call if call.head().is_bare() => {
let head = call.head();
let name = head.source(source);
match context.has_command(name) {
true => {
2019-06-22 05:43:37 +02:00
let command = context.get_command(name);
let config = command.config();
let scope = Scope::empty();
2019-07-12 21:22:08 +02:00
trace!(target: "nu::build_pipeline", "classifying {:?}", config);
2019-06-22 05:43:37 +02:00
let args = config.evaluate_args(call, context, &scope, source)?;
Ok(ClassifiedCommand::Internal(InternalCommand {
command,
2019-06-22 05:43:37 +02:00
name_span: Some(head.span().clone()),
source_map: context.source_map.clone(),
args,
}))
2019-06-04 23:42:31 +02:00
}
2019-06-22 05:43:37 +02:00
false => match context.has_sink(name) {
2019-06-07 08:34:42 +02:00
true => {
2019-06-22 05:43:37 +02:00
let command = context.get_sink(name);
2019-06-07 08:34:42 +02:00
let config = command.config();
let scope = Scope::empty();
2019-06-22 05:43:37 +02:00
let args = config.evaluate_args(call, context, &scope, source)?;
2019-06-07 08:34:42 +02:00
2019-06-08 00:35:07 +02:00
Ok(ClassifiedCommand::Sink(SinkCommand {
command,
2019-06-22 05:43:37 +02:00
name_span: Some(head.span().clone()),
2019-06-08 00:35:07 +02:00
args,
}))
2019-06-07 08:34:42 +02:00
}
false => {
2019-06-22 05:43:37 +02:00
let arg_list_strings: Vec<Spanned<String>> = match call.children() {
//Some(args) => args.iter().map(|i| i.as_external_arg(source)).collect(),
2019-06-15 06:20:58 +02:00
Some(args) => args
.iter()
2019-06-24 02:55:31 +02:00
.filter_map(|i| match i {
TokenNode::Whitespace(_) => None,
other => Some(Spanned::from_item(
other.as_external_arg(source),
other.span(),
)),
})
2019-06-15 06:20:58 +02:00
.collect(),
2019-06-07 08:34:42 +02:00
None => vec![],
};
Ok(ClassifiedCommand::External(ExternalCommand {
name: name.to_string(),
2019-06-22 05:43:37 +02:00
name_span: Some(head.span().clone()),
2019-06-07 08:34:42 +02:00
args: arg_list_strings,
}))
}
},
2019-06-22 05:43:37 +02:00
}
2019-06-04 23:42:31 +02:00
}
2019-06-22 05:43:37 +02:00
2019-06-24 02:55:31 +02:00
call => Err(ShellError::diagnostic(
language_reporting::Diagnostic::new(
language_reporting::Severity::Error,
"Invalid command",
)
.with_label(language_reporting::Label::new_primary(call.head().span())),
2019-06-22 05:43:37 +02:00
)),
2019-05-23 06:30:43 +02:00
}
}