Add flag for case-insensitive sort-by (#2225)

* Add flag for case-insensitive sort-by

* Fix test names

* Fix documentation comments
This commit is contained in:
Joseph T. Lyons 2020-07-20 13:31:58 -04:00 committed by GitHub
parent 7b1a15b223
commit 6eb2c94209
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 201 additions and 7 deletions

View file

@ -73,7 +73,7 @@ pub fn median(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
sorted.push(item.clone()); sorted.push(item.clone());
} }
crate::commands::sort_by::sort(&mut sorted, &[], name)?; crate::commands::sort_by::sort(&mut sorted, &[], name, false)?;
match take { match take {
Pick::Median => { Pick::Median => {

View file

@ -77,7 +77,7 @@ pub fn mode(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
} }
} }
crate::commands::sort_by::sort(&mut modes, &[], name)?; crate::commands::sort_by::sort(&mut modes, &[], name, false)?;
Ok(UntaggedValue::Table(modes).into_value(name)) Ok(UntaggedValue::Table(modes).into_value(name))
} }

View file

@ -11,6 +11,7 @@ pub struct SortBy;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct SortByArgs { pub struct SortByArgs {
rest: Vec<Tagged<String>>, rest: Vec<Tagged<String>>,
insensitive: bool,
} }
#[async_trait] #[async_trait]
@ -20,7 +21,13 @@ impl WholeStreamCommand for SortBy {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("sort-by").rest(SyntaxShape::String, "the column(s) to sort by") Signature::build("sort-by")
.switch(
"insensitive",
"Sort string-based columns case insensitively",
Some('i'),
)
.rest(SyntaxShape::String, "the column(s) to sort by")
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -57,6 +64,24 @@ impl WholeStreamCommand for SortBy {
example: "ls | sort-by type size", example: "ls | sort-by type size",
result: None, result: None,
}, },
Example {
description: "Sort strings (case sensitive)",
example: "echo [airplane Truck Car] | sort-by",
result: Some(vec![
UntaggedValue::string("Car").into(),
UntaggedValue::string("Truck").into(),
UntaggedValue::string("airplane").into(),
]),
},
Example {
description: "Sort strings (case insensitive)",
example: "echo [airplane Truck Car] | sort-by -i",
result: Some(vec![
UntaggedValue::string("airplane").into(),
UntaggedValue::string("Car").into(),
UntaggedValue::string("Truck").into(),
]),
},
] ]
} }
} }
@ -68,10 +93,10 @@ async fn sort_by(
let registry = registry.clone(); let registry = registry.clone();
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let (SortByArgs { rest }, mut input) = args.process(&registry).await?; let (SortByArgs { rest, insensitive }, mut input) = args.process(&registry).await?;
let mut vec = input.drain_vec().await; let mut vec = input.drain_vec().await;
sort(&mut vec, &rest, &tag)?; sort(&mut vec, &rest, &tag, insensitive)?;
Ok(futures::stream::iter(vec.into_iter()).to_output_stream()) Ok(futures::stream::iter(vec.into_iter()).to_output_stream())
} }
@ -80,6 +105,7 @@ pub fn sort(
vec: &mut [Value], vec: &mut [Value],
keys: &[Tagged<String>], keys: &[Tagged<String>],
tag: impl Into<Tag>, tag: impl Into<Tag>,
insensitive: bool,
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
let tag = tag.into(); let tag = tag.into();
@ -107,12 +133,38 @@ pub fn sort(
value: UntaggedValue::Primitive(_), value: UntaggedValue::Primitive(_),
.. ..
} => { } => {
vec.sort_by(|a, b| coerce_compare(a, b).expect("Unimplemented BUG: What about primitives that don't have an order defined?").compare()); let should_sort_case_insensitively = insensitive && vec.iter().all(|x| x.is_string());
vec.sort_by(|a, b| {
if should_sort_case_insensitively {
let lowercase_a_string = a.expect_string().to_ascii_lowercase();
let lowercase_b_string = b.expect_string().to_ascii_lowercase();
lowercase_a_string.cmp(&lowercase_b_string)
} else {
coerce_compare(a, b).expect("Unimplemented BUG: What about primitives that don't have an order defined?").compare()
}
});
} }
_ => { _ => {
let calc_key = |item: &Value| { let calc_key = |item: &Value| {
keys.iter() keys.iter()
.map(|f| get_data_by_key(item, f.borrow_spanned())) .map(|f| {
let mut value_option = get_data_by_key(item, f.borrow_spanned());
if insensitive {
if let Some(value) = &value_option {
if let Ok(string_value) = value.as_string() {
value_option = Some(
UntaggedValue::string(string_value.to_ascii_lowercase())
.into_value(value.tag.clone()),
)
}
}
}
value_option
})
.collect::<Vec<Option<Value>>>() .collect::<Vec<Option<Value>>>()
}; };
vec.sort_by_cached_key(calc_key); vec.sort_by_cached_key(calc_key);

View file

@ -62,3 +62,71 @@ fn sort_primitive_values() {
assert_eq!(actual.out, "authors = [\"The Nu Project Contributors\"]"); assert_eq!(actual.out, "authors = [\"The Nu Project Contributors\"]");
} }
#[test]
fn ls_sort_by_name_sensitive() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
open sample-ls-output.json
| sort-by name
| select name
| to json
"#
));
let json_output = r#"[{"name":"B.txt"},{"name":"C"},{"name":"a.txt"}]"#;
assert_eq!(actual.out, json_output);
}
#[test]
fn ls_sort_by_name_insensitive() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
open sample-ls-output.json
| sort-by -i name
| select name
| to json
"#
));
let json_output = r#"[{"name":"a.txt"},{"name":"B.txt"},{"name":"C"}]"#;
assert_eq!(actual.out, json_output);
}
#[test]
fn ls_sort_by_type_name_sensitive() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
open sample-ls-output.json
| sort-by type name
| select name type
| to json
"#
));
let json_output = r#"[{"name":"C","type":"Dir"},{"name":"B.txt","type":"File"},{"name":"a.txt","type":"File"}]"#;
assert_eq!(actual.out, json_output);
}
#[test]
fn ls_sort_by_type_name_insensitive() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
open sample-ls-output.json
| sort-by -i type name
| select name type
| to json
"#
));
let json_output = r#"[{"name":"C","type":"Dir"},{"name":"a.txt","type":"File"},{"name":"B.txt","type":"File"}]"#;
assert_eq!(actual.out, json_output);
}

View file

@ -85,6 +85,11 @@ impl UntaggedValue {
matches!(self, UntaggedValue::Table(_)) matches!(self, UntaggedValue::Table(_))
} }
/// Returns true if this value represents a string
pub fn is_string(&self) -> bool {
matches!(self, UntaggedValue::Primitive(Primitive::String(_)))
}
/// Returns true if the value represents something other than Nothing /// Returns true if the value represents something other than Nothing
pub fn is_some(&self) -> bool { pub fn is_some(&self) -> bool {
!self.is_none() !self.is_none()

View file

@ -5,6 +5,10 @@ The `sort-by` command sorts the table being displayed in the terminal by a chose
`sort-by` takes multiple arguments (being the names of columns) sorting by each argument in order. `sort-by` takes multiple arguments (being the names of columns) sorting by each argument in order.
## Flags
* `-i`, `--insensitive`: Sort string-based columns case insensitively
## Examples ## Examples
```shell ```shell
@ -53,3 +57,67 @@ The `sort-by` command sorts the table being displayed in the terminal by a chose
7 │ az │ File │ │ 18 B │ 5 minutes ago │ 5 minutes ago 7 │ az │ File │ │ 18 B │ 5 minutes ago │ 5 minutes ago
━━━┷━━━━━━┷━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━ ━━━┷━━━━━━┷━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━
``` ```
Within the Nushell repository...
```shell
> ls | sort-by --insensitive name
────┬────────────────────┬──────┬──────────┬──────────────
# │ name │ type │ size │ modified
────┼────────────────────┼──────┼──────────┼──────────────
0 │ assets │ Dir │ 128 B │ 6 months ago
1 │ build.rs │ File │ 78 B │ 5 months ago
2 │ Cargo.lock │ File │ 118.3 KB │ 1 hour ago
3 │ Cargo.toml │ File │ 5.5 KB │ 1 hour ago
4 │ CODE_OF_CONDUCT.md │ File │ 3.4 KB │ 1 hour ago
5 │ CONTRIBUTING.md │ File │ 1.3 KB │ 1 hour ago
6 │ crates │ Dir │ 832 B │ 1 hour ago
7 │ debian │ Dir │ 352 B │ 6 months ago
8 │ docker │ Dir │ 288 B │ 4 months ago
9 │ docs │ Dir │ 192 B │ 1 hour ago
10 │ features.toml │ File │ 632 B │ 5 months ago
11 │ images │ Dir │ 160 B │ 6 months ago
12 │ LICENSE │ File │ 1.1 KB │ 4 months ago
13 │ Makefile.toml │ File │ 449 B │ 6 months ago
14 │ README.build.txt │ File │ 192 B │ 1 hour ago
15 │ README.md │ File │ 16.0 KB │ 1 hour ago
16 │ rustfmt.toml │ File │ 16 B │ 6 months ago
17 │ src │ Dir │ 128 B │ 1 week ago
18 │ target │ Dir │ 160 B │ 1 day ago
19 │ tests │ Dir │ 192 B │ 4 months ago
20 │ TODO.md │ File │ 0 B │ 1 week ago
21 │ wix │ Dir │ 128 B │ 1 hour ago
────┴────────────────────┴──────┴──────────┴──────────────
```
Within the Nushell repository...
```shell
> ls | sort-by --insensitive type name
────┬────────────────────┬──────┬──────────┬──────────────
# │ name │ type │ size │ modified
────┼────────────────────┼──────┼──────────┼──────────────
0 │ assets │ Dir │ 128 B │ 6 months ago
1 │ crates │ Dir │ 832 B │ 1 hour ago
2 │ debian │ Dir │ 352 B │ 6 months ago
3 │ docker │ Dir │ 288 B │ 4 months ago
4 │ docs │ Dir │ 192 B │ 1 hour ago
5 │ images │ Dir │ 160 B │ 6 months ago
6 │ src │ Dir │ 128 B │ 1 week ago
7 │ target │ Dir │ 160 B │ 1 day ago
8 │ tests │ Dir │ 192 B │ 4 months ago
9 │ wix │ Dir │ 128 B │ 1 hour ago
10 │ build.rs │ File │ 78 B │ 5 months ago
11 │ Cargo.lock │ File │ 118.3 KB │ 1 hour ago
12 │ Cargo.toml │ File │ 5.5 KB │ 1 hour ago
13 │ CODE_OF_CONDUCT.md │ File │ 3.4 KB │ 1 hour ago
14 │ CONTRIBUTING.md │ File │ 1.3 KB │ 1 hour ago
15 │ features.toml │ File │ 632 B │ 5 months ago
16 │ LICENSE │ File │ 1.1 KB │ 4 months ago
17 │ Makefile.toml │ File │ 449 B │ 6 months ago
18 │ README.build.txt │ File │ 192 B │ 1 hour ago
19 │ README.md │ File │ 16.0 KB │ 1 hour ago
20 │ rustfmt.toml │ File │ 16 B │ 6 months ago
21 │ TODO.md │ File │ 0 B │ 1 week ago
────┴────────────────────┴──────┴──────────┴──────────────
```

View file

@ -0,0 +1 @@
[{"name":"a.txt","type":"File","size":3444,"modified":"2020-07-1918:26:30.560716967UTC"},{"name":"B.txt","type":"File","size":1341,"modified":"2020-07-1918:26:30.561021953UTC"},{"name":"C","type":"Dir","size":118253,"modified":"2020-07-1918:26:30.562092480UTC"}]