2022-01-21 21:28:21 +01:00
|
|
|
use nu_engine::column::get_columns;
|
|
|
|
use nu_engine::CallExt;
|
|
|
|
use nu_protocol::ast::Call;
|
|
|
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
|
|
use nu_protocol::{
|
2022-02-19 16:24:48 +01:00
|
|
|
Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
|
|
|
SyntaxShape, Value,
|
2022-01-21 21:28:21 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Transpose;
|
|
|
|
|
|
|
|
pub struct TransposeArgs {
|
|
|
|
rest: Vec<Spanned<String>>,
|
|
|
|
header_row: bool,
|
|
|
|
ignore_titles: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Command for Transpose {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
"transpose"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn signature(&self) -> Signature {
|
|
|
|
Signature::build("transpose")
|
|
|
|
.switch(
|
|
|
|
"header-row",
|
|
|
|
"treat the first row as column names",
|
|
|
|
Some('r'),
|
|
|
|
)
|
|
|
|
.switch(
|
|
|
|
"ignore-titles",
|
|
|
|
"don't transpose the column names into values",
|
|
|
|
Some('i'),
|
|
|
|
)
|
|
|
|
.rest(
|
|
|
|
"rest",
|
|
|
|
SyntaxShape::String,
|
|
|
|
"the names to give columns once transposed",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
|
|
|
"Transposes the table contents so rows become columns and columns become rows."
|
|
|
|
}
|
|
|
|
|
2022-04-05 14:01:21 +02:00
|
|
|
fn search_terms(&self) -> Vec<&str> {
|
|
|
|
vec!["pivot"]
|
|
|
|
}
|
|
|
|
|
2022-01-21 21:28:21 +01:00
|
|
|
fn run(
|
|
|
|
&self,
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &mut Stack,
|
|
|
|
call: &Call,
|
|
|
|
input: PipelineData,
|
|
|
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
|
|
|
transpose(engine_state, stack, call, input)
|
|
|
|
}
|
2022-02-19 16:24:48 +01:00
|
|
|
|
|
|
|
fn examples(&self) -> Vec<Example> {
|
|
|
|
let span = Span::test_data();
|
|
|
|
vec![
|
|
|
|
Example {
|
|
|
|
description: "Transposes the table contents with default column names",
|
|
|
|
example: "echo [[c1 c2]; [1 2]] | transpose",
|
|
|
|
result: Some(Value::List {
|
|
|
|
vals: vec![
|
|
|
|
Value::Record {
|
2022-02-20 01:26:47 +01:00
|
|
|
cols: vec!["column0".to_string(), "column1".to_string()],
|
2022-02-19 16:24:48 +01:00
|
|
|
vals: vec![Value::test_string("c1"), Value::test_int(1)],
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
Value::Record {
|
2022-02-20 01:26:47 +01:00
|
|
|
cols: vec!["column0".to_string(), "column1".to_string()],
|
2022-02-19 16:24:48 +01:00
|
|
|
vals: vec![Value::test_string("c2"), Value::test_int(2)],
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
span,
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
Example {
|
|
|
|
description: "Transposes the table contents with specified column names",
|
|
|
|
example: "echo [[c1 c2]; [1 2]] | transpose key val",
|
|
|
|
result: Some(Value::List {
|
|
|
|
vals: vec![
|
|
|
|
Value::Record {
|
|
|
|
cols: vec!["key".to_string(), "val".to_string()],
|
|
|
|
vals: vec![Value::test_string("c1"), Value::test_int(1)],
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
Value::Record {
|
|
|
|
cols: vec!["key".to_string(), "val".to_string()],
|
|
|
|
vals: vec![Value::test_string("c2"), Value::test_int(2)],
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
span,
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
Example {
|
|
|
|
description:
|
|
|
|
"Transposes the table without column names and specify a new column name",
|
|
|
|
example: "echo [[c1 c2]; [1 2]] | transpose -i val",
|
|
|
|
result: Some(Value::List {
|
|
|
|
vals: vec![
|
|
|
|
Value::Record {
|
|
|
|
cols: vec!["val".to_string()],
|
|
|
|
vals: vec![Value::test_int(1)],
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
Value::Record {
|
|
|
|
cols: vec!["val".to_string()],
|
|
|
|
vals: vec![Value::test_int(2)],
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
span,
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
]
|
|
|
|
}
|
2022-01-21 21:28:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn transpose(
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &mut Stack,
|
|
|
|
call: &Call,
|
|
|
|
input: PipelineData,
|
|
|
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
|
|
|
let name = call.head;
|
|
|
|
let transpose_args = TransposeArgs {
|
|
|
|
header_row: call.has_flag("header-row"),
|
|
|
|
ignore_titles: call.has_flag("ignore-titles"),
|
|
|
|
rest: call.rest(engine_state, stack, 0)?,
|
|
|
|
};
|
|
|
|
|
|
|
|
let ctrlc = engine_state.ctrlc.clone();
|
2022-03-28 13:43:09 +02:00
|
|
|
let metadata = input.metadata();
|
2022-01-21 21:28:21 +01:00
|
|
|
let input: Vec<_> = input.into_iter().collect();
|
|
|
|
let args = transpose_args;
|
|
|
|
|
|
|
|
let descs = get_columns(&input);
|
|
|
|
|
|
|
|
let mut headers: Vec<String> = vec![];
|
|
|
|
|
|
|
|
if !args.rest.is_empty() && args.header_row {
|
|
|
|
return Err(ShellError::SpannedLabeledError(
|
|
|
|
"Can not provide header names and use header row".into(),
|
|
|
|
"using header row".into(),
|
|
|
|
name,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if args.header_row {
|
|
|
|
for i in input.clone() {
|
|
|
|
if let Some(desc) = descs.get(0) {
|
|
|
|
match &i.get_data_by_key(desc) {
|
|
|
|
Some(x) => {
|
|
|
|
if let Ok(s) = x.as_string() {
|
|
|
|
headers.push(s.to_string());
|
|
|
|
} else {
|
|
|
|
return Err(ShellError::SpannedLabeledError(
|
|
|
|
"Header row needs string headers".into(),
|
|
|
|
"used non-string headers".into(),
|
|
|
|
name,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
return Err(ShellError::SpannedLabeledError(
|
|
|
|
"Header row is incomplete and can't be used".into(),
|
|
|
|
"using incomplete header row".into(),
|
|
|
|
name,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return Err(ShellError::SpannedLabeledError(
|
|
|
|
"Header row is incomplete and can't be used".into(),
|
|
|
|
"using incomplete header row".into(),
|
|
|
|
name,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for i in 0..=input.len() {
|
|
|
|
if let Some(name) = args.rest.get(i) {
|
|
|
|
headers.push(name.item.clone())
|
|
|
|
} else {
|
2022-02-20 01:26:47 +01:00
|
|
|
headers.push(format!("column{}", i));
|
2022-01-21 21:28:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let descs: Vec<_> = if args.header_row {
|
|
|
|
descs.into_iter().skip(1).collect()
|
|
|
|
} else {
|
|
|
|
descs
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok((descs.into_iter().map(move |desc| {
|
|
|
|
let mut column_num: usize = 0;
|
|
|
|
let mut cols = vec![];
|
|
|
|
let mut vals = vec![];
|
|
|
|
|
|
|
|
if !args.ignore_titles && !args.header_row {
|
|
|
|
cols.push(headers[column_num].clone());
|
|
|
|
vals.push(Value::string(desc.clone(), name));
|
|
|
|
column_num += 1
|
|
|
|
}
|
|
|
|
|
|
|
|
for i in input.clone() {
|
|
|
|
match &i.get_data_by_key(&desc) {
|
|
|
|
Some(x) => {
|
|
|
|
cols.push(headers[column_num].clone());
|
|
|
|
vals.push(x.clone());
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
cols.push(headers[column_num].clone());
|
|
|
|
vals.push(Value::nothing(name));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
column_num += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Value::Record {
|
|
|
|
cols,
|
|
|
|
vals,
|
|
|
|
span: name,
|
|
|
|
}
|
|
|
|
}))
|
2022-03-28 13:43:09 +02:00
|
|
|
.into_pipeline_data(ctrlc)
|
|
|
|
.set_metadata(metadata))
|
2022-01-21 21:28:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_examples() {
|
|
|
|
use crate::test_examples;
|
|
|
|
|
|
|
|
test_examples(Transpose {})
|
|
|
|
}
|
|
|
|
}
|