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-traits",
|
||||
"puffin-warnings",
|
||||
"puffin-workspace",
|
||||
"pypi-types",
|
||||
"pyproject-toml",
|
||||
"regex",
|
||||
|
|
@ -2905,20 +2904,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "pyo3"
|
||||
version = "0.20.2"
|
||||
|
|
|
|||
|
|
@ -95,7 +95,6 @@ tokio = { version = "1.35.1", features = ["rt-multi-thread"] }
|
|||
tokio-tar = { version = "0.3.1" }
|
||||
tokio-util = { version = "0.7.10", features = ["compat"] }
|
||||
toml = { version = "0.8.8" }
|
||||
toml_edit = { version = "0.21.0" }
|
||||
tracing = { version = "0.1.40" }
|
||||
tracing-durations-export = { version = "0.2.0", features = ["plot"] }
|
||||
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-traits = { path = "../puffin-traits" }
|
||||
puffin-warnings = { path = "../puffin-warnings" }
|
||||
puffin-workspace = { path = "../puffin-workspace" }
|
||||
pypi-types = { path = "../pypi-types" }
|
||||
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::time::Duration;
|
||||
|
||||
pub(crate) use add::add;
|
||||
pub(crate) use clean::clean;
|
||||
use distribution_types::InstalledMetadata;
|
||||
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_sync::pip_sync;
|
||||
pub(crate) use pip_uninstall::pip_uninstall;
|
||||
pub(crate) use remove::remove;
|
||||
pub(crate) use venv::venv;
|
||||
|
||||
mod add;
|
||||
mod clean;
|
||||
mod freeze;
|
||||
mod pip_compile;
|
||||
mod pip_install;
|
||||
mod pip_sync;
|
||||
mod pip_uninstall;
|
||||
mod remove;
|
||||
mod reporters;
|
||||
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),
|
||||
/// Clear the cache.
|
||||
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)]
|
||||
|
|
@ -878,8 +872,6 @@ async fn run() -> Result<ExitStatus> {
|
|||
)
|
||||
.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