Fix to-csv command (#1635)

* Fix --headerless option of to-csv and to-tsv

Before to-csv --headerless split the "headerfull" output on lines,
skipped the first, and then concatenated them together. That meant
that all remaining output would be put on the same line, without
any delimiter, making it unusable. Now we replace the range of the
first line with the empty string, maintaining the rest of the
output unchanged.

* Remove extra space in indentation of to_delimited_data

* Add --separator <string> argument to to-csv

This functionaliy has been present before, but wasn't exposed
to the command.
This commit is contained in:
Lars Mühmel 2020-04-22 19:08:53 +02:00 committed by GitHub
parent 88f06c81b2
commit b3c623396f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 46 additions and 38 deletions

View File

@ -2,7 +2,7 @@ use crate::commands::to_delimited_data::to_delimited_data;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, UntaggedValue, Value};
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
pub struct ToCSV;
@ -18,11 +18,18 @@ impl WholeStreamCommand for ToCSV {
}
fn signature(&self) -> Signature {
Signature::build("to-csv").switch(
"headerless",
"do not output the columns names as the first row",
None,
)
Signature::build("to-csv")
.named(
"separator",
SyntaxShape::String,
"a character to separate columns, defaults to ','",
Some('s'),
)
.switch(
"headerless",
"do not output the columns names as the first row",
None,
)
}
fn usage(&self) -> &str {

View File

@ -175,40 +175,41 @@ pub fn to_delimited_data(
let name_span = name_tag.span;
let stream = async_stream! {
let input: Vec<Value> = input.collect().await;
let input: Vec<Value> = input.collect().await;
let to_process_input = if input.len() > 1 {
let tag = input[0].tag.clone();
vec![Value { value: UntaggedValue::Table(input), tag } ]
} else if input.len() == 1 {
input
} else {
vec![]
};
let to_process_input = if input.len() > 1 {
let tag = input[0].tag.clone();
vec![Value { value: UntaggedValue::Table(input), tag } ]
} else if input.len() == 1 {
input
} else {
vec![]
};
for value in to_process_input {
match from_value_to_delimited_string(&clone_tagged_value(&value), sep) {
Ok(x) => {
let converted = if headerless {
x.lines().skip(1).collect()
} else {
x
};
yield ReturnSuccess::value(UntaggedValue::Primitive(Primitive::String(converted)).into_value(&name_tag))
}
Err(x) => {
let expected = format!("Expected a table with {}-compatible structure from pipeline", format_name);
let requires = format!("requires {}-compatible input", format_name);
yield Err(ShellError::labeled_error_with_secondary(
expected,
requires,
name_span,
"originates from here".to_string(),
value.tag.span,
))
}
}
}
for value in to_process_input {
match from_value_to_delimited_string(&clone_tagged_value(&value), sep) {
Ok(mut x) => {
if headerless {
x.find('\n').map(|second_line|{
let start = second_line + 1;
x.replace_range(0..start, "");
});
}
yield ReturnSuccess::value(UntaggedValue::Primitive(Primitive::String(x)).into_value(&name_tag))
}
Err(x) => {
let expected = format!("Expected a table with {}-compatible structure from pipeline", format_name);
let requires = format!("requires {}-compatible input", format_name);
yield Err(ShellError::labeled_error_with_secondary(
expected,
requires,
name_span,
"originates from here".to_string(),
value.tag.span,
))
}
}
}
};
Ok(stream.to_output_stream())