Skip to content

Commit 2ec2bb0

Browse files
committed
feat: yazi-cli: support raw git urls
1 parent 9782c0f commit 2ec2bb0

File tree

3 files changed

+181
-31
lines changed

3 files changed

+181
-31
lines changed

Diff for: Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: yazi-cli/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ anyhow = { workspace = true }
1818
clap = { workspace = true }
1919
crossterm = { workspace = true }
2020
md-5 = { workspace = true }
21+
regex = { workspace = true }
2122
serde_json = { workspace = true }
2223
tokio = { workspace = true }
2324
toml_edit = "0.22.22"

Diff for: yazi-cli/src/package/package.rs

+179-31
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,86 @@
1-
use std::{borrow::Cow, io::BufWriter, path::PathBuf};
1+
use std::{borrow::Cow, io::BufWriter, path::PathBuf, sync::OnceLock};
22

3-
use anyhow::{Context, Result};
3+
use anyhow::{Error, Result};
44
use md5::{Digest, Md5};
5+
use regex::Regex;
56
use yazi_shared::Xdg;
67

8+
static PACKAGE_RAW_URL_RE: OnceLock<Regex> = OnceLock::new();
9+
static PACKAGE_SHORT_URL_RE: OnceLock<Regex> = OnceLock::new();
10+
11+
#[inline]
12+
fn package_raw_url_re() -> &'static Regex {
13+
PACKAGE_RAW_URL_RE.get_or_init(|| {
14+
Regex::new(r"^(?P<proto>[^:]+)://(?P<host>[^/]+)/(?P<url_path>[^:]+)(:(?P<child>.*))?$")
15+
.unwrap()
16+
})
17+
}
18+
19+
#[inline]
20+
fn package_short_url_re() -> &'static Regex {
21+
PACKAGE_SHORT_URL_RE.get_or_init(|| {
22+
Regex::new(r"^((?P<host>[^/]+)/)?(?P<owner>[^/]+)/(?P<repo>[^:]+)(:(?P<child>.*))?$").unwrap()
23+
})
24+
}
25+
26+
#[derive(Debug)]
727
pub(crate) struct Package {
28+
pub(crate) proto: String,
829
pub(crate) host: String,
9-
pub(crate) owner: String,
10-
pub(crate) repo_name: String,
30+
pub(crate) url_path: String,
1131
pub(crate) child: String,
1232
pub(crate) rev: String,
1333
pub(super) is_flavor: bool,
1434
}
1535

1636
impl Package {
1737
pub(super) fn new(url: &str, rev: Option<&str>) -> Result<Self> {
18-
let mut parts = url.splitn(2, ':');
38+
let rev = rev.unwrap_or_default().to_owned();
39+
let is_flavor = false;
40+
41+
if let Some(raw_url_match) = package_raw_url_re().captures(url) {
42+
let proto = raw_url_match["proto"].to_owned();
43+
let host = raw_url_match["host"].to_owned();
44+
let mut url_path = raw_url_match["url_path"].to_owned();
45+
let child = if let Some(child) = raw_url_match.name("child") {
46+
format!("{}.yazi", child.as_str())
47+
} else {
48+
url_path.push_str(".yazi");
49+
String::new()
50+
};
51+
52+
Ok(Self { proto, host, url_path, child, rev, is_flavor })
53+
} else if let Some(short_url_match) = package_short_url_re().captures(url) {
54+
let proto = "https".to_owned();
55+
let host =
56+
short_url_match.name("host").map(|m| m.as_str()).unwrap_or("github.com").to_owned();
57+
let owner = &short_url_match["owner"];
58+
let repo = &short_url_match["repo"];
59+
let mut url_path = format!("{owner}/{repo}");
60+
61+
let child = if let Some(child) = short_url_match.name("child") {
62+
format!("{}.yazi", child.as_str())
63+
} else {
64+
url_path.push_str(".yazi");
65+
String::new()
66+
};
1967

20-
let mut repo_part = parts.next().unwrap_or_default().to_owned();
21-
let child = if let Some(s) = parts.next() {
22-
format!("{s}.yazi")
68+
Ok(Self { proto, host, url_path, child, rev, is_flavor })
2369
} else {
24-
repo_part.push_str(".yazi");
25-
String::new()
26-
};
27-
28-
let mut repo = repo_part.rsplit('/');
29-
let repo_name = repo.next().context("failed to get repo name")?.to_owned();
30-
let owner = repo.next().context("failed to get repo owner")?.to_owned();
31-
let host = repo.next().unwrap_or("github.com").to_owned();
32-
33-
Ok(Self {
34-
repo_name,
35-
owner,
36-
host,
37-
child,
38-
rev: rev.unwrap_or_default().to_owned(),
39-
is_flavor: false,
40-
})
70+
Err(Error::msg("invalid package url"))
71+
}
4172
}
4273

4374
#[inline]
4475
pub(super) fn use_(&self) -> Cow<str> {
4576
if self.child.is_empty() {
46-
format!("{}/{}/{}", self.host, self.owner, self.repo_name.trim_end_matches(".yazi")).into()
77+
format!("{}://{}/{}", self.proto, self.host, self.url_path.trim_end_matches(".yazi")).into()
4778
} else {
4879
format!(
49-
"{}/{}/{}:{}",
80+
"{}://{}/{}:{}",
81+
self.proto,
5082
self.host,
51-
self.owner,
52-
self.repo_name,
83+
self.url_path,
5384
self.child.trim_end_matches(".yazi")
5485
)
5586
.into()
@@ -58,7 +89,11 @@ impl Package {
5889

5990
#[inline]
6091
pub(super) fn name(&self) -> &str {
61-
if self.child.is_empty() { self.repo_name.as_str() } else { self.child.as_str() }
92+
if self.child.is_empty() {
93+
self.url_path.rsplit('/').next().unwrap_or(&self.url_path)
94+
} else {
95+
self.child.as_str()
96+
}
6297
}
6398

6499
#[inline]
@@ -70,7 +105,7 @@ impl Package {
70105

71106
#[inline]
72107
pub(super) fn remote(&self) -> String {
73-
format!("https://{}/{}/{}.git", self.host, self.owner, self.repo_name)
108+
format!("{}://{}/{}", self.proto, self.host, self.url_path)
74109
}
75110

76111
pub(super) fn header(&self, s: &str) -> Result<()> {
@@ -90,3 +125,116 @@ impl Package {
90125
Ok(())
91126
}
92127
}
128+
129+
#[cfg(test)]
130+
mod tests {
131+
use super::*;
132+
133+
#[test]
134+
fn two_component_short_url() -> Result<()> {
135+
let url = "owner/repo";
136+
137+
let pkg = Package::new(url, None)?;
138+
139+
assert_eq!(pkg.proto, "https");
140+
assert_eq!(pkg.host, "github.com");
141+
assert_eq!(pkg.url_path, "owner/repo.yazi");
142+
assert_eq!(pkg.child, "");
143+
144+
assert_eq!(pkg.remote(), "https://github.com/owner/repo.yazi");
145+
146+
Ok(())
147+
}
148+
149+
#[test]
150+
fn three_component_short_url() -> Result<()> {
151+
let url = "codeberg.org/owner/repo";
152+
153+
let pkg = Package::new(url, None)?;
154+
155+
assert_eq!(pkg.proto, "https");
156+
assert_eq!(pkg.host, "codeberg.org");
157+
assert_eq!(pkg.url_path, "owner/repo.yazi");
158+
assert_eq!(pkg.child, "");
159+
160+
assert_eq!(pkg.remote(), "https://codeberg.org/owner/repo.yazi");
161+
162+
Ok(())
163+
}
164+
165+
#[test]
166+
fn two_component_short_url_with_child_path() -> Result<()> {
167+
let url = "owner/repo:my-plugin";
168+
169+
let pkg = Package::new(url, None)?;
170+
171+
assert_eq!(pkg.proto, "https");
172+
assert_eq!(pkg.host, "github.com");
173+
assert_eq!(pkg.url_path, "owner/repo");
174+
assert_eq!(pkg.child, "my-plugin.yazi");
175+
176+
assert_eq!(pkg.remote(), "https://github.com/owner/repo");
177+
assert_eq!(pkg.use_(), "https://github.com/owner/repo:my-plugin");
178+
179+
Ok(())
180+
}
181+
182+
#[test]
183+
fn raw_ssh_url() -> Result<()> {
184+
let url = "ssh://git@my-host:6969/my-plugin";
185+
186+
let pkg = Package::new(url, None)?;
187+
188+
assert_eq!(pkg.proto, "ssh");
189+
assert_eq!(pkg.host, "git@my-host:6969");
190+
assert_eq!(pkg.url_path, "my-plugin.yazi");
191+
assert_eq!(pkg.child, "");
192+
193+
assert_eq!(pkg.remote(), "ssh://git@my-host:6969/my-plugin.yazi");
194+
195+
Ok(())
196+
}
197+
198+
#[test]
199+
fn raw_ssh_url_with_child_path() -> Result<()> {
200+
let url = "ssh://[email protected]:2222/~/my-repo.git:my-plugin";
201+
202+
let pkg = Package::new(url, None)?;
203+
204+
assert_eq!(pkg.proto, "ssh");
205+
assert_eq!(pkg.host, "[email protected]:2222");
206+
assert_eq!(pkg.url_path, "~/my-repo.git");
207+
assert_eq!(pkg.child, "my-plugin.yazi");
208+
209+
assert_eq!(pkg.remote(), "ssh://[email protected]:2222/~/my-repo.git");
210+
assert_eq!(pkg.use_(), "ssh://[email protected]:2222/~/my-repo.git:my-plugin");
211+
212+
Ok(())
213+
}
214+
215+
#[test]
216+
fn raw_http_url_with_non_standard_path() -> Result<()> {
217+
let url = "https://example.com/xxx/yyy/zzz/owner/repo:my-plugin";
218+
219+
let pkg = Package::new(url, None)?;
220+
221+
assert_eq!(pkg.proto, "https");
222+
assert_eq!(pkg.host, "example.com");
223+
assert_eq!(pkg.url_path, "xxx/yyy/zzz/owner/repo");
224+
assert_eq!(pkg.child, "my-plugin.yazi");
225+
226+
assert_eq!(pkg.remote(), "https://example.com/xxx/yyy/zzz/owner/repo");
227+
assert_eq!(pkg.use_(), "https://example.com/xxx/yyy/zzz/owner/repo:my-plugin");
228+
229+
Ok(())
230+
}
231+
232+
#[test]
233+
fn invalid_url() {
234+
let url = "one-component-url???";
235+
236+
let pkg = Package::new(url, None);
237+
238+
assert!(pkg.is_err());
239+
}
240+
}

0 commit comments

Comments
 (0)