Skip to content

Commit

Permalink
feat: new ya pack -d subcommand to delete packages (#2181)
Browse files Browse the repository at this point in the history
Co-authored-by: sxyazi <[email protected]>
  • Loading branch information
MrAsler and sxyazi authored Jan 11, 2025
1 parent 856f37b commit 5c88f26
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 44 deletions.
3 changes: 3 additions & 0 deletions yazi-cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ pub(super) struct CommandPack {
/// Add a package.
#[arg(short = 'a', long)]
pub(super) add: Option<String>,
/// Delete a package.
#[arg(short = 'd', long)]
pub(super) delete: Option<String>,
/// Install all packages.
#[arg(short = 'i', long)]
pub(super) install: bool,
Expand Down
2 changes: 2 additions & 0 deletions yazi-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ async fn run() -> anyhow::Result<()> {
package::Package::load().await?.install(true).await?;
} else if let Some(repo) = cmd.add {
package::Package::load().await?.add(&repo).await?;
} else if let Some(repo) = cmd.delete {
package::Package::load().await?.delete(&repo).await?;
}
}

Expand Down
68 changes: 68 additions & 0 deletions yazi-cli/src/package/delete.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use anyhow::{Context, Result, bail};
use tokio::fs;
use yazi_fs::{maybe_exists, ok_or_not_found, remove_dir_clean};
use yazi_macro::outln;

use super::Dependency;

impl Dependency {
pub(super) async fn delete(&self) -> Result<()> {
self.header("Deleting package `{name}`")?;

let dir = self.target();
if !maybe_exists(&dir).await {
return Ok(outln!("Not found, skipping")?);
}

if self.hash != self.hash().await? {
bail!(
"You have modified the contents of the `{}` {}. For safety, the operation has been aborted.
Please manually delete it from: {}",
self.name,
if self.is_flavor { "flavor" } else { "plugin" },
dir.display()
);
}

let files = if self.is_flavor {
&["flavor.toml", "tmtheme.xml", "README.md", "preview.png", "LICENSE", "LICENSE-tmtheme"][..]
} else {
&["main.lua", "README.md", "LICENSE"][..]
};
for p in files.iter().map(|&f| dir.join(f)) {
ok_or_not_found(fs::remove_file(&p).await)
.with_context(|| format!("failed to delete `{}`", p.display()))?;
}

self.delete_assets().await?;
if ok_or_not_found(fs::remove_dir(&dir).await).is_ok() {
outln!("Done!")?;
} else {
outln!(
"Done!
For safety, user data has been preserved, please manually delete them within: {}",
dir.display()
)?;
}

Ok(())
}

pub(super) async fn delete_assets(&self) -> Result<()> {
let assets = self.target().join("assets");
match fs::read_dir(&assets).await {
Ok(mut it) => {
while let Some(entry) = it.next_entry().await? {
fs::remove_file(entry.path())
.await
.with_context(|| format!("failed to remove `{}`", entry.path().display()))?;
}
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => Err(e).context(format!("failed to read `{}`", assets.display()))?,
};

remove_dir_clean(&assets).await;
Ok(())
}
}
19 changes: 15 additions & 4 deletions yazi-cli/src/package/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
use twox_hash::XxHash3_128;
use yazi_fs::Xdg;

#[derive(Default)]
#[derive(Clone, Default)]
pub(crate) struct Dependency {
pub(crate) use_: String, // owner/repo:child
pub(crate) name: String, // child.yazi

pub(crate) parent: String, // owner/repo
pub(crate) child: String, // child
pub(crate) child: String, // child.yazi

pub(crate) rev: String,
pub(crate) hash: String,
Expand All @@ -20,19 +20,30 @@ pub(crate) struct Dependency {
}

impl Dependency {
#[inline]
pub(super) fn local(&self) -> PathBuf {
Xdg::state_dir()
.join("packages")
.join(format!("{:x}", XxHash3_128::oneshot(self.remote().as_bytes())))
}

#[inline]
pub(super) fn remote(&self) -> String {
// Support more Git hosting services in the future
format!("https://github.com/{}.git", self.parent)
}

pub(super) fn target(&self) -> PathBuf {
if self.is_flavor {
Xdg::config_dir().join(format!("flavors/{}", self.name))
} else {
Xdg::config_dir().join(format!("plugins/{}", self.name))
}
}

#[inline]
pub(super) fn identical(&self, other: &Self) -> bool {
self.parent == other.parent && self.child == other.child
}

pub(super) fn header(&self, s: &str) -> Result<()> {
use crossterm::style::{Attribute, Print, SetAttributes};

Expand Down
37 changes: 11 additions & 26 deletions yazi-cli/src/package/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::path::PathBuf;

use anyhow::{Context, Result, bail};
use tokio::fs;
use yazi_fs::{Xdg, copy_and_seal, maybe_exists, remove_dir_clean};
use yazi_fs::{copy_and_seal, maybe_exists};
use yazi_macro::outln;

use super::Dependency;
Expand All @@ -13,17 +13,15 @@ impl Dependency {

self.header("Deploying package `{name}`")?;
self.is_flavor = maybe_exists(&from.join("flavor.toml")).await;
let to = if self.is_flavor {
Xdg::config_dir().join(format!("flavors/{}", self.name))
} else {
Xdg::config_dir().join(format!("plugins/{}", self.name))
};

let to = self.target();
if maybe_exists(&to).await && self.hash != self.hash().await? {
bail!(
"The user has modified the contents of the `{}` package. For safety, the operation has been aborted.
Please manually delete it from your plugins/flavors directory and re-run the command.",
self.name
"You have modified the contents of the `{}` {}. For safety, the operation has been aborted.
Please manually delete it from `{}` and re-run the command.",
self.name,
if self.is_flavor { "flavor" } else { "plugin" },
to.display()
);
}

Expand Down Expand Up @@ -51,28 +49,16 @@ Please manually delete it from your plugins/flavors directory and re-run the com
.with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()))?;
}

self.delete_assets().await?;
Self::deploy_assets(from.join("assets"), to.join("assets")).await?;

self.hash = self.hash().await?;
outln!("Done!")?;

Ok(())
}

async fn deploy_assets(from: PathBuf, to: PathBuf) -> Result<()> {
use std::io::ErrorKind::NotFound;

match fs::read_dir(&to).await {
Ok(mut it) => {
while let Some(entry) = it.next_entry().await? {
fs::remove_file(entry.path())
.await
.with_context(|| format!("failed to remove `{}`", entry.path().display()))?;
}
}
Err(e) if e.kind() == NotFound => {}
Err(e) => Err(e).context(format!("failed to read `{}`", to.display()))?,
};

remove_dir_clean(&to).await;
match fs::read_dir(&from).await {
Ok(mut it) => {
fs::create_dir_all(&to).await?;
Expand All @@ -83,10 +69,9 @@ Please manually delete it from your plugins/flavors directory and re-run the com
})?;
}
}
Err(e) if e.kind() == NotFound => {}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => Err(e).context(format!("failed to read `{}`", from.display()))?,
}

Ok(())
}
}
9 changes: 2 additions & 7 deletions yazi-cli/src/package/hash.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
use anyhow::{Context, Result};
use tokio::fs;
use twox_hash::XxHash3_128;
use yazi_fs::{Xdg, ok_or_not_found};
use yazi_fs::ok_or_not_found;

use super::Dependency;

impl Dependency {
pub(crate) async fn hash(&self) -> Result<String> {
let dir = if self.is_flavor {
Xdg::config_dir().join(format!("flavors/{}", self.name))
} else {
Xdg::config_dir().join(format!("plugins/{}", self.name))
};

let dir = self.target();
let files = if self.is_flavor {
&[
"LICENSE",
Expand Down
2 changes: 1 addition & 1 deletion yazi-cli/src/package/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use super::{Dependency, Git};

impl Dependency {
pub(super) async fn install(&mut self) -> Result<()> {
self.header("Installing package `{name}`")?;
self.header("Fetching package `{name}`")?;

let path = self.local();
if !must_exists(&path).await {
Expand Down
2 changes: 1 addition & 1 deletion yazi-cli/src/package/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![allow(clippy::module_inception)]

yazi_macro::mod_flat!(add dependency deploy git hash install package upgrade);
yazi_macro::mod_flat!(add delete dependency deploy git hash install package upgrade);

use anyhow::Context;
use yazi_fs::Xdg;
Expand Down
32 changes: 27 additions & 5 deletions yazi-cli/src/package/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ impl Package {

pub(crate) async fn add(&mut self, use_: &str) -> Result<()> {
let mut dep = Dependency::from_str(use_)?;
if self.plugins.iter().any(|d| d.parent == dep.parent && d.child == dep.child) {
bail!("Plugin `{}` already exists in package.toml", dep.name);
}
if self.flavors.iter().any(|d| d.parent == dep.parent && d.child == dep.child) {
bail!("Flavor `{}` already exists in package.toml", dep.name);
if let Some(d) = self.identical(&dep) {
bail!(
"{} `{}` already exists in package.toml",
if d.is_flavor { "Flavor" } else { "Plugin" },
dep.name
)
}

dep.add().await?;
Expand All @@ -43,6 +44,22 @@ impl Package {
create_and_seal(&Self::toml(), s.as_bytes()).await.context("Failed to write package.toml")
}

pub(crate) async fn delete(&mut self, use_: &str) -> Result<()> {
let Some(dep) = self.identical(&Dependency::from_str(use_)?).cloned() else {
bail!("`{}` was not found in package.toml", use_)
};

dep.delete().await?;
if dep.is_flavor {
self.flavors.retain(|d| !d.identical(&dep));
} else {
self.plugins.retain(|d| !d.identical(&dep));
}

let s = toml::to_string_pretty(self)?;
create_and_seal(&Self::toml(), s.as_bytes()).await.context("Failed to write package.toml")
}

pub(crate) async fn install(&mut self, upgrade: bool) -> Result<()> {
for d in &mut self.plugins {
if upgrade {
Expand Down Expand Up @@ -167,6 +184,11 @@ impl Package {

#[inline]
fn toml() -> PathBuf { Xdg::config_dir().join("package.toml") }

#[inline]
fn identical(&self, other: &Dependency) -> Option<&Dependency> {
self.plugins.iter().chain(&self.flavors).find(|d| d.identical(other))
}
}

impl<'de> Deserialize<'de> for Package {
Expand Down

0 comments on commit 5c88f26

Please sign in to comment.