Skip to content

Commit

Permalink
add --workspace option to uv add
Browse files Browse the repository at this point in the history
  • Loading branch information
ibraheemdev committed Jun 17, 2024
1 parent a813a1d commit d714a7f
Show file tree
Hide file tree
Showing 6 changed files with 425 additions and 239 deletions.
196 changes: 163 additions & 33 deletions crates/uv-distribution/src/pyproject_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use std::fmt;
use std::str::FromStr;

use thiserror::Error;
use toml_edit::{Array, DocumentMut, Item, RawString, Table, TomlError, Value};
use toml_edit::{Array, DocumentMut, InlineTable, Item, RawString, Table, TomlError, Value};

use pep508_rs::{PackageName, Requirement};
use pypi_types::VerbatimParsedUrl;

use crate::pyproject::PyProjectToml;
use crate::pyproject::{PyProjectToml, Source};

/// Raw and mutable representation of a `pyproject.toml`.
///
Expand All @@ -23,6 +23,8 @@ pub enum Error {
Parse(#[from] Box<TomlError>),
#[error("Dependencies in `pyproject.toml` are malformed")]
MalformedDependencies,
#[error("Sources in `pyproject.toml` are malformed")]
MalformedSources,
}

impl PyProjectTomlMut {
Expand All @@ -34,47 +36,165 @@ impl PyProjectTomlMut {
}

/// Adds a dependency to `project.dependencies`.
pub fn add_dependency(&mut self, req: &Requirement) -> Result<(), Error> {
add_dependency(req, &mut self.doc["project"]["dependencies"])
pub fn add_dependency(
&mut self,
req: &Requirement,
source: Option<&Source>,
) -> Result<(), Error> {
// Get or create `project.dependencies`.
let dependencies = self
.doc
.entry("project")
.or_insert(Item::Table(Table::new()))
.as_table_mut()
.ok_or(Error::MalformedDependencies)?
.entry("dependencies")
.or_insert(Item::Value(Value::Array(Array::new())))
.as_array_mut()
.ok_or(Error::MalformedDependencies)?;

add_dependency(req, dependencies);

if let Some(source) = source {
// Get or create `tool.uv.sources`.
let sources = self
.doc
.entry("tool")
.or_insert(implicit())
.as_table_mut()
.ok_or(Error::MalformedSources)?
.entry("uv")
.or_insert(implicit())
.as_table_mut()
.ok_or(Error::MalformedSources)?
.entry("sources")
.or_insert(Item::Table(Table::new()))
.as_table_mut()
.ok_or(Error::MalformedSources)?;

add_source(req, source, sources);
}

Ok(())
}

/// Adds a development dependency to `tool.uv.dev-dependencies`.
pub fn add_dev_dependency(&mut self, req: &Requirement) -> Result<(), Error> {
let tool = self.doc["tool"].or_insert({
let mut tool = Table::new();
tool.set_implicit(true);
Item::Table(tool)
});
let tool_uv = tool["uv"].or_insert(Item::Table(Table::new()));

add_dependency(req, &mut tool_uv["dev-dependencies"])
pub fn add_dev_dependency(
&mut self,
req: &Requirement,
source: Option<&Source>,
) -> Result<(), Error> {
// Get or create `tool.uv`.
let tool_uv = self
.doc
.entry("tool")
.or_insert(implicit())
.as_table_mut()
.ok_or(Error::MalformedSources)?
.entry("uv")
.or_insert(Item::Table(Table::new()))
.as_table_mut()
.ok_or(Error::MalformedSources)?;

// Get or create the `tool.uv.dev-dependencies` array.
let dev_dependencies = tool_uv
.entry("dev-dependencies")
.or_insert(Item::Value(Value::Array(Array::new())))
.as_array_mut()
.ok_or(Error::MalformedDependencies)?;

add_dependency(req, dev_dependencies);

if let Some(source) = source {
// Get or create `tool.uv.sources`.
let sources = tool_uv
.entry("sources")
.or_insert(Item::Table(Table::new()))
.as_table_mut()
.ok_or(Error::MalformedSources)?;

add_source(req, source, sources);
}

Ok(())
}

/// Removes all occurrences of dependencies with the given name.
pub fn remove_dependency(&mut self, req: &PackageName) -> Result<Vec<Requirement>, Error> {
remove_dependency(req, &mut self.doc["project"]["dependencies"])
// Try to get `project.dependencies`.
let Some(dependencies) = self
.doc
.get_mut("project")
.and_then(Item::as_table_mut)
.and_then(|project| project.get_mut("dependencies"))
else {
return Ok(Vec::new());
};
let dependencies = dependencies
.as_array_mut()
.ok_or(Error::MalformedDependencies)?;

let requirements = remove_dependency(req, dependencies);

// Remove a matching source from `tool.uv.sources`, if it exists.
if let Some(sources) = self
.doc
.get_mut("tool")
.and_then(Item::as_table_mut)
.and_then(|tool| tool.get_mut("uv"))
.and_then(Item::as_table_mut)
.and_then(|tool_uv| tool_uv.get_mut("sources"))
{
let sources = sources.as_table_mut().ok_or(Error::MalformedSources)?;
sources.remove(req.as_ref());
}

Ok(requirements)
}

/// Removes all occurrences of development dependencies with the given name.
pub fn remove_dev_dependency(&mut self, req: &PackageName) -> Result<Vec<Requirement>, Error> {
let Some(tool_uv) = self.doc.get_mut("tool").and_then(|tool| tool.get_mut("uv")) else {
let Some(tool_uv) = self
.doc
.get_mut("tool")
.and_then(Item::as_table_mut)
.and_then(|tool| tool.get_mut("uv"))
.and_then(Item::as_table_mut)
else {
return Ok(Vec::new());
};

remove_dependency(req, &mut tool_uv["dev-dependencies"])
// Try to get `tool.uv.dev-dependencies`.
let Some(dev_dependencies) = tool_uv.get_mut("dev-dependencies") else {
return Ok(Vec::new());
};
let dev_dependencies = dev_dependencies
.as_array_mut()
.ok_or(Error::MalformedDependencies)?;

let requirements = remove_dependency(req, dev_dependencies);

// Remove a matching source from `tool.uv.sources`, if it exists.
if let Some(sources) = tool_uv.get_mut("sources") {
let sources = sources.as_table_mut().ok_or(Error::MalformedSources)?;
sources.remove(req.as_ref());
};

Ok(requirements)
}
}

/// Adds a dependency to the given `deps` array.
pub fn add_dependency(req: &Requirement, deps: &mut Item) -> Result<(), Error> {
let deps = deps
.or_insert(Item::Value(Value::Array(Array::new())))
.as_array_mut()
.ok_or(Error::MalformedDependencies)?;
/// Returns an implicit table.
fn implicit() -> Item {
let mut table = Table::new();
table.set_implicit(true);
Item::Table(table)
}

/// Adds a dependency to the given `deps` array.
pub fn add_dependency(req: &Requirement, deps: &mut Array) {
// Find matching dependencies.
let to_replace = find_dependencies(&req.name, deps);

if to_replace.is_empty() {
deps.push(req.to_string());
} else {
Expand All @@ -84,19 +204,11 @@ pub fn add_dependency(req: &Requirement, deps: &mut Item) -> Result<(), Error> {
deps.remove(i);
}
}

reformat_array_multiline(deps);
Ok(())
}

/// Removes all occurrences of dependencies with the given name from the given `deps` array.
fn remove_dependency(req: &PackageName, deps: &mut Item) -> Result<Vec<Requirement>, Error> {
if deps.is_none() {
return Ok(Vec::new());
}

let deps = deps.as_array_mut().ok_or(Error::MalformedDependencies)?;

fn remove_dependency(req: &PackageName, deps: &mut Array) -> Vec<Requirement> {
// Remove matching dependencies.
let removed = find_dependencies(req, deps)
.into_iter()
Expand All @@ -112,7 +224,7 @@ fn remove_dependency(req: &PackageName, deps: &mut Item) -> Result<Vec<Requireme
reformat_array_multiline(deps);
}

Ok(removed)
removed
}

// Returns a `Vec` containing the indices of all dependencies with the given name.
Expand All @@ -131,6 +243,24 @@ fn find_dependencies(name: &PackageName, deps: &Array) -> Vec<usize> {
to_replace
}

// Add a source to `tool.uv.sources`.
fn add_source(req: &Requirement, source: &Source, sources: &mut Table) {
match source {
Source::Workspace {
workspace,
editable,
} => {
let mut value = InlineTable::new();
value.insert("workspace", Value::from(*workspace));
if let Some(editable) = editable {
value.insert("editable", Value::from(*editable));
}
sources.insert(req.name.as_ref(), Item::Value(Value::InlineTable(value)));
}
_ => unimplemented!(),
}
}

impl fmt::Display for PyProjectTomlMut {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.doc.fmt(f)
Expand Down
4 changes: 4 additions & 0 deletions crates/uv/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1611,6 +1611,10 @@ pub(crate) struct AddArgs {
#[arg(long)]
pub(crate) dev: bool,

/// Add the requirements as workspace dependencies.
#[arg(long)]
pub(crate) workspace: bool,

#[command(flatten)]
pub(crate) installer: ResolverInstallerArgs,

Expand Down
6 changes: 4 additions & 2 deletions crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::Result;
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_dispatch::BuildDispatch;
use uv_distribution::pyproject::Source;
use uv_distribution::pyproject_mut::PyProjectTomlMut;
use uv_git::GitResolver;
use uv_requirements::{NamedRequirementsResolver, RequirementsSource, RequirementsSpecification};
Expand All @@ -23,6 +24,7 @@ use crate::settings::ResolverInstallerSettings;
#[allow(clippy::too_many_arguments)]
pub(crate) async fn add(
requirements: Vec<RequirementsSource>,
source: Option<Source>,
dev: bool,
python: Option<String>,
settings: ResolverInstallerSettings,
Expand Down Expand Up @@ -126,9 +128,9 @@ pub(crate) async fn add(
let mut pyproject = PyProjectTomlMut::from_toml(project.current_project().pyproject_toml())?;
for req in requirements.into_iter().map(pep508_rs::Requirement::from) {
if dev {
pyproject.add_dev_dependency(&req)?;
pyproject.add_dev_dependency(&req, source.as_ref())?;
} else {
pyproject.add_dependency(&req)?;
pyproject.add_dependency(&req, source.as_ref())?;
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,7 @@ async fn run() -> Result<ExitStatus> {

commands::add(
requirements,
args.source,
args.dev,
args.python,
args.settings,
Expand Down
13 changes: 13 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use uv_configuration::{
KeyringProviderType, NoBinary, NoBuild, PreviewMode, Reinstall, SetupPyStrategy, TargetTriple,
Upgrade,
};
use uv_distribution::pyproject::Source;
use uv_normalize::PackageName;
use uv_resolver::{AnnotationStyle, DependencyMode, ExcludeNewer, PreReleaseMode, ResolutionMode};
use uv_settings::{
Expand Down Expand Up @@ -375,6 +376,7 @@ impl LockSettings {
pub(crate) struct AddSettings {
pub(crate) requirements: Vec<String>,
pub(crate) dev: bool,
pub(crate) source: Option<Source>,
pub(crate) python: Option<String>,
pub(crate) refresh: Refresh,
pub(crate) settings: ResolverInstallerSettings,
Expand All @@ -387,14 +389,25 @@ impl AddSettings {
let AddArgs {
requirements,
dev,
workspace,
installer,
build,
refresh,
python,
} = args;

let source = if workspace {
Some(Source::Workspace {
workspace: true,
editable: None,
})
} else {
None
};

Self {
requirements,
source,
dev,
python,
refresh: Refresh::from(refresh),
Expand Down
Loading

0 comments on commit d714a7f

Please sign in to comment.