Merge remote-tracking branch 'origin/master' into cleanup-wip

This commit is contained in:
Yehuda Katz 2019-11-25 19:25:12 -08:00
commit 2eae5a2a89
15 changed files with 788 additions and 536 deletions

View File

@ -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 |

View File

@ -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,
));
}
}
}

View 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,
}

View 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
}
}

View 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())
}
}

View 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(),
}
}
}

View 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"), ">"))
}
}

View File

@ -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())
} }

View File

@ -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),

View File

@ -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
View 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()))
}
}

View File

@ -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(()),
} }
} }

View File

@ -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]

View File

@ -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
View 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");
}