forked from extern/nushell
Merge remote-tracking branch 'origin/master' into cleanup-wip
This commit is contained in:
commit
2eae5a2a89
@ -281,7 +281,7 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat
|
|||||||
| inc (column-or-column-path) | Increment a value or version. Optionally use the column of a table |
|
| inc (column-or-column-path) | Increment a value or version. Optionally use the column of a table |
|
||||||
| insert column-or-column-path value | Insert a new column to the table |
|
| insert column-or-column-path value | Insert a new column to the table |
|
||||||
| last amount | Show only the last number of rows |
|
| last amount | Show only the last number of rows |
|
||||||
| nth row-number | Return only the selected row |
|
| nth ...row-numbers | Return only the selected rows |
|
||||||
| pick ...columns | Down-select table to only these columns |
|
| pick ...columns | Down-select table to only these columns |
|
||||||
| pivot --header-row <headers> | Pivot the tables, making columns into rows and vice versa |
|
| pivot --header-row <headers> | Pivot the tables, making columns into rows and vice versa |
|
||||||
| prepend row-data | Prepend a row to the beginning of the table |
|
| prepend row-data | Prepend a row to the beginning of the table |
|
||||||
|
@ -1,513 +0,0 @@
|
|||||||
use crate::parser::{hir, TokenNode};
|
|
||||||
use crate::prelude::*;
|
|
||||||
use bytes::{BufMut, BytesMut};
|
|
||||||
use derive_new::new;
|
|
||||||
use futures::stream::StreamExt;
|
|
||||||
use futures_codec::{Decoder, Encoder, Framed};
|
|
||||||
use log::{log_enabled, trace};
|
|
||||||
use nu_source::PrettyDebug;
|
|
||||||
use std::io::{Error, ErrorKind};
|
|
||||||
use subprocess::Exec;
|
|
||||||
|
|
||||||
/// A simple `Codec` implementation that splits up data into lines.
|
|
||||||
pub struct LinesCodec {}
|
|
||||||
|
|
||||||
impl Encoder for LinesCodec {
|
|
||||||
type Item = String;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
|
||||||
dst.put(item);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Decoder for LinesCodec {
|
|
||||||
type Item = String;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
|
||||||
match src.iter().position(|b| b == &b'\n') {
|
|
||||||
Some(pos) if !src.is_empty() => {
|
|
||||||
let buf = src.split_to(pos + 1);
|
|
||||||
String::from_utf8(buf.to_vec())
|
|
||||||
.map(Some)
|
|
||||||
.map_err(|e| Error::new(ErrorKind::InvalidData, e))
|
|
||||||
}
|
|
||||||
_ if !src.is_empty() => {
|
|
||||||
let drained = src.take();
|
|
||||||
String::from_utf8(drained.to_vec())
|
|
||||||
.map(Some)
|
|
||||||
.map_err(|e| Error::new(ErrorKind::InvalidData, e))
|
|
||||||
}
|
|
||||||
_ => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct ClassifiedInputStream {
|
|
||||||
pub(crate) objects: InputStream,
|
|
||||||
pub(crate) stdin: Option<std::fs::File>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClassifiedInputStream {
|
|
||||||
pub(crate) fn new() -> ClassifiedInputStream {
|
|
||||||
ClassifiedInputStream {
|
|
||||||
objects: vec![UntaggedValue::nothing().into_value(Tag::unknown())].into(),
|
|
||||||
stdin: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn from_input_stream(stream: impl Into<InputStream>) -> ClassifiedInputStream {
|
|
||||||
ClassifiedInputStream {
|
|
||||||
objects: stream.into(),
|
|
||||||
stdin: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn from_stdout(stdout: std::fs::File) -> ClassifiedInputStream {
|
|
||||||
ClassifiedInputStream {
|
|
||||||
objects: VecDeque::new().into(),
|
|
||||||
stdin: Some(stdout),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Commands {
|
|
||||||
pub list: Vec<ClassifiedCommand>,
|
|
||||||
pub span: Span,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Deref for Commands {
|
|
||||||
type Target = [ClassifiedCommand];
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub(crate) struct ClassifiedPipeline {
|
|
||||||
pub commands: Commands,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClassifiedPipeline {
|
|
||||||
pub fn commands(list: Vec<ClassifiedCommand>, span: impl Into<Span>) -> ClassifiedPipeline {
|
|
||||||
ClassifiedPipeline {
|
|
||||||
commands: Commands {
|
|
||||||
list,
|
|
||||||
span: span.into(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrettyDebugWithSource for ClassifiedPipeline {
|
|
||||||
fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
|
|
||||||
b::intersperse(
|
|
||||||
self.commands.iter().map(|c| c.pretty_debug(source)),
|
|
||||||
b::operator(" | "),
|
|
||||||
)
|
|
||||||
.or(b::delimit("<", b::description("empty pipeline"), ">"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasSpan for ClassifiedPipeline {
|
|
||||||
fn span(&self) -> Span {
|
|
||||||
self.commands.span
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
||||||
pub enum ClassifiedCommand {
|
|
||||||
#[allow(unused)]
|
|
||||||
Expr(TokenNode),
|
|
||||||
#[allow(unused)]
|
|
||||||
Dynamic(hir::Call),
|
|
||||||
Internal(InternalCommand),
|
|
||||||
External(ExternalCommand),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrettyDebugWithSource for ClassifiedCommand {
|
|
||||||
fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
|
|
||||||
match self {
|
|
||||||
ClassifiedCommand::Expr(token) => b::typed("command", token.pretty_debug(source)),
|
|
||||||
ClassifiedCommand::Dynamic(call) => b::typed("command", call.pretty_debug(source)),
|
|
||||||
ClassifiedCommand::Internal(internal) => internal.pretty_debug(source),
|
|
||||||
ClassifiedCommand::External(external) => external.pretty_debug(source),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasSpan for ClassifiedCommand {
|
|
||||||
fn span(&self) -> Span {
|
|
||||||
match self {
|
|
||||||
ClassifiedCommand::Expr(node) => node.span(),
|
|
||||||
ClassifiedCommand::Internal(command) => command.span(),
|
|
||||||
ClassifiedCommand::Dynamic(call) => call.span,
|
|
||||||
ClassifiedCommand::External(command) => command.span(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(new, Debug, Clone, Eq, PartialEq)]
|
|
||||||
pub struct InternalCommand {
|
|
||||||
pub(crate) name: String,
|
|
||||||
pub(crate) name_tag: Tag,
|
|
||||||
pub(crate) args: hir::Call,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrettyDebugWithSource for InternalCommand {
|
|
||||||
fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
|
|
||||||
b::typed(
|
|
||||||
"internal command",
|
|
||||||
b::description(&self.name) + b::space() + self.args.pretty_debug(source),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasSpan for InternalCommand {
|
|
||||||
fn span(&self) -> Span {
|
|
||||||
let start = self.name_tag.span;
|
|
||||||
|
|
||||||
start.until(self.args.span)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(new, Debug, Eq, PartialEq)]
|
|
||||||
pub(crate) struct DynamicCommand {
|
|
||||||
pub(crate) args: hir::Call,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InternalCommand {
|
|
||||||
pub(crate) fn run(
|
|
||||||
self,
|
|
||||||
context: &mut Context,
|
|
||||||
input: ClassifiedInputStream,
|
|
||||||
source: Text,
|
|
||||||
) -> Result<InputStream, ShellError> {
|
|
||||||
if log_enabled!(log::Level::Trace) {
|
|
||||||
trace!(target: "nu::run::internal", "->");
|
|
||||||
trace!(target: "nu::run::internal", "{}", self.name);
|
|
||||||
trace!(target: "nu::run::internal", "{}", self.args.debug(&source));
|
|
||||||
}
|
|
||||||
|
|
||||||
let objects: InputStream =
|
|
||||||
trace_stream!(target: "nu::trace_stream::internal", "input" = input.objects);
|
|
||||||
|
|
||||||
let command = context.expect_command(&self.name);
|
|
||||||
|
|
||||||
let result =
|
|
||||||
{ context.run_command(command, self.name_tag.clone(), self.args, &source, objects) };
|
|
||||||
|
|
||||||
let result = trace_out_stream!(target: "nu::trace_stream::internal", "output" = result);
|
|
||||||
let mut result = result.values;
|
|
||||||
let mut context = context.clone();
|
|
||||||
|
|
||||||
let stream = async_stream! {
|
|
||||||
let mut soft_errs: Vec<ShellError> = vec![];
|
|
||||||
let mut yielded = false;
|
|
||||||
|
|
||||||
while let Some(item) = result.next().await {
|
|
||||||
match item {
|
|
||||||
Ok(ReturnSuccess::Action(action)) => match action {
|
|
||||||
CommandAction::ChangePath(path) => {
|
|
||||||
context.shell_manager.set_path(path);
|
|
||||||
}
|
|
||||||
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
|
|
||||||
CommandAction::Error(err) => {
|
|
||||||
context.error(err);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
CommandAction::EnterHelpShell(value) => {
|
|
||||||
match value {
|
|
||||||
Value {
|
|
||||||
value: UntaggedValue::Primitive(Primitive::String(cmd)),
|
|
||||||
tag,
|
|
||||||
} => {
|
|
||||||
context.shell_manager.insert_at_current(Box::new(
|
|
||||||
HelpShell::for_command(
|
|
||||||
UntaggedValue::string(cmd).into_value(tag),
|
|
||||||
&context.registry(),
|
|
||||||
).unwrap(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
context.shell_manager.insert_at_current(Box::new(
|
|
||||||
HelpShell::index(&context.registry()).unwrap(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CommandAction::EnterValueShell(value) => {
|
|
||||||
context
|
|
||||||
.shell_manager
|
|
||||||
.insert_at_current(Box::new(ValueShell::new(value)));
|
|
||||||
}
|
|
||||||
CommandAction::EnterShell(location) => {
|
|
||||||
context.shell_manager.insert_at_current(Box::new(
|
|
||||||
FilesystemShell::with_location(location, context.registry().clone()).unwrap(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
CommandAction::PreviousShell => {
|
|
||||||
context.shell_manager.prev();
|
|
||||||
}
|
|
||||||
CommandAction::NextShell => {
|
|
||||||
context.shell_manager.next();
|
|
||||||
}
|
|
||||||
CommandAction::LeaveShell => {
|
|
||||||
context.shell_manager.remove_at_current();
|
|
||||||
if context.shell_manager.is_empty() {
|
|
||||||
std::process::exit(0); // TODO: save history.txt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Ok(ReturnSuccess::Value(v)) => {
|
|
||||||
yielded = true;
|
|
||||||
yield Ok(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ReturnSuccess::DebugValue(v)) => {
|
|
||||||
yielded = true;
|
|
||||||
|
|
||||||
let doc = PrettyDebug::pretty_doc(&v);
|
|
||||||
let mut buffer = termcolor::Buffer::ansi();
|
|
||||||
|
|
||||||
doc.render_raw(
|
|
||||||
context.with_host(|host| host.width() - 5),
|
|
||||||
&mut crate::parser::debug::TermColored::new(&mut buffer),
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
let value = String::from_utf8_lossy(buffer.as_slice());
|
|
||||||
|
|
||||||
yield Ok(UntaggedValue::string(value).into_untagged_value())
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(err) => {
|
|
||||||
context.error(err);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(stream.to_input_stream())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
||||||
pub struct ExternalArg {
|
|
||||||
pub arg: String,
|
|
||||||
pub tag: Tag,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Deref for ExternalArg {
|
|
||||||
type Target = str;
|
|
||||||
|
|
||||||
fn deref(&self) -> &str {
|
|
||||||
&self.arg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
||||||
pub struct ExternalArgs {
|
|
||||||
pub list: Vec<ExternalArg>,
|
|
||||||
pub span: Span,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExternalArgs {
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &ExternalArg> {
|
|
||||||
self.list.iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Deref for ExternalArgs {
|
|
||||||
type Target = [ExternalArg];
|
|
||||||
|
|
||||||
fn deref(&self) -> &[ExternalArg] {
|
|
||||||
&self.list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
||||||
pub struct ExternalCommand {
|
|
||||||
pub(crate) name: String,
|
|
||||||
|
|
||||||
pub(crate) name_tag: Tag,
|
|
||||||
pub(crate) args: ExternalArgs,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrettyDebug for ExternalCommand {
|
|
||||||
fn pretty(&self) -> DebugDocBuilder {
|
|
||||||
b::typed(
|
|
||||||
"external command",
|
|
||||||
b::description(&self.name)
|
|
||||||
+ b::preceded(
|
|
||||||
b::space(),
|
|
||||||
b::intersperse(
|
|
||||||
self.args.iter().map(|a| b::primitive(format!("{}", a.arg))),
|
|
||||||
b::space(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasSpan for ExternalCommand {
|
|
||||||
fn span(&self) -> Span {
|
|
||||||
self.name_tag.span.until(self.args.span)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) enum StreamNext {
|
|
||||||
Last,
|
|
||||||
External,
|
|
||||||
Internal,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExternalCommand {
|
|
||||||
pub(crate) async fn run(
|
|
||||||
self,
|
|
||||||
context: &mut Context,
|
|
||||||
input: ClassifiedInputStream,
|
|
||||||
stream_next: StreamNext,
|
|
||||||
) -> Result<ClassifiedInputStream, ShellError> {
|
|
||||||
let stdin = input.stdin;
|
|
||||||
let inputs: Vec<Value> = input.objects.into_vec().await;
|
|
||||||
|
|
||||||
trace!(target: "nu::run::external", "-> {}", self.name);
|
|
||||||
trace!(target: "nu::run::external", "inputs = {:?}", inputs);
|
|
||||||
|
|
||||||
let mut arg_string = format!("{}", self.name);
|
|
||||||
for arg in self.args.iter() {
|
|
||||||
arg_string.push_str(&arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
trace!(target: "nu::run::external", "command = {:?}", self.name);
|
|
||||||
|
|
||||||
let mut process;
|
|
||||||
if arg_string.contains("$it") {
|
|
||||||
let input_strings = inputs
|
|
||||||
.iter()
|
|
||||||
.map(|i| {
|
|
||||||
i.as_string().map(|s| s.to_string()).map_err(|_| {
|
|
||||||
let arg = self.args.iter().find(|arg| arg.contains("$it"));
|
|
||||||
if let Some(arg) = arg {
|
|
||||||
ShellError::labeled_error(
|
|
||||||
"External $it needs string data",
|
|
||||||
"given row instead of string data",
|
|
||||||
&arg.tag,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ShellError::labeled_error(
|
|
||||||
"$it needs string data",
|
|
||||||
"given something else",
|
|
||||||
self.name_tag.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<String>, ShellError>>()?;
|
|
||||||
|
|
||||||
let commands = input_strings.iter().map(|i| {
|
|
||||||
let args = self.args.iter().filter_map(|arg| {
|
|
||||||
if arg.chars().all(|c| c.is_whitespace()) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(arg.replace("$it", &i))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
format!("{} {}", self.name, itertools::join(args, " "))
|
|
||||||
});
|
|
||||||
|
|
||||||
process = Exec::shell(itertools::join(commands, " && "))
|
|
||||||
} else {
|
|
||||||
process = Exec::cmd(&self.name);
|
|
||||||
for arg in self.args.iter() {
|
|
||||||
let arg_chars: Vec<_> = arg.chars().collect();
|
|
||||||
if arg_chars.len() > 1
|
|
||||||
&& arg_chars[0] == '"'
|
|
||||||
&& arg_chars[arg_chars.len() - 1] == '"'
|
|
||||||
{
|
|
||||||
// quoted string
|
|
||||||
let new_arg: String = arg_chars[1..arg_chars.len() - 1].iter().collect();
|
|
||||||
process = process.arg(new_arg);
|
|
||||||
} else {
|
|
||||||
process = process.arg(arg.arg.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
process = process.cwd(context.shell_manager.path());
|
|
||||||
|
|
||||||
trace!(target: "nu::run::external", "cwd = {:?}", context.shell_manager.path());
|
|
||||||
|
|
||||||
let mut process = match stream_next {
|
|
||||||
StreamNext::Last => process,
|
|
||||||
StreamNext::External | StreamNext::Internal => {
|
|
||||||
process.stdout(subprocess::Redirection::Pipe)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
trace!(target: "nu::run::external", "set up stdout pipe");
|
|
||||||
|
|
||||||
if let Some(stdin) = stdin {
|
|
||||||
process = process.stdin(stdin);
|
|
||||||
}
|
|
||||||
|
|
||||||
trace!(target: "nu::run::external", "set up stdin pipe");
|
|
||||||
trace!(target: "nu::run::external", "built process {:?}", process);
|
|
||||||
|
|
||||||
let popen = process.popen();
|
|
||||||
|
|
||||||
trace!(target: "nu::run::external", "next = {:?}", stream_next);
|
|
||||||
|
|
||||||
let name_tag = self.name_tag.clone();
|
|
||||||
if let Ok(mut popen) = popen {
|
|
||||||
match stream_next {
|
|
||||||
StreamNext::Last => {
|
|
||||||
let _ = popen.detach();
|
|
||||||
loop {
|
|
||||||
match popen.poll() {
|
|
||||||
None => {
|
|
||||||
let _ = std::thread::sleep(std::time::Duration::new(0, 100000000));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let _ = popen.terminate();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(ClassifiedInputStream::new())
|
|
||||||
}
|
|
||||||
StreamNext::External => {
|
|
||||||
let _ = popen.detach();
|
|
||||||
let stdout = popen.stdout.take().unwrap();
|
|
||||||
Ok(ClassifiedInputStream::from_stdout(stdout))
|
|
||||||
}
|
|
||||||
StreamNext::Internal => {
|
|
||||||
let _ = popen.detach();
|
|
||||||
let stdout = popen.stdout.take().unwrap();
|
|
||||||
let file = futures::io::AllowStdIo::new(stdout);
|
|
||||||
let stream = Framed::new(file, LinesCodec {});
|
|
||||||
let stream = stream.map(move |line| {
|
|
||||||
UntaggedValue::string(line.unwrap()).into_value(&name_tag)
|
|
||||||
});
|
|
||||||
Ok(ClassifiedInputStream::from_input_stream(
|
|
||||||
stream.boxed() as BoxStream<'static, Value>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::labeled_error(
|
|
||||||
"Command not found",
|
|
||||||
"command not found",
|
|
||||||
name_tag,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
7
src/commands/classified/dynamic.rs
Normal file
7
src/commands/classified/dynamic.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use crate::parser::hir;
|
||||||
|
use derive_new::new;
|
||||||
|
|
||||||
|
#[derive(new, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) struct Command {
|
||||||
|
pub(crate) args: hir::Call,
|
||||||
|
}
|
259
src/commands/classified/external.rs
Normal file
259
src/commands/classified/external.rs
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
use super::ClassifiedInputStream;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use bytes::{BufMut, BytesMut};
|
||||||
|
use futures::stream::StreamExt;
|
||||||
|
use futures_codec::{Decoder, Encoder, Framed};
|
||||||
|
use log::trace;
|
||||||
|
use std::io::{Error, ErrorKind};
|
||||||
|
use subprocess::Exec;
|
||||||
|
|
||||||
|
/// A simple `Codec` implementation that splits up data into lines.
|
||||||
|
pub struct LinesCodec {}
|
||||||
|
|
||||||
|
impl Encoder for LinesCodec {
|
||||||
|
type Item = String;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
||||||
|
dst.put(item);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decoder for LinesCodec {
|
||||||
|
type Item = String;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||||
|
match src.iter().position(|b| b == &b'\n') {
|
||||||
|
Some(pos) if !src.is_empty() => {
|
||||||
|
let buf = src.split_to(pos + 1);
|
||||||
|
String::from_utf8(buf.to_vec())
|
||||||
|
.map(Some)
|
||||||
|
.map_err(|e| Error::new(ErrorKind::InvalidData, e))
|
||||||
|
}
|
||||||
|
_ if !src.is_empty() => {
|
||||||
|
let drained = src.take();
|
||||||
|
String::from_utf8(drained.to_vec())
|
||||||
|
.map(Some)
|
||||||
|
.map_err(|e| Error::new(ErrorKind::InvalidData, e))
|
||||||
|
}
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct Command {
|
||||||
|
pub(crate) name: String,
|
||||||
|
|
||||||
|
pub(crate) name_tag: Tag,
|
||||||
|
pub(crate) args: ExternalArgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasSpan for Command {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
self.name_tag.span.until(self.args.span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyDebug for Command {
|
||||||
|
fn pretty(&self) -> DebugDocBuilder {
|
||||||
|
b::typed(
|
||||||
|
"external command",
|
||||||
|
b::description(&self.name)
|
||||||
|
+ b::preceded(
|
||||||
|
b::space(),
|
||||||
|
b::intersperse(
|
||||||
|
self.args.iter().map(|a| b::primitive(format!("{}", a.arg))),
|
||||||
|
b::space(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum StreamNext {
|
||||||
|
Last,
|
||||||
|
External,
|
||||||
|
Internal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub(crate) async fn run(
|
||||||
|
self,
|
||||||
|
context: &mut Context,
|
||||||
|
input: ClassifiedInputStream,
|
||||||
|
stream_next: StreamNext,
|
||||||
|
) -> Result<ClassifiedInputStream, ShellError> {
|
||||||
|
let stdin = input.stdin;
|
||||||
|
let inputs: Vec<Value> = input.objects.into_vec().await;
|
||||||
|
|
||||||
|
trace!(target: "nu::run::external", "-> {}", self.name);
|
||||||
|
trace!(target: "nu::run::external", "inputs = {:?}", inputs);
|
||||||
|
|
||||||
|
let mut arg_string = format!("{}", self.name);
|
||||||
|
for arg in &self.args.list {
|
||||||
|
arg_string.push_str(&arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!(target: "nu::run::external", "command = {:?}", self.name);
|
||||||
|
|
||||||
|
let mut process;
|
||||||
|
if arg_string.contains("$it") {
|
||||||
|
let input_strings = inputs
|
||||||
|
.iter()
|
||||||
|
.map(|i| {
|
||||||
|
i.as_string().map_err(|_| {
|
||||||
|
let arg = self.args.iter().find(|arg| arg.arg.contains("$it"));
|
||||||
|
if let Some(arg) = arg {
|
||||||
|
ShellError::labeled_error(
|
||||||
|
"External $it needs string data",
|
||||||
|
"given row instead of string data",
|
||||||
|
&arg.tag,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ShellError::labeled_error(
|
||||||
|
"$it needs string data",
|
||||||
|
"given something else",
|
||||||
|
self.name_tag.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<String>, ShellError>>()?;
|
||||||
|
|
||||||
|
let commands = input_strings.iter().map(|i| {
|
||||||
|
let args = self.args.iter().filter_map(|arg| {
|
||||||
|
if arg.chars().all(|c| c.is_whitespace()) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(arg.replace("$it", &i))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
format!("{} {}", self.name, itertools::join(args, " "))
|
||||||
|
});
|
||||||
|
|
||||||
|
process = Exec::shell(itertools::join(commands, " && "))
|
||||||
|
} else {
|
||||||
|
process = Exec::cmd(&self.name);
|
||||||
|
for arg in &self.args.list {
|
||||||
|
let arg_chars: Vec<_> = arg.chars().collect();
|
||||||
|
if arg_chars.len() > 1
|
||||||
|
&& arg_chars[0] == '"'
|
||||||
|
&& arg_chars[arg_chars.len() - 1] == '"'
|
||||||
|
{
|
||||||
|
// quoted string
|
||||||
|
let new_arg: String = arg_chars[1..arg_chars.len() - 1].iter().collect();
|
||||||
|
process = process.arg(new_arg);
|
||||||
|
} else {
|
||||||
|
process = process.arg(arg.arg.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process = process.cwd(context.shell_manager.path());
|
||||||
|
|
||||||
|
trace!(target: "nu::run::external", "cwd = {:?}", context.shell_manager.path());
|
||||||
|
|
||||||
|
let mut process = match stream_next {
|
||||||
|
StreamNext::Last => process,
|
||||||
|
StreamNext::External | StreamNext::Internal => {
|
||||||
|
process.stdout(subprocess::Redirection::Pipe)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!(target: "nu::run::external", "set up stdout pipe");
|
||||||
|
|
||||||
|
if let Some(stdin) = stdin {
|
||||||
|
process = process.stdin(stdin);
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!(target: "nu::run::external", "set up stdin pipe");
|
||||||
|
trace!(target: "nu::run::external", "built process {:?}", process);
|
||||||
|
|
||||||
|
let popen = process.popen();
|
||||||
|
|
||||||
|
trace!(target: "nu::run::external", "next = {:?}", stream_next);
|
||||||
|
|
||||||
|
let name_tag = self.name_tag.clone();
|
||||||
|
if let Ok(mut popen) = popen {
|
||||||
|
match stream_next {
|
||||||
|
StreamNext::Last => {
|
||||||
|
let _ = popen.detach();
|
||||||
|
loop {
|
||||||
|
match popen.poll() {
|
||||||
|
None => {
|
||||||
|
let _ = std::thread::sleep(std::time::Duration::new(0, 100000000));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let _ = popen.terminate();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ClassifiedInputStream::new())
|
||||||
|
}
|
||||||
|
StreamNext::External => {
|
||||||
|
let _ = popen.detach();
|
||||||
|
let stdout = popen.stdout.take().unwrap();
|
||||||
|
Ok(ClassifiedInputStream::from_stdout(stdout))
|
||||||
|
}
|
||||||
|
StreamNext::Internal => {
|
||||||
|
let _ = popen.detach();
|
||||||
|
let stdout = popen.stdout.take().unwrap();
|
||||||
|
let file = futures::io::AllowStdIo::new(stdout);
|
||||||
|
let stream = Framed::new(file, LinesCodec {});
|
||||||
|
let stream = stream.map(move |line| {
|
||||||
|
UntaggedValue::string(line.unwrap()).into_value(&name_tag)
|
||||||
|
});
|
||||||
|
Ok(ClassifiedInputStream::from_input_stream(
|
||||||
|
stream.boxed() as BoxStream<'static, Value>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"Command not found",
|
||||||
|
"command not found",
|
||||||
|
name_tag,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct ExternalArg {
|
||||||
|
pub arg: String,
|
||||||
|
pub tag: Tag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for ExternalArg {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &str {
|
||||||
|
&self.arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct ExternalArgs {
|
||||||
|
pub list: Vec<ExternalArg>,
|
||||||
|
pub span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExternalArgs {
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &ExternalArg> {
|
||||||
|
self.list.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for ExternalArgs {
|
||||||
|
type Target = [ExternalArg];
|
||||||
|
|
||||||
|
fn deref(&self) -> &[ExternalArg] {
|
||||||
|
&self.list
|
||||||
|
}
|
||||||
|
}
|
147
src/commands/classified/internal.rs
Normal file
147
src/commands/classified/internal.rs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
use crate::parser::hir;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use derive_new::new;
|
||||||
|
use log::{log_enabled, trace};
|
||||||
|
|
||||||
|
use super::ClassifiedInputStream;
|
||||||
|
|
||||||
|
#[derive(new, Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct Command {
|
||||||
|
pub(crate) name: String,
|
||||||
|
pub(crate) name_tag: Tag,
|
||||||
|
pub(crate) args: hir::Call,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasSpan for Command {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
let start = self.name_tag.span;
|
||||||
|
|
||||||
|
start.until(self.args.span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyDebugWithSource for Command {
|
||||||
|
fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
|
||||||
|
b::typed(
|
||||||
|
"internal command",
|
||||||
|
b::description(&self.name) + b::space() + self.args.pretty_debug(source),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub(crate) fn run(
|
||||||
|
self,
|
||||||
|
context: &mut Context,
|
||||||
|
input: ClassifiedInputStream,
|
||||||
|
source: Text,
|
||||||
|
) -> Result<InputStream, ShellError> {
|
||||||
|
if log_enabled!(log::Level::Trace) {
|
||||||
|
trace!(target: "nu::run::internal", "->");
|
||||||
|
trace!(target: "nu::run::internal", "{}", self.name);
|
||||||
|
trace!(target: "nu::run::internal", "{}", self.args.debug(&source));
|
||||||
|
}
|
||||||
|
|
||||||
|
let objects: InputStream =
|
||||||
|
trace_stream!(target: "nu::trace_stream::internal", "input" = input.objects);
|
||||||
|
|
||||||
|
let command = context.expect_command(&self.name);
|
||||||
|
|
||||||
|
let result =
|
||||||
|
{ context.run_command(command, self.name_tag.clone(), self.args, &source, objects) };
|
||||||
|
|
||||||
|
let result = trace_out_stream!(target: "nu::trace_stream::internal", "output" = result);
|
||||||
|
let mut result = result.values;
|
||||||
|
let mut context = context.clone();
|
||||||
|
|
||||||
|
let stream = async_stream! {
|
||||||
|
let mut soft_errs: Vec<ShellError> = vec![];
|
||||||
|
let mut yielded = false;
|
||||||
|
|
||||||
|
while let Some(item) = result.next().await {
|
||||||
|
match item {
|
||||||
|
Ok(ReturnSuccess::Action(action)) => match action {
|
||||||
|
CommandAction::ChangePath(path) => {
|
||||||
|
context.shell_manager.set_path(path);
|
||||||
|
}
|
||||||
|
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
|
||||||
|
CommandAction::Error(err) => {
|
||||||
|
context.error(err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
CommandAction::EnterHelpShell(value) => {
|
||||||
|
match value {
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::String(cmd)),
|
||||||
|
tag,
|
||||||
|
} => {
|
||||||
|
context.shell_manager.insert_at_current(Box::new(
|
||||||
|
HelpShell::for_command(
|
||||||
|
UntaggedValue::string(cmd).into_value(tag),
|
||||||
|
&context.registry(),
|
||||||
|
).unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
context.shell_manager.insert_at_current(Box::new(
|
||||||
|
HelpShell::index(&context.registry()).unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandAction::EnterValueShell(value) => {
|
||||||
|
context
|
||||||
|
.shell_manager
|
||||||
|
.insert_at_current(Box::new(ValueShell::new(value)));
|
||||||
|
}
|
||||||
|
CommandAction::EnterShell(location) => {
|
||||||
|
context.shell_manager.insert_at_current(Box::new(
|
||||||
|
FilesystemShell::with_location(location, context.registry().clone()).unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
CommandAction::PreviousShell => {
|
||||||
|
context.shell_manager.prev();
|
||||||
|
}
|
||||||
|
CommandAction::NextShell => {
|
||||||
|
context.shell_manager.next();
|
||||||
|
}
|
||||||
|
CommandAction::LeaveShell => {
|
||||||
|
context.shell_manager.remove_at_current();
|
||||||
|
if context.shell_manager.is_empty() {
|
||||||
|
std::process::exit(0); // TODO: save history.txt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(ReturnSuccess::Value(v)) => {
|
||||||
|
yielded = true;
|
||||||
|
yield Ok(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ReturnSuccess::DebugValue(v)) => {
|
||||||
|
yielded = true;
|
||||||
|
|
||||||
|
let doc = PrettyDebug::pretty_doc(&v);
|
||||||
|
let mut buffer = termcolor::Buffer::ansi();
|
||||||
|
|
||||||
|
doc.render_raw(
|
||||||
|
context.with_host(|host| host.width() - 5),
|
||||||
|
&mut crate::parser::debug::TermColored::new(&mut buffer),
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
let value = String::from_utf8_lossy(buffer.as_slice());
|
||||||
|
|
||||||
|
yield Ok(UntaggedValue::string(value).into_untagged_value())
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(err) => {
|
||||||
|
context.error(err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(stream.to_input_stream())
|
||||||
|
}
|
||||||
|
}
|
74
src/commands/classified/mod.rs
Normal file
74
src/commands/classified/mod.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use crate::parser::{hir, TokenNode};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
mod dynamic;
|
||||||
|
mod external;
|
||||||
|
mod internal;
|
||||||
|
mod pipeline;
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
pub(crate) use dynamic::Command as DynamicCommand;
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
pub(crate) use external::{Command as ExternalCommand, ExternalArg, ExternalArgs, StreamNext};
|
||||||
|
pub(crate) use internal::Command as InternalCommand;
|
||||||
|
pub(crate) use pipeline::Pipeline as ClassifiedPipeline;
|
||||||
|
|
||||||
|
pub(crate) struct ClassifiedInputStream {
|
||||||
|
pub(crate) objects: InputStream,
|
||||||
|
pub(crate) stdin: Option<std::fs::File>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClassifiedInputStream {
|
||||||
|
pub(crate) fn new() -> ClassifiedInputStream {
|
||||||
|
ClassifiedInputStream {
|
||||||
|
objects: vec![UntaggedValue::nothing().into_untagged_value()].into(),
|
||||||
|
stdin: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_input_stream(stream: impl Into<InputStream>) -> ClassifiedInputStream {
|
||||||
|
ClassifiedInputStream {
|
||||||
|
objects: stream.into(),
|
||||||
|
stdin: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_stdout(stdout: std::fs::File) -> ClassifiedInputStream {
|
||||||
|
ClassifiedInputStream {
|
||||||
|
objects: VecDeque::new().into(),
|
||||||
|
stdin: Some(stdout),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub enum ClassifiedCommand {
|
||||||
|
#[allow(unused)]
|
||||||
|
Expr(TokenNode),
|
||||||
|
#[allow(unused)]
|
||||||
|
Dynamic(hir::Call),
|
||||||
|
Internal(InternalCommand),
|
||||||
|
External(ExternalCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyDebugWithSource for ClassifiedCommand {
|
||||||
|
fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
|
||||||
|
match self {
|
||||||
|
ClassifiedCommand::Expr(token) => b::typed("command", token.pretty_debug(source)),
|
||||||
|
ClassifiedCommand::Dynamic(call) => b::typed("command", call.pretty_debug(source)),
|
||||||
|
ClassifiedCommand::Internal(internal) => internal.pretty_debug(source),
|
||||||
|
ClassifiedCommand::External(external) => external.pretty_debug(source),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasSpan for ClassifiedCommand {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
ClassifiedCommand::Expr(node) => node.span(),
|
||||||
|
ClassifiedCommand::Internal(command) => command.span(),
|
||||||
|
ClassifiedCommand::Dynamic(call) => call.span,
|
||||||
|
ClassifiedCommand::External(command) => command.span(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
src/commands/classified/pipeline.rs
Normal file
40
src/commands/classified/pipeline.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
use super::ClassifiedCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct Pipeline {
|
||||||
|
pub(crate) commands: ClassifiedCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pipeline {
|
||||||
|
pub fn commands(list: Vec<ClassifiedCommand>, span: impl Into<Span>) -> Pipeline {
|
||||||
|
Pipeline {
|
||||||
|
commands: ClassifiedCommands {
|
||||||
|
list,
|
||||||
|
span: span.into(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ClassifiedCommands {
|
||||||
|
pub list: Vec<ClassifiedCommand>,
|
||||||
|
pub span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasSpan for Pipeline {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
self.commands.span
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyDebugWithSource for Pipeline {
|
||||||
|
fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
|
||||||
|
b::intersperse(
|
||||||
|
self.commands.list.iter().map(|c| c.pretty_debug(source)),
|
||||||
|
b::operator(" | "),
|
||||||
|
)
|
||||||
|
.or(b::delimit("<", b::description("empty pipeline"), ">"))
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,8 @@ use nu_source::Tagged;
|
|||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct NthArgs {
|
struct NthArgs {
|
||||||
amount: Tagged<i64>,
|
row_number: Tagged<u64>,
|
||||||
|
rest: Vec<Tagged<u64>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Nth;
|
pub struct Nth;
|
||||||
@ -17,15 +18,17 @@ impl WholeStreamCommand for Nth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("nth").required(
|
Signature::build("nth")
|
||||||
"row number",
|
.required(
|
||||||
SyntaxShape::Any,
|
"row number",
|
||||||
"the number of the row to return",
|
SyntaxShape::Any,
|
||||||
)
|
"the number of the row to return",
|
||||||
|
)
|
||||||
|
.rest(SyntaxShape::Any, "Optionally return more rows")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Return only the selected row"
|
"Return only the selected rows"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -38,10 +41,35 @@ impl WholeStreamCommand for Nth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn nth(
|
fn nth(
|
||||||
NthArgs { amount }: NthArgs,
|
NthArgs {
|
||||||
|
row_number,
|
||||||
|
rest: and_rows,
|
||||||
|
}: NthArgs,
|
||||||
RunnableContext { input, .. }: RunnableContext,
|
RunnableContext { input, .. }: RunnableContext,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
Ok(OutputStream::from_input(
|
let stream = input
|
||||||
input.values.skip(amount.item as u64).take(1),
|
.values
|
||||||
))
|
.enumerate()
|
||||||
|
.map(move |(idx, item)| {
|
||||||
|
let row_number = vec![row_number.clone()];
|
||||||
|
|
||||||
|
let row_numbers = vec![&row_number, &and_rows]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<&Tagged<u64>>>();
|
||||||
|
|
||||||
|
let mut result = VecDeque::new();
|
||||||
|
|
||||||
|
if row_numbers
|
||||||
|
.iter()
|
||||||
|
.any(|requested| requested.item == idx as u64)
|
||||||
|
{
|
||||||
|
result.push_back(ReturnSuccess::value(item.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
})
|
||||||
|
.flatten();
|
||||||
|
|
||||||
|
Ok(stream.to_output_stream())
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::data::base::Block;
|
use crate::data::base::Block;
|
||||||
use crate::errors::ArgumentError;
|
use crate::errors::ArgumentError;
|
||||||
|
use crate::evaluate::operator::apply_operator;
|
||||||
use crate::parser::hir::path::{ColumnPath, UnspannedPathMember};
|
use crate::parser::hir::path::{ColumnPath, UnspannedPathMember};
|
||||||
use crate::parser::{
|
use crate::parser::{
|
||||||
hir::{self, Expression, RawExpression},
|
hir::{self, Expression, RawExpression},
|
||||||
@ -71,8 +72,8 @@ pub(crate) fn evaluate_baseline_expr(
|
|||||||
|
|
||||||
trace!("left={:?} right={:?}", left.value, right.value);
|
trace!("left={:?} right={:?}", left.value, right.value);
|
||||||
|
|
||||||
match left.compare(binary.op(), &right) {
|
match apply_operator(binary.op(), &left, &right) {
|
||||||
Ok(result) => Ok(UntaggedValue::boolean(result).into_value(tag)),
|
Ok(result) => Ok(result.into_value(tag)),
|
||||||
Err((left_type, right_type)) => Err(ShellError::coerce_error(
|
Err((left_type, right_type)) => Err(ShellError::coerce_error(
|
||||||
left_type.spanned(binary.left().span),
|
left_type.spanned(binary.left().span),
|
||||||
right_type.spanned(binary.right().span),
|
right_type.spanned(binary.right().span),
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
pub(crate) mod evaluator;
|
pub(crate) mod evaluator;
|
||||||
|
pub(crate) mod operator;
|
||||||
|
|
||||||
pub(crate) use evaluator::{evaluate_baseline_expr, Scope};
|
pub(crate) use evaluator::{evaluate_baseline_expr, Scope};
|
||||||
|
39
src/evaluate/operator.rs
Normal file
39
src/evaluate/operator.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use crate::data::base::{Primitive, UntaggedValue, Value};
|
||||||
|
use crate::parser::Operator;
|
||||||
|
use crate::traits::ShellTypeName;
|
||||||
|
use std::ops::Not;
|
||||||
|
|
||||||
|
pub fn apply_operator(
|
||||||
|
op: &Operator,
|
||||||
|
left: &Value,
|
||||||
|
right: &Value,
|
||||||
|
) -> Result<UntaggedValue, (&'static str, &'static str)> {
|
||||||
|
match *op {
|
||||||
|
Operator::Equal
|
||||||
|
| Operator::NotEqual
|
||||||
|
| Operator::LessThan
|
||||||
|
| Operator::GreaterThan
|
||||||
|
| Operator::LessThanOrEqual
|
||||||
|
| Operator::GreaterThanOrEqual => left.compare(op, right).map(UntaggedValue::boolean),
|
||||||
|
Operator::Dot => Ok(UntaggedValue::boolean(false)),
|
||||||
|
Operator::Contains => contains(left, right).map(UntaggedValue::boolean),
|
||||||
|
Operator::NotContains => contains(left, right)
|
||||||
|
.map(Not::not)
|
||||||
|
.map(UntaggedValue::boolean),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains(
|
||||||
|
left: &UntaggedValue,
|
||||||
|
right: &UntaggedValue,
|
||||||
|
) -> Result<bool, (&'static str, &'static str)> {
|
||||||
|
if let (
|
||||||
|
UntaggedValue::Primitive(Primitive::String(l)),
|
||||||
|
UntaggedValue::Primitive(Primitive::String(r)),
|
||||||
|
) = (left, right)
|
||||||
|
{
|
||||||
|
Ok(l.contains(r))
|
||||||
|
} else {
|
||||||
|
Err((left.type_name(), right.type_name()))
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,8 @@ pub enum Operator {
|
|||||||
LessThanOrEqual,
|
LessThanOrEqual,
|
||||||
GreaterThanOrEqual,
|
GreaterThanOrEqual,
|
||||||
Dot,
|
Dot,
|
||||||
|
Contains,
|
||||||
|
NotContains,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrettyDebug for Operator {
|
impl PrettyDebug for Operator {
|
||||||
@ -35,6 +37,8 @@ impl Operator {
|
|||||||
Operator::LessThanOrEqual => "<=",
|
Operator::LessThanOrEqual => "<=",
|
||||||
Operator::GreaterThanOrEqual => ">=",
|
Operator::GreaterThanOrEqual => ">=",
|
||||||
Operator::Dot => ".",
|
Operator::Dot => ".",
|
||||||
|
Operator::Contains => "=~",
|
||||||
|
Operator::NotContains => "!~",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,6 +60,8 @@ impl FromStr for Operator {
|
|||||||
"<=" => Ok(Operator::LessThanOrEqual),
|
"<=" => Ok(Operator::LessThanOrEqual),
|
||||||
">=" => Ok(Operator::GreaterThanOrEqual),
|
">=" => Ok(Operator::GreaterThanOrEqual),
|
||||||
"." => Ok(Operator::Dot),
|
"." => Ok(Operator::Dot),
|
||||||
|
"=~" => Ok(Operator::Contains),
|
||||||
|
"!~" => Ok(Operator::NotContains),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ macro_rules! operator {
|
|||||||
#[tracable_parser]
|
#[tracable_parser]
|
||||||
pub fn $name(input: NomSpan) -> IResult<NomSpan, TokenNode> {
|
pub fn $name(input: NomSpan) -> IResult<NomSpan, TokenNode> {
|
||||||
let start = input.offset;
|
let start = input.offset;
|
||||||
let (input, tag) = tag(stringify!($token))(input)?;
|
let (input, tag) = tag($token)(input)?;
|
||||||
let end = input.offset;
|
let end = input.offset;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
@ -41,13 +41,15 @@ macro_rules! operator {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
operator! { gt: > }
|
operator! { gt: ">" }
|
||||||
operator! { lt: < }
|
operator! { lt: "<" }
|
||||||
operator! { gte: >= }
|
operator! { gte: ">=" }
|
||||||
operator! { lte: <= }
|
operator! { lte: "<=" }
|
||||||
operator! { eq: == }
|
operator! { eq: "==" }
|
||||||
operator! { neq: != }
|
operator! { neq: "!=" }
|
||||||
operator! { dot: . }
|
operator! { dot: "." }
|
||||||
|
operator! { cont: "=~" }
|
||||||
|
operator! { ncont: "!~" }
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
|
||||||
pub enum Number {
|
pub enum Number {
|
||||||
@ -205,7 +207,7 @@ pub fn raw_number(input: NomSpan) -> IResult<NomSpan, RawNumber> {
|
|||||||
|
|
||||||
#[tracable_parser]
|
#[tracable_parser]
|
||||||
pub fn operator(input: NomSpan) -> IResult<NomSpan, TokenNode> {
|
pub fn operator(input: NomSpan) -> IResult<NomSpan, TokenNode> {
|
||||||
let (input, operator) = alt((gte, lte, neq, gt, lt, eq))(input)?;
|
let (input, operator) = alt((gte, lte, neq, gt, lt, eq, cont, ncont))(input)?;
|
||||||
|
|
||||||
Ok((input, operator))
|
Ok((input, operator))
|
||||||
}
|
}
|
||||||
@ -805,6 +807,16 @@ mod tests {
|
|||||||
<nodes>
|
<nodes>
|
||||||
"!=" -> b::token_list(vec![b::op("!=")])
|
"!=" -> b::token_list(vec![b::op("!=")])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
equal_tokens! {
|
||||||
|
<nodes>
|
||||||
|
"=~" -> b::token_list(vec![b::op("=~")])
|
||||||
|
}
|
||||||
|
|
||||||
|
equal_tokens! {
|
||||||
|
<nodes>
|
||||||
|
"!~" -> b::token_list(vec![b::op("!~")])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -3,6 +3,45 @@ mod helpers;
|
|||||||
use helpers as h;
|
use helpers as h;
|
||||||
use helpers::{Playground, Stub::*};
|
use helpers::{Playground, Stub::*};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nth_selects_a_row() {
|
||||||
|
Playground::setup("nth_test_1", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![EmptyFile("notes.txt"), EmptyFile("arepas.txt")]);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(), h::pipeline(
|
||||||
|
r#"
|
||||||
|
ls
|
||||||
|
| sort-by name
|
||||||
|
| nth 0
|
||||||
|
| get name
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "arepas.txt");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nth_selects_many_rows() {
|
||||||
|
Playground::setup("nth_test_2", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![EmptyFile("notes.txt"), EmptyFile("arepas.txt")]);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(), h::pipeline(
|
||||||
|
r#"
|
||||||
|
ls
|
||||||
|
| get name
|
||||||
|
| nth 1 0
|
||||||
|
| count
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "2");
|
||||||
|
});
|
||||||
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn default_row_data_if_column_missing() {
|
fn default_row_data_if_column_missing() {
|
||||||
Playground::setup("default_test_1", |dirs, sandbox| {
|
Playground::setup("default_test_1", |dirs, sandbox| {
|
||||||
|
112
tests/filter_where_tests.rs
Normal file
112
tests/filter_where_tests.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
mod helpers;
|
||||||
|
|
||||||
|
use helpers as h;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compare() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", h::pipeline(
|
||||||
|
r#"
|
||||||
|
open sample.db
|
||||||
|
| where table_name == ints
|
||||||
|
| get table_values
|
||||||
|
| first 4
|
||||||
|
| where z > 4200
|
||||||
|
| get z
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "4253");
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", h::pipeline(
|
||||||
|
r#"
|
||||||
|
open sample.db
|
||||||
|
| where table_name == ints
|
||||||
|
| get table_values
|
||||||
|
| first 4
|
||||||
|
| where z >= 4253
|
||||||
|
| get z
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "4253");
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", h::pipeline(
|
||||||
|
r#"
|
||||||
|
open sample.db
|
||||||
|
| where table_name == ints
|
||||||
|
| get table_values
|
||||||
|
| first 4
|
||||||
|
| where z < 10
|
||||||
|
| get z
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "1");
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", h::pipeline(
|
||||||
|
r#"
|
||||||
|
open sample.db
|
||||||
|
| where table_name == ints
|
||||||
|
| get table_values
|
||||||
|
| first 4
|
||||||
|
| where z <= 1
|
||||||
|
| get z
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "1");
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", h::pipeline(
|
||||||
|
r#"
|
||||||
|
open sample.db
|
||||||
|
| where table_name == ints
|
||||||
|
| get table_values
|
||||||
|
| where z != 1
|
||||||
|
| first 1
|
||||||
|
| get z
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "42");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_contains() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", h::pipeline(
|
||||||
|
r#"
|
||||||
|
open sample.db
|
||||||
|
| where table_name == strings
|
||||||
|
| get table_values
|
||||||
|
| where x =~ ell
|
||||||
|
| count
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "4");
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", h::pipeline(
|
||||||
|
r#"
|
||||||
|
open sample.db
|
||||||
|
| where table_name == strings
|
||||||
|
| get table_values
|
||||||
|
| where x !~ ell
|
||||||
|
| count
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "2");
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user