Skip to content

Commit

Permalink
feat(remove): operate on projects
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcjkb committed Feb 27, 2025
1 parent 74106b9 commit ff17d41
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 93 deletions.
2 changes: 1 addition & 1 deletion lux-cli/src/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub async fn add(data: Add, config: Config) -> Result<()> {

if !data.package_req.is_empty() {
// NOTE: We only update the lockfile if one exists.
// Otherwise, the next `lux build` will install the packages.
// Otherwise, the next `lx build` will install the packages.
if let Some(lockfile) = project.try_lockfile()? {
let mut lockfile = lockfile.write_guard();
Sync::new(&tree, &mut lockfile, &config)
Expand Down
135 changes: 80 additions & 55 deletions lux-cli/src/remove.rs
Original file line number Diff line number Diff line change
@@ -1,72 +1,97 @@
use std::io;

use clap::Args;
use eyre::{eyre, Result};
use itertools::Itertools;
use eyre::{Context, OptionExt, Result};
use lux_lib::{
config::{Config, LuaVersion},
operations,
package::PackageReq,
tree::RockMatches,
config::Config, luarocks::luarocks_installation::LuaRocksInstallation, operations::Sync,
package::PackageName, progress::MultiProgress, project::Project, rockspec::Rockspec,
};

// NOTE: This is currently functionally equivalent
// to `lux uninstall`, but that will change
// when we can use it to edit projects' lux.toml files.

#[derive(Args)]
pub struct Remove {
/// The package or packages to remove.
packages: Vec<PackageReq>,
}

pub async fn remove(remove_args: Remove, config: Config) -> Result<()> {
let tree = config.tree(LuaVersion::from(&config)?)?;
/// Package or list of packages to remove from the dependencies.
package: Vec<PackageName>,

let package_matches = remove_args
.packages
.iter()
.map(|package_req| tree.match_rocks(package_req))
.try_collect::<_, Vec<_>, io::Error>()?;
/// Remove a development dependency.
/// Also called `dev`.
#[arg(short, long, alias = "dev", visible_short_aliases = ['d', 'b'])]
build: Option<Vec<PackageName>>,

let (packages, nonexistent_packages, duplicate_packages) = package_matches.into_iter().fold(
(Vec::new(), Vec::new(), Vec::new()),
|(mut p, mut n, mut d), rock_match| {
match rock_match {
RockMatches::NotFound(req) => n.push(req),
RockMatches::Single(package) => p.push(package),
RockMatches::Many(packages) => d.extend(packages),
};
/// Remove a test dependency.
#[arg(short, long)]
test: Option<Vec<PackageName>>,
}

(p, n, d)
},
);
pub async fn remove(data: Remove, config: Config) -> Result<()> {
let mut project = Project::current()?.ok_or_eyre("No project found")?;
let tree = project.tree(&config)?;
let progress = MultiProgress::new_arc();

if !nonexistent_packages.is_empty() {
// TODO(vhyrro): Render this in the form of a tree.
return Err(eyre!(
"The following packages were not found: {:#?}",
nonexistent_packages
));
if !data.package.is_empty() {
project
.remove(lux_lib::project::DependencyType::Regular(data.package))
.await?;
// NOTE: We only update the lockfile if one exists.
// Otherwise, the next `lx build` will remove the packages.
if let Some(lockfile) = project.try_lockfile()? {
let mut lockfile = lockfile.write_guard();
let packages = project
.toml()
.into_validated()?
.dependencies()
.current_platform()
.clone();
Sync::new(&tree, &mut lockfile, &config)
.packages(packages)
.progress(progress.clone())
.sync_dependencies()
.await
.wrap_err("syncing dependencies with the project lockfile failed.")?;
}
}

if !duplicate_packages.is_empty() {
return Err(eyre!(
"
Multiple packages satisfying your version requirements were found:
{:#?}
Please specify the exact package to uninstall:
> lux remove '<name>@<version>'
",
duplicate_packages,
));
let build_packages = data.build.unwrap_or_default();
if !build_packages.is_empty() {
project
.remove(lux_lib::project::DependencyType::Build(build_packages))
.await?;
if let Some(lockfile) = project.try_lockfile()? {
let luarocks = LuaRocksInstallation::new(&config)?;
let mut lockfile = lockfile.write_guard();
let packages = project
.toml()
.into_validated()?
.build_dependencies()
.current_platform()
.clone();
Sync::new(luarocks.tree(), &mut lockfile, luarocks.config())
.packages(packages)
.progress(progress.clone())
.sync_build_dependencies()
.await
.wrap_err("syncing build dependencies with the project lockfile failed.")?;
}
}

operations::Remove::new(&config)
.packages(packages)
.remove()
.await?;
let test_packages = data.test.unwrap_or_default();
if !test_packages.is_empty() {
project
.remove(lux_lib::project::DependencyType::Test(test_packages))
.await?;
if let Some(lockfile) = project.try_lockfile()? {
let mut lockfile = lockfile.write_guard();
let packages = project
.toml()
.into_validated()?
.test_dependencies()
.current_platform()
.clone();
Sync::new(&tree, &mut lockfile, &config)
.packages(packages)
.progress(progress.clone())
.sync_test_dependencies()
.await
.wrap_err("syncing test dependencies with the project lockfile failed.")?;
}
}

Ok(())
}
165 changes: 128 additions & 37 deletions lux-lib/src/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::{
str::FromStr,
};
use thiserror::Error;
use toml_edit::{DocumentMut, Item};

use crate::{
config::{Config, LuaVersion},
Expand All @@ -15,7 +16,7 @@ use crate::{
ExternalDependencySpec, LuaRockspec, LuaVersionError, PartialLuaRockspec,
PartialRockspecError, RockSourceSpec, RockspecError,
},
package::PackageReq,
package::{PackageName, PackageReq},
remote_package_db::RemotePackageDB,
rockspec::{LuaVersionCompatibility, Rockspec},
tree::Tree,
Expand Down Expand Up @@ -50,10 +51,10 @@ pub enum ProjectEditError {
Toml(#[from] toml_edit::TomlError),
}

pub enum DependencyType {
Regular(Vec<PackageReq>),
Build(Vec<PackageReq>),
Test(Vec<PackageReq>),
pub enum DependencyType<T> {
Regular(Vec<T>),
Build(Vec<T>),
Test(Vec<T>),
External(HashMap<String, ExternalDependencySpec>),
}

Expand Down Expand Up @@ -193,43 +194,13 @@ impl Project {

pub async fn add(
&mut self,
dependencies: DependencyType,
dependencies: DependencyType<PackageReq>,
package_db: &RemotePackageDB,
) -> Result<(), ProjectEditError> {
let mut project_toml =
toml_edit::DocumentMut::from_str(&tokio::fs::read_to_string(self.toml_path()).await?)?;

if !project_toml.contains_table("dependencies") {
let mut table = toml_edit::table().into_table().unwrap();
table.set_implicit(true);

project_toml["dependencies"] = toml_edit::Item::Table(table);
}
if !project_toml.contains_table("build_dependencies") {
let mut table = toml_edit::table().into_table().unwrap();
table.set_implicit(true);

project_toml["build_dependencies"] = toml_edit::Item::Table(table);
}
if !project_toml.contains_table("test_dependencies") {
let mut table = toml_edit::table().into_table().unwrap();
table.set_implicit(true);

project_toml["test_dependencies"] = toml_edit::Item::Table(table);
}
if !project_toml.contains_table("external_dependencies") {
let mut table = toml_edit::table().into_table().unwrap();
table.set_implicit(true);

project_toml["external_dependencies"] = toml_edit::Item::Table(table);
}

let table = match dependencies {
DependencyType::Regular(_) => &mut project_toml["dependencies"],
DependencyType::Build(_) => &mut project_toml["build_dependencies"],
DependencyType::Test(_) => &mut project_toml["test_dependencies"],
DependencyType::External(_) => &mut project_toml["external_dependencies"],
};
let table = prepare_dependency_tables(&mut project_toml, &dependencies);

match dependencies {
DependencyType::Regular(ref deps)
Expand Down Expand Up @@ -319,6 +290,126 @@ impl Project {

Ok(())
}

pub async fn remove(
&mut self,
dependencies: DependencyType<PackageName>,
) -> Result<(), ProjectEditError> {
let mut project_toml =
toml_edit::DocumentMut::from_str(&tokio::fs::read_to_string(self.toml_path()).await?)?;

let table = prepare_dependency_tables(&mut project_toml, &dependencies);

match dependencies {
DependencyType::Regular(ref deps)
| DependencyType::Build(ref deps)
| DependencyType::Test(ref deps) => {
for dep in deps {
table[dep.to_string()] = Item::None;
}
}
DependencyType::External(ref deps) => {
for (name, dep) in deps {
match dep {
ExternalDependencySpec::Header(_) => {
table[name]["header"] = Item::None;
}
ExternalDependencySpec::Library(_) => {
table[name]["library"] = Item::None;
}
}
}
}
};

tokio::fs::write(self.toml_path(), project_toml.to_string()).await?;

match dependencies {
DependencyType::Regular(deps) => {
self.toml.dependencies = Some(
self.toml
.dependencies
.take()
.unwrap_or_default()
.into_iter()
.filter(|dep| !&deps.iter().any(|d| d == dep.name()))
.collect(),
)
}
DependencyType::Build(deps) => {
self.toml.build_dependencies = Some(
self.toml
.build_dependencies
.take()
.unwrap_or_default()
.into_iter()
.filter(|dep| !&deps.iter().any(|d| d == dep.name()))
.collect(),
)
}
DependencyType::Test(deps) => {
self.toml.test_dependencies = Some(
self.toml
.test_dependencies
.take()
.unwrap_or_default()
.into_iter()
.filter(|dep| !&deps.iter().any(|d| d == dep.name()))
.collect(),
)
}
DependencyType::External(deps) => {
self.toml.external_dependencies = Some(
self.toml
.external_dependencies
.take()
.unwrap_or_default()
.into_iter()
.filter(|(dep, _)| !&deps.iter().any(|(d, _)| d == dep))
.collect(),
)
}
};

Ok(())
}
}

fn prepare_dependency_tables<'a, T>(
project_toml: &'a mut DocumentMut,
dependencies: &'a DependencyType<T>,
) -> &'a mut Item {
if !project_toml.contains_table("dependencies") {
let mut table = toml_edit::table().into_table().unwrap();
table.set_implicit(true);

project_toml["dependencies"] = toml_edit::Item::Table(table);
}
if !project_toml.contains_table("build_dependencies") {
let mut table = toml_edit::table().into_table().unwrap();
table.set_implicit(true);

project_toml["build_dependencies"] = toml_edit::Item::Table(table);
}
if !project_toml.contains_table("test_dependencies") {
let mut table = toml_edit::table().into_table().unwrap();
table.set_implicit(true);

project_toml["test_dependencies"] = toml_edit::Item::Table(table);
}
if !project_toml.contains_table("external_dependencies") {
let mut table = toml_edit::table().into_table().unwrap();
table.set_implicit(true);

project_toml["external_dependencies"] = toml_edit::Item::Table(table);
}

match dependencies {
DependencyType::Regular(_) => &mut project_toml["dependencies"],
DependencyType::Build(_) => &mut project_toml["build_dependencies"],
DependencyType::Test(_) => &mut project_toml["test_dependencies"],
DependencyType::External(_) => &mut project_toml["external_dependencies"],
}
}

// TODO: More project-based test
Expand Down

0 comments on commit ff17d41

Please sign in to comment.