mirror of
https://github.com/sxyazi/yazi.git
synced 2024-08-15 21:30:31 +03:00
feat: package manager (#985)
This commit is contained in:
parent
fdecf629a6
commit
faa1d9f37b
36
Cargo.lock
generated
36
Cargo.lock
generated
@ -168,9 +168,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.0"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "better-panic"
|
||||
@ -1086,9 +1086,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
version = "0.2.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
@ -1408,9 +1408,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
@ -1695,9 +1695,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.198"
|
||||
version = "1.0.199"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
|
||||
checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@ -1714,9 +1714,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.198"
|
||||
version = "1.0.199"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
|
||||
checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2257,9 +2257,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.11"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||
checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
@ -2280,9 +2280,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uzers"
|
||||
version = "0.11.3"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76d283dc7e8c901e79e32d077866eaf599156cbf427fffa8289aecc52c5c3f63"
|
||||
checksum = "7d85875e16d59b3b1549efce83ff8251a64923b03bef94add0a1862847448de4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
@ -2696,7 +2696,7 @@ version = "0.2.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"base64 0.22.0",
|
||||
"base64 0.22.1",
|
||||
"color_quant",
|
||||
"crossterm",
|
||||
"futures",
|
||||
@ -2735,10 +2735,14 @@ dependencies = [
|
||||
"clap_complete",
|
||||
"clap_complete_fig",
|
||||
"clap_complete_nushell",
|
||||
"crossterm",
|
||||
"md-5",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"toml_edit",
|
||||
"vergen",
|
||||
"yazi-dds",
|
||||
"yazi-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2842,7 +2846,7 @@ version = "0.2.5"
|
||||
dependencies = [
|
||||
"ansi-to-tui",
|
||||
"anyhow",
|
||||
"base64 0.22.0",
|
||||
"base64 0.22.1",
|
||||
"clipboard-win",
|
||||
"crossterm",
|
||||
"futures",
|
||||
|
@ -14,6 +14,7 @@ Yazi (means "duck") is a terminal file manager written in Rust, based on non-blo
|
||||
- 🌟 **Built-in Code Highlighting and Image Decoding**: Combined with the pre-loading mechanism, greatly accelerates image and normal file loading.
|
||||
- 🔌 **Concurrent Plugin System**: UI plugins (rewriting most of the UI), functional plugins, custom previewer, and custom preloader; Just some pieces of Lua.
|
||||
- 📡 **Data Distribution Service**: Built on a client-server architecture (no additional server process required), integrated with a Lua-based publish-subscribe model, achieving cross-instance communication and state persistence.
|
||||
- 📦 **Package Manager**: Install plugins and themes with one command, keeping them always up to date, or pin them to a specific version.
|
||||
- 🧰 Integration with fd, rg, fzf, zoxide
|
||||
- 💫 Vim-like input/select/which/notify component, auto-completion for cd paths
|
||||
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, directories, code, etc.)
|
||||
|
@ -15,7 +15,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
|
||||
# External dependencies
|
||||
anyhow = "1.0.82"
|
||||
arc-swap = "1.7.1"
|
||||
base64 = "0.22.0"
|
||||
base64 = "0.22.1"
|
||||
color_quant = "1.1.0"
|
||||
crossterm = "0.27.0"
|
||||
futures = "0.3.30"
|
||||
|
@ -15,7 +15,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
|
||||
|
||||
# External dependencies
|
||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||
serde = { version = "1.0.198", features = [ "derive" ] }
|
||||
serde = { version = "1.0.199", features = [ "derive" ] }
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||
|
@ -148,7 +148,7 @@ impl Default for Boot {
|
||||
.map(|s| s.split(',').map(|s| s.to_owned()).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let boot = Self {
|
||||
Self {
|
||||
cwd,
|
||||
file,
|
||||
|
||||
@ -159,11 +159,7 @@ impl Default for Boot {
|
||||
plugin_dir: config_dir.join("plugins"),
|
||||
config_dir,
|
||||
state_dir: Xdg::state_dir(),
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(&boot.flavor_dir).ok();
|
||||
std::fs::create_dir_all(&boot.plugin_dir).ok();
|
||||
boot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,13 +9,17 @@ homepage = "https://yazi-rs.github.io"
|
||||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[dependencies]
|
||||
yazi-dds = { path = "../yazi-dds", version = "0.2.5" }
|
||||
yazi-dds = { path = "../yazi-dds", version = "0.2.5" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = "1.0.82"
|
||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||
crossterm = "0.27.0"
|
||||
md-5 = "0.10.6"
|
||||
serde_json = "1.0.116"
|
||||
tokio = { version = "1.37.0", features = [ "full" ] }
|
||||
toml_edit = "0.22.12"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.82"
|
||||
|
@ -20,6 +20,8 @@ pub(super) enum Command {
|
||||
Pub(CommandPub),
|
||||
/// Publish a static message to all remote instances.
|
||||
PubStatic(CommandPubStatic),
|
||||
/// Manage packages.
|
||||
Pack(CommandPack),
|
||||
}
|
||||
|
||||
#[derive(clap::Args)]
|
||||
@ -79,3 +81,16 @@ impl CommandPubStatic {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(super) struct CommandPack {
|
||||
/// Add a package.
|
||||
#[arg(short = 'a', long)]
|
||||
pub(super) add: Option<String>,
|
||||
/// Install all packages.
|
||||
#[arg(short = 'i', long)]
|
||||
pub(super) install: bool,
|
||||
/// Upgrade all packages.
|
||||
#[arg(short = 'u', long)]
|
||||
pub(super) upgrade: bool,
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
mod args;
|
||||
mod package;
|
||||
|
||||
use args::*;
|
||||
use clap::Parser;
|
||||
@ -30,6 +31,18 @@ async fn main() -> anyhow::Result<()> {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
Command::Pack(cmd) => {
|
||||
package::init();
|
||||
if cmd.install {
|
||||
package::Package::install_from_config("plugin", false).await?;
|
||||
package::Package::install_from_config("flavor", false).await?;
|
||||
} else if cmd.upgrade {
|
||||
package::Package::install_from_config("plugin", true).await?;
|
||||
package::Package::install_from_config("flavor", true).await?;
|
||||
} else if let Some(repo) = &cmd.add {
|
||||
package::Package::add_to_config(repo).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
20
yazi-cli/src/package/add.rs
Normal file
20
yazi-cli/src/package/add.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use anyhow::Result;
|
||||
use yazi_shared::fs::must_exists;
|
||||
|
||||
use super::{Git, Package};
|
||||
|
||||
impl Package {
|
||||
pub(super) async fn add(&mut self) -> Result<()> {
|
||||
self.output("Upgrading package `{name}`")?;
|
||||
|
||||
let path = self.local();
|
||||
if !must_exists(&path).await {
|
||||
Git::clone(&self.remote(), &path).await?;
|
||||
} else {
|
||||
Git::pull(&path).await?;
|
||||
};
|
||||
|
||||
self.commit = Git::hash(&path).await?;
|
||||
self.deploy().await
|
||||
}
|
||||
}
|
50
yazi-cli/src/package/deploy.rs
Normal file
50
yazi-cli/src/package/deploy.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use tokio::fs;
|
||||
use yazi_shared::{fs::{maybe_exists, must_exists}, Xdg};
|
||||
|
||||
use super::Package;
|
||||
|
||||
const TRACKER: &str = "DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY";
|
||||
|
||||
impl Package {
|
||||
pub(super) async fn deploy(&mut self) -> Result<()> {
|
||||
let Some(name) = self.name().map(ToOwned::to_owned) else { bail!("Invalid package url") };
|
||||
let from = self.local().join(&self.child);
|
||||
|
||||
self.output("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/{name}"))
|
||||
} else {
|
||||
Xdg::config_dir().join(format!("plugins/{name}"))
|
||||
};
|
||||
|
||||
let tracker = to.join(TRACKER);
|
||||
if maybe_exists(&to).await && !must_exists(&tracker).await {
|
||||
bail!(
|
||||
"A user package with the same name `{name}` already exists.
|
||||
For safety, please manually delete it from your plugin/flavor directory and re-run the command."
|
||||
);
|
||||
}
|
||||
|
||||
fs::create_dir_all(&to).await?;
|
||||
fs::write(tracker, []).await?;
|
||||
|
||||
let files = if self.is_flavor {
|
||||
&["flavor.toml", "tmtheme.xml", "README.md", "preview.png", "LICENSE", "LICENSE-tmtheme"][..]
|
||||
} else {
|
||||
&["init.lua", "README.md", "LICENSE"][..]
|
||||
};
|
||||
|
||||
for file in files {
|
||||
let (from, to) = (from.join(file), to.join(file));
|
||||
|
||||
fs::copy(&from, &to)
|
||||
.await
|
||||
.with_context(|| format!("Failed to copy `{}` to `{}`", from.display(), to.display()))?;
|
||||
}
|
||||
|
||||
println!("Done!");
|
||||
Ok(())
|
||||
}
|
||||
}
|
55
yazi-cli/src/package/git.rs
Normal file
55
yazi-cli/src/package/git.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use tokio::process::Command;
|
||||
use yazi_shared::strip_trailing_newline;
|
||||
|
||||
pub(super) struct Git;
|
||||
|
||||
impl Git {
|
||||
pub(super) async fn clone(url: &str, path: &Path) -> Result<()> {
|
||||
Self::exec(|c| c.args(["clone", url]).arg(path)).await
|
||||
}
|
||||
|
||||
pub(super) async fn fetch(path: &Path) -> Result<()> {
|
||||
Self::exec(|c| c.current_dir(path).arg("fetch")).await
|
||||
}
|
||||
|
||||
pub(super) async fn checkout(path: &Path, commit: &str) -> Result<()> {
|
||||
Self::exec(|c| c.current_dir(path).args(["checkout", commit])).await
|
||||
}
|
||||
|
||||
pub(super) async fn pull(path: &Path) -> Result<()> {
|
||||
Self::fetch(path).await?;
|
||||
Self::checkout(path, "origin/HEAD").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) async fn hash(path: &Path) -> Result<String> {
|
||||
let output = Command::new("git")
|
||||
.current_dir(path)
|
||||
.args(["rev-parse", "--short", "HEAD"])
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to get current commit hash")?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!("Getting commit hash failed: {}", output.status);
|
||||
}
|
||||
|
||||
Ok(strip_trailing_newline(
|
||||
String::from_utf8(output.stdout).context("Failed to parse commit hash")?,
|
||||
))
|
||||
}
|
||||
|
||||
async fn exec(f: impl FnOnce(&mut Command) -> &mut Command) -> Result<()> {
|
||||
let status =
|
||||
f(&mut Command::new("git")).status().await.context("Failed to execute `git` command")?;
|
||||
|
||||
if !status.success() {
|
||||
bail!("`git` command failed: {status}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
25
yazi-cli/src/package/install.rs
Normal file
25
yazi-cli/src/package/install.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use anyhow::Result;
|
||||
use yazi_shared::fs::must_exists;
|
||||
|
||||
use super::{Git, Package};
|
||||
|
||||
impl Package {
|
||||
pub(super) async fn install(&mut self) -> Result<()> {
|
||||
self.output("Installing package `{name}`")?;
|
||||
|
||||
let path = self.local();
|
||||
if !must_exists(&path).await {
|
||||
Git::clone(&self.remote(), &path).await?;
|
||||
} else {
|
||||
Git::fetch(&path).await?;
|
||||
};
|
||||
|
||||
if self.commit.is_empty() {
|
||||
self.commit = Git::hash(&path).await?;
|
||||
} else {
|
||||
Git::checkout(&path, self.commit.trim_start_matches('=')).await?;
|
||||
}
|
||||
|
||||
self.deploy().await
|
||||
}
|
||||
}
|
17
yazi-cli/src/package/mod.rs
Normal file
17
yazi-cli/src/package/mod.rs
Normal file
@ -0,0 +1,17 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
|
||||
mod add;
|
||||
mod deploy;
|
||||
mod git;
|
||||
mod install;
|
||||
mod package;
|
||||
mod parser;
|
||||
mod upgrade;
|
||||
|
||||
use git::*;
|
||||
pub(super) use package::*;
|
||||
|
||||
pub(super) fn init() {
|
||||
let root = yazi_shared::Xdg::state_dir().join("packages");
|
||||
std::fs::create_dir_all(root).expect("Failed to create packages directory");
|
||||
}
|
79
yazi-cli/src/package/package.rs
Normal file
79
yazi-cli/src/package/package.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use std::{borrow::Cow, io::BufWriter, path::PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use md5::{Digest, Md5};
|
||||
use yazi_shared::Xdg;
|
||||
|
||||
pub(crate) struct Package {
|
||||
pub(crate) repo: String,
|
||||
pub(crate) child: String,
|
||||
pub(crate) commit: String,
|
||||
pub(super) is_flavor: bool,
|
||||
}
|
||||
|
||||
impl Package {
|
||||
pub(super) fn new(url: &str, commit: Option<&str>) -> Self {
|
||||
let mut parts = url.splitn(2, '#');
|
||||
|
||||
let mut repo = parts.next().unwrap_or_default().to_owned();
|
||||
let child = if let Some(s) = parts.next() {
|
||||
format!("{s}.yazi")
|
||||
} else {
|
||||
repo.push_str(".yazi");
|
||||
String::new()
|
||||
};
|
||||
|
||||
Self { repo, child, commit: commit.unwrap_or_default().to_owned(), is_flavor: false }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn use_(&self) -> Cow<str> {
|
||||
if self.child.is_empty() {
|
||||
self.repo.trim_end_matches(".yazi").into()
|
||||
} else {
|
||||
format!("{}#{}", self.repo, self.child.trim_end_matches(".yazi")).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn name(&self) -> Option<&str> {
|
||||
let s = if self.child.is_empty() {
|
||||
self.repo.split('/').last().filter(|s| !s.is_empty())
|
||||
} else {
|
||||
Some(self.child.as_str())
|
||||
};
|
||||
|
||||
s.filter(|s| s.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-' | b'.')))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn local(&self) -> PathBuf {
|
||||
Xdg::state_dir()
|
||||
.join("packages")
|
||||
.join(format!("{:x}", Md5::new_with_prefix(self.remote()).finalize()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn remote(&self) -> String {
|
||||
// Support more Git hosting services in the future
|
||||
format!("https://github.com/{}.git", self.repo)
|
||||
}
|
||||
|
||||
pub(super) fn output(&self, s: &str) -> Result<()> {
|
||||
use crossterm::style::{Attribute, Print, SetAttributes};
|
||||
|
||||
crossterm::execute!(
|
||||
BufWriter::new(std::io::stdout()),
|
||||
Print("\n"),
|
||||
SetAttributes(Attribute::Reverse.into()),
|
||||
SetAttributes(Attribute::Bold.into()),
|
||||
Print(" "),
|
||||
Print(s.replacen("{name}", self.name().unwrap_or_default(), 1)),
|
||||
Print(" "),
|
||||
SetAttributes(Attribute::NoBold.into()),
|
||||
SetAttributes(Attribute::NoReverse.into()),
|
||||
Print("\n\n"),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
113
yazi-cli/src/package/parser.rs
Normal file
113
yazi-cli/src/package/parser.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use tokio::fs;
|
||||
use toml_edit::{Array, DocumentMut, InlineTable, Item, Value};
|
||||
use yazi_shared::Xdg;
|
||||
|
||||
use super::Package;
|
||||
|
||||
impl Package {
|
||||
pub(crate) async fn add_to_config(use_: &str) -> Result<()> {
|
||||
let mut package = Self::new(use_, None);
|
||||
let Some(name) = package.name() else { bail!("Invalid package `use`") };
|
||||
|
||||
let path = Xdg::config_dir().join("package.toml");
|
||||
let mut doc = Self::ensure_config(&fs::read_to_string(&path).await.unwrap_or_default())?;
|
||||
|
||||
Self::ensure_unique(&doc, name)?;
|
||||
package.add().await?;
|
||||
|
||||
let mut table = InlineTable::new();
|
||||
table.insert("use", package.use_().as_ref().into());
|
||||
if !package.commit.is_empty() {
|
||||
table.insert("commit", package.commit.into());
|
||||
}
|
||||
|
||||
if package.is_flavor {
|
||||
doc["flavor"]["deps"].as_array_mut().unwrap().push(table);
|
||||
} else {
|
||||
doc["plugin"]["deps"].as_array_mut().unwrap().push(table);
|
||||
}
|
||||
|
||||
fs::write(path, doc.to_string()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn install_from_config(section: &str, upgrade: bool) -> Result<()> {
|
||||
let path = Xdg::config_dir().join("package.toml");
|
||||
let Ok(s) = fs::read_to_string(&path).await else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let mut doc = s.parse::<DocumentMut>().context("Failed to parse package.toml")?;
|
||||
let Some(deps) = doc.get_mut(section).and_then(|d| d.get_mut("deps")) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let deps = deps.as_array_mut().context("`deps` must be an array")?;
|
||||
for dep in deps.iter_mut() {
|
||||
let dep = dep.as_inline_table_mut().context("Dependency must be an inline table")?;
|
||||
let use_ = dep.get("use").and_then(|d| d.as_str()).context("Missing `use` field")?;
|
||||
let commit = dep.get("commit").and_then(|d| d.as_str());
|
||||
|
||||
let mut package = Package::new(use_, commit);
|
||||
if upgrade {
|
||||
package.upgrade().await?;
|
||||
} else {
|
||||
package.install().await?;
|
||||
}
|
||||
|
||||
if package.commit.is_empty() {
|
||||
dep.remove("commit");
|
||||
} else {
|
||||
dep.insert("commit", package.commit.into());
|
||||
}
|
||||
}
|
||||
|
||||
fs::write(path, doc.to_string()).await.context("Failed to write package.toml")
|
||||
}
|
||||
|
||||
fn ensure_config(s: &str) -> Result<DocumentMut> {
|
||||
let mut doc = s.parse::<DocumentMut>().context("Failed to parse package.toml")?;
|
||||
|
||||
doc
|
||||
.entry("plugin")
|
||||
.or_insert(toml_edit::table())
|
||||
.as_table_mut()
|
||||
.context("Failed to get `plugin` table")?
|
||||
.entry("deps")
|
||||
.or_insert(Item::Value(Array::new().into()))
|
||||
.as_array()
|
||||
.context("Failed to get `deps` array")?;
|
||||
|
||||
doc
|
||||
.entry("flavor")
|
||||
.or_insert(toml_edit::table())
|
||||
.as_table_mut()
|
||||
.context("Failed to get `flavor` table")?
|
||||
.entry("deps")
|
||||
.or_insert(Item::Value(Array::new().into()))
|
||||
.as_array()
|
||||
.context("Failed to get `deps` array")?;
|
||||
|
||||
Ok(doc)
|
||||
}
|
||||
|
||||
fn ensure_unique(doc: &DocumentMut, name: &str) -> Result<()> {
|
||||
#[inline]
|
||||
fn same(v: &Value, name: &str) -> bool {
|
||||
v.as_inline_table()
|
||||
.and_then(|t| t.get("use"))
|
||||
.and_then(|v| v.as_str())
|
||||
.is_some_and(|s| Package::new(s, None).name() == Some(name))
|
||||
}
|
||||
|
||||
if doc["plugin"]["deps"].as_array().unwrap().into_iter().any(|v| same(v, name)) {
|
||||
bail!("Plugin `{name}` already exists in package.toml");
|
||||
}
|
||||
if doc["flavor"]["deps"].as_array().unwrap().into_iter().any(|v| same(v, name)) {
|
||||
bail!("Flavor `{name}` already exists in package.toml");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
9
yazi-cli/src/package/upgrade.rs
Normal file
9
yazi-cli/src/package/upgrade.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use super::Package;
|
||||
|
||||
impl Package {
|
||||
pub(super) async fn upgrade(&mut self) -> Result<()> {
|
||||
if self.commit.starts_with('=') { Ok(()) } else { self.add().await }
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ crossterm = "0.27.0"
|
||||
globset = "0.4.14"
|
||||
indexmap = "2.2.6"
|
||||
ratatui = "=0.26.1"
|
||||
serde = { version = "1.0.198", features = [ "derive" ] }
|
||||
serde = { version = "1.0.199", features = [ "derive" ] }
|
||||
shell-words = "1.1.0"
|
||||
toml = { version = "0.8.12", features = [ "preserve_order" ] }
|
||||
validator = { version = "0.18.1", features = [ "derive" ] }
|
||||
|
@ -24,18 +24,18 @@ bitflags = "2.5.0"
|
||||
crossterm = "0.27.0"
|
||||
futures = "0.3.30"
|
||||
notify = { version = "6.1.1", default-features = false, features = [ "macos_fsevent" ] }
|
||||
parking_lot = "0.12.1"
|
||||
parking_lot = "0.12.2"
|
||||
ratatui = "=0.26.1"
|
||||
regex = "1.10.4"
|
||||
scopeguard = "1.2.0"
|
||||
serde = "1.0.198"
|
||||
serde = "1.0.199"
|
||||
tokio = { version = "1.37.0", features = [ "full" ] }
|
||||
tokio-stream = "0.1.15"
|
||||
tokio-util = "0.7.10"
|
||||
unicode-width = "0.1.11"
|
||||
unicode-width = "0.1.12"
|
||||
|
||||
# Logging
|
||||
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = "0.2.153"
|
||||
libc = "0.2.154"
|
||||
|
@ -2,7 +2,7 @@ use std::{collections::{HashMap, HashSet}, fs::Metadata, mem, ops::Deref, sync::
|
||||
|
||||
use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}};
|
||||
use yazi_config::{manager::SortBy, MANAGER};
|
||||
use yazi_shared::fs::{accessible, File, FilesOp, Url, FILES_TICKET};
|
||||
use yazi_shared::fs::{maybe_exists, File, FilesOp, Url, FILES_TICKET};
|
||||
|
||||
use super::{FilesSorter, Filter};
|
||||
|
||||
@ -101,7 +101,7 @@ impl Files {
|
||||
Ok(m) if mtime == m.modified().ok() => {}
|
||||
Ok(m) => return Some(m),
|
||||
Err(e) => {
|
||||
if accessible(url).await {
|
||||
if maybe_exists(url).await {
|
||||
FilesOp::IOErr(url.clone(), e.kind()).emit();
|
||||
} else if let Some(p) = url.parent_url() {
|
||||
FilesOp::Deleting(p, vec![url.clone()]).emit();
|
||||
|
@ -6,7 +6,7 @@ use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
|
||||
use yazi_config::{OPEN, PREVIEW};
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_proxy::{AppProxy, TasksProxy, HIDER, WATCHER};
|
||||
use yazi_shared::{fs::{accessible, max_common_root, File, FilesOp, Url}, term::Term};
|
||||
use yazi_shared::{fs::{max_common_root, maybe_exists, File, FilesOp, Url}, term::Term};
|
||||
|
||||
use crate::manager::Manager;
|
||||
|
||||
@ -84,7 +84,7 @@ impl Manager {
|
||||
for (o, n) in todo {
|
||||
let (old, new) = (root.join(&o), root.join(&n));
|
||||
|
||||
if accessible(&new).await {
|
||||
if maybe_exists(&new).await {
|
||||
failed.push((o, n, anyhow!("Destination already exists")));
|
||||
} else if let Err(e) = fs::rename(&old, &new).await {
|
||||
failed.push((o, n, e.into()));
|
||||
|
@ -3,7 +3,7 @@ use std::path::PathBuf;
|
||||
use tokio::fs;
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_proxy::{InputProxy, ManagerProxy};
|
||||
use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}};
|
||||
use yazi_shared::{event::Cmd, fs::{maybe_exists, File, FilesOp, Url}};
|
||||
|
||||
use crate::manager::Manager;
|
||||
|
||||
@ -26,7 +26,7 @@ impl Manager {
|
||||
};
|
||||
|
||||
let path = cwd.join(&name);
|
||||
if !opt.force && accessible(&path).await {
|
||||
if !opt.force && maybe_exists(&path).await {
|
||||
match InputProxy::show(InputCfg::overwrite()).recv().await {
|
||||
Some(Ok(c)) if c == "y" || c == "Y" => (),
|
||||
_ => return Ok(()),
|
||||
|
@ -5,7 +5,7 @@ use tokio::fs;
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_proxy::{InputProxy, ManagerProxy, WATCHER};
|
||||
use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}};
|
||||
use yazi_shared::{event::Cmd, fs::{maybe_exists, File, FilesOp, Url}};
|
||||
|
||||
use crate::manager::Manager;
|
||||
|
||||
@ -62,7 +62,7 @@ impl Manager {
|
||||
}
|
||||
|
||||
let new = hovered.parent().unwrap().join(name);
|
||||
if opt.force || !accessible(&new).await {
|
||||
if opt.force || !maybe_exists(&new).await {
|
||||
Self::rename_do(tab, hovered, Url::from(new)).await.ok();
|
||||
return;
|
||||
}
|
||||
|
@ -19,12 +19,12 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
|
||||
# External dependencies
|
||||
anyhow = "1.0.82"
|
||||
mlua = { version = "0.9.7", features = [ "lua54" ] }
|
||||
parking_lot = "0.12.1"
|
||||
serde = { version = "1.0.198", features = [ "derive" ] }
|
||||
parking_lot = "0.12.2"
|
||||
serde = { version = "1.0.199", features = [ "derive" ] }
|
||||
serde_json = "1.0.116"
|
||||
tokio = { version = "1.37.0", features = [ "full" ] }
|
||||
tokio-stream = "0.1.15"
|
||||
tokio-util = "0.7.10"
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
uzers = "0.11.3"
|
||||
uzers = "0.12.0"
|
||||
|
@ -41,7 +41,7 @@ tracing-appender = "0.2.3"
|
||||
tracing-subscriber = "0.3.18"
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = "0.2.153"
|
||||
libc = "0.2.154"
|
||||
signal-hook-tokio = { version = "0.3.1", features = [ "futures-v0_3" ] }
|
||||
|
||||
[target.'cfg(all(not(target_os = "macos"), not(target_os = "windows")))'.dependencies]
|
||||
|
@ -23,28 +23,28 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
|
||||
# External dependencies
|
||||
ansi-to-tui = "3.1.0"
|
||||
anyhow = "1.0.82"
|
||||
base64 = "0.22.0"
|
||||
base64 = "0.22.1"
|
||||
crossterm = "0.27.0"
|
||||
futures = "0.3.30"
|
||||
md-5 = "0.10.6"
|
||||
mlua = { version = "0.9.7", features = [ "lua54", "serialize", "macros", "async" ] }
|
||||
parking_lot = "0.12.1"
|
||||
parking_lot = "0.12.2"
|
||||
ratatui = "=0.26.1"
|
||||
serde = "1.0.198"
|
||||
serde = "1.0.199"
|
||||
serde_json = "1.0.116"
|
||||
shell-escape = "0.1.5"
|
||||
shell-words = "1.1.0"
|
||||
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||
tokio = { version = "1.37.0", features = [ "full" ] }
|
||||
tokio-util = "0.7.10"
|
||||
unicode-width = "0.1.11"
|
||||
unicode-width = "0.1.12"
|
||||
yazi-prebuild = "0.1.2"
|
||||
|
||||
# Logging
|
||||
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
uzers = "0.11.3"
|
||||
uzers = "0.12.0"
|
||||
|
||||
[target."cfg(windows)".dependencies]
|
||||
clipboard-win = "5.3.1"
|
||||
|
@ -19,7 +19,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
|
||||
anyhow = "1.0.82"
|
||||
async-priority-channel = "0.2.0"
|
||||
futures = "0.3.30"
|
||||
parking_lot = "0.12.1"
|
||||
parking_lot = "0.12.2"
|
||||
scopeguard = "1.2.0"
|
||||
tokio = { version = "1.37.0", features = [ "full" ] }
|
||||
|
||||
@ -27,7 +27,7 @@ tokio = { version = "1.37.0", features = [ "full" ] }
|
||||
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = "0.2.153"
|
||||
libc = "0.2.154"
|
||||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
trash = "4.1.0"
|
||||
|
@ -5,7 +5,7 @@ use futures::{future::BoxFuture, FutureExt};
|
||||
use tokio::{fs, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
|
||||
use tracing::warn;
|
||||
use yazi_config::TASKS;
|
||||
use yazi_shared::fs::{accessible, calculate_size, copy_with_progress, path_relative_to, Url};
|
||||
use yazi_shared::fs::{calculate_size, copy_with_progress, maybe_exists, path_relative_to, Url};
|
||||
|
||||
use super::{FileOp, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash};
|
||||
use crate::{TaskOp, TaskProg, LOW, NORMAL};
|
||||
@ -108,7 +108,7 @@ impl File {
|
||||
}
|
||||
FileOp::Delete(task) => {
|
||||
if let Err(e) = fs::remove_file(&task.target).await {
|
||||
if e.kind() != NotFound && accessible(&task.target).await {
|
||||
if e.kind() != NotFound && maybe_exists(&task.target).await {
|
||||
self.fail(task.id, format!("Delete task failed: {:?}, {e}", task))?;
|
||||
Err(e)?
|
||||
}
|
||||
|
@ -15,15 +15,15 @@ crossterm = "0.27.0"
|
||||
dirs = "5.0.1"
|
||||
filetime = "0.2.23"
|
||||
futures = "0.3.30"
|
||||
parking_lot = "0.12.1"
|
||||
parking_lot = "0.12.2"
|
||||
percent-encoding = "2.3.1"
|
||||
ratatui = "=0.26.1"
|
||||
regex = "1.10.4"
|
||||
serde = { version = "1.0.198", features = [ "derive" ] }
|
||||
serde = { version = "1.0.199", features = [ "derive" ] }
|
||||
tokio = { version = "1.37.0", features = [ "full" ] }
|
||||
|
||||
# Logging
|
||||
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = "0.2.153"
|
||||
libc = "0.2.154"
|
||||
|
@ -18,3 +18,13 @@ impl CharKind {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn strip_trailing_newline(mut s: String) -> String {
|
||||
if s.ends_with('\n') {
|
||||
s.pop();
|
||||
}
|
||||
if s.ends_with('\r') {
|
||||
s.pop();
|
||||
}
|
||||
s
|
||||
}
|
||||
|
@ -4,8 +4,10 @@ use anyhow::Result;
|
||||
use filetime::{set_file_mtime, FileTime};
|
||||
use tokio::{fs, io, select, sync::{mpsc, oneshot}, time};
|
||||
|
||||
pub async fn accessible(path: &Path) -> bool {
|
||||
match fs::symlink_metadata(path).await {
|
||||
pub async fn must_exists(p: impl AsRef<Path>) -> bool { fs::symlink_metadata(p).await.is_ok() }
|
||||
|
||||
pub async fn maybe_exists(p: impl AsRef<Path>) -> bool {
|
||||
match fs::symlink_metadata(p).await {
|
||||
Ok(_) => true,
|
||||
Err(e) => e.kind() != io::ErrorKind::NotFound,
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::{borrow::Cow, env, ffi::OsString, path::{Component, Path, PathBuf, MAIN_SEPARATOR}};
|
||||
|
||||
use super::accessible;
|
||||
use super::maybe_exists;
|
||||
use crate::fs::Url;
|
||||
|
||||
#[inline]
|
||||
@ -76,7 +76,7 @@ pub async fn unique_path(mut p: Url) -> Url {
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut i = 0;
|
||||
while accessible(&p).await {
|
||||
while maybe_exists(&p).await {
|
||||
i += 1;
|
||||
|
||||
let mut name = OsString::with_capacity(stem.len() + ext.len() + 5);
|
||||
|
Loading…
Reference in New Issue
Block a user