"maybe text codec" version 2 ()

* Add a RawStream that can be binary or string

* Finish up updating the into's
This commit is contained in:
JT 2022-01-28 13:32:33 -05:00 committed by GitHub
parent 3f9fa28ae3
commit 020ad24b25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 326 additions and 433 deletions

View File

@ -2,7 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -98,7 +99,15 @@ fn into_binary(
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?; let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
match input { match input {
PipelineData::ByteStream(..) => Ok(input), PipelineData::RawStream(stream, ..) => {
// TODO: in the future, we may want this to stream out, converting each to bytes
let output = stream.into_bytes()?;
Ok(Value::Binary {
val: output,
span: head,
}
.into_pipeline_data())
}
_ => input.map( _ => input.map(
move |v| { move |v| {
if column_paths.is_empty() { if column_paths.is_empty() {

View File

@ -2,7 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Config, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Value,
}; };
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml) // TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
@ -148,30 +149,41 @@ fn string_helper(
} }
} }
input.map( match input {
move |v| { PipelineData::RawStream(stream, ..) => {
if column_paths.is_empty() { // TODO: in the future, we may want this to stream out, converting each to bytes
action(&v, head, decimals, decimals_value, false, &config) let output = stream.into_string()?;
} else { Ok(Value::String {
let mut ret = v; val: output,
for path in &column_paths { span: head,
let config = config.clone();
let r = ret.update_cell_path(
&path.members,
Box::new(move |old| {
action(old, head, decimals, decimals_value, false, &config)
}),
);
if let Err(error) = r {
return Value::Error { error };
}
}
ret
} }
}, .into_pipeline_data())
engine_state.ctrlc.clone(), }
) _ => input.map(
move |v| {
if column_paths.is_empty() {
action(&v, head, decimals, decimals_value, false, &config)
} else {
let mut ret = v;
for path in &column_paths {
let config = config.clone();
let r = ret.update_cell_path(
&path.members,
Box::new(move |old| {
action(old, head, decimals, decimals_value, false, &config)
}),
);
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
),
}
} }
pub fn action( pub fn action(

View File

@ -26,9 +26,9 @@ impl Command for Describe {
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
if matches!(input, PipelineData::ByteStream(..)) { if matches!(input, PipelineData::RawStream(..)) {
Ok(PipelineData::Value( Ok(PipelineData::Value(
Value::string("binary", call.head), Value::string("raw input", call.head),
None, None,
)) ))
} else { } else {

View File

@ -2,7 +2,7 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Value, ValueStream, Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -35,7 +35,7 @@ impl Command for Echo {
match n.cmp(&1usize) { match n.cmp(&1usize) {
// More than one value is converted in a stream of values // More than one value is converted in a stream of values
std::cmp::Ordering::Greater => PipelineData::ListStream( std::cmp::Ordering::Greater => PipelineData::ListStream(
ValueStream::from_stream(to_be_echoed.into_iter(), engine_state.ctrlc.clone()), ListStream::from_stream(to_be_echoed.into_iter(), engine_state.ctrlc.clone()),
None, None,
), ),

View File

@ -2,7 +2,7 @@ use nu_engine::{get_full_help, CallExt};
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
ByteStream, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, Category, Example, IntoPipelineData, PipelineData, RawStream, ShellError, Signature, Spanned,
SyntaxShape, Value, SyntaxShape, Value,
}; };
use std::io::{BufRead, BufReader, Read}; use std::io::{BufRead, BufReader, Read};
@ -120,11 +120,8 @@ impl Command for Open {
let buf_reader = BufReader::new(file); let buf_reader = BufReader::new(file);
let output = PipelineData::ByteStream( let output = PipelineData::RawStream(
ByteStream { RawStream::new(Box::new(BufferedReader { input: buf_reader }), ctrlc),
stream: Box::new(BufferedReader { input: buf_reader }),
ctrlc,
},
call_span, call_span,
None, None,
); );

View File

@ -82,7 +82,7 @@ fn getcol(
.map(move |x| Value::String { val: x, span }) .map(move |x| Value::String { val: x, span })
.into_pipeline_data(engine_state.ctrlc.clone())) .into_pipeline_data(engine_state.ctrlc.clone()))
} }
PipelineData::Value(..) | PipelineData::StringStream(..) | PipelineData::ByteStream(..) => { PipelineData::Value(..) | PipelineData::RawStream(..) => {
let cols = vec![]; let cols = vec![];
let vals = vec![]; let vals = vec![];
Ok(Value::Record { cols, vals, span }.into_pipeline_data()) Ok(Value::Record { cols, vals, span }.into_pipeline_data())

View File

@ -111,54 +111,14 @@ impl Command for Each {
} }
}) })
.into_pipeline_data(ctrlc)), .into_pipeline_data(ctrlc)),
PipelineData::ByteStream(stream, ..) => Ok(stream PipelineData::RawStream(stream, ..) => Ok(stream
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(move |(idx, x)| { .map(move |(idx, x)| {
stack.with_env(&orig_env_vars, &orig_env_hidden); stack.with_env(&orig_env_vars, &orig_env_hidden);
let x = match x { let x = match x {
Ok(x) => Value::Binary { val: x, span }, Ok(x) => x,
Err(err) => return Value::Error { error: err },
};
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
if numbered {
stack.add_var(
*var_id,
Value::Record {
cols: vec!["index".into(), "item".into()],
vals: vec![
Value::Int {
val: idx as i64,
span,
},
x,
],
span,
},
);
} else {
stack.add_var(*var_id, x);
}
}
}
match eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)) {
Ok(v) => v.into_value(span),
Err(error) => Value::Error { error },
}
})
.into_pipeline_data(ctrlc)),
PipelineData::StringStream(stream, ..) => Ok(stream
.into_iter()
.enumerate()
.map(move |(idx, x)| {
stack.with_env(&orig_env_vars, &orig_env_hidden);
let x = match x {
Ok(x) => Value::String { val: x, span },
Err(err) => return Value::Error { error: err }, Err(err) => return Value::Error { error: err },
}; };

View File

@ -96,7 +96,7 @@ fn getcol(
.map(move |x| Value::String { val: x, span }) .map(move |x| Value::String { val: x, span })
.into_pipeline_data(engine_state.ctrlc.clone())) .into_pipeline_data(engine_state.ctrlc.clone()))
} }
PipelineData::Value(..) | PipelineData::StringStream(..) | PipelineData::ByteStream(..) => { PipelineData::Value(..) | PipelineData::RawStream(..) => {
let cols = vec![]; let cols = vec![];
let vals = vec![]; let vals = vec![];
Ok(Value::Record { cols, vals, span }.into_pipeline_data()) Ok(Value::Record { cols, vals, span }.into_pipeline_data())

View File

@ -88,41 +88,11 @@ impl Command for Lines {
Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) Ok(iter.into_pipeline_data(engine_state.ctrlc.clone()))
} }
PipelineData::StringStream(stream, span, ..) => {
let mut split_char = "\n";
let iter = stream
.into_iter()
.map(move |value| match value {
Ok(value) => {
if split_char != "\r\n" && value.contains("\r\n") {
split_char = "\r\n";
}
value
.split(split_char)
.filter_map(|s| {
if !s.is_empty() {
Some(Value::String {
val: s.into(),
span,
})
} else {
None
}
})
.collect::<Vec<Value>>()
}
Err(err) => vec![Value::Error { error: err }],
})
.flatten();
Ok(iter.into_pipeline_data(engine_state.ctrlc.clone()))
}
PipelineData::Value(val, ..) => Err(ShellError::UnsupportedInput( PipelineData::Value(val, ..) => Err(ShellError::UnsupportedInput(
format!("Not supported input: {}", val.as_string()?), format!("Not supported input: {}", val.as_string()?),
call.head, call.head,
)), )),
PipelineData::ByteStream(..) => { PipelineData::RawStream(..) => {
let config = stack.get_config()?; let config = stack.get_config()?;
//FIXME: Make sure this can fail in the future to let the user //FIXME: Make sure this can fail in the future to let the user

View File

@ -177,56 +177,12 @@ impl Command for ParEach {
.into_iter() .into_iter()
.flatten() .flatten()
.into_pipeline_data(ctrlc)), .into_pipeline_data(ctrlc)),
PipelineData::StringStream(stream, ..) => Ok(stream PipelineData::RawStream(stream, ..) => Ok(stream
.enumerate() .enumerate()
.par_bridge() .par_bridge()
.map(move |(idx, x)| { .map(move |(idx, x)| {
let x = match x { let x = match x {
Ok(x) => Value::String { val: x, span }, Ok(x) => x,
Err(err) => return Value::Error { error: err }.into_pipeline_data(),
};
let block = engine_state.get_block(block_id);
let mut stack = stack.clone();
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
if numbered {
stack.add_var(
*var_id,
Value::Record {
cols: vec!["index".into(), "item".into()],
vals: vec![
Value::Int {
val: idx as i64,
span,
},
x,
],
span,
},
);
} else {
stack.add_var(*var_id, x);
}
}
}
match eval_block(&engine_state, &mut stack, block, PipelineData::new(span)) {
Ok(v) => v,
Err(error) => Value::Error { error }.into_pipeline_data(),
}
})
.collect::<Vec<_>>()
.into_iter()
.flatten()
.into_pipeline_data(ctrlc)),
PipelineData::ByteStream(stream, ..) => Ok(stream
.enumerate()
.par_bridge()
.map(move |(idx, x)| {
let x = match x {
Ok(x) => Value::Binary { val: x, span },
Err(err) => return Value::Error { error: err }.into_pipeline_data(), Err(err) => return Value::Error { error: err }.into_pipeline_data(),
}; };

View File

@ -50,13 +50,9 @@ impl Command for Wrap {
span, span,
}) })
.into_pipeline_data(engine_state.ctrlc.clone())), .into_pipeline_data(engine_state.ctrlc.clone())),
PipelineData::StringStream(stream, ..) => Ok(Value::String { PipelineData::RawStream(..) => Ok(Value::Record {
val: stream.into_string("")?, cols: vec![name],
span, vals: vec![input.into_value(call.head)],
}
.into_pipeline_data()),
PipelineData::ByteStream(stream, ..) => Ok(Value::Binary {
val: stream.into_vec()?,
span, span,
} }
.into_pipeline_data()), .into_pipeline_data()),

View File

@ -2,7 +2,7 @@ use base64::encode;
use nu_engine::CallExt; use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::ByteStream; use nu_protocol::RawStream;
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
@ -356,13 +356,13 @@ fn response_to_buffer(
) -> nu_protocol::PipelineData { ) -> nu_protocol::PipelineData {
let buffered_input = BufReader::new(response); let buffered_input = BufReader::new(response);
PipelineData::ByteStream( PipelineData::RawStream(
ByteStream { RawStream::new(
stream: Box::new(BufferedReader { Box::new(BufferedReader {
input: buffered_input, input: buffered_input,
}), }),
ctrlc: engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
}, ),
span, span,
None, None,
) )

View File

@ -5,8 +5,8 @@ use std::{
use nu_engine::CallExt; use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
engine::Command, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, engine::Command, Example, ListStream, PipelineData, ShellError, Signature, Span, Spanned,
Value, ValueStream, SyntaxShape, Value,
}; };
use super::PathSubcommandArguments; use super::PathSubcommandArguments;
@ -68,7 +68,7 @@ the output of 'path parse' and 'path split' subcommands."#
Ok(PipelineData::Value(handle_value(val, &args, head), md)) Ok(PipelineData::Value(handle_value(val, &args, head), md))
} }
PipelineData::ListStream(stream, md) => Ok(PipelineData::ListStream( PipelineData::ListStream(stream, md) => Ok(PipelineData::ListStream(
ValueStream::from_stream( ListStream::from_stream(
stream.map(move |val| handle_value(val, &args, head)), stream.map(move |val| handle_value(val, &args, head)),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
), ),

View File

@ -2,7 +2,7 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Value, ValueStream, Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Value,
}; };
use rand::prelude::{thread_rng, Rng}; use rand::prelude::{thread_rng, Rng};
@ -80,7 +80,7 @@ fn dice(
}); });
Ok(PipelineData::ListStream( Ok(PipelineData::ListStream(
ValueStream::from_stream(iter, engine_state.ctrlc.clone()), ListStream::from_stream(iter, engine_state.ctrlc.clone()),
None, None,
)) ))
} }

View File

@ -44,8 +44,8 @@ impl Command for Decode {
let encoding: Spanned<String> = call.req(engine_state, stack, 0)?; let encoding: Spanned<String> = call.req(engine_state, stack, 0)?;
match input { match input {
PipelineData::ByteStream(stream, ..) => { PipelineData::RawStream(stream, ..) => {
let bytes: Vec<u8> = stream.into_vec()?; let bytes: Vec<u8> = stream.into_bytes()?;
let encoding = match Encoding::for_label(encoding.item.as_bytes()) { let encoding = match Encoding::for_label(encoding.item.as_bytes()) {
None => Err(ShellError::SpannedLabeledError( None => Err(ShellError::SpannedLabeledError(

View File

@ -2,7 +2,7 @@ use nu_engine::CallExt;
use nu_protocol::ast::{Call, PathMember}; use nu_protocol::ast::{Call, PathMember};
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, ValueStream, Category, Example, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -152,7 +152,7 @@ fn format(
} }
Ok(PipelineData::ListStream( Ok(PipelineData::ListStream(
ValueStream::from_stream(list.into_iter(), None), ListStream::from_stream(list.into_iter(), None),
None, None,
)) ))
} }

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, Category, Example, ListStream, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape,
ValueStream, Value,
}; };
use regex::Regex; use regex::Regex;
@ -127,7 +127,7 @@ fn operate(
} }
Ok(PipelineData::ListStream( Ok(PipelineData::ListStream(
ValueStream::from_stream(parsed.into_iter(), ctrlc), ListStream::from_stream(parsed.into_iter(), ctrlc),
None, None,
)) ))
} }

View File

@ -39,6 +39,7 @@ impl Command for StrCollect {
let config = stack.get_config().unwrap_or_default(); let config = stack.get_config().unwrap_or_default();
// let output = input.collect_string(&separator.unwrap_or_default(), &config)?;
// Hmm, not sure what we actually want. If you don't use debug_string, Date comes out as human readable // Hmm, not sure what we actually want. If you don't use debug_string, Date comes out as human readable
// which feels funny // which feels funny
#[allow(clippy::needless_collect)] #[allow(clippy::needless_collect)]

View File

@ -8,7 +8,7 @@ use std::sync::mpsc;
use nu_engine::env_to_strings; use nu_engine::env_to_strings;
use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{ast::Call, engine::Command, ShellError, Signature, SyntaxShape, Value}; use nu_protocol::{ast::Call, engine::Command, ShellError, Signature, SyntaxShape, Value};
use nu_protocol::{ByteStream, Category, Config, PipelineData, Span, Spanned}; use nu_protocol::{Category, Config, PipelineData, RawStream, Span, Spanned};
use itertools::Itertools; use itertools::Itertools;
@ -242,11 +242,8 @@ impl ExternalCommand {
}); });
let receiver = ChannelReceiver::new(rx); let receiver = ChannelReceiver::new(rx);
Ok(PipelineData::ByteStream( Ok(PipelineData::RawStream(
ByteStream { RawStream::new(Box::new(receiver), output_ctrlc),
stream: Box::new(receiver),
ctrlc: output_ctrlc,
},
head, head,
None, None,
)) ))

View File

@ -5,8 +5,8 @@ use nu_engine::{env_to_string, CallExt};
use nu_protocol::ast::{Call, PathMember}; use nu_protocol::ast::{Call, PathMember};
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Config, DataSource, IntoPipelineData, PipelineData, PipelineMetadata, ShellError, Category, Config, DataSource, IntoPipelineData, ListStream, PipelineData, PipelineMetadata,
Signature, Span, StringStream, SyntaxShape, Value, ValueStream, RawStream, ShellError, Signature, Span, SyntaxShape, Value,
}; };
use nu_table::{StyledString, TextStyle, Theme}; use nu_table::{StyledString, TextStyle, Theme};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
@ -62,32 +62,15 @@ impl Command for Table {
}; };
match input { match input {
PipelineData::ByteStream(stream, ..) => Ok(PipelineData::StringStream( PipelineData::RawStream(..) => Ok(input),
StringStream::from_stream( PipelineData::Value(Value::Binary { val, .. }, ..) => Ok(PipelineData::RawStream(
stream.map(move |x| { RawStream::new(
Ok(if x.iter().all(|x| x.is_ascii()) { Box::new(
format!("{}", String::from_utf8_lossy(&x?)) vec![Ok(format!("{}\n", nu_pretty_hex::pretty_hex(&val))
} else { .as_bytes()
format!("{}\n", nu_pretty_hex::pretty_hex(&x?)) .to_vec())]
}) .into_iter(),
}), ),
ctrlc,
),
head,
None,
)),
PipelineData::Value(Value::Binary { val, .. }, ..) => Ok(PipelineData::StringStream(
StringStream::from_stream(
vec![Ok(
if val.iter().all(|x| {
*x < 128 && (*x >= b' ' || *x == b'\t' || *x == b'\r' || *x == b'\n')
}) {
format!("{}", String::from_utf8_lossy(&val))
} else {
format!("{}\n", nu_pretty_hex::pretty_hex(&val))
},
)]
.into_iter(),
ctrlc, ctrlc,
), ),
head, head,
@ -127,7 +110,7 @@ impl Command for Table {
None => LsColors::default(), None => LsColors::default(),
}; };
ValueStream::from_stream( ListStream::from_stream(
stream.map(move |mut x| match &mut x { stream.map(move |mut x| match &mut x {
Value::Record { cols, vals, .. } => { Value::Record { cols, vals, .. } => {
let mut idx = 0; let mut idx = 0;
@ -194,15 +177,15 @@ impl Command for Table {
let head = call.head; let head = call.head;
Ok(PipelineData::StringStream( Ok(PipelineData::RawStream(
StringStream::from_stream( RawStream::new(
PagingTableCreator { Box::new(PagingTableCreator {
row_offset, row_offset,
config, config,
ctrlc: ctrlc.clone(), ctrlc: ctrlc.clone(),
head, head,
stream, stream,
}, }),
ctrlc, ctrlc,
), ),
head, head,
@ -381,14 +364,14 @@ fn convert_with_precision(val: &str, precision: usize) -> Result<String, ShellEr
struct PagingTableCreator { struct PagingTableCreator {
head: Span, head: Span,
stream: ValueStream, stream: ListStream,
ctrlc: Option<Arc<AtomicBool>>, ctrlc: Option<Arc<AtomicBool>>,
config: Config, config: Config,
row_offset: usize, row_offset: usize,
} }
impl Iterator for PagingTableCreator { impl Iterator for PagingTableCreator {
type Item = Result<String, ShellError>; type Item = Result<Vec<u8>, ShellError>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let mut batch = vec![]; let mut batch = vec![];
@ -443,7 +426,7 @@ impl Iterator for PagingTableCreator {
Ok(Some(table)) => { Ok(Some(table)) => {
let result = nu_table::draw_table(&table, term_width, &color_hm, &self.config); let result = nu_table::draw_table(&table, term_width, &color_hm, &self.config);
Some(Ok(result)) Some(Ok(result.as_bytes().to_vec()))
} }
Err(err) => Some(Err(err)), Err(err) => Some(Err(err)),
_ => None, _ => None,

View File

@ -6,7 +6,7 @@ use std::io::BufReader;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ast::Call, Signature, Value}; use nu_protocol::{ast::Call, Signature};
use nu_protocol::{PipelineData, ShellError}; use nu_protocol::{PipelineData, ShellError};
#[derive(Clone)] #[derive(Clone)]
@ -70,33 +70,7 @@ impl Command for PluginDeclaration {
) )
})?; })?;
let input = match input { let input = input.into_value(call.head);
PipelineData::Value(value, ..) => value,
PipelineData::ListStream(stream, ..) => {
let values = stream.collect::<Vec<Value>>();
Value::List {
vals: values,
span: call.head,
}
}
PipelineData::StringStream(stream, ..) => {
let val = stream.into_string("")?;
Value::String {
val,
span: call.head,
}
}
PipelineData::ByteStream(stream, ..) => {
let val = stream.into_vec()?;
Value::Binary {
val,
span: call.head,
}
}
};
// Create message to plugin to indicate that signature is required and // Create message to plugin to indicate that signature is required and
// send call to plugin asking for signature // send call to plugin asking for signature

View File

@ -1,8 +1,6 @@
use std::sync::{atomic::AtomicBool, Arc}; use std::sync::{atomic::AtomicBool, Arc};
use crate::{ use crate::{ast::PathMember, Config, ListStream, RawStream, ShellError, Span, Value};
ast::PathMember, ByteStream, Config, ShellError, Span, StringStream, Value, ValueStream,
};
/// The foundational abstraction for input and output to commands /// The foundational abstraction for input and output to commands
/// ///
@ -36,9 +34,8 @@ use crate::{
#[derive(Debug)] #[derive(Debug)]
pub enum PipelineData { pub enum PipelineData {
Value(Value, Option<PipelineMetadata>), Value(Value, Option<PipelineMetadata>),
ListStream(ValueStream, Option<PipelineMetadata>), ListStream(ListStream, Option<PipelineMetadata>),
StringStream(StringStream, Span, Option<PipelineMetadata>), RawStream(RawStream, Span, Option<PipelineMetadata>),
ByteStream(ByteStream, Span, Option<PipelineMetadata>),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -63,8 +60,7 @@ impl PipelineData {
pub fn metadata(&self) -> Option<PipelineMetadata> { pub fn metadata(&self) -> Option<PipelineMetadata> {
match self { match self {
PipelineData::ListStream(_, x) => x.clone(), PipelineData::ListStream(_, x) => x.clone(),
PipelineData::ByteStream(_, _, x) => x.clone(), PipelineData::RawStream(_, _, x) => x.clone(),
PipelineData::StringStream(_, _, x) => x.clone(),
PipelineData::Value(_, x) => x.clone(), PipelineData::Value(_, x) => x.clone(),
} }
} }
@ -72,8 +68,7 @@ impl PipelineData {
pub fn set_metadata(mut self, metadata: Option<PipelineMetadata>) -> Self { pub fn set_metadata(mut self, metadata: Option<PipelineMetadata>) -> Self {
match &mut self { match &mut self {
PipelineData::ListStream(_, x) => *x = metadata, PipelineData::ListStream(_, x) => *x = metadata,
PipelineData::ByteStream(_, _, x) => *x = metadata, PipelineData::RawStream(_, _, x) => *x = metadata,
PipelineData::StringStream(_, _, x) => *x = metadata,
PipelineData::Value(_, x) => *x = metadata, PipelineData::Value(_, x) => *x = metadata,
} }
@ -88,33 +83,51 @@ impl PipelineData {
vals: s.collect(), vals: s.collect(),
span, // FIXME? span, // FIXME?
}, },
PipelineData::StringStream(s, ..) => { PipelineData::RawStream(mut s, ..) => {
let mut output = String::new(); let mut items = vec![];
for item in s { for val in &mut s {
match item { match val {
Ok(s) => output.push_str(&s), Ok(val) => {
Err(err) => return Value::Error { error: err }, items.push(val);
} }
} Err(e) => {
Value::String { return Value::Error { error: e };
val: output, }
span, // FIXME?
}
}
PipelineData::ByteStream(s, ..) => {
let mut output = vec![];
for item in s {
match item {
Ok(s) => output.extend(&s),
Err(err) => return Value::Error { error: err },
} }
} }
Value::Binary { if s.is_binary {
val: output, let mut output = vec![];
span, // FIXME? for item in items {
match item.as_binary() {
Ok(item) => {
output.extend(item);
}
Err(err) => {
return Value::Error { error: err };
}
}
}
Value::Binary {
val: output,
span, // FIXME?
}
} else {
let mut output = String::new();
for item in items {
match item.as_string() {
Ok(s) => output.push_str(&s),
Err(err) => {
return Value::Error { error: err };
}
}
}
Value::String {
val: output,
span, // FIXME?
}
} }
} }
} }
@ -134,9 +147,30 @@ impl PipelineData {
match self { match self {
PipelineData::Value(v, ..) => Ok(v.into_string(separator, config)), PipelineData::Value(v, ..) => Ok(v.into_string(separator, config)),
PipelineData::ListStream(s, ..) => Ok(s.into_string(separator, config)), PipelineData::ListStream(s, ..) => Ok(s.into_string(separator, config)),
PipelineData::StringStream(s, ..) => s.into_string(separator), PipelineData::RawStream(s, ..) => {
PipelineData::ByteStream(s, ..) => { let mut items = vec![];
Ok(String::from_utf8_lossy(&s.into_vec()?).to_string())
for val in s {
match val {
Ok(val) => {
items.push(val);
}
Err(e) => {
return Err(e);
}
}
}
let mut output = String::new();
for item in items {
match item.as_string() {
Ok(s) => output.push_str(&s),
Err(err) => {
return Err(err);
}
}
}
Ok(output)
} }
} }
} }
@ -191,9 +225,9 @@ impl PipelineData {
Ok(vals.into_iter().map(f).into_pipeline_data(ctrlc)) Ok(vals.into_iter().map(f).into_pipeline_data(ctrlc))
} }
PipelineData::ListStream(stream, ..) => Ok(stream.map(f).into_pipeline_data(ctrlc)), PipelineData::ListStream(stream, ..) => Ok(stream.map(f).into_pipeline_data(ctrlc)),
PipelineData::StringStream(stream, span, ..) => Ok(stream PipelineData::RawStream(stream, ..) => Ok(stream
.map(move |x| match x { .map(move |x| match x {
Ok(s) => f(Value::String { val: s, span }), Ok(v) => f(v),
Err(err) => Value::Error { error: err }, Err(err) => Value::Error { error: err },
}) })
.into_pipeline_data(ctrlc)), .into_pipeline_data(ctrlc)),
@ -205,11 +239,6 @@ impl PipelineData {
Value::Error { error } => Err(error), Value::Error { error } => Err(error),
v => Ok(v.into_pipeline_data()), v => Ok(v.into_pipeline_data()),
}, },
PipelineData::ByteStream(_, span, ..) => Err(ShellError::UnsupportedInput(
"Binary output from this command may need to be decoded using the 'decode' command"
.into(),
span,
)),
} }
} }
@ -232,9 +261,9 @@ impl PipelineData {
PipelineData::ListStream(stream, ..) => { PipelineData::ListStream(stream, ..) => {
Ok(stream.map(f).flatten().into_pipeline_data(ctrlc)) Ok(stream.map(f).flatten().into_pipeline_data(ctrlc))
} }
PipelineData::StringStream(stream, span, ..) => Ok(stream PipelineData::RawStream(stream, ..) => Ok(stream
.map(move |x| match x { .map(move |x| match x {
Ok(s) => Value::String { val: s, span }, Ok(v) => v,
Err(err) => Value::Error { error: err }, Err(err) => Value::Error { error: err },
}) })
.map(f) .map(f)
@ -245,11 +274,6 @@ impl PipelineData {
Err(error) => Err(error), Err(error) => Err(error),
}, },
PipelineData::Value(v, ..) => Ok(f(v).into_iter().into_pipeline_data(ctrlc)), PipelineData::Value(v, ..) => Ok(f(v).into_iter().into_pipeline_data(ctrlc)),
PipelineData::ByteStream(_, span, ..) => Err(ShellError::UnsupportedInput(
"Binary output from this command may need to be decoded using the 'decode' command"
.into(),
span,
)),
} }
} }
@ -267,14 +291,13 @@ impl PipelineData {
Ok(vals.into_iter().filter(f).into_pipeline_data(ctrlc)) Ok(vals.into_iter().filter(f).into_pipeline_data(ctrlc))
} }
PipelineData::ListStream(stream, ..) => Ok(stream.filter(f).into_pipeline_data(ctrlc)), PipelineData::ListStream(stream, ..) => Ok(stream.filter(f).into_pipeline_data(ctrlc)),
PipelineData::StringStream(stream, span, ..) => Ok(stream PipelineData::RawStream(stream, ..) => Ok(stream
.map(move |x| match x { .map(move |x| match x {
Ok(s) => Value::String { val: s, span }, Ok(v) => v,
Err(err) => Value::Error { error: err }, Err(err) => Value::Error { error: err },
}) })
.filter(f) .filter(f)
.into_pipeline_data(ctrlc)), .into_pipeline_data(ctrlc)),
PipelineData::Value(Value::Range { val, .. }, ..) => { PipelineData::Value(Value::Range { val, .. }, ..) => {
Ok(val.into_range_iter()?.filter(f).into_pipeline_data(ctrlc)) Ok(val.into_range_iter()?.filter(f).into_pipeline_data(ctrlc))
} }
@ -285,11 +308,6 @@ impl PipelineData {
Ok(Value::Nothing { span: v.span()? }.into_pipeline_data()) Ok(Value::Nothing { span: v.span()? }.into_pipeline_data())
} }
} }
PipelineData::ByteStream(_, span, ..) => Err(ShellError::UnsupportedInput(
"Binary output from this command may need to be decoded using the 'decode' command"
.into(),
span,
)),
} }
} }
} }
@ -305,7 +323,7 @@ impl IntoIterator for PipelineData {
match self { match self {
PipelineData::Value(Value::List { vals, .. }, metadata) => { PipelineData::Value(Value::List { vals, .. }, metadata) => {
PipelineIterator(PipelineData::ListStream( PipelineIterator(PipelineData::ListStream(
ValueStream { ListStream {
stream: Box::new(vals.into_iter()), stream: Box::new(vals.into_iter()),
ctrlc: None, ctrlc: None,
}, },
@ -315,14 +333,14 @@ impl IntoIterator for PipelineData {
PipelineData::Value(Value::Range { val, .. }, metadata) => { PipelineData::Value(Value::Range { val, .. }, metadata) => {
match val.into_range_iter() { match val.into_range_iter() {
Ok(iter) => PipelineIterator(PipelineData::ListStream( Ok(iter) => PipelineIterator(PipelineData::ListStream(
ValueStream { ListStream {
stream: Box::new(iter), stream: Box::new(iter),
ctrlc: None, ctrlc: None,
}, },
metadata, metadata,
)), )),
Err(error) => PipelineIterator(PipelineData::ListStream( Err(error) => PipelineIterator(PipelineData::ListStream(
ValueStream { ListStream {
stream: Box::new(std::iter::once(Value::Error { error })), stream: Box::new(std::iter::once(Value::Error { error })),
ctrlc: None, ctrlc: None,
}, },
@ -343,18 +361,8 @@ impl Iterator for PipelineIterator {
PipelineData::Value(Value::Nothing { .. }, ..) => None, PipelineData::Value(Value::Nothing { .. }, ..) => None,
PipelineData::Value(v, ..) => Some(std::mem::take(v)), PipelineData::Value(v, ..) => Some(std::mem::take(v)),
PipelineData::ListStream(stream, ..) => stream.next(), PipelineData::ListStream(stream, ..) => stream.next(),
PipelineData::StringStream(stream, span, ..) => stream.next().map(|x| match x { PipelineData::RawStream(stream, ..) => stream.next().map(|x| match x {
Ok(x) => Value::String { Ok(x) => x,
val: x,
span: *span,
},
Err(err) => Value::Error { error: err },
}),
PipelineData::ByteStream(stream, span, ..) => stream.next().map(|x| match x {
Ok(x) => Value::Binary {
val: x,
span: *span,
},
Err(err) => Value::Error { error: err }, Err(err) => Value::Error { error: err },
}), }),
} }
@ -391,7 +399,7 @@ where
{ {
fn into_pipeline_data(self, ctrlc: Option<Arc<AtomicBool>>) -> PipelineData { fn into_pipeline_data(self, ctrlc: Option<Arc<AtomicBool>>) -> PipelineData {
PipelineData::ListStream( PipelineData::ListStream(
ValueStream { ListStream {
stream: Box::new(self.into_iter().map(Into::into)), stream: Box::new(self.into_iter().map(Into::into)),
ctrlc, ctrlc,
}, },
@ -405,7 +413,7 @@ where
ctrlc: Option<Arc<AtomicBool>>, ctrlc: Option<Arc<AtomicBool>>,
) -> PipelineData { ) -> PipelineData {
PipelineData::ListStream( PipelineData::ListStream(
ValueStream { ListStream {
stream: Box::new(self.into_iter().map(Into::into)), stream: Box::new(self.into_iter().map(Into::into)),
ctrlc, ctrlc,
}, },

View File

@ -239,6 +239,18 @@ impl Value {
} }
} }
pub fn as_binary(&self) -> Result<&[u8], ShellError> {
match self {
Value::Binary { val, .. } => Ok(val),
Value::String { val, .. } => Ok(val.as_bytes()),
x => Err(ShellError::CantConvert(
"binary".into(),
x.get_type().to_string(),
self.span()?,
)),
}
}
pub fn as_record(&self) -> Result<(&[String], &[Value]), ShellError> { pub fn as_record(&self) -> Result<(&[String], &[Value]), ShellError> {
match self { match self {
Value::Record { cols, vals, .. } => Ok((cols, vals)), Value::Record { cols, vals, .. } => Ok((cols, vals)),

View File

@ -7,95 +7,139 @@ use std::{
}, },
}; };
/// A single buffer of binary data streamed over multiple parts. Optionally contains ctrl-c that can be used pub struct RawStream {
/// to break the stream.
pub struct ByteStream {
pub stream: Box<dyn Iterator<Item = Result<Vec<u8>, ShellError>> + Send + 'static>, pub stream: Box<dyn Iterator<Item = Result<Vec<u8>, ShellError>> + Send + 'static>,
pub leftover: Vec<u8>,
pub ctrlc: Option<Arc<AtomicBool>>, pub ctrlc: Option<Arc<AtomicBool>>,
pub is_binary: bool,
pub span: Span,
} }
impl ByteStream {
pub fn into_vec(self) -> Result<Vec<u8>, ShellError> { impl RawStream {
pub fn new(
stream: Box<dyn Iterator<Item = Result<Vec<u8>, ShellError>> + Send + 'static>,
ctrlc: Option<Arc<AtomicBool>>,
) -> Self {
Self {
stream,
leftover: vec![],
ctrlc,
is_binary: false,
span: Span::new(0, 0),
}
}
pub fn into_bytes(self) -> Result<Vec<u8>, ShellError> {
let mut output = vec![]; let mut output = vec![];
for item in self.stream { for item in self.stream {
output.append(&mut item?); output.extend(item?);
} }
Ok(output) Ok(output)
} }
}
impl Debug for ByteStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ByteStream").finish()
}
}
impl Iterator for ByteStream { pub fn into_string(self) -> Result<String, ShellError> {
type Item = Result<Vec<u8>, ShellError>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(ctrlc) = &self.ctrlc {
if ctrlc.load(Ordering::SeqCst) {
None
} else {
self.stream.next()
}
} else {
self.stream.next()
}
}
}
/// A single string streamed over multiple parts. Optionally contains ctrl-c that can be used
/// to break the stream.
pub struct StringStream {
pub stream: Box<dyn Iterator<Item = Result<String, ShellError>> + Send + 'static>,
pub ctrlc: Option<Arc<AtomicBool>>,
}
impl StringStream {
pub fn into_string(self, separator: &str) -> Result<String, ShellError> {
let mut output = String::new(); let mut output = String::new();
let mut first = true; for item in self {
for s in self.stream { output.push_str(&item?.as_string()?);
output.push_str(&s?);
if !first {
output.push_str(separator);
} else {
first = false;
}
} }
Ok(output) Ok(output)
} }
pub fn from_stream(
input: impl Iterator<Item = Result<String, ShellError>> + Send + 'static,
ctrlc: Option<Arc<AtomicBool>>,
) -> StringStream {
StringStream {
stream: Box::new(input),
ctrlc,
}
}
} }
impl Debug for StringStream { impl Debug for RawStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StringStream").finish() f.debug_struct("RawStream").finish()
} }
} }
impl Iterator for RawStream {
impl Iterator for StringStream { type Item = Result<Value, ShellError>;
type Item = Result<String, ShellError>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if let Some(ctrlc) = &self.ctrlc { // If we know we're already binary, just output that
if ctrlc.load(Ordering::SeqCst) { if self.is_binary {
None match self.stream.next() {
} else { Some(buffer) => match buffer {
self.stream.next() Ok(mut v) => {
while let Some(b) = self.leftover.pop() {
v.insert(0, b);
}
Some(Ok(Value::Binary {
val: v,
span: self.span,
}))
}
Err(e) => Some(Err(e)),
},
None => None,
} }
} else { } else {
self.stream.next() // We *may* be text. We're only going to try utf-8. Other decodings
// needs to be taken as binary first, then passed through `decode`.
match self.stream.next() {
Some(buffer) => match buffer {
Ok(mut v) => {
while let Some(b) = self.leftover.pop() {
v.insert(0, b);
}
match String::from_utf8(v.clone()) {
Ok(s) => {
// Great, we have a complete string, let's output it
Some(Ok(Value::String {
val: s,
span: self.span,
}))
}
Err(err) => {
// Okay, we *might* have a string but we've also got some errors
if v.is_empty() {
// We can just end here
None
} else if v.len() > 3
&& (v.len() - err.utf8_error().valid_up_to() > 3)
{
// As UTF-8 characters are max 4 bytes, if we have more than that in error we know
// that it's not just a character spanning two frames.
// We now know we are definitely binary, so switch to binary and stay there.
self.is_binary = true;
Some(Ok(Value::Binary {
val: v,
span: self.span,
}))
} else {
// Okay, we have a tiny bit of error at the end of the buffer. This could very well be
// a character that spans two frames. Since this is the case, remove the error from
// the current frame an dput it in the leftover buffer.
self.leftover =
v[(err.utf8_error().valid_up_to() + 1)..].to_vec();
let buf = v[0..err.utf8_error().valid_up_to()].to_vec();
match String::from_utf8(buf) {
Ok(s) => Some(Ok(Value::String {
val: s,
span: self.span,
})),
Err(_) => {
// Something is definitely wrong. Switch to binary, and stay there
self.is_binary = true;
Some(Ok(Value::Binary {
val: v,
span: self.span,
}))
}
}
}
}
}
}
Err(e) => Some(Err(e)),
},
None => None,
}
} }
} }
} }
@ -106,12 +150,12 @@ impl Iterator for StringStream {
/// In practice, a "stream" here means anything which can be iterated and produce Values as it iterates. /// In practice, a "stream" here means anything which can be iterated and produce Values as it iterates.
/// Like other iterators in Rust, observing values from this stream will drain the items as you view them /// Like other iterators in Rust, observing values from this stream will drain the items as you view them
/// and the stream cannot be replayed. /// and the stream cannot be replayed.
pub struct ValueStream { pub struct ListStream {
pub stream: Box<dyn Iterator<Item = Value> + Send + 'static>, pub stream: Box<dyn Iterator<Item = Value> + Send + 'static>,
pub ctrlc: Option<Arc<AtomicBool>>, pub ctrlc: Option<Arc<AtomicBool>>,
} }
impl ValueStream { impl ListStream {
pub fn into_string(self, separator: &str, config: &Config) -> String { pub fn into_string(self, separator: &str, config: &Config) -> String {
self.map(|x: Value| x.into_string(", ", config)) self.map(|x: Value| x.into_string(", ", config))
.collect::<Vec<String>>() .collect::<Vec<String>>()
@ -121,21 +165,21 @@ impl ValueStream {
pub fn from_stream( pub fn from_stream(
input: impl Iterator<Item = Value> + Send + 'static, input: impl Iterator<Item = Value> + Send + 'static,
ctrlc: Option<Arc<AtomicBool>>, ctrlc: Option<Arc<AtomicBool>>,
) -> ValueStream { ) -> ListStream {
ValueStream { ListStream {
stream: Box::new(input), stream: Box::new(input),
ctrlc, ctrlc,
} }
} }
} }
impl Debug for ValueStream { impl Debug for ListStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ValueStream").finish() f.debug_struct("ValueStream").finish()
} }
} }
impl Iterator for ValueStream { impl Iterator for ListStream {
type Item = Value; type Item = Value;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {

View File

@ -17,7 +17,7 @@ use nu_parser::parse;
use nu_protocol::{ use nu_protocol::{
ast::{Call, Expr, Expression, Pipeline, Statement}, ast::{Call, Expr, Expression, Pipeline, Statement},
engine::{Command, EngineState, Stack, StateWorkingSet}, engine::{Command, EngineState, Stack, StateWorkingSet},
ByteStream, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Category, Example, IntoPipelineData, PipelineData, RawStream, ShellError, Signature, Span,
Spanned, SyntaxShape, Value, CONFIG_VARIABLE_ID, Spanned, SyntaxShape, Value, CONFIG_VARIABLE_ID,
}; };
use std::{ use std::{
@ -119,11 +119,8 @@ fn main() -> Result<()> {
let stdin = std::io::stdin(); let stdin = std::io::stdin();
let buf_reader = BufReader::new(stdin); let buf_reader = BufReader::new(stdin);
PipelineData::ByteStream( PipelineData::RawStream(
ByteStream { RawStream::new(Box::new(BufferedReader::new(buf_reader)), Some(ctrlc)),
stream: Box::new(BufferedReader::new(buf_reader)),
ctrlc: Some(ctrlc),
},
redirect_stdin.span, redirect_stdin.span,
None, None,
) )

View File

@ -198,36 +198,13 @@ fn print_pipeline_data(
let config = stack.get_config().unwrap_or_default(); let config = stack.get_config().unwrap_or_default();
match input { let mut stdout = std::io::stdout();
PipelineData::StringStream(stream, _, _) => {
for s in stream {
print!("{}", s?);
let _ = std::io::stdout().flush();
}
return Ok(());
}
PipelineData::ByteStream(stream, _, _) => {
let mut address_offset = 0;
for v in stream {
let cfg = nu_pretty_hex::HexConfig {
title: false,
address_offset,
..Default::default()
};
let v = v?; if let PipelineData::RawStream(stream, _, _) = input {
address_offset += v.len(); for s in stream {
let _ = stdout.write(s?.as_binary()?);
let s = if v.iter().all(|x| x.is_ascii()) {
format!("{}", String::from_utf8_lossy(&v))
} else {
nu_pretty_hex::config_hex(&v, cfg)
};
println!("{}", s);
}
return Ok(());
} }
_ => {} return Ok(());
} }
match engine_state.find_decl("table".as_bytes()) { match engine_state.find_decl("table".as_bytes()) {