2019-11-19 16:13:10 +01:00
|
|
|
use crate::prelude::*;
|
|
|
|
use csv::WriterBuilder;
|
2019-11-04 16:47:03 +01:00
|
|
|
use indexmap::{indexset, IndexSet};
|
Extract core stuff into own crates
This commit extracts five new crates:
- nu-source, which contains the core source-code handling logic in Nu,
including Text, Span, and also the pretty.rs-based debug logic
- nu-parser, which is the parser and expander logic
- nu-protocol, which is the bulk of the types and basic conveniences
used by plugins
- nu-errors, which contains ShellError, ParseError and error handling
conveniences
- nu-textview, which is the textview plugin extracted into a crate
One of the major consequences of this refactor is that it's no longer
possible to `impl X for Spanned<Y>` outside of the `nu-source` crate, so
a lot of types became more concrete (Value became a concrete type
instead of Spanned<Value>, for example).
This also turned a number of inherent methods in the main nu crate into
plain functions (impl Value {} became a bunch of functions in the
`value` namespace in `crate::data::value`).
2019-11-26 03:30:48 +01:00
|
|
|
use nu_errors::ShellError;
|
2021-04-15 09:43:33 +02:00
|
|
|
use nu_protocol::{Primitive, UntaggedValue, Value};
|
2019-11-21 15:33:14 +01:00
|
|
|
use nu_source::Spanned;
|
2020-10-13 05:46:58 +02:00
|
|
|
use nu_value_ext::{as_string, ValueExt};
|
2019-11-19 16:13:10 +01:00
|
|
|
|
|
|
|
fn from_value_to_delimited_string(
|
2019-11-21 15:33:14 +01:00
|
|
|
tagged_value: &Value,
|
2019-11-19 16:13:10 +01:00
|
|
|
separator: char,
|
|
|
|
) -> Result<String, ShellError> {
|
2019-11-21 15:33:14 +01:00
|
|
|
let v = &tagged_value.value;
|
2019-11-19 16:13:10 +01:00
|
|
|
|
|
|
|
match v {
|
2019-11-21 15:33:14 +01:00
|
|
|
UntaggedValue::Row(o) => {
|
2019-11-19 16:13:10 +01:00
|
|
|
let mut wtr = WriterBuilder::new()
|
|
|
|
.delimiter(separator as u8)
|
|
|
|
.from_writer(vec![]);
|
|
|
|
let mut fields: VecDeque<String> = VecDeque::new();
|
|
|
|
let mut values: VecDeque<String> = VecDeque::new();
|
|
|
|
|
|
|
|
for (k, v) in o.entries.iter() {
|
|
|
|
fields.push_back(k.clone());
|
|
|
|
|
|
|
|
values.push_back(to_string_tagged_value(&v)?);
|
|
|
|
}
|
|
|
|
|
|
|
|
wtr.write_record(fields).expect("can not write.");
|
|
|
|
wtr.write_record(values).expect("can not write.");
|
|
|
|
|
2019-12-06 16:28:26 +01:00
|
|
|
let v = String::from_utf8(wtr.into_inner().map_err(|_| {
|
2019-11-19 16:13:10 +01:00
|
|
|
ShellError::labeled_error(
|
|
|
|
"Could not convert record",
|
|
|
|
"original value",
|
|
|
|
&tagged_value.tag,
|
|
|
|
)
|
|
|
|
})?)
|
|
|
|
.map_err(|_| {
|
|
|
|
ShellError::labeled_error(
|
|
|
|
"Could not convert record",
|
|
|
|
"original value",
|
|
|
|
&tagged_value.tag,
|
|
|
|
)
|
2019-12-06 16:28:26 +01:00
|
|
|
})?;
|
|
|
|
Ok(v)
|
2019-11-19 16:13:10 +01:00
|
|
|
}
|
2019-11-21 15:33:14 +01:00
|
|
|
UntaggedValue::Table(list) => {
|
2019-11-19 16:13:10 +01:00
|
|
|
let mut wtr = WriterBuilder::new()
|
|
|
|
.delimiter(separator as u8)
|
|
|
|
.from_writer(vec![]);
|
|
|
|
|
|
|
|
let merged_descriptors = merge_descriptors(&list);
|
2019-11-04 16:47:03 +01:00
|
|
|
|
2020-01-08 12:12:59 +01:00
|
|
|
if merged_descriptors.is_empty() {
|
|
|
|
wtr.write_record(
|
|
|
|
list.iter()
|
|
|
|
.map(|ele| to_string_tagged_value(ele).unwrap_or_else(|_| String::new()))
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
)
|
|
|
|
.expect("can not write");
|
|
|
|
} else {
|
|
|
|
wtr.write_record(merged_descriptors.iter().map(|item| &item.item[..]))
|
|
|
|
.expect("can not write.");
|
|
|
|
|
|
|
|
for l in list {
|
|
|
|
let mut row = vec![];
|
|
|
|
for desc in &merged_descriptors {
|
2020-10-13 05:46:58 +02:00
|
|
|
row.push(match l.get_data_by_key(desc.borrow_spanned()) {
|
2020-01-08 12:12:59 +01:00
|
|
|
Some(s) => to_string_tagged_value(&s)?,
|
|
|
|
None => String::new(),
|
|
|
|
});
|
2019-11-19 16:13:10 +01:00
|
|
|
}
|
2020-01-08 12:12:59 +01:00
|
|
|
wtr.write_record(&row).expect("can not write");
|
2019-11-19 16:13:10 +01:00
|
|
|
}
|
|
|
|
}
|
2019-12-06 16:28:26 +01:00
|
|
|
let v = String::from_utf8(wtr.into_inner().map_err(|_| {
|
2019-11-19 16:13:10 +01:00
|
|
|
ShellError::labeled_error(
|
|
|
|
"Could not convert record",
|
|
|
|
"original value",
|
|
|
|
&tagged_value.tag,
|
|
|
|
)
|
|
|
|
})?)
|
|
|
|
.map_err(|_| {
|
|
|
|
ShellError::labeled_error(
|
|
|
|
"Could not convert record",
|
|
|
|
"original value",
|
|
|
|
&tagged_value.tag,
|
|
|
|
)
|
2019-12-06 16:28:26 +01:00
|
|
|
})?;
|
|
|
|
Ok(v)
|
2019-11-19 16:13:10 +01:00
|
|
|
}
|
2019-12-06 16:28:26 +01:00
|
|
|
_ => to_string_tagged_value(tagged_value),
|
2019-11-19 16:13:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-21 15:33:14 +01:00
|
|
|
// NOTE: could this be useful more widely and implemented on Value ?
|
|
|
|
pub fn clone_tagged_value(v: &Value) -> Value {
|
|
|
|
match &v.value {
|
|
|
|
UntaggedValue::Primitive(Primitive::String(s)) => {
|
|
|
|
UntaggedValue::Primitive(Primitive::String(s.clone()))
|
|
|
|
}
|
|
|
|
UntaggedValue::Primitive(Primitive::Nothing) => {
|
|
|
|
UntaggedValue::Primitive(Primitive::Nothing)
|
|
|
|
}
|
|
|
|
UntaggedValue::Primitive(Primitive::Boolean(b)) => {
|
2019-12-06 16:28:26 +01:00
|
|
|
UntaggedValue::Primitive(Primitive::Boolean(*b))
|
2019-11-21 15:33:14 +01:00
|
|
|
}
|
|
|
|
UntaggedValue::Primitive(Primitive::Decimal(f)) => {
|
|
|
|
UntaggedValue::Primitive(Primitive::Decimal(f.clone()))
|
|
|
|
}
|
|
|
|
UntaggedValue::Primitive(Primitive::Int(i)) => {
|
|
|
|
UntaggedValue::Primitive(Primitive::Int(i.clone()))
|
|
|
|
}
|
2021-01-08 08:30:41 +01:00
|
|
|
UntaggedValue::Primitive(Primitive::FilePath(x)) => {
|
|
|
|
UntaggedValue::Primitive(Primitive::FilePath(x.clone()))
|
2019-11-21 15:33:14 +01:00
|
|
|
}
|
2020-07-11 04:17:37 +02:00
|
|
|
UntaggedValue::Primitive(Primitive::Filesize(b)) => {
|
2021-01-29 23:35:18 +01:00
|
|
|
UntaggedValue::Primitive(Primitive::Filesize(b.clone()))
|
2019-11-21 15:33:14 +01:00
|
|
|
}
|
|
|
|
UntaggedValue::Primitive(Primitive::Date(d)) => {
|
2019-12-06 16:28:26 +01:00
|
|
|
UntaggedValue::Primitive(Primitive::Date(*d))
|
2019-11-21 15:33:14 +01:00
|
|
|
}
|
|
|
|
UntaggedValue::Row(o) => UntaggedValue::Row(o.clone()),
|
|
|
|
UntaggedValue::Table(l) => UntaggedValue::Table(l.clone()),
|
|
|
|
UntaggedValue::Block(_) => UntaggedValue::Primitive(Primitive::Nothing),
|
|
|
|
_ => UntaggedValue::Primitive(Primitive::Nothing),
|
2019-11-19 16:13:10 +01:00
|
|
|
}
|
2019-11-21 15:33:14 +01:00
|
|
|
.into_value(v.tag.clone())
|
2019-11-19 16:13:10 +01:00
|
|
|
}
|
|
|
|
|
2019-11-21 15:33:14 +01:00
|
|
|
// NOTE: could this be useful more widely and implemented on Value ?
|
|
|
|
fn to_string_tagged_value(v: &Value) -> Result<String, ShellError> {
|
|
|
|
match &v.value {
|
2020-01-08 12:12:59 +01:00
|
|
|
UntaggedValue::Primitive(Primitive::String(_))
|
2020-07-11 04:17:37 +02:00
|
|
|
| UntaggedValue::Primitive(Primitive::Filesize(_))
|
2020-01-08 12:12:59 +01:00
|
|
|
| UntaggedValue::Primitive(Primitive::Boolean(_))
|
|
|
|
| UntaggedValue::Primitive(Primitive::Decimal(_))
|
2021-01-08 08:30:41 +01:00
|
|
|
| UntaggedValue::Primitive(Primitive::FilePath(_))
|
2020-01-08 12:12:59 +01:00
|
|
|
| UntaggedValue::Primitive(Primitive::Int(_)) => as_string(v),
|
2019-11-21 15:33:14 +01:00
|
|
|
UntaggedValue::Primitive(Primitive::Date(d)) => Ok(d.to_string()),
|
2020-03-16 21:50:45 +01:00
|
|
|
UntaggedValue::Primitive(Primitive::Nothing) => Ok(String::new()),
|
2019-12-06 16:28:26 +01:00
|
|
|
UntaggedValue::Table(_) => Ok(String::from("[Table]")),
|
|
|
|
UntaggedValue::Row(_) => Ok(String::from("[Row]")),
|
|
|
|
_ => Err(ShellError::labeled_error(
|
|
|
|
"Unexpected value",
|
|
|
|
"",
|
|
|
|
v.tag.clone(),
|
|
|
|
)),
|
2019-11-19 16:13:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-21 15:33:14 +01:00
|
|
|
fn merge_descriptors(values: &[Value]) -> Vec<Spanned<String>> {
|
2019-11-04 16:47:03 +01:00
|
|
|
let mut ret: Vec<Spanned<String>> = vec![];
|
|
|
|
let mut seen: IndexSet<String> = indexset! {};
|
2019-11-19 16:13:10 +01:00
|
|
|
for value in values {
|
|
|
|
for desc in value.data_descriptors() {
|
2019-11-04 16:47:03 +01:00
|
|
|
if !seen.contains(&desc[..]) {
|
|
|
|
seen.insert(desc.clone());
|
|
|
|
ret.push(desc.spanned(value.tag.span));
|
2019-11-19 16:13:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ret
|
|
|
|
}
|
|
|
|
|
2021-04-06 18:19:43 +02:00
|
|
|
pub fn to_delimited_data(
|
2021-02-22 08:25:17 +01:00
|
|
|
noheaders: bool,
|
2019-11-19 16:13:10 +01:00
|
|
|
sep: char,
|
|
|
|
format_name: &'static str,
|
2020-05-16 05:18:24 +02:00
|
|
|
input: InputStream,
|
|
|
|
name: Tag,
|
2021-04-15 09:43:33 +02:00
|
|
|
) -> Result<OutputStream, ShellError> {
|
2019-11-19 16:13:10 +01:00
|
|
|
let name_tag = name;
|
2019-11-21 15:33:14 +01:00
|
|
|
let name_span = name_tag.span;
|
2019-11-19 16:13:10 +01:00
|
|
|
|
2021-04-06 18:19:43 +02:00
|
|
|
let input: Vec<Value> = input.collect();
|
2020-04-22 19:08:53 +02:00
|
|
|
|
2020-06-13 21:13:36 +02:00
|
|
|
let to_process_input = match input.len() {
|
|
|
|
x if x > 1 => {
|
2020-04-22 19:08:53 +02:00
|
|
|
let tag = input[0].tag.clone();
|
2020-06-13 21:13:36 +02:00
|
|
|
vec![Value {
|
|
|
|
value: UntaggedValue::Table(input),
|
|
|
|
tag,
|
|
|
|
}]
|
|
|
|
}
|
|
|
|
1 => input,
|
|
|
|
_ => vec![],
|
|
|
|
};
|
|
|
|
|
2021-04-06 18:19:43 +02:00
|
|
|
Ok((to_process_input.into_iter().map(move |value| {
|
|
|
|
match from_value_to_delimited_string(&clone_tagged_value(&value), sep) {
|
|
|
|
Ok(mut x) => {
|
|
|
|
if noheaders {
|
|
|
|
if let Some(second_line) = x.find('\n') {
|
|
|
|
let start = second_line + 1;
|
|
|
|
x.replace_range(0..start, "");
|
2020-04-22 19:08:53 +02:00
|
|
|
}
|
|
|
|
}
|
2021-04-15 09:43:33 +02:00
|
|
|
UntaggedValue::Primitive(Primitive::String(x)).into_value(&name_tag)
|
2020-04-22 19:08:53 +02:00
|
|
|
}
|
2021-04-06 18:19:43 +02:00
|
|
|
Err(_) => {
|
|
|
|
let expected = format!(
|
|
|
|
"Expected a table with {}-compatible structure from pipeline",
|
|
|
|
format_name
|
|
|
|
);
|
|
|
|
let requires = format!("requires {}-compatible input", format_name);
|
2021-04-15 09:43:33 +02:00
|
|
|
Value::error(ShellError::labeled_error_with_secondary(
|
2021-04-06 18:19:43 +02:00
|
|
|
expected,
|
|
|
|
requires,
|
|
|
|
name_span,
|
|
|
|
"originates from here".to_string(),
|
|
|
|
value.tag.span,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}))
|
2021-04-15 09:43:33 +02:00
|
|
|
.to_output_stream())
|
2019-11-19 16:13:10 +01:00
|
|
|
}
|