mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Add a puffin remove
command (#120)
This commit is contained in:
parent
1fc03780f9
commit
2d14c0647e
7 changed files with 142 additions and 8 deletions
|
@ -5,6 +5,7 @@ pub(crate) use add::add;
|
||||||
pub(crate) use clean::clean;
|
pub(crate) use clean::clean;
|
||||||
pub(crate) use compile::compile;
|
pub(crate) use compile::compile;
|
||||||
pub(crate) use freeze::freeze;
|
pub(crate) use freeze::freeze;
|
||||||
|
pub(crate) use remove::remove;
|
||||||
pub(crate) use sync::{sync, SyncFlags};
|
pub(crate) use sync::{sync, SyncFlags};
|
||||||
pub(crate) use uninstall::uninstall;
|
pub(crate) use uninstall::uninstall;
|
||||||
pub(crate) use venv::venv;
|
pub(crate) use venv::venv;
|
||||||
|
@ -13,6 +14,7 @@ mod add;
|
||||||
mod clean;
|
mod clean;
|
||||||
mod compile;
|
mod compile;
|
||||||
mod freeze;
|
mod freeze;
|
||||||
|
mod remove;
|
||||||
mod reporters;
|
mod reporters;
|
||||||
mod sync;
|
mod sync;
|
||||||
mod uninstall;
|
mod uninstall;
|
||||||
|
|
73
crates/puffin-cli/src/commands/remove.rs
Normal file
73
crates/puffin-cli/src/commands/remove.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use miette::{Diagnostic, IntoDiagnostic};
|
||||||
|
use thiserror::Error;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
use puffin_workspace::WorkspaceError;
|
||||||
|
|
||||||
|
use crate::commands::ExitStatus;
|
||||||
|
use crate::printer::Printer;
|
||||||
|
|
||||||
|
/// Remove a dependency from the workspace.
|
||||||
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
|
pub(crate) fn remove(name: &str, _printer: Printer) -> Result<ExitStatus> {
|
||||||
|
match remove_impl(name) {
|
||||||
|
Ok(status) => Ok(status),
|
||||||
|
Err(err) => {
|
||||||
|
#[allow(clippy::print_stderr)]
|
||||||
|
{
|
||||||
|
eprint!("{err:?}");
|
||||||
|
}
|
||||||
|
Ok(ExitStatus::Failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
enum RemoveError {
|
||||||
|
#[error(
|
||||||
|
"Could not find a `pyproject.toml` file in the current directory or any of its parents"
|
||||||
|
)]
|
||||||
|
#[diagnostic(code(puffin::remove::workspace_not_found))]
|
||||||
|
WorkspaceNotFound,
|
||||||
|
|
||||||
|
#[error("Failed to parse `pyproject.toml` at: `{0}`")]
|
||||||
|
#[diagnostic(code(puffin::remove::parse_error))]
|
||||||
|
ParseError(PathBuf, #[source] WorkspaceError),
|
||||||
|
|
||||||
|
#[error("Failed to write `pyproject.toml` to: `{0}`")]
|
||||||
|
#[diagnostic(code(puffin::remove::write_error))]
|
||||||
|
WriteError(PathBuf, #[source] WorkspaceError),
|
||||||
|
|
||||||
|
#[error("Failed to remove `{0}` from `pyproject.toml`")]
|
||||||
|
#[diagnostic(code(puffin::remove::parse_error))]
|
||||||
|
RemovalError(String, #[source] WorkspaceError),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_impl(name: &str) -> miette::Result<ExitStatus> {
|
||||||
|
// Locate the workspace.
|
||||||
|
let cwd = std::env::current_dir().into_diagnostic()?;
|
||||||
|
let Some(workspace_root) = puffin_workspace::find_pyproject_toml(cwd) else {
|
||||||
|
return Err(RemoveError::WorkspaceNotFound.into());
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Found workspace at: {}", workspace_root.display());
|
||||||
|
|
||||||
|
// Parse the manifest.
|
||||||
|
let mut manifest = puffin_workspace::Workspace::try_from(workspace_root.as_path())
|
||||||
|
.map_err(|err| RemoveError::ParseError(workspace_root.clone(), err))?;
|
||||||
|
|
||||||
|
// Remove the dependency.
|
||||||
|
manifest
|
||||||
|
.remove_dependency(name)
|
||||||
|
.map_err(|err| RemoveError::RemovalError(name.to_string(), err))?;
|
||||||
|
|
||||||
|
// Write the manifest back to disk.
|
||||||
|
manifest
|
||||||
|
.save(&workspace_root)
|
||||||
|
.map_err(|err| RemoveError::WriteError(workspace_root.clone(), err))?;
|
||||||
|
|
||||||
|
Ok(ExitStatus::Success)
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use fs_err::tokio as fs;
|
use fs_err::tokio as fs;
|
||||||
|
|
||||||
|
@ -29,8 +29,8 @@ pub(crate) async fn venv(
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// If the path already exists, remove it.
|
// If the path already exists, remove it.
|
||||||
fs::remove_file(path).await.context("Foo")?;
|
fs::remove_file(path).await.ok();
|
||||||
fs::remove_dir_all(path).await?;
|
fs::remove_dir_all(path).await.ok();
|
||||||
|
|
||||||
writeln!(
|
writeln!(
|
||||||
printer,
|
printer,
|
||||||
|
|
|
@ -47,6 +47,8 @@ enum Commands {
|
||||||
Venv(VenvArgs),
|
Venv(VenvArgs),
|
||||||
/// Add a dependency to the workspace.
|
/// Add a dependency to the workspace.
|
||||||
Add(AddArgs),
|
Add(AddArgs),
|
||||||
|
/// Remove a dependency from the workspace.
|
||||||
|
Remove(RemoveArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
@ -87,7 +89,13 @@ struct VenvArgs {
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
struct AddArgs {
|
struct AddArgs {
|
||||||
/// The name of the package to add.
|
/// The name of the package to add (e.g., `Django==4.2.6`).
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
struct RemoveArgs {
|
||||||
|
/// The name of the package to remove (e.g., `Django`).
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +170,7 @@ async fn main() -> ExitCode {
|
||||||
}
|
}
|
||||||
Commands::Venv(args) => commands::venv(&args.name, args.python.as_deref(), printer).await,
|
Commands::Venv(args) => commands::venv(&args.name, args.python.as_deref(), printer).await,
|
||||||
Commands::Add(args) => commands::add(&args.name, printer),
|
Commands::Add(args) => commands::add(&args.name, printer),
|
||||||
|
Commands::Remove(args) => commands::remove(&args.name, printer),
|
||||||
};
|
};
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
|
|
|
@ -14,4 +14,13 @@ pub enum WorkspaceError {
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
InvalidRequirement(#[from] pep508_rs::Pep508Error),
|
InvalidRequirement(#[from] pep508_rs::Pep508Error),
|
||||||
|
|
||||||
|
#[error("no `[project]` table found in `pyproject.toml`")]
|
||||||
|
MissingProjectTable,
|
||||||
|
|
||||||
|
#[error("no `[project.dependencies]` array found in `pyproject.toml`")]
|
||||||
|
MissingProjectDependenciesArray,
|
||||||
|
|
||||||
|
#[error("unable to find package: `{0}`")]
|
||||||
|
MissingPackage(String),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
/// Reformat a TOML array to use multiline format.
|
/// Reformat a TOML array to use multiline format.
|
||||||
pub(crate) fn format_multiline_array(dependencies: &mut toml_edit::Array) {
|
pub(crate) fn format_multiline_array(dependencies: &mut toml_edit::Array) {
|
||||||
|
if dependencies.is_empty() {
|
||||||
|
dependencies.set_trailing("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for item in dependencies.iter_mut() {
|
for item in dependencies.iter_mut() {
|
||||||
let decor = item.decor_mut();
|
let decor = item.decor_mut();
|
||||||
let mut prefix = String::new();
|
let mut prefix = String::new();
|
||||||
|
|
|
@ -76,9 +76,7 @@ impl Workspace {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO(charlie): Awkward `drop` pattern required to work around destructors, apparently.
|
let index = dependencies.iter().position(|item| {
|
||||||
let mut iter = dependencies.iter();
|
|
||||||
let index = iter.position(|item| {
|
|
||||||
let Some(item) = item.as_str() else {
|
let Some(item) = item.as_str() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
@ -90,7 +88,6 @@ impl Workspace {
|
||||||
PackageName::normalize(&requirement.requirement.name)
|
PackageName::normalize(&requirement.requirement.name)
|
||||||
== PackageName::normalize(existing.name)
|
== PackageName::normalize(existing.name)
|
||||||
});
|
});
|
||||||
drop(iter);
|
|
||||||
|
|
||||||
if let Some(index) = index {
|
if let Some(index) = index {
|
||||||
dependencies.replace(index, requirement.given_name);
|
dependencies.replace(index, requirement.given_name);
|
||||||
|
@ -101,6 +98,45 @@ impl Workspace {
|
||||||
format_multiline_array(dependencies);
|
format_multiline_array(dependencies);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove a dependency from the workspace.
|
||||||
|
pub fn remove_dependency(&mut self, name: &str) -> Result<(), WorkspaceError> {
|
||||||
|
let Some(project) = self
|
||||||
|
.document
|
||||||
|
.get_mut("project")
|
||||||
|
.map(|project| project.as_table_mut().unwrap())
|
||||||
|
else {
|
||||||
|
return Err(WorkspaceError::MissingProjectTable);
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(dependencies) = project
|
||||||
|
.get_mut("dependencies")
|
||||||
|
.map(|dependencies| dependencies.as_array_mut().unwrap())
|
||||||
|
else {
|
||||||
|
return Err(WorkspaceError::MissingProjectDependenciesArray);
|
||||||
|
};
|
||||||
|
|
||||||
|
let index = dependencies.iter().position(|item| {
|
||||||
|
let Some(item) = item.as_str() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(existing) = Requirement::from_str(item) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
PackageName::normalize(name) == PackageName::normalize(existing.name)
|
||||||
|
});
|
||||||
|
|
||||||
|
let Some(index) = index else {
|
||||||
|
return Err(WorkspaceError::MissingPackage(name.to_string()));
|
||||||
|
};
|
||||||
|
|
||||||
|
dependencies.remove(index);
|
||||||
|
format_multiline_array(dependencies);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Save the workspace to disk.
|
/// Save the workspace to disk.
|
||||||
pub fn save(&self, path: impl AsRef<Path>) -> Result<(), WorkspaceError> {
|
pub fn save(&self, path: impl AsRef<Path>) -> Result<(), WorkspaceError> {
|
||||||
let file = fs::File::create(path.as_ref())?;
|
let file = fs::File::create(path.as_ref())?;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue