mirror of https://github.com/astral-sh/uv
Remove `add` and `remove` commands (#1259)
## Summary These add and remove dependencies from a `pyproject.toml` -- but they're currently hidden, and don't match the rest of the workflow. We can re-add them when the time is right.
This commit is contained in:
parent
d4bbaf1755
commit
62416286e2
|
|
@ -2475,7 +2475,6 @@ dependencies = [
|
||||||
"puffin-resolver",
|
"puffin-resolver",
|
||||||
"puffin-traits",
|
"puffin-traits",
|
||||||
"puffin-warnings",
|
"puffin-warnings",
|
||||||
"puffin-workspace",
|
|
||||||
"pypi-types",
|
"pypi-types",
|
||||||
"pyproject-toml",
|
"pyproject-toml",
|
||||||
"regex",
|
"regex",
|
||||||
|
|
@ -2905,20 +2904,6 @@ dependencies = [
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "puffin-workspace"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"fs-err",
|
|
||||||
"pep440_rs",
|
|
||||||
"pep508_rs",
|
|
||||||
"puffin-normalize",
|
|
||||||
"pyproject-toml",
|
|
||||||
"serde",
|
|
||||||
"thiserror",
|
|
||||||
"toml_edit",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyo3"
|
name = "pyo3"
|
||||||
version = "0.20.2"
|
version = "0.20.2"
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,6 @@ tokio = { version = "1.35.1", features = ["rt-multi-thread"] }
|
||||||
tokio-tar = { version = "0.3.1" }
|
tokio-tar = { version = "0.3.1" }
|
||||||
tokio-util = { version = "0.7.10", features = ["compat"] }
|
tokio-util = { version = "0.7.10", features = ["compat"] }
|
||||||
toml = { version = "0.8.8" }
|
toml = { version = "0.8.8" }
|
||||||
toml_edit = { version = "0.21.0" }
|
|
||||||
tracing = { version = "0.1.40" }
|
tracing = { version = "0.1.40" }
|
||||||
tracing-durations-export = { version = "0.2.0", features = ["plot"] }
|
tracing-durations-export = { version = "0.2.0", features = ["plot"] }
|
||||||
tracing-indicatif = { version = "0.3.6" }
|
tracing-indicatif = { version = "0.3.6" }
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "puffin-workspace"
|
|
||||||
version = "0.0.1"
|
|
||||||
edition = { workspace = true }
|
|
||||||
rust-version = { workspace = true }
|
|
||||||
homepage = { workspace = true }
|
|
||||||
documentation = { workspace = true }
|
|
||||||
repository = { workspace = true }
|
|
||||||
authors = { workspace = true }
|
|
||||||
license = { workspace = true }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
pep440_rs = { path = "../pep440-rs" }
|
|
||||||
pep508_rs = { path = "../pep508-rs" }
|
|
||||||
puffin-normalize = { path = "../puffin-normalize" }
|
|
||||||
|
|
||||||
fs-err = { workspace = true }
|
|
||||||
pyproject-toml = { workspace = true }
|
|
||||||
serde = { workspace = true }
|
|
||||||
thiserror = { workspace = true }
|
|
||||||
toml_edit = { workspace = true, features = ["serde"] }
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
use std::io;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum WorkspaceError {
|
|
||||||
#[error(transparent)]
|
|
||||||
IO(#[from] io::Error),
|
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
InvalidToml(#[from] toml_edit::TomlError),
|
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
InvalidPyproject(#[from] toml_edit::de::Error),
|
|
||||||
|
|
||||||
#[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,21 +0,0 @@
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
pub use error::WorkspaceError;
|
|
||||||
pub use verbatim::VerbatimRequirement;
|
|
||||||
pub use workspace::Workspace;
|
|
||||||
|
|
||||||
mod error;
|
|
||||||
mod toml;
|
|
||||||
mod verbatim;
|
|
||||||
mod workspace;
|
|
||||||
|
|
||||||
/// Find the closest `pyproject.toml` file to the given path.
|
|
||||||
pub fn find_pyproject_toml(path: impl AsRef<Path>) -> Option<PathBuf> {
|
|
||||||
for directory in path.as_ref().ancestors() {
|
|
||||||
let pyproject_toml = directory.join("pyproject.toml");
|
|
||||||
if pyproject_toml.is_file() {
|
|
||||||
return Some(pyproject_toml);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
/// 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();
|
|
||||||
for comment in find_comments(decor.prefix()).chain(find_comments(decor.suffix())) {
|
|
||||||
prefix.push_str("\n ");
|
|
||||||
prefix.push_str(comment);
|
|
||||||
}
|
|
||||||
prefix.push_str("\n ");
|
|
||||||
decor.set_prefix(prefix);
|
|
||||||
decor.set_suffix("");
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies.set_trailing(&{
|
|
||||||
let mut comments = find_comments(Some(dependencies.trailing())).peekable();
|
|
||||||
let mut value = String::new();
|
|
||||||
if comments.peek().is_some() {
|
|
||||||
for comment in comments {
|
|
||||||
value.push_str("\n ");
|
|
||||||
value.push_str(comment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
value.push('\n');
|
|
||||||
value
|
|
||||||
});
|
|
||||||
|
|
||||||
dependencies.set_trailing_comma(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return an iterator over the comments in a raw string.
|
|
||||||
fn find_comments(raw_string: Option<&toml_edit::RawString>) -> impl Iterator<Item = &str> {
|
|
||||||
raw_string
|
|
||||||
.and_then(toml_edit::RawString::as_str)
|
|
||||||
.unwrap_or("")
|
|
||||||
.lines()
|
|
||||||
.filter_map(|line| {
|
|
||||||
let line = line.trim();
|
|
||||||
line.starts_with('#').then_some(line)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use pep508_rs::Requirement;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct VerbatimRequirement<'a> {
|
|
||||||
/// The name of the requirement as provided by the user.
|
|
||||||
pub given_name: &'a str,
|
|
||||||
/// The normalized requirement.
|
|
||||||
pub requirement: Requirement,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a str> for VerbatimRequirement<'a> {
|
|
||||||
type Error = pep508_rs::Pep508Error;
|
|
||||||
|
|
||||||
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
|
|
||||||
let requirement = Requirement::from_str(s)?;
|
|
||||||
Ok(Self {
|
|
||||||
given_name: s,
|
|
||||||
requirement,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
use std::io;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use fs_err as fs;
|
|
||||||
use pyproject_toml::PyProjectToml;
|
|
||||||
use toml_edit::Document;
|
|
||||||
|
|
||||||
use pep508_rs::Requirement;
|
|
||||||
use puffin_normalize::PackageName;
|
|
||||||
|
|
||||||
use crate::toml::format_multiline_array;
|
|
||||||
use crate::verbatim::VerbatimRequirement;
|
|
||||||
use crate::WorkspaceError;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Workspace {
|
|
||||||
/// The parsed `pyproject.toml`.
|
|
||||||
#[allow(unused)]
|
|
||||||
pyproject_toml: PyProjectToml,
|
|
||||||
|
|
||||||
/// The raw document.
|
|
||||||
document: Document,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Workspace {
|
|
||||||
/// Add a dependency to the workspace.
|
|
||||||
pub fn add_dependency(&mut self, requirement: &VerbatimRequirement<'_>) {
|
|
||||||
let Some(project) = self
|
|
||||||
.document
|
|
||||||
.get_mut("project")
|
|
||||||
.map(|project| project.as_table_mut().unwrap())
|
|
||||||
else {
|
|
||||||
// No `project` table.
|
|
||||||
let mut dependencies = toml_edit::Array::new();
|
|
||||||
dependencies.push(requirement.given_name);
|
|
||||||
format_multiline_array(&mut dependencies);
|
|
||||||
|
|
||||||
let mut project = toml_edit::Table::new();
|
|
||||||
project.insert(
|
|
||||||
"dependencies",
|
|
||||||
toml_edit::Item::Value(toml_edit::Value::Array(dependencies)),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.document
|
|
||||||
.insert("project", toml_edit::Item::Table(project));
|
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(dependencies) = project
|
|
||||||
.get_mut("dependencies")
|
|
||||||
.map(|dependencies| dependencies.as_array_mut().unwrap())
|
|
||||||
else {
|
|
||||||
// No `dependencies` array.
|
|
||||||
let mut dependencies = toml_edit::Array::new();
|
|
||||||
dependencies.push(requirement.given_name);
|
|
||||||
format_multiline_array(&mut dependencies);
|
|
||||||
|
|
||||||
project.insert(
|
|
||||||
"dependencies",
|
|
||||||
toml_edit::Item::Value(toml_edit::Value::Array(dependencies)),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
requirement.requirement.name == existing.name
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(index) = index {
|
|
||||||
dependencies.replace(index, requirement.given_name);
|
|
||||||
} else {
|
|
||||||
dependencies.push(requirement.given_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
format_multiline_array(dependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a dependency from the workspace.
|
|
||||||
pub fn remove_dependency(&mut self, name: &PackageName) -> 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
name == &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())?;
|
|
||||||
self.write(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write the workspace to a writer.
|
|
||||||
fn write(&self, mut writer: impl io::Write) -> Result<(), WorkspaceError> {
|
|
||||||
writer.write_all(self.document.to_string().as_bytes())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&Path> for Workspace {
|
|
||||||
type Error = WorkspaceError;
|
|
||||||
|
|
||||||
fn try_from(path: &Path) -> Result<Self, Self::Error> {
|
|
||||||
// Read the `pyproject.toml` from disk.
|
|
||||||
let contents = fs::read_to_string(path)?;
|
|
||||||
|
|
||||||
// Parse the `pyproject.toml` file.
|
|
||||||
let pyproject_toml = toml_edit::de::from_str::<PyProjectToml>(&contents)?;
|
|
||||||
|
|
||||||
// Parse the raw document.
|
|
||||||
let document = contents.parse::<Document>()?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
pyproject_toml,
|
|
||||||
document,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -34,7 +34,6 @@ puffin-normalize = { path = "../puffin-normalize" }
|
||||||
puffin-resolver = { path = "../puffin-resolver", features = ["clap"] }
|
puffin-resolver = { path = "../puffin-resolver", features = ["clap"] }
|
||||||
puffin-traits = { path = "../puffin-traits" }
|
puffin-traits = { path = "../puffin-traits" }
|
||||||
puffin-warnings = { path = "../puffin-warnings" }
|
puffin-warnings = { path = "../puffin-warnings" }
|
||||||
puffin-workspace = { path = "../puffin-workspace" }
|
|
||||||
pypi-types = { path = "../pypi-types" }
|
pypi-types = { path = "../pypi-types" }
|
||||||
requirements-txt = { path = "../requirements-txt" }
|
requirements-txt = { path = "../requirements-txt" }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
||||||
/// Add a dependency to the workspace.
|
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
|
||||||
pub(crate) fn add(name: &str, _printer: Printer) -> Result<ExitStatus> {
|
|
||||||
match add_impl(name) {
|
|
||||||
Ok(status) => Ok(status),
|
|
||||||
Err(err) => {
|
|
||||||
#[allow(clippy::print_stderr)]
|
|
||||||
{
|
|
||||||
eprint!("{err:?}");
|
|
||||||
}
|
|
||||||
Ok(ExitStatus::Failure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug, Diagnostic)]
|
|
||||||
enum AddError {
|
|
||||||
#[error(
|
|
||||||
"Could not find a `pyproject.toml` file in the current directory or any of its parents"
|
|
||||||
)]
|
|
||||||
#[diagnostic(code(puffin::add::workspace_not_found))]
|
|
||||||
WorkspaceNotFound,
|
|
||||||
|
|
||||||
#[error("Failed to parse requirement: `{0}`")]
|
|
||||||
#[diagnostic(code(puffin::add::invalid_requirement))]
|
|
||||||
InvalidRequirement(String, #[source] pep508_rs::Pep508Error),
|
|
||||||
|
|
||||||
#[error("Failed to parse `pyproject.toml` at: `{0}`")]
|
|
||||||
#[diagnostic(code(puffin::add::parse))]
|
|
||||||
ParseError(PathBuf, #[source] WorkspaceError),
|
|
||||||
|
|
||||||
#[error("Failed to write `pyproject.toml` to: `{0}`")]
|
|
||||||
#[diagnostic(code(puffin::add::write))]
|
|
||||||
WriteError(PathBuf, #[source] WorkspaceError),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_impl(name: &str) -> miette::Result<ExitStatus> {
|
|
||||||
let requirement = puffin_workspace::VerbatimRequirement::try_from(name)
|
|
||||||
.map_err(|err| AddError::InvalidRequirement(name.to_string(), err))?;
|
|
||||||
|
|
||||||
// Locate the workspace.
|
|
||||||
let cwd = std::env::current_dir().into_diagnostic()?;
|
|
||||||
let Some(workspace_root) = puffin_workspace::find_pyproject_toml(cwd) else {
|
|
||||||
return Err(AddError::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| AddError::ParseError(workspace_root.clone(), err))?;
|
|
||||||
|
|
||||||
// Add the dependency.
|
|
||||||
manifest.add_dependency(&requirement);
|
|
||||||
|
|
||||||
// Write the manifest back to disk.
|
|
||||||
manifest
|
|
||||||
.save(&workspace_root)
|
|
||||||
.map_err(|err| AddError::WriteError(workspace_root.clone(), err))?;
|
|
||||||
|
|
||||||
Ok(ExitStatus::Success)
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub(crate) use add::add;
|
|
||||||
pub(crate) use clean::clean;
|
pub(crate) use clean::clean;
|
||||||
use distribution_types::InstalledMetadata;
|
use distribution_types::InstalledMetadata;
|
||||||
pub(crate) use freeze::freeze;
|
pub(crate) use freeze::freeze;
|
||||||
|
|
@ -9,17 +8,14 @@ pub(crate) use pip_compile::{extra_name_with_clap_error, pip_compile, Upgrade};
|
||||||
pub(crate) use pip_install::pip_install;
|
pub(crate) use pip_install::pip_install;
|
||||||
pub(crate) use pip_sync::pip_sync;
|
pub(crate) use pip_sync::pip_sync;
|
||||||
pub(crate) use pip_uninstall::pip_uninstall;
|
pub(crate) use pip_uninstall::pip_uninstall;
|
||||||
pub(crate) use remove::remove;
|
|
||||||
pub(crate) use venv::venv;
|
pub(crate) use venv::venv;
|
||||||
|
|
||||||
mod add;
|
|
||||||
mod clean;
|
mod clean;
|
||||||
mod freeze;
|
mod freeze;
|
||||||
mod pip_compile;
|
mod pip_compile;
|
||||||
mod pip_install;
|
mod pip_install;
|
||||||
mod pip_sync;
|
mod pip_sync;
|
||||||
mod pip_uninstall;
|
mod pip_uninstall;
|
||||||
mod remove;
|
|
||||||
mod reporters;
|
mod reporters;
|
||||||
mod venv;
|
mod venv;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use miette::{Diagnostic, IntoDiagnostic};
|
|
||||||
use thiserror::Error;
|
|
||||||
use tracing::info;
|
|
||||||
|
|
||||||
use puffin_normalize::PackageName;
|
|
||||||
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: &PackageName, _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))]
|
|
||||||
ParseError(PathBuf, #[source] WorkspaceError),
|
|
||||||
|
|
||||||
#[error("Failed to write `pyproject.toml` to: `{0}`")]
|
|
||||||
#[diagnostic(code(puffin::remove::write))]
|
|
||||||
WriteError(PathBuf, #[source] WorkspaceError),
|
|
||||||
|
|
||||||
#[error("Failed to remove `{0}` from `pyproject.toml`")]
|
|
||||||
#[diagnostic(code(puffin::remove::parse))]
|
|
||||||
RemovalError(String, #[source] WorkspaceError),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_impl(name: &PackageName) -> 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)
|
|
||||||
}
|
|
||||||
|
|
@ -110,12 +110,6 @@ enum Commands {
|
||||||
Venv(VenvArgs),
|
Venv(VenvArgs),
|
||||||
/// Clear the cache.
|
/// Clear the cache.
|
||||||
Clean(CleanArgs),
|
Clean(CleanArgs),
|
||||||
/// Add a dependency to the workspace.
|
|
||||||
#[clap(hide = true)]
|
|
||||||
Add(AddArgs),
|
|
||||||
/// Remove a dependency from the workspace.
|
|
||||||
#[clap(hide = true)]
|
|
||||||
Remove(RemoveArgs),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
|
@ -878,8 +872,6 @@ async fn run() -> Result<ExitStatus> {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
Commands::Add(args) => commands::add(&args.name, printer),
|
|
||||||
Commands::Remove(args) => commands::remove(&args.name, printer),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,168 +0,0 @@
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use assert_fs::prelude::*;
|
|
||||||
|
|
||||||
use crate::common::{get_bin, puffin_snapshot};
|
|
||||||
|
|
||||||
mod common;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn missing_pyproject_toml() -> Result<()> {
|
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
|
||||||
let pyproject_toml = temp_dir.child("pyproject.toml");
|
|
||||||
|
|
||||||
puffin_snapshot!(Command::new(get_bin())
|
|
||||||
.arg("add")
|
|
||||||
.arg("flask")
|
|
||||||
.current_dir(&temp_dir), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
puffin::add::workspace_not_found
|
|
||||||
|
|
||||||
× Could not find a `pyproject.toml` file in the current directory or any of
|
|
||||||
│ its parents
|
|
||||||
"###);
|
|
||||||
|
|
||||||
pyproject_toml.assert(predicates::path::missing());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn missing_project_table() -> Result<()> {
|
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
|
||||||
let pyproject_toml = temp_dir.child("pyproject.toml");
|
|
||||||
pyproject_toml.touch()?;
|
|
||||||
|
|
||||||
puffin_snapshot!(Command::new(get_bin())
|
|
||||||
.arg("add")
|
|
||||||
.arg("flask")
|
|
||||||
.current_dir(&temp_dir), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
|
|
||||||
pyproject_toml.assert(
|
|
||||||
r#"[project]
|
|
||||||
dependencies = [
|
|
||||||
"flask",
|
|
||||||
]
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn missing_dependencies_array() -> Result<()> {
|
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
|
||||||
let pyproject_toml = temp_dir.child("pyproject.toml");
|
|
||||||
pyproject_toml.touch()?;
|
|
||||||
pyproject_toml.write_str(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
puffin_snapshot!(Command::new(get_bin())
|
|
||||||
.arg("add")
|
|
||||||
.arg("flask")
|
|
||||||
.current_dir(&temp_dir), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
|
|
||||||
pyproject_toml.assert(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = [
|
|
||||||
"flask",
|
|
||||||
]
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn replace_dependency() -> Result<()> {
|
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
|
||||||
let pyproject_toml = temp_dir.child("pyproject.toml");
|
|
||||||
pyproject_toml.touch()?;
|
|
||||||
pyproject_toml.write_str(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = [
|
|
||||||
"flask==1.0.0",
|
|
||||||
]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
puffin_snapshot!(Command::new(get_bin())
|
|
||||||
.arg("add")
|
|
||||||
.arg("flask==2.0.0")
|
|
||||||
.current_dir(&temp_dir), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
|
|
||||||
pyproject_toml.assert(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = [
|
|
||||||
"flask==2.0.0",
|
|
||||||
]
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn reformat_array() -> Result<()> {
|
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
|
||||||
let pyproject_toml = temp_dir.child("pyproject.toml");
|
|
||||||
pyproject_toml.touch()?;
|
|
||||||
pyproject_toml.write_str(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = ["flask==1.0.0"]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
puffin_snapshot!(Command::new(get_bin())
|
|
||||||
.arg("add")
|
|
||||||
.arg("requests")
|
|
||||||
.current_dir(&temp_dir), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
|
|
||||||
pyproject_toml.assert(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = [
|
|
||||||
"flask==1.0.0",
|
|
||||||
"requests",
|
|
||||||
]
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
@ -1,281 +0,0 @@
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use assert_fs::prelude::*;
|
|
||||||
|
|
||||||
use crate::common::{get_bin, puffin_snapshot};
|
|
||||||
|
|
||||||
mod common;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn missing_pyproject_toml() -> Result<()> {
|
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
|
||||||
let pyproject_toml = temp_dir.child("pyproject.toml");
|
|
||||||
|
|
||||||
puffin_snapshot!(Command::new(get_bin())
|
|
||||||
.arg("remove")
|
|
||||||
.arg("flask")
|
|
||||||
.current_dir(&temp_dir), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
puffin::remove::workspace_not_found
|
|
||||||
|
|
||||||
× Could not find a `pyproject.toml` file in the current directory or any of
|
|
||||||
│ its parents
|
|
||||||
"###);
|
|
||||||
|
|
||||||
pyproject_toml.assert(predicates::path::missing());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn missing_project_table() -> Result<()> {
|
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
|
||||||
let pyproject_toml = temp_dir.child("pyproject.toml");
|
|
||||||
pyproject_toml.touch()?;
|
|
||||||
|
|
||||||
puffin_snapshot!(Command::new(get_bin())
|
|
||||||
.arg("remove")
|
|
||||||
.arg("flask")
|
|
||||||
.current_dir(&temp_dir), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
puffin::remove::parse
|
|
||||||
|
|
||||||
× Failed to remove `flask` from `pyproject.toml`
|
|
||||||
╰─▶ no `[project]` table found in `pyproject.toml`
|
|
||||||
"###);
|
|
||||||
|
|
||||||
pyproject_toml.assert(predicates::str::is_empty());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn missing_dependencies_array() -> Result<()> {
|
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
|
||||||
let pyproject_toml = temp_dir.child("pyproject.toml");
|
|
||||||
pyproject_toml.touch()?;
|
|
||||||
pyproject_toml.write_str(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
puffin_snapshot!(Command::new(get_bin())
|
|
||||||
.arg("remove")
|
|
||||||
.arg("flask")
|
|
||||||
.current_dir(&temp_dir), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
puffin::remove::parse
|
|
||||||
|
|
||||||
× Failed to remove `flask` from `pyproject.toml`
|
|
||||||
╰─▶ no `[project.dependencies]` array found in `pyproject.toml`
|
|
||||||
"###);
|
|
||||||
|
|
||||||
pyproject_toml.assert(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn missing_dependency() -> Result<()> {
|
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
|
||||||
let pyproject_toml = temp_dir.child("pyproject.toml");
|
|
||||||
pyproject_toml.touch()?;
|
|
||||||
pyproject_toml.write_str(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = [
|
|
||||||
"flask==1.0.0",
|
|
||||||
]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
puffin_snapshot!(Command::new(get_bin())
|
|
||||||
.arg("remove")
|
|
||||||
.arg("requests")
|
|
||||||
.current_dir(&temp_dir), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
puffin::remove::parse
|
|
||||||
|
|
||||||
× Failed to remove `requests` from `pyproject.toml`
|
|
||||||
╰─▶ unable to find package: `requests`
|
|
||||||
"###);
|
|
||||||
|
|
||||||
pyproject_toml.assert(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = [
|
|
||||||
"flask==1.0.0",
|
|
||||||
]
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn remove_dependency() -> Result<()> {
|
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
|
||||||
let pyproject_toml = temp_dir.child("pyproject.toml");
|
|
||||||
pyproject_toml.touch()?;
|
|
||||||
pyproject_toml.write_str(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = [
|
|
||||||
"flask==1.0.0",
|
|
||||||
"requests",
|
|
||||||
]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
puffin_snapshot!(Command::new(get_bin())
|
|
||||||
.arg("remove")
|
|
||||||
.arg("flask")
|
|
||||||
.current_dir(&temp_dir), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
|
|
||||||
pyproject_toml.assert(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = [
|
|
||||||
"requests",
|
|
||||||
]
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_array() -> Result<()> {
|
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
|
||||||
let pyproject_toml = temp_dir.child("pyproject.toml");
|
|
||||||
pyproject_toml.touch()?;
|
|
||||||
pyproject_toml.write_str(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = [
|
|
||||||
"requests",
|
|
||||||
]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
puffin_snapshot!(Command::new(get_bin())
|
|
||||||
.arg("remove")
|
|
||||||
.arg("requests")
|
|
||||||
.current_dir(&temp_dir), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
|
|
||||||
pyproject_toml.assert(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = []
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn normalize_name() -> Result<()> {
|
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
|
||||||
let pyproject_toml = temp_dir.child("pyproject.toml");
|
|
||||||
pyproject_toml.touch()?;
|
|
||||||
pyproject_toml.write_str(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = [
|
|
||||||
"flask==1.0.0",
|
|
||||||
"requests",
|
|
||||||
]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
puffin_snapshot!(Command::new(get_bin())
|
|
||||||
.arg("remove")
|
|
||||||
.arg("Flask")
|
|
||||||
.current_dir(&temp_dir), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
|
|
||||||
pyproject_toml.assert(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = [
|
|
||||||
"requests",
|
|
||||||
]
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn reformat_array() -> Result<()> {
|
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
|
||||||
let pyproject_toml = temp_dir.child("pyproject.toml");
|
|
||||||
pyproject_toml.touch()?;
|
|
||||||
pyproject_toml.write_str(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = ["flask==1.0.0", "requests"]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
puffin_snapshot!(Command::new(get_bin())
|
|
||||||
.arg("remove")
|
|
||||||
.arg("requests")
|
|
||||||
.current_dir(&temp_dir), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
|
|
||||||
pyproject_toml.assert(
|
|
||||||
r#"[project]
|
|
||||||
name = "project"
|
|
||||||
dependencies = [
|
|
||||||
"flask==1.0.0",
|
|
||||||
]
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue