mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: new ya pack -d subcommand to delete packages (#2181)
Co-authored-by: sxyazi <sxyazi@gmail.com>
This commit is contained in:
parent
856f37b5b3
commit
5c88f26896
9 changed files with 130 additions and 44 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
68
yazi-cli/src/package/delete.rs
Normal file
68
yazi-cli/src/package/delete.rs
Normal file
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
@ -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};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -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?;
|
||||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue