Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4419,6 +4419,10 @@ pub struct ToolListArgs {
#[arg(long)]
pub show_with: bool,

/// Whether to display the extra requirements installed with each tool.
#[arg(long)]
pub show_extras: bool,

// Hide unused global Python options.
#[arg(long, hide = true)]
pub python_preference: Option<PythonPreference>,
Expand Down
27 changes: 25 additions & 2 deletions crates/uv/src/commands/tool/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ use crate::commands::ExitStatus;
use crate::printer::Printer;

/// List installed tools.
#[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn list(
show_paths: bool,
show_version_specifiers: bool,
show_with: bool,
show_extras: bool,
cache: &Cache,
printer: Printer,
) -> Result<ExitStatus> {
Expand Down Expand Up @@ -80,6 +82,21 @@ pub(crate) async fn list(
})
.unwrap_or_default();

let extra_requirements = show_extras
.then(|| {
tool.requirements()
.iter()
.filter(|req| req.name == name)
.flat_map(|req| req.extras.iter()) // Flatten the extras from all matching requirements
.peekable()
})
.take_if(|extras| extras.peek().is_some())
.map(|extras| {
let extras_str = extras.map(ToString::to_string).join(", ");
format!(" [extras: {extras_str}]")
})
.unwrap_or_default();

let with_requirements = show_with
.then(|| {
tool.requirements()
Expand All @@ -100,14 +117,20 @@ pub(crate) async fn list(
writeln!(
printer.stdout(),
"{} ({})",
format!("{name} v{version}{version_specifier}{with_requirements}").bold(),
format!(
"{name} v{version}{version_specifier}{extra_requirements}{with_requirements}"
)
.bold(),
installed_tools.tool_dir(&name).simplified_display().cyan(),
)?;
} else {
writeln!(
printer.stdout(),
"{}",
format!("{name} v{version}{version_specifier}{with_requirements}").bold()
format!(
"{name} v{version}{version_specifier}{extra_requirements}{with_requirements}"
)
.bold()
)?;
}

Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args.show_paths,
args.show_version_specifiers,
args.show_with,
args.show_extras,
&cache,
printer,
)
Expand Down
3 changes: 3 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,7 @@ pub(crate) struct ToolListSettings {
pub(crate) show_paths: bool,
pub(crate) show_version_specifiers: bool,
pub(crate) show_with: bool,
pub(crate) show_extras: bool,
}

impl ToolListSettings {
Expand All @@ -792,6 +793,7 @@ impl ToolListSettings {
show_paths,
show_version_specifiers,
show_with,
show_extras,
python_preference: _,
no_python_downloads: _,
} = args;
Expand All @@ -800,6 +802,7 @@ impl ToolListSettings {
show_paths,
show_version_specifiers,
show_with,
show_extras,
}
}
}
Expand Down
111 changes: 111 additions & 0 deletions crates/uv/tests/it/tool_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,3 +452,114 @@ fn tool_list_show_with() {
----- stderr -----
"###);
}

#[test]
fn tool_list_show_extras() {
let context = TestContext::new("3.12").with_filtered_exe_suffix();
let tool_dir = context.temp_dir.child("tools");
let bin_dir = context.temp_dir.child("bin");

// Install `black` without extras
context
.tool_install()
.arg("black==24.2.0")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
.assert()
.success();

// Install `flask` with extras and additional requirements
context
.tool_install()
.arg("flask[async,dotenv]")
.arg("--with")
.arg("requests")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
.assert()
.success();

// Test with --show-extras only
uv_snapshot!(context.filters(), context.tool_list().arg("--show-extras")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###"
success: true
exit_code: 0
----- stdout -----
black v24.2.0
- black
- blackd
flask v3.0.2 [extras: async, dotenv]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should just show extras by default? e.g.,

flask[async, dotenv] v3.02

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(It's not very pretty)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was actually my first hunch as well, but seemed like @charliermarsh preferred the flag #13761 (comment). And I guess extras aren't always that important which I could see as an argument to hide it behind the flag.

- flask

----- stderr -----
"###);

// Test with both --show-extras and --show-with
uv_snapshot!(context.filters(), context.tool_list().arg("--show-extras").arg("--show-with")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###"
success: true
exit_code: 0
----- stdout -----
black v24.2.0
- black
- blackd
flask v3.0.2 [extras: async, dotenv] [with: requests]
- flask

----- stderr -----
"###);

// Test with --show-extras and --show-paths
uv_snapshot!(context.filters(), context.tool_list().arg("--show-extras").arg("--show-paths")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###"
success: true
exit_code: 0
----- stdout -----
black v24.2.0 ([TEMP_DIR]/tools/black)
- black ([TEMP_DIR]/bin/black)
- blackd ([TEMP_DIR]/bin/blackd)
flask v3.0.2 [extras: async, dotenv] ([TEMP_DIR]/tools/flask)
- flask ([TEMP_DIR]/bin/flask)

----- stderr -----
"###);

// Test with --show-extras and --show-version-specifiers
uv_snapshot!(context.filters(), context.tool_list().arg("--show-extras").arg("--show-version-specifiers")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###"
success: true
exit_code: 0
----- stdout -----
black v24.2.0 [required: ==24.2.0]
- black
- blackd
flask v3.0.2 [extras: async, dotenv]
- flask

----- stderr -----
"###);

// Test with all flags including --show-extras
uv_snapshot!(context.filters(), context.tool_list()
.arg("--show-extras")
.arg("--show-with")
.arg("--show-version-specifiers")
.arg("--show-paths")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###"
success: true
exit_code: 0
----- stdout -----
black v24.2.0 [required: ==24.2.0] ([TEMP_DIR]/tools/black)
- black ([TEMP_DIR]/bin/black)
- blackd ([TEMP_DIR]/bin/blackd)
flask v3.0.2 [extras: async, dotenv] [with: requests] ([TEMP_DIR]/tools/flask)
- flask ([TEMP_DIR]/bin/flask)

----- stderr -----
"###);
}
1 change: 1 addition & 0 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -2293,6 +2293,7 @@ uv tool list [OPTIONS]
<p>This setting has no effect when used in the <code>uv pip</code> interface.</p>
<p>May also be set with the <code>UV_PROJECT</code> environment variable.</p></dd><dt id="uv-tool-list--quiet"><a href="#uv-tool-list--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output.</p>
<p>Repeating this option, e.g., <code>-qq</code>, will enable a silent mode in which uv will write no output to stdout.</p>
</dd><dt id="uv-tool-list--show-extras"><a href="#uv-tool-list--show-extras"><code>--show-extras</code></a></dt><dd><p>Whether to display the extra requirements installed with each tool</p>
</dd><dt id="uv-tool-list--show-paths"><a href="#uv-tool-list--show-paths"><code>--show-paths</code></a></dt><dd><p>Whether to display the path to each tool environment and installed executable</p>
</dd><dt id="uv-tool-list--show-version-specifiers"><a href="#uv-tool-list--show-version-specifiers"><code>--show-version-specifiers</code></a></dt><dd><p>Whether to display the version specifier(s) used to install each tool</p>
</dd><dt id="uv-tool-list--show-with"><a href="#uv-tool-list--show-with"><code>--show-with</code></a></dt><dd><p>Whether to display the additional requirements installed with each tool</p>
Expand Down
Loading