Remove deprecated commands (#14726)

# Description

Remove commands which were deprecated in 0.101:

* `split-by` (#14019)
* `date to-record` and `date to-table` (#14319)

# User-Facing Changes

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting

TODO: `grep` (`ag`) doc repo for any usage of these commands
This commit is contained in:
Douglas 2025-01-06 18:37:51 -05:00 committed by GitHub
parent ac12b02437
commit 6eb14522b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 1 additions and 630 deletions

View File

@ -3,8 +3,6 @@ mod humanize;
mod list_timezone;
mod now;
mod parser;
mod to_record;
mod to_table;
mod to_timezone;
mod utils;
@ -12,7 +10,5 @@ pub use date_::Date;
pub use humanize::SubCommand as DateHumanize;
pub use list_timezone::SubCommand as DateListTimezones;
pub use now::SubCommand as DateNow;
pub use to_record::SubCommand as DateToRecord;
pub use to_table::SubCommand as DateToTable;
pub use to_timezone::SubCommand as DateToTimezone;
pub(crate) use utils::{generate_strftime_list, parse_date_from_string};

View File

@ -1,148 +0,0 @@
use crate::date::utils::parse_date_from_string;
use chrono::{DateTime, Datelike, FixedOffset, Local, Timelike};
use nu_engine::command_prelude::*;
use nu_protocol::{report_parse_warning, ParseWarning};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"date to-record"
}
fn signature(&self) -> Signature {
Signature::build("date to-record")
.input_output_types(vec![
(Type::Date, Type::record()),
(Type::String, Type::record()),
])
.allow_variants_without_examples(true) // https://github.com/nushell/nushell/issues/7032
.category(Category::Deprecated)
}
fn description(&self) -> &str {
"Convert the date into a record."
}
fn search_terms(&self) -> Vec<&str> {
vec!["structured", "table"]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
report_parse_warning(
&StateWorkingSet::new(engine_state),
&ParseWarning::DeprecatedWarning {
old_command: "date to-record".into(),
new_suggestion: "see `into record` command examples".into(),
span: head,
url: "`help into record`".into(),
},
);
let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
input.map(move |value| helper(value, head), engine_state.signals())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Convert the current date into a record.",
example: "date now | date to-record",
result: None,
},
Example {
description: "Convert a date string into a record.",
example: "'2020-04-12T22:10:57.123+02:00' | date to-record",
result: Some(Value::test_record(record!(
"year" => Value::test_int(2020),
"month" => Value::test_int(4),
"day" => Value::test_int(12),
"hour" => Value::test_int(22),
"minute" => Value::test_int(10),
"second" => Value::test_int(57),
"nanosecond" => Value::test_int(123_000_000),
"timezone" => Value::test_string("+02:00"),
))),
},
Example {
description: "Convert a date into a record.",
example: "'2020-04-12 22:10:57 +0200' | into datetime | date to-record",
result: Some(Value::test_record(record!(
"year" => Value::test_int(2020),
"month" => Value::test_int(4),
"day" => Value::test_int(12),
"hour" => Value::test_int(22),
"minute" => Value::test_int(10),
"second" => Value::test_int(57),
"nanosecond" => Value::test_int(0),
"timezone" => Value::test_string("+02:00"),
))),
},
]
}
}
fn parse_date_into_table(date: DateTime<FixedOffset>, head: Span) -> Value {
Value::record(
record! {
"year" => Value::int(date.year() as i64, head),
"month" => Value::int(date.month() as i64, head),
"day" => Value::int(date.day() as i64, head),
"hour" => Value::int(date.hour() as i64, head),
"minute" => Value::int(date.minute() as i64, head),
"second" => Value::int(date.second() as i64, head),
"nanosecond" => Value::int(date.nanosecond() as i64, head),
"timezone" => Value::string(date.offset().to_string(), head),
},
head,
)
}
fn helper(val: Value, head: Span) -> Value {
let span = val.span();
match val {
Value::String { val, .. } => match parse_date_from_string(&val, span) {
Ok(date) => parse_date_into_table(date, head),
Err(e) => e,
},
Value::Nothing { .. } => {
let now = Local::now();
let n = now.with_timezone(now.offset());
parse_date_into_table(n, head)
}
Value::Date { val, .. } => parse_date_into_table(val, head),
_ => Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "date, string (that represents datetime), or nothing".into(),
wrong_type: val.get_type().to_string(),
dst_span: head,
src_span: span,
},
head,
),
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -1,146 +0,0 @@
use crate::date::utils::parse_date_from_string;
use chrono::{DateTime, Datelike, FixedOffset, Local, Timelike};
use nu_engine::command_prelude::*;
use nu_protocol::{report_parse_warning, ParseWarning};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"date to-table"
}
fn signature(&self) -> Signature {
Signature::build("date to-table")
.input_output_types(vec![
(Type::Date, Type::table()),
(Type::String, Type::table()),
])
.allow_variants_without_examples(true) // https://github.com/nushell/nushell/issues/7032
.category(Category::Deprecated)
}
fn description(&self) -> &str {
"Convert the date into a structured table."
}
fn search_terms(&self) -> Vec<&str> {
vec!["structured"]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
report_parse_warning(
&StateWorkingSet::new(engine_state),
&ParseWarning::DeprecatedWarning {
old_command: "date to-table".into(),
new_suggestion: "see `into record` command examples".into(),
span: head,
url: "`help into record`".into(),
},
);
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
input.map(move |value| helper(value, head), engine_state.signals())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Convert the current date into a table.",
example: "date now | date to-table",
result: None,
},
Example {
description: "Convert a given date into a table.",
example: "2020-04-12T22:10:57.000000789+02:00 | date to-table",
result: Some(Value::test_list(vec![Value::test_record(record!(
"year" => Value::test_int(2020),
"month" => Value::test_int(4),
"day" => Value::test_int(12),
"hour" => Value::test_int(22),
"minute" => Value::test_int(10),
"second" => Value::test_int(57),
"nanosecond" => Value::test_int(789),
"timezone" => Value::test_string("+02:00".to_string()),
))])),
},
Example {
description: "Convert a given date into a table.",
example: "'2020-04-12 22:10:57 +0200' | into datetime | date to-table",
result: Some(Value::test_list(vec![Value::test_record(record!(
"year" => Value::test_int(2020),
"month" => Value::test_int(4),
"day" => Value::test_int(12),
"hour" => Value::test_int(22),
"minute" => Value::test_int(10),
"second" => Value::test_int(57),
"nanosecond" => Value::test_int(0),
"timezone" => Value::test_string("+02:00".to_string()),
))])),
},
]
}
}
fn parse_date_into_table(date: DateTime<FixedOffset>, head: Span) -> Value {
let record = record! {
"year" => Value::int(date.year() as i64, head),
"month" => Value::int(date.month() as i64, head),
"day" => Value::int(date.day() as i64, head),
"hour" => Value::int(date.hour() as i64, head),
"minute" => Value::int(date.minute() as i64, head),
"second" => Value::int(date.second() as i64, head),
"nanosecond" => Value::int(date.nanosecond() as i64, head),
"timezone" => Value::string(date.offset().to_string(), head),
};
Value::list(vec![Value::record(record, head)], head)
}
fn helper(val: Value, head: Span) -> Value {
let val_span = val.span();
match val {
Value::String { val, .. } => match parse_date_from_string(&val, val_span) {
Ok(date) => parse_date_into_table(date, head),
Err(e) => e,
},
Value::Nothing { .. } => {
let now = Local::now();
let n = now.with_timezone(now.offset());
parse_date_into_table(n, head)
}
Value::Date { val, .. } => parse_date_into_table(val, head),
_ => Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "date, string (that represents datetime), or nothing".into(),
wrong_type: val.get_type().to_string(),
dst_span: head,
src_span: val_span,
},
head,
),
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -58,7 +58,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
Interleave,
Items,
Join,
SplitBy,
Take,
Merge,
MergeDeep,
@ -275,8 +274,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
DateHumanize,
DateListTimezones,
DateNow,
DateToRecord,
DateToTable,
DateToTimezone,
};

View File

@ -42,7 +42,6 @@ mod shuffle;
mod skip;
mod sort;
mod sort_by;
mod split_by;
mod take;
mod tee;
mod transpose;
@ -102,7 +101,6 @@ pub use shuffle::Shuffle;
pub use skip::*;
pub use sort::Sort;
pub use sort_by::SortBy;
pub use split_by::SplitBy;
pub use take::*;
pub use tee::Tee;
pub use transpose::Transpose;

View File

@ -1,257 +0,0 @@
use indexmap::IndexMap;
use nu_engine::command_prelude::*;
use nu_protocol::report_shell_warning;
#[derive(Clone)]
pub struct SplitBy;
impl Command for SplitBy {
fn name(&self) -> &str {
"split-by"
}
fn signature(&self) -> Signature {
Signature::build("split-by")
.input_output_types(vec![(Type::record(), Type::record())])
.optional("splitter", SyntaxShape::Any, "The splitter value to use.")
.category(Category::Deprecated)
}
fn description(&self) -> &str {
"Split a record into groups."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
report_shell_warning(
engine_state,
&ShellError::Deprecated {
deprecated: "The `split_by` command",
suggestion: "Please use the `group-by` command instead.",
span: call.head,
help: None,
},
);
split_by(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "split items by column named \"lang\"",
example: r#"{
'2019': [
{ name: 'andres', lang: 'rb', year: '2019' },
{ name: 'jt', lang: 'rs', year: '2019' }
],
'2021': [
{ name: 'storm', lang: 'rs', 'year': '2021' }
]
} | split-by lang"#,
result: Some(Value::test_record(record! {
"rb" => Value::test_record(record! {
"2019" => Value::test_list(
vec![Value::test_record(record! {
"name" => Value::test_string("andres"),
"lang" => Value::test_string("rb"),
"year" => Value::test_string("2019"),
})],
),
}),
"rs" => Value::test_record(record! {
"2019" => Value::test_list(
vec![Value::test_record(record! {
"name" => Value::test_string("jt"),
"lang" => Value::test_string("rs"),
"year" => Value::test_string("2019"),
})],
),
"2021" => Value::test_list(
vec![Value::test_record(record! {
"name" => Value::test_string("storm"),
"lang" => Value::test_string("rs"),
"year" => Value::test_string("2021"),
})],
),
}),
})),
}]
}
}
enum Grouper {
ByColumn(Option<Spanned<String>>),
}
pub fn split_by(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let name = call.head;
let config = engine_state.get_config();
let splitter: Option<Value> = call.opt(engine_state, stack, 0)?;
match splitter {
Some(v) => {
let splitter = Some(Spanned {
item: v.to_abbreviated_string(config),
span: name,
});
Ok(split(splitter.as_ref(), input, name, config)?)
}
// This uses the same format as the 'requires a column name' error in sort_utils.rs
None => Err(ShellError::GenericError {
error: "expected name".into(),
msg: "requires a column name for splitting".into(),
span: Some(name),
help: None,
inner: vec![],
}),
}
}
pub fn split(
column_name: Option<&Spanned<String>>,
values: PipelineData,
span: Span,
config: &nu_protocol::Config,
) -> Result<PipelineData, ShellError> {
let grouper = if let Some(column_name) = column_name {
Grouper::ByColumn(Some(column_name.clone()))
} else {
Grouper::ByColumn(None)
};
match grouper {
Grouper::ByColumn(Some(column_name)) => {
let block = move |_, row: &Value| {
let group_key = if let Value::Record { val: row, .. } = row {
row.get(&column_name.item)
} else {
None
};
match group_key {
Some(group_key) => Ok(group_key.to_abbreviated_string(config)),
None => Err(ShellError::CantFindColumn {
col_name: column_name.item.to_string(),
span: Some(column_name.span),
src_span: row.span(),
}),
}
};
data_split(values, Some(&block), span, config)
}
Grouper::ByColumn(None) => {
let block = move |_, row: &Value| Ok(row.to_abbreviated_string(config));
data_split(values, Some(&block), span, config)
}
}
}
#[allow(clippy::type_complexity)]
fn data_group(
values: &Value,
grouper: Option<&dyn Fn(usize, &Value) -> Result<String, ShellError>>,
span: Span,
config: &nu_protocol::Config,
) -> Result<Value, ShellError> {
let mut groups: IndexMap<String, Vec<Value>> = IndexMap::new();
for (idx, value) in values.clone().into_pipeline_data().into_iter().enumerate() {
let group_key = if let Some(ref grouper) = grouper {
grouper(idx, &value)
} else {
Ok(value.to_abbreviated_string(config))
};
let group = groups.entry(group_key?).or_default();
group.push(value);
}
Ok(Value::record(
groups
.into_iter()
.map(|(k, v)| (k, Value::list(v, span)))
.collect(),
span,
))
}
#[allow(clippy::type_complexity)]
pub fn data_split(
value: PipelineData,
splitter: Option<&dyn Fn(usize, &Value) -> Result<String, ShellError>>,
dst_span: Span,
config: &nu_protocol::Config,
) -> Result<PipelineData, ShellError> {
let mut splits = indexmap::IndexMap::new();
match value {
PipelineData::Value(v, _) => {
let span = v.span();
match v {
Value::Record { val: grouped, .. } => {
for (outer_key, list) in grouped.into_owned() {
match data_group(&list, splitter, span, config) {
Ok(grouped_vals) => {
if let Value::Record { val: sub, .. } = grouped_vals {
for (inner_key, subset) in sub.into_owned() {
let s: &mut IndexMap<String, Value> =
splits.entry(inner_key).or_default();
s.insert(outer_key.clone(), subset.clone());
}
}
}
Err(reason) => return Err(reason),
}
}
}
_ => {
return Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "Record".into(),
wrong_type: v.get_type().to_string(),
dst_span,
src_span: v.span(),
})
}
}
}
PipelineData::Empty => return Err(ShellError::PipelineEmpty { dst_span }),
_ => {
return Err(ShellError::PipelineMismatch {
exp_input_type: "record".into(),
dst_span,
src_span: value.span().unwrap_or(Span::unknown()),
})
}
}
let record = splits
.into_iter()
.map(|(k, rows)| (k, Value::record(rows.into_iter().collect(), dst_span)))
.collect();
Ok(PipelineData::Value(Value::record(record, dst_span), None))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SplitBy {})
}
}

View File

@ -110,7 +110,6 @@ fn validate(vec: &[Value], columns: &[String], span: Span) -> Result<(), ShellEr
let val_span = v.span();
if let Value::Record { val: record, .. } = &v {
if columns.is_empty() {
// This uses the same format as the 'requires a column name' error in split_by.rs
return Err(ShellError::GenericError {
error: "expected name".into(),
msg: "requires a column name to filter table data".into(),

View File

@ -58,7 +58,6 @@ pub fn sort_by(
natural: bool,
) -> Result<(), ShellError> {
if comparators.is_empty() {
// This uses the same format as the 'requires a column name' error in split_by.rs
return Err(ShellError::GenericError {
error: "expected name".into(),
msg: "requires a cell path or closure to sort data".into(),

View File

@ -103,7 +103,6 @@ mod skip;
mod sort;
mod sort_by;
mod source_env;
mod split_by;
mod split_column;
mod split_row;
mod str_;

View File

@ -1,66 +0,0 @@
use nu_test_support::fs::Stub::EmptyFile;
use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline};
#[test]
fn splits() {
let sample = r#"
[[first_name, last_name, rusty_at, type];
[Andrés, Robalino, "10/11/2013", A],
[JT, Turner, "10/12/2013", B],
[Yehuda, Katz, "10/11/2013", A]]
"#;
let actual = nu!(pipeline(&format!(
r#"
{sample}
| group-by rusty_at
| split-by type
| get A."10/11/2013"
| length
"#
)));
assert_eq!(actual.out, "2");
}
#[test]
fn errors_if_no_input() {
Playground::setup("split_by_no_input", |dirs, _sandbox| {
let actual = nu!(cwd: dirs.test(), pipeline("split-by type"));
assert!(actual.err.contains("no input value was piped in"));
})
}
#[test]
fn errors_if_non_record_input() {
Playground::setup("split_by_test_2", |dirs, sandbox| {
sandbox.with_files(&[
EmptyFile("los.txt"),
EmptyFile("tres.txt"),
EmptyFile("amigos.txt"),
EmptyFile("arepas.clu"),
]);
let input_mismatch = nu!(cwd: dirs.test(), pipeline("5 | split-by type"));
assert!(input_mismatch.err.contains("doesn't support int input"));
let only_supports = nu!(
cwd: dirs.test(), pipeline(
"
ls
| get name
| split-by type
"
));
assert!(
only_supports
.err
.contains("only Record input data is supported")
|| only_supports.err.contains("expected: record")
);
})
}

View File

@ -663,7 +663,7 @@ fn comment_in_multiple_pipelines() -> TestResult {
#[test]
fn date_literal() -> TestResult {
run_test(r#"2022-09-10 | date to-record | get day"#, "10")
run_test(r#"2022-09-10 | into record | get day"#, "10")
}
#[test]