Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add uninstall subcommand #70

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions lib/manifests/rokit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,25 @@ impl RokitManifest {
}
}

/**
Remove a tool from the manifest.

If the tool doesn't exist, this will return `false` and do nothing.
*/
pub fn remove_tool(&mut self, alias: &ToolAlias) -> bool {
let doc = self.document.as_table_mut();
if !doc.contains_table("tools") {
return false;
}
let tools = doc["tools"].as_table_mut().unwrap();
if tools.contains_value(alias.name()) {
tools.remove(alias.name());
true
} else {
false
}
}

/**
Updates a tool in the manifest with a new tool specification.

Expand Down
3 changes: 2 additions & 1 deletion lib/storage/tool_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ impl ToolStorage {
(tool_dir, tool_file)
}

fn alias_path(&self, alias: &ToolAlias) -> PathBuf {
#[must_use]
pub fn alias_path(&self, alias: &ToolAlias) -> PathBuf {
let alias_file_name = format!("{}{EXE_SUFFIX}", alias.name.uncased_str());
self.aliases_dir.join(alias_file_name)
}
Expand Down
4 changes: 4 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use tracing::level_filters::LevelFilter;

use rokit::storage::Home;
use rokit::system::ProcessParent;
use uninstall::UninstallSubcommand;

use crate::util::init_tracing;

Expand All @@ -17,6 +18,7 @@ mod self_install;
mod self_update;
mod system_info;
mod trust;
mod uninstall;
mod update;

use self::add::AddSubcommand;
Expand Down Expand Up @@ -122,6 +124,7 @@ pub enum Subcommand {
SelfUpdate(SelfUpdateSubcommand),
SystemInfo(SystemInfoSubcommand),
Trust(TrustSubcommand),
Uninstall(UninstallSubcommand),
Update(UpdateSubcommand),
}

Expand All @@ -137,6 +140,7 @@ impl Subcommand {
Self::SelfUpdate(cmd) => cmd.run(home).await,
Self::SystemInfo(cmd) => cmd.run(home).await,
Self::Trust(cmd) => cmd.run(home).await,
Self::Uninstall(cmd) => cmd.run(home).await,
Self::Update(cmd) => cmd.run(home).await,
}
}
Expand Down
68 changes: 68 additions & 0 deletions src/cli/uninstall.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use anyhow::{bail, Result};
use clap::Parser;
use console::style;
use rokit::{
discovery::{discover_all_manifests, discover_tool_spec},
manifests::RokitManifest,
storage::Home,
tool::ToolAlias,
};
use tokio::fs::{read_dir, remove_dir, remove_dir_all, remove_file};

use crate::util::{CliProgressTracker, ToolAliasOrId};

/// Removes a tool from Rokit and uninstalls it.
#[derive(Debug, Parser)]
pub struct UninstallSubcommand {
/// The tool alias or identifier to uninstall.
pub tool: ToolAliasOrId,
}

impl UninstallSubcommand {
pub async fn run(self, home: &Home) -> Result<()> {
let tool_storage = home.tool_storage();
let tool_cache = home.tool_cache();

let alias: ToolAlias = match self.tool {
ToolAliasOrId::Alias(alias) => alias,
ToolAliasOrId::Id(id) => id.into(),
};
let Some(spec) = discover_tool_spec(&alias, true, false).await else {
bail!("Failed to find tool '{alias}' in any project manifest file.")
};

// 1. Remove the tool from all manifests that contain it
let pt = CliProgressTracker::new_with_message("Uninstalling", 1);
let manifests = discover_all_manifests(true, false).await;
for manifest in manifests {
let manifest_path = manifest.path.parent().unwrap();
let mut manifest = RokitManifest::load(&manifest_path).await?;
if manifest.has_tool(&alias) {
manifest.remove_tool(&alias);
manifest.save(&manifest_path).await?;
}
}

// 2. Uninstall the tool binary and remove it from the install cache
let tool_path = tool_storage.tool_path(&spec);
let tool_dir = tool_path.ancestors().nth(2).unwrap();
let author_dir = tool_dir.parent().unwrap();

remove_file(tool_storage.alias_path(&alias)).await?;
remove_dir_all(tool_dir).await?;
if read_dir(&author_dir).await?.next_entry().await?.is_none() {
remove_dir(author_dir).await?;
}

let _ = tool_cache.remove_installed(&spec);

// 3. Finally, display a nice message to the user
pt.finish_with_message(format!(
"Uninstalled tool {} {}",
style(spec.name()).bold().magenta(),
pt.formatted_elapsed()
));

Ok(())
}
}
49 changes: 49 additions & 0 deletions src/util/alias_or_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::str::FromStr;

use serde_with::DeserializeFromStr;

use rokit::tool::{ToolAlias, ToolId};

/**
A tool alias *or* identifier.

See [`ToolAlias`] and [`ToolId`] for more information.
*/
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, DeserializeFromStr)]
pub enum ToolAliasOrId {
Alias(ToolAlias),
Id(ToolId),
}

impl FromStr for ToolAliasOrId {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.contains('/') {
Ok(Self::Id(s.parse()?))
} else {
Ok(Self::Alias(s.parse()?))
}
}
}

impl From<ToolAlias> for ToolAliasOrId {
fn from(alias: ToolAlias) -> Self {
Self::Alias(alias)
}
}

impl From<ToolId> for ToolAliasOrId {
fn from(id: ToolId) -> Self {
Self::Id(id)
}
}

impl From<ToolAliasOrId> for ToolAlias {
fn from(alias_or_id: ToolAliasOrId) -> Self {
let name = match alias_or_id {
ToolAliasOrId::Alias(alias) => alias.name().to_string(),
ToolAliasOrId::Id(id) => id.name().to_string(),
};
Self::from_str(&name).expect("Derived alias is always valid")
}
}
2 changes: 2 additions & 0 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod alias_or_id;
mod alias_or_id_or_spec;
mod artifacts;
mod constants;
Expand All @@ -6,6 +7,7 @@ mod progress;
mod prompts;
mod tracing;

pub use self::alias_or_id::ToolAliasOrId;
pub use self::alias_or_id_or_spec::ToolAliasOrIdOrSpec;
pub use self::artifacts::find_most_compatible_artifact;
pub use self::id_or_spec::ToolIdOrSpec;
Expand Down