From db06edc5d34125b3efc6647d473e7a779bafb8d8 Mon Sep 17 00:00:00 2001
From: Darren Schroeder <343840+fdncred@users.noreply.github.com>
Date: Tue, 27 Dec 2022 12:46:23 -0600
Subject: [PATCH] add `--mime-type(-m)` to `ls` in the `type` column (#7616)
# Description
This PR adds the `mime-type` to the `type` column if you add the
`--mime-type(-m)` flag to `ls`.
# User-Facing Changes
If you specify the `-m` flag, you get the "guessed at" mime type. The
guess is based on the file name and uses this crate
https://docs.rs/mime_guess/latest/mime_guess/ for the guessing.
Part of issue #7612 and and #7524
There's some debate on if the `mime-type` should be added to the `type`
column or if there should be a separate `mime` column. I tend to lean on
the side of `type` since it's technically a type and it's only in that
column if you ask it to be there. Also, I'd prefer to reuse a column
rather than having a list of sprawling columns. Also, as @KodiCraft
suggested, there is precedence as with `ls -d` where the summed size is
in the size column.
I could go either way and if someone wants to create a `mime` column,
we'd probably accept it.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
---
Cargo.lock | 20 ++++++++++++++++++
crates/nu-cli/tests/completions.rs | 4 +++-
crates/nu-command/Cargo.toml | 1 +
crates/nu-command/src/filesystem/ls.rs | 29 ++++++++++++++++++++------
4 files changed, 47 insertions(+), 7 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 13a0131e5f..e5c6360d48 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2271,6 +2271,16 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+[[package]]
+name = "mime_guess"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@@ -2620,6 +2630,7 @@ dependencies = [
"md-5",
"meval",
"mime",
+ "mime_guess",
"notify",
"nu-ansi-term",
"nu-color-config",
@@ -5241,6 +5252,15 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check",
+]
+
[[package]]
name = "unicode-bidi"
version = "0.3.8"
diff --git a/crates/nu-cli/tests/completions.rs b/crates/nu-cli/tests/completions.rs
index dec2afb2b4..fb293b69b3 100644
--- a/crates/nu-cli/tests/completions.rs
+++ b/crates/nu-cli/tests/completions.rs
@@ -459,7 +459,7 @@ fn flag_completions() {
// Test completions for the 'ls' flags
let suggestions = completer.complete("ls -", 4);
- assert_eq!(14, suggestions.len());
+ assert_eq!(16, suggestions.len());
let expected: Vec = vec![
"--all".into(),
@@ -468,6 +468,7 @@ fn flag_completions() {
"--full-paths".into(),
"--help".into(),
"--long".into(),
+ "--mime-type".into(),
"--short-names".into(),
"-D".into(),
"-a".into(),
@@ -475,6 +476,7 @@ fn flag_completions() {
"-f".into(),
"-h".into(),
"-l".into(),
+ "-m".into(),
"-s".into(),
];
diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml
index 5649fce3da..01439241cd 100644
--- a/crates/nu-command/Cargo.toml
+++ b/crates/nu-command/Cargo.toml
@@ -59,6 +59,7 @@ lscolors = { version = "0.12.0", features = ["crossterm"], default-features = fa
md5 = { package = "md-5", version = "0.10.0" }
meval = "0.2.0"
mime = "0.3.16"
+mime_guess = "2.0.4"
notify = "4.0.17"
num = { version = "0.4.0", optional = true }
num-traits = "0.2.14"
diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs
index 948a944a21..f90f1ab036 100644
--- a/crates/nu-command/src/filesystem/ls.rs
+++ b/crates/nu-command/src/filesystem/ls.rs
@@ -62,6 +62,7 @@ impl Command for Ls {
"List the specified directory itself instead of its contents",
Some('D'),
)
+ .switch("mime-type", "Show mime-type in type column", Some('m'))
.category(Category::FileSystem)
}
@@ -78,6 +79,7 @@ impl Command for Ls {
let full_paths = call.has_flag("full-paths");
let du = call.has_flag("du");
let directory = call.has_flag("directory");
+ let use_mime_type = call.has_flag("mime-type");
let ctrl_c = engine_state.ctrlc.clone();
let call_span = call.head;
let cwd = current_dir(engine_state, stack)?;
@@ -250,6 +252,7 @@ impl Command for Ls {
long,
du,
ctrl_c.clone(),
+ use_mime_type,
);
match entry {
Ok(value) => Some(value),
@@ -365,7 +368,7 @@ use std::os::unix::fs::FileTypeExt;
use std::path::Path;
use std::sync::atomic::AtomicBool;
-pub fn get_file_type(md: &std::fs::Metadata) -> &str {
+pub fn get_file_type(md: &std::fs::Metadata, display_name: &str, use_mime_type: bool) -> String {
let ft = md.file_type();
let mut file_type = "unknown";
if ft.is_dir() {
@@ -388,18 +391,32 @@ pub fn get_file_type(md: &std::fs::Metadata) -> &str {
}
}
}
- file_type
+ if use_mime_type {
+ let guess = mime_guess::from_path(display_name);
+ let mime_guess = match guess.first() {
+ Some(mime_type) => mime_type.essence_str().to_string(),
+ None => "unknown".to_string(),
+ };
+ if file_type == "file" {
+ mime_guess
+ } else {
+ file_type.to_string()
+ }
+ } else {
+ file_type.to_string()
+ }
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn dir_entry_dict(
filename: &std::path::Path, // absolute path
- display_name: &str, // gile name to be displayed
+ display_name: &str, // file name to be displayed
metadata: Option<&std::fs::Metadata>,
span: Span,
long: bool,
du: bool,
ctrl_c: Option>,
+ use_mime_type: bool,
) -> Result {
#[cfg(windows)]
if metadata.is_none() {
@@ -408,7 +425,7 @@ pub(crate) fn dir_entry_dict(
let mut cols = vec![];
let mut vals = vec![];
- let mut file_type = "unknown";
+ let mut file_type = "unknown".to_string();
cols.push("name".into());
vals.push(Value::String {
@@ -417,10 +434,10 @@ pub(crate) fn dir_entry_dict(
});
if let Some(md) = metadata {
- file_type = get_file_type(md);
+ file_type = get_file_type(md, display_name, use_mime_type);
cols.push("type".into());
vals.push(Value::String {
- val: file_type.to_string(),
+ val: file_type.clone(),
span,
});
} else {