mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
Merge 2ec2bb0254 into 565f9cc898
This commit is contained in:
commit
2dbb3f1e85
5 changed files with 196 additions and 33 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -3214,6 +3214,7 @@ dependencies = [
|
||||||
"clap_complete_nushell",
|
"clap_complete_nushell",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"md-5",
|
"md-5",
|
||||||
|
"regex",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml_edit",
|
"toml_edit",
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ anyhow = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
md-5 = { workspace = true }
|
md-5 = { workspace = true }
|
||||||
|
regex = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
toml_edit = "0.22.22"
|
toml_edit = "0.22.22"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const TRACKER: &str = "DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY";
|
||||||
|
|
||||||
impl Package {
|
impl Package {
|
||||||
pub(super) async fn deploy(&mut self) -> Result<()> {
|
pub(super) async fn deploy(&mut self) -> Result<()> {
|
||||||
let Some(name) = self.name().map(ToOwned::to_owned) else { bail!("Invalid package url") };
|
let name = self.name().to_owned();
|
||||||
let from = self.local().join(&self.child);
|
let from = self.local().join(&self.child);
|
||||||
|
|
||||||
self.header("Deploying package `{name}`")?;
|
self.header("Deploying package `{name}`")?;
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,99 @@
|
||||||
use std::{borrow::Cow, io::BufWriter, path::PathBuf};
|
use std::{borrow::Cow, io::BufWriter, path::PathBuf, sync::OnceLock};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Error, Result};
|
||||||
use md5::{Digest, Md5};
|
use md5::{Digest, Md5};
|
||||||
|
use regex::Regex;
|
||||||
use yazi_shared::Xdg;
|
use yazi_shared::Xdg;
|
||||||
|
|
||||||
|
static PACKAGE_RAW_URL_RE: OnceLock<Regex> = OnceLock::new();
|
||||||
|
static PACKAGE_SHORT_URL_RE: OnceLock<Regex> = OnceLock::new();
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn package_raw_url_re() -> &'static Regex {
|
||||||
|
PACKAGE_RAW_URL_RE.get_or_init(|| {
|
||||||
|
Regex::new(r"^(?P<proto>[^:]+)://(?P<host>[^/]+)/(?P<url_path>[^:]+)(:(?P<child>.*))?$")
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn package_short_url_re() -> &'static Regex {
|
||||||
|
PACKAGE_SHORT_URL_RE.get_or_init(|| {
|
||||||
|
Regex::new(r"^((?P<host>[^/]+)/)?(?P<owner>[^/]+)/(?P<repo>[^:]+)(:(?P<child>.*))?$").unwrap()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub(crate) struct Package {
|
pub(crate) struct Package {
|
||||||
pub(crate) repo: String,
|
pub(crate) proto: String,
|
||||||
|
pub(crate) host: String,
|
||||||
|
pub(crate) url_path: String,
|
||||||
pub(crate) child: String,
|
pub(crate) child: String,
|
||||||
pub(crate) rev: String,
|
pub(crate) rev: String,
|
||||||
pub(super) is_flavor: bool,
|
pub(super) is_flavor: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Package {
|
impl Package {
|
||||||
pub(super) fn new(url: &str, rev: Option<&str>) -> Self {
|
pub(super) fn new(url: &str, rev: Option<&str>) -> Result<Self> {
|
||||||
let mut parts = url.splitn(2, ':');
|
let rev = rev.unwrap_or_default().to_owned();
|
||||||
|
let is_flavor = false;
|
||||||
|
|
||||||
let mut repo = parts.next().unwrap_or_default().to_owned();
|
if let Some(raw_url_match) = package_raw_url_re().captures(url) {
|
||||||
let child = if let Some(s) = parts.next() {
|
let proto = raw_url_match["proto"].to_owned();
|
||||||
format!("{s}.yazi")
|
let host = raw_url_match["host"].to_owned();
|
||||||
|
let mut url_path = raw_url_match["url_path"].to_owned();
|
||||||
|
let child = if let Some(child) = raw_url_match.name("child") {
|
||||||
|
format!("{}.yazi", child.as_str())
|
||||||
} else {
|
} else {
|
||||||
repo.push_str(".yazi");
|
url_path.push_str(".yazi");
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
Self { repo, child, rev: rev.unwrap_or_default().to_owned(), is_flavor: false }
|
Ok(Self { proto, host, url_path, child, rev, is_flavor })
|
||||||
|
} else if let Some(short_url_match) = package_short_url_re().captures(url) {
|
||||||
|
let proto = "https".to_owned();
|
||||||
|
let host =
|
||||||
|
short_url_match.name("host").map(|m| m.as_str()).unwrap_or("github.com").to_owned();
|
||||||
|
let owner = &short_url_match["owner"];
|
||||||
|
let repo = &short_url_match["repo"];
|
||||||
|
let mut url_path = format!("{owner}/{repo}");
|
||||||
|
|
||||||
|
let child = if let Some(child) = short_url_match.name("child") {
|
||||||
|
format!("{}.yazi", child.as_str())
|
||||||
|
} else {
|
||||||
|
url_path.push_str(".yazi");
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self { proto, host, url_path, child, rev, is_flavor })
|
||||||
|
} else {
|
||||||
|
Err(Error::msg("invalid package url"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn use_(&self) -> Cow<str> {
|
pub(super) fn use_(&self) -> Cow<str> {
|
||||||
if self.child.is_empty() {
|
if self.child.is_empty() {
|
||||||
self.repo.trim_end_matches(".yazi").into()
|
format!("{}://{}/{}", self.proto, self.host, self.url_path.trim_end_matches(".yazi")).into()
|
||||||
} else {
|
} else {
|
||||||
format!("{}:{}", self.repo, self.child.trim_end_matches(".yazi")).into()
|
format!(
|
||||||
|
"{}://{}/{}:{}",
|
||||||
|
self.proto,
|
||||||
|
self.host,
|
||||||
|
self.url_path,
|
||||||
|
self.child.trim_end_matches(".yazi")
|
||||||
|
)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn name(&self) -> Option<&str> {
|
pub(super) fn name(&self) -> &str {
|
||||||
let s = if self.child.is_empty() {
|
if self.child.is_empty() {
|
||||||
self.repo.split('/').last().filter(|s| !s.is_empty())
|
self.url_path.rsplit('/').next().unwrap_or(&self.url_path)
|
||||||
} else {
|
} else {
|
||||||
Some(self.child.as_str())
|
self.child.as_str()
|
||||||
};
|
}
|
||||||
|
|
||||||
s.filter(|s| s.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-' | b'.')))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -55,8 +105,7 @@ impl Package {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn remote(&self) -> String {
|
pub(super) fn remote(&self) -> String {
|
||||||
// Support more Git hosting services in the future
|
format!("{}://{}/{}", self.proto, self.host, self.url_path)
|
||||||
format!("https://github.com/{}.git", self.repo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn header(&self, s: &str) -> Result<()> {
|
pub(super) fn header(&self, s: &str) -> Result<()> {
|
||||||
|
|
@ -68,7 +117,7 @@ impl Package {
|
||||||
SetAttributes(Attribute::Reverse.into()),
|
SetAttributes(Attribute::Reverse.into()),
|
||||||
SetAttributes(Attribute::Bold.into()),
|
SetAttributes(Attribute::Bold.into()),
|
||||||
Print(" "),
|
Print(" "),
|
||||||
Print(s.replacen("{name}", self.name().unwrap_or_default(), 1)),
|
Print(s.replacen("{name}", self.name(), 1)),
|
||||||
Print(" "),
|
Print(" "),
|
||||||
SetAttributes(Attribute::Reset.into()),
|
SetAttributes(Attribute::Reset.into()),
|
||||||
Print("\n\n"),
|
Print("\n\n"),
|
||||||
|
|
@ -76,3 +125,116 @@ impl Package {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_component_short_url() -> Result<()> {
|
||||||
|
let url = "owner/repo";
|
||||||
|
|
||||||
|
let pkg = Package::new(url, None)?;
|
||||||
|
|
||||||
|
assert_eq!(pkg.proto, "https");
|
||||||
|
assert_eq!(pkg.host, "github.com");
|
||||||
|
assert_eq!(pkg.url_path, "owner/repo.yazi");
|
||||||
|
assert_eq!(pkg.child, "");
|
||||||
|
|
||||||
|
assert_eq!(pkg.remote(), "https://github.com/owner/repo.yazi");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn three_component_short_url() -> Result<()> {
|
||||||
|
let url = "codeberg.org/owner/repo";
|
||||||
|
|
||||||
|
let pkg = Package::new(url, None)?;
|
||||||
|
|
||||||
|
assert_eq!(pkg.proto, "https");
|
||||||
|
assert_eq!(pkg.host, "codeberg.org");
|
||||||
|
assert_eq!(pkg.url_path, "owner/repo.yazi");
|
||||||
|
assert_eq!(pkg.child, "");
|
||||||
|
|
||||||
|
assert_eq!(pkg.remote(), "https://codeberg.org/owner/repo.yazi");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_component_short_url_with_child_path() -> Result<()> {
|
||||||
|
let url = "owner/repo:my-plugin";
|
||||||
|
|
||||||
|
let pkg = Package::new(url, None)?;
|
||||||
|
|
||||||
|
assert_eq!(pkg.proto, "https");
|
||||||
|
assert_eq!(pkg.host, "github.com");
|
||||||
|
assert_eq!(pkg.url_path, "owner/repo");
|
||||||
|
assert_eq!(pkg.child, "my-plugin.yazi");
|
||||||
|
|
||||||
|
assert_eq!(pkg.remote(), "https://github.com/owner/repo");
|
||||||
|
assert_eq!(pkg.use_(), "https://github.com/owner/repo:my-plugin");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_ssh_url() -> Result<()> {
|
||||||
|
let url = "ssh://git@my-host:6969/my-plugin";
|
||||||
|
|
||||||
|
let pkg = Package::new(url, None)?;
|
||||||
|
|
||||||
|
assert_eq!(pkg.proto, "ssh");
|
||||||
|
assert_eq!(pkg.host, "git@my-host:6969");
|
||||||
|
assert_eq!(pkg.url_path, "my-plugin.yazi");
|
||||||
|
assert_eq!(pkg.child, "");
|
||||||
|
|
||||||
|
assert_eq!(pkg.remote(), "ssh://git@my-host:6969/my-plugin.yazi");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_ssh_url_with_child_path() -> Result<()> {
|
||||||
|
let url = "ssh://git@192.168.0.69:2222/~/my-repo.git:my-plugin";
|
||||||
|
|
||||||
|
let pkg = Package::new(url, None)?;
|
||||||
|
|
||||||
|
assert_eq!(pkg.proto, "ssh");
|
||||||
|
assert_eq!(pkg.host, "git@192.168.0.69:2222");
|
||||||
|
assert_eq!(pkg.url_path, "~/my-repo.git");
|
||||||
|
assert_eq!(pkg.child, "my-plugin.yazi");
|
||||||
|
|
||||||
|
assert_eq!(pkg.remote(), "ssh://git@192.168.0.69:2222/~/my-repo.git");
|
||||||
|
assert_eq!(pkg.use_(), "ssh://git@192.168.0.69:2222/~/my-repo.git:my-plugin");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_http_url_with_non_standard_path() -> Result<()> {
|
||||||
|
let url = "https://example.com/xxx/yyy/zzz/owner/repo:my-plugin";
|
||||||
|
|
||||||
|
let pkg = Package::new(url, None)?;
|
||||||
|
|
||||||
|
assert_eq!(pkg.proto, "https");
|
||||||
|
assert_eq!(pkg.host, "example.com");
|
||||||
|
assert_eq!(pkg.url_path, "xxx/yyy/zzz/owner/repo");
|
||||||
|
assert_eq!(pkg.child, "my-plugin.yazi");
|
||||||
|
|
||||||
|
assert_eq!(pkg.remote(), "https://example.com/xxx/yyy/zzz/owner/repo");
|
||||||
|
assert_eq!(pkg.use_(), "https://example.com/xxx/yyy/zzz/owner/repo:my-plugin");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_url() {
|
||||||
|
let url = "one-component-url???";
|
||||||
|
|
||||||
|
let pkg = Package::new(url, None);
|
||||||
|
|
||||||
|
assert!(pkg.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ use super::Package;
|
||||||
|
|
||||||
impl Package {
|
impl Package {
|
||||||
pub(crate) async fn add_to_config(use_: &str) -> Result<()> {
|
pub(crate) async fn add_to_config(use_: &str) -> Result<()> {
|
||||||
let mut package = Self::new(use_, None);
|
let mut package = Self::new(use_, None).context("Invalid package `use`")?;
|
||||||
let Some(name) = package.name() else { bail!("Invalid package `use`") };
|
let name = package.name();
|
||||||
|
|
||||||
let path = Xdg::config_dir().join("package.toml");
|
let path = Xdg::config_dir().join("package.toml");
|
||||||
let mut doc = Self::ensure_config(&fs::read_to_string(&path).await.unwrap_or_default())?;
|
let mut doc = Self::ensure_config(&fs::read_to_string(&path).await.unwrap_or_default())?;
|
||||||
|
|
@ -49,7 +49,7 @@ impl Package {
|
||||||
let use_ = dep.get("use").and_then(|d| d.as_str()).context("Missing `use` field")?;
|
let use_ = dep.get("use").and_then(|d| d.as_str()).context("Missing `use` field")?;
|
||||||
let rev = dep.get("rev").and_then(|d| d.as_str());
|
let rev = dep.get("rev").and_then(|d| d.as_str());
|
||||||
|
|
||||||
let mut package = Package::new(use_, rev);
|
let mut package = Package::new(use_, rev)?;
|
||||||
if upgrade {
|
if upgrade {
|
||||||
package.upgrade().await?;
|
package.upgrade().await?;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -117,10 +117,9 @@ impl Package {
|
||||||
fn ensure_unique(doc: &DocumentMut, name: &str) -> Result<()> {
|
fn ensure_unique(doc: &DocumentMut, name: &str) -> Result<()> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn same(v: &Value, name: &str) -> bool {
|
fn same(v: &Value, name: &str) -> bool {
|
||||||
v.as_inline_table()
|
v.as_inline_table().and_then(|t| t.get("use")).and_then(|v| v.as_str()).is_some_and(|s| {
|
||||||
.and_then(|t| t.get("use"))
|
if let Ok(pkg) = Package::new(s, None) { pkg.name() == name } else { false }
|
||||||
.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)) {
|
if doc["plugin"]["deps"].as_array().unwrap().into_iter().any(|v| same(v, name)) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue