diff --git a/lux-cli/src/add.rs b/lux-cli/src/add.rs index b5a25dc9..a4ab008f 100644 --- a/lux-cli/src/add.rs +++ b/lux-cli/src/add.rs @@ -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) diff --git a/lux-cli/src/remove.rs b/lux-cli/src/remove.rs index aaedeaac..7eb781eb 100644 --- a/lux-cli/src/remove.rs +++ b/lux-cli/src/remove.rs @@ -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, -} - -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, - 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>, - 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>, +} - (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 '@' -", - 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(()) } diff --git a/lux-lib/src/project/mod.rs b/lux-lib/src/project/mod.rs index f46ecfd5..b1d8ad31 100644 --- a/lux-lib/src/project/mod.rs +++ b/lux-lib/src/project/mod.rs @@ -7,6 +7,7 @@ use std::{ str::FromStr, }; use thiserror::Error; +use toml_edit::{DocumentMut, Item}; use crate::{ config::{Config, LuaVersion}, @@ -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, @@ -50,10 +51,10 @@ pub enum ProjectEditError { Toml(#[from] toml_edit::TomlError), } -pub enum DependencyType { - Regular(Vec), - Build(Vec), - Test(Vec), +pub enum DependencyType { + Regular(Vec), + Build(Vec), + Test(Vec), External(HashMap), } @@ -193,43 +194,13 @@ impl Project { pub async fn add( &mut self, - dependencies: DependencyType, + dependencies: DependencyType, 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) @@ -319,6 +290,126 @@ impl Project { Ok(()) } + + pub async fn remove( + &mut self, + dependencies: DependencyType, + ) -> 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, +) -> &'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