mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25: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 compile::compile;
|
||||
pub(crate) use freeze::freeze;
|
||||
pub(crate) use remove::remove;
|
||||
pub(crate) use sync::{sync, SyncFlags};
|
||||
pub(crate) use uninstall::uninstall;
|
||||
pub(crate) use venv::venv;
|
||||
|
@ -13,6 +14,7 @@ mod add;
|
|||
mod clean;
|
||||
mod compile;
|
||||
mod freeze;
|
||||
mod remove;
|
||||
mod reporters;
|
||||
mod sync;
|
||||
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::path::Path;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use fs_err::tokio as fs;
|
||||
|
||||
|
@ -29,8 +29,8 @@ pub(crate) async fn venv(
|
|||
)?;
|
||||
|
||||
// If the path already exists, remove it.
|
||||
fs::remove_file(path).await.context("Foo")?;
|
||||
fs::remove_dir_all(path).await?;
|
||||
fs::remove_file(path).await.ok();
|
||||
fs::remove_dir_all(path).await.ok();
|
||||
|
||||
writeln!(
|
||||
printer,
|
||||
|
|
|
@ -47,6 +47,8 @@ enum Commands {
|
|||
Venv(VenvArgs),
|
||||
/// Add a dependency to the workspace.
|
||||
Add(AddArgs),
|
||||
/// Remove a dependency from the workspace.
|
||||
Remove(RemoveArgs),
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
@ -87,7 +89,13 @@ struct VenvArgs {
|
|||
|
||||
#[derive(Args)]
|
||||
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,
|
||||
}
|
||||
|
||||
|
@ -162,6 +170,7 @@ async fn main() -> ExitCode {
|
|||
}
|
||||
Commands::Venv(args) => commands::venv(&args.name, args.python.as_deref(), printer).await,
|
||||
Commands::Add(args) => commands::add(&args.name, printer),
|
||||
Commands::Remove(args) => commands::remove(&args.name, printer),
|
||||
};
|
||||
|
||||
match result {
|
||||
|
|
|
@ -14,4 +14,13 @@ pub enum WorkspaceError {
|
|||
|
||||
#[error(transparent)]
|
||||
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.
|
||||
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() {
|
||||
let decor = item.decor_mut();
|
||||
let mut prefix = String::new();
|
||||
|
|
|
@ -76,9 +76,7 @@ impl Workspace {
|
|||
return;
|
||||
};
|
||||
|
||||
// TODO(charlie): Awkward `drop` pattern required to work around destructors, apparently.
|
||||
let mut iter = dependencies.iter();
|
||||
let index = iter.position(|item| {
|
||||
let index = dependencies.iter().position(|item| {
|
||||
let Some(item) = item.as_str() else {
|
||||
return false;
|
||||
};
|
||||
|
@ -90,7 +88,6 @@ impl Workspace {
|
|||
PackageName::normalize(&requirement.requirement.name)
|
||||
== PackageName::normalize(existing.name)
|
||||
});
|
||||
drop(iter);
|
||||
|
||||
if let Some(index) = index {
|
||||
dependencies.replace(index, requirement.given_name);
|
||||
|
@ -101,6 +98,45 @@ impl Workspace {
|
|||
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.
|
||||
pub fn save(&self, path: impl AsRef<Path>) -> Result<(), WorkspaceError> {
|
||||
let file = fs::File::create(path.as_ref())?;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue