mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 14:51:03 +00:00
feat: new hub VFS kind (#4131)
This commit is contained in:
parent
b25ae9f82d
commit
52640fcece
84 changed files with 1247 additions and 547 deletions
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||
use yazi_core::cmp::CmpItem;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::cmp::ShowForm;
|
||||
use yazi_shared::{data::Data, path::{AsPath, PathDyn}, strand::StrandLike};
|
||||
use yazi_shared::{data::Data, path::{DynPath, PathDyn}, strand::StrandLike};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ impl Actor for Show {
|
|||
succ!();
|
||||
};
|
||||
|
||||
cmp.matches = Self::match_candidates(opt.word.as_path(), cache);
|
||||
cmp.matches = Self::match_candidates(opt.word.dyn_path(), cache);
|
||||
if cmp.matches.is_empty() {
|
||||
succ!(render!(mem::replace(&mut cmp.visible, false)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use yazi_fs::{engine::{DirReader, FileHolder}, path::clean_url};
|
|||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::cmp::TriggerForm;
|
||||
use yazi_proxy::CmpProxy;
|
||||
use yazi_shared::{AnyAsciiChar, BytePredictor, data::Data, natsort, path::{AsPath, PathBufDyn, PathLike}, spec::Spec, strand::{AsStrand, StrandLike}, url::{UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_shared::{AnyAsciiChar, BytePredictor, data::Data, natsort, path::{DynPath, PathBufDyn, PathLike}, spec::Spec, strand::{AsStrand, StrandLike}, url::{UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_vfs::engine;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -78,7 +78,7 @@ impl Trigger {
|
|||
}
|
||||
|
||||
// Child
|
||||
let child = path.rsplit_pred(AnyAsciiChar::SEP).map_or(path.as_path(), |(_, c)| c).to_owned();
|
||||
let child = path.rsplit_pred(AnyAsciiChar::SEP).map_or(path.dyn_path(), |(_, c)| c).to_owned();
|
||||
|
||||
// Parent
|
||||
let url = UrlCow::try_from((spec.clone().zeroed(), path)).ok()?;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods};
|
|||
use yazi_binding::{Range, style::Style};
|
||||
use yazi_config::THEME;
|
||||
use yazi_fs::file::FileInventory;
|
||||
use yazi_shared::{path::AsPath, url::UrlLike};
|
||||
use yazi_shared::{path::DynPath, url::UrlLike};
|
||||
|
||||
use super::{FILE_CACHE, Lives};
|
||||
use crate::lives::{CoreRef, PtrCell};
|
||||
|
|
@ -73,7 +73,11 @@ impl UserData for File {
|
|||
Ok(yazi_config::THEME.icon.matches(me, me.is_hovered()))
|
||||
});
|
||||
methods.add_method("size", |_, me, ()| {
|
||||
Ok(if me.is_dir() { me.folder.entries.sizes.get(&me.urn()).copied() } else { Some(me.len) })
|
||||
Ok(if me.is_dir() {
|
||||
me.folder.entries.sizes.get(&me.entry_key()).copied()
|
||||
} else {
|
||||
Some(me.len)
|
||||
})
|
||||
});
|
||||
methods.add_method("mime", |lua, me, ()| {
|
||||
let core: CoreRef = lua.named_registry_value("cx")?;
|
||||
|
|
@ -86,7 +90,7 @@ impl UserData for File {
|
|||
|
||||
let mut comp = me.url.try_strip_prefix(me.url.trail()).unwrap_or(me.url.loc()).components();
|
||||
comp.next_back();
|
||||
Some(lua.create_string(comp.as_path().encoded_bytes())).transpose()
|
||||
Some(lua.create_string(comp.dyn_path().encoded_bytes())).transpose()
|
||||
});
|
||||
methods.add_method("style", |lua, me, ()| {
|
||||
let core: CoreRef = lua.named_registry_value("cx")?;
|
||||
|
|
@ -122,7 +126,7 @@ impl UserData for File {
|
|||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(idx) = finder.matched_idx(&me.folder, me.urn()) else {
|
||||
let Some(idx) = finder.matched_idx(&me.folder, me.entry_key()) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -81,8 +81,8 @@ impl Cd {
|
|||
return MgrProxy::cd(&url, CdSource::Cd);
|
||||
}
|
||||
|
||||
if let Some(p) = url.parent() {
|
||||
FilesOp::Upserting(p.into(), [(url.urn().into(), file)].into()).emit();
|
||||
if let Some((p, k)) = url.pair2() {
|
||||
FilesOp::Upserting(p.into(), [(k.into(), file)].into()).emit();
|
||||
}
|
||||
MgrProxy::reveal(url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,10 +64,10 @@ impl Create {
|
|||
if dir {
|
||||
engine::create_dir_all(&new).await?;
|
||||
} else if let Ok(real) = engine::casefold(&new).await
|
||||
&& let Some((parent, urn)) = real.pair()
|
||||
&& let Some((parent, key)) = real.pair2()
|
||||
{
|
||||
ok_or_not_found!(engine::remove_file(&new).await);
|
||||
FilesOp::Deleting(parent.into(), [urn.into()].into()).emit();
|
||||
FilesOp::Deleting(parent.into(), [key.into()].into()).emit();
|
||||
engine::create(&new).await?;
|
||||
} else if let Some(parent) = new.parent() {
|
||||
engine::create_dir_all(parent).await.ok();
|
||||
|
|
@ -78,10 +78,10 @@ impl Create {
|
|||
}
|
||||
|
||||
if let Ok(real) = engine::casefold(&new).await
|
||||
&& let Some((parent, urn)) = real.pair()
|
||||
&& let Some((parent, key)) = real.pair2()
|
||||
{
|
||||
let file = File::new(&real).await?;
|
||||
FilesOp::Upserting(parent.into(), [(urn.into(), file)].into()).emit();
|
||||
FilesOp::Upserting(parent.into(), [(key.into(), file)].into()).emit();
|
||||
MgrProxy::reveal(&real);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ impl Actor for FilterDo {
|
|||
fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
|
||||
let filter = if opt.query.is_empty() { None } else { Some(Filter::new(&opt.query, opt.case)?) };
|
||||
|
||||
let hovered = cx.hovered().map(|f| f.urn().into());
|
||||
let hovered = cx.hovered().map(|f| f.entry_key().into());
|
||||
cx.current_mut().entries.set_filter(filter);
|
||||
|
||||
if cx.hovered().map(|f| f.urn()) != hovered.as_ref().map(Into::into) {
|
||||
if cx.hovered().map(|f| f.entry_key()) != hovered.as_ref().map(Into::into) {
|
||||
act!(mgr:hover, cx, hovered)?;
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ impl Actor for Hidden {
|
|||
let state = form.state.bool(cx.tab().pref.show_hidden);
|
||||
cx.tab_mut().pref.show_hidden = state;
|
||||
|
||||
let hovered = cx.hovered().map(|f| f.urn().to_owned());
|
||||
let hovered = cx.hovered().map(|f| f.entry_key().to_owned());
|
||||
let apply = |f: &mut Folder| {
|
||||
if f.stage == FolderStage::Loading {
|
||||
render!();
|
||||
|
|
@ -43,7 +43,7 @@ impl Actor for Hidden {
|
|||
{
|
||||
render!(h.repos(None));
|
||||
act!(mgr:peek, cx, true)?;
|
||||
} else if cx.hovered().map(|f| f.urn()) != hovered.as_ref().map(Into::into) {
|
||||
} else if cx.hovered().map(|f| f.entry_key()) != hovered.as_ref().map(Into::into) {
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,19 +18,19 @@ impl Actor for Hover {
|
|||
|
||||
// Parent should always track CWD
|
||||
if let Some(p) = &mut tab.parent {
|
||||
render!(p.repos(tab.current.url.try_strip_prefix(&p.url).ok()));
|
||||
render!(p.repos(Some(tab.current.url.entry_key())));
|
||||
}
|
||||
|
||||
// Repos CWD
|
||||
render!(tab.current.repos(form.urn.as_ref().map(Into::into)));
|
||||
render!(tab.current.repos(form.key.as_ref().map(Into::into)));
|
||||
|
||||
// Turn on tracing
|
||||
if let (Some(h), Some(u)) = (tab.hovered(), form.urn)
|
||||
&& h.urn() == u
|
||||
if let (Some(h), Some(key)) = (tab.hovered(), form.key)
|
||||
&& h.entry_key() == key
|
||||
{
|
||||
// `hover(Some)` occurs after user actions, such as create, rename, reveal, etc.
|
||||
// At this point, it's intuitive to track the file location regardless.
|
||||
tab.current.trace = Some(u.clone());
|
||||
// At this point, it's intuitive to track the entry regardless.
|
||||
tab.current.trace = Some(key.clone());
|
||||
cx.tasks.scheduler.behavior.reset();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,8 +66,8 @@ impl Actor for Rename {
|
|||
|
||||
impl Rename {
|
||||
async fn r#do(tab: Id, old: UrlBuf, new: UrlBuf) -> Result<()> {
|
||||
let Some((old_p, old_n)) = old.pair() else { return Ok(()) };
|
||||
let Some(_) = new.pair() else { return Ok(()) };
|
||||
let Some((old_p, old_k)) = old.pair2() else { return Ok(()) };
|
||||
let Some(_) = new.pair2() else { return Ok(()) };
|
||||
let _permit = WATCHER.acquire().await.unwrap();
|
||||
|
||||
let overwritten = engine::casefold(&new).await;
|
||||
|
|
@ -75,21 +75,21 @@ impl Rename {
|
|||
|
||||
if let Ok(u) = overwritten
|
||||
&& u != new
|
||||
&& let Some((parent, urn)) = u.pair()
|
||||
&& let Some((parent, key)) = u.pair2()
|
||||
{
|
||||
ok_or_not_found!(engine::rename(&u, &new).await);
|
||||
FilesOp::Deleting(parent.to_owned(), [urn.into()].into()).emit();
|
||||
FilesOp::Deleting(parent.to_owned(), [key.into()].into()).emit();
|
||||
}
|
||||
|
||||
let new = engine::casefold(&new).await?;
|
||||
let Some((new_p, new_n)) = new.pair() else { return Ok(()) };
|
||||
let Some((new_p, new_k)) = new.pair2() else { return Ok(()) };
|
||||
|
||||
let file = File::new(&new).await?;
|
||||
if new_p == old_p {
|
||||
FilesOp::Upserting(old_p.into(), [(old_n.into(), file)].into()).emit();
|
||||
FilesOp::Upserting(old_p.into(), [(old_k.into(), file)].into()).emit();
|
||||
} else {
|
||||
FilesOp::Deleting(old_p.into(), [old_n.into()].into()).emit();
|
||||
FilesOp::Upserting(new_p.into(), [(new_n.into(), file)].into()).emit();
|
||||
FilesOp::Deleting(old_p.into(), [old_k.into()].into()).emit();
|
||||
FilesOp::Upserting(new_p.into(), [(new_k.into(), file)].into()).emit();
|
||||
}
|
||||
|
||||
MgrProxy::reveal(&new);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ impl Actor for Reveal {
|
|||
const NAME: &str = "reveal";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let Some((parent, child)) = form.target.pair() else { succ!() };
|
||||
let Some((parent, child)) = form.target.pair2() else { succ!() };
|
||||
|
||||
// Cd to the parent directory
|
||||
act!(mgr:cd, cx, (parent, form.source))?;
|
||||
|
|
@ -25,7 +25,7 @@ impl Actor for Reveal {
|
|||
|
||||
// If the child is not hovered, which means it doesn't exist,
|
||||
// create a dummy file
|
||||
if !form.no_dummy && tab.hovered().is_none_or(|f| child != f.urn()) {
|
||||
if !form.no_dummy && tab.hovered().is_none_or(|f| f.entry_key() != child) {
|
||||
let op = FilesOp::Creating(parent.into(), vec![File::from_dummy(&form.target, None)]);
|
||||
tab.current.update_pub(tab.id, op);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ impl Actor for Sort {
|
|||
pref.sort_fallback = form.fallback.unwrap_or(pref.sort_fallback);
|
||||
|
||||
let sorter = FilesSorter::from(&*pref);
|
||||
let hovered = cx.hovered().map(|f| f.urn().to_owned());
|
||||
let hovered = cx.hovered().map(|f| f.entry_key().to_owned());
|
||||
let apply = |f: &mut Folder| {
|
||||
if f.stage == FolderStage::Loading {
|
||||
render!();
|
||||
|
|
@ -50,7 +50,7 @@ impl Actor for Sort {
|
|||
{
|
||||
render!(h.repos(None));
|
||||
act!(mgr:peek, cx, true)?;
|
||||
} else if cx.hovered().map(|f| f.urn()) != hovered.as_ref().map(Into::into) {
|
||||
} else if cx.hovered().map(|f| f.entry_key()) != hovered.as_ref().map(Into::into) {
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,12 +57,12 @@ impl UpdateFiles {
|
|||
fn update_parent(cx: &mut Ctx, op: FilesOp) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
|
||||
let urn = tab.current.url.urn();
|
||||
let leave = matches!(op, FilesOp::Deleting(_, ref urns) if urns.contains(&urn));
|
||||
let key = tab.current.url.entry_key();
|
||||
let leave = matches!(op, FilesOp::Deleting(_, ref keys) if keys.contains(&key));
|
||||
|
||||
if let Some(f) = tab.parent.as_mut() {
|
||||
render!(f.update_pub(tab.id, op));
|
||||
render!(f.hover(urn));
|
||||
render!(f.hover(key));
|
||||
}
|
||||
|
||||
if leave {
|
||||
|
|
@ -97,8 +97,8 @@ impl UpdateFiles {
|
|||
|
||||
fn update_history(cx: &mut Ctx, op: FilesOp) -> Result<Data> {
|
||||
let tab = &mut cx.tab_mut();
|
||||
let leave = tab.parent.as_ref().and_then(|f| f.url.parent().map(|p| (p, f.url.urn()))).is_some_and(
|
||||
|(p, n)| matches!(op, FilesOp::Deleting(ref parent, ref urns) if *parent == p && urns.contains(&n)),
|
||||
let leave = tab.parent.as_ref().and_then(|f| f.url.pair2()).is_some_and(
|
||||
|(pp, key)| matches!(&op, FilesOp::Deleting(parent, keys) if parent == pp && keys.contains(&key)),
|
||||
);
|
||||
|
||||
tab.history.get_or_insert_with(op.cwd(), |u| Folder::from(u)).update_pub(tab.id, op);
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ impl Actor for UpdateSucceed {
|
|||
|
||||
if form.track
|
||||
&& form.id == cx.tasks.scheduler.behavior.first_id()
|
||||
&& let Some((parent, urn)) = form.urls[0].pair()
|
||||
&& let Some((parent, key)) = form.urls[0].pair2()
|
||||
&& parent == *cx.cwd()
|
||||
{
|
||||
cx.current_mut().trace = Some(urn.into());
|
||||
cx.current_mut().trace = Some(key.into());
|
||||
act!(mgr:hover, cx)?;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use serde::Deserialize;
|
||||
use yazi_binding::position::{Offset, Origin, Position};
|
||||
use yazi_codegen::{DeserializeOver, DeserializeOver2};
|
||||
use yazi_shared::{spec::Encode as EncodeSpec, url::Url};
|
||||
use yazi_shared::{spec::EncodeSpec, url::Url};
|
||||
use yazi_widgets::input::InputOpt;
|
||||
|
||||
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
|
||||
|
|
|
|||
|
|
@ -1,16 +1,28 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Deserializer, de::{MapAccess, Visitor}};
|
||||
use yazi_shared::auth::Scheme;
|
||||
use yazi_shared::auth::{Auth, Domain, Scheme};
|
||||
use yazi_shim::toml::DeserializeOverWith;
|
||||
|
||||
use super::{DomainSeed, Domains, Service};
|
||||
use super::{DomainSeed, Domains};
|
||||
use crate::vfs::Service;
|
||||
|
||||
pub struct Authorities(HashMap<Scheme, Domains>);
|
||||
|
||||
impl Authorities {
|
||||
pub fn get(&self, scheme: &Scheme, domain: &str) -> Option<&Service> {
|
||||
pub fn service(&self, scheme: &Scheme, domain: &Domain<'_>) -> Option<&Service> {
|
||||
self.0.get(scheme)?.get(domain)
|
||||
}
|
||||
|
||||
pub fn auth(&self, scheme: &Scheme, domain: &Domain<'_>) -> Option<Arc<Auth>> {
|
||||
let service = self.service(scheme, domain)?;
|
||||
if service.auth().domain.is_catchall() {
|
||||
Some(Auth::new(service.kind(), scheme.clone(), domain.clone()))
|
||||
} else {
|
||||
Some(service.auth().clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Authorities {
|
||||
|
|
@ -41,7 +53,7 @@ impl<'de> Deserialize<'de> for Authorities {
|
|||
impl DeserializeOverWith for Authorities {
|
||||
fn deserialize_over_with<'de, D: Deserializer<'de>>(mut self, de: D) -> Result<Self, D::Error> {
|
||||
for (scheme, domains) in Self::deserialize(de)?.0 {
|
||||
self.0.entry(scheme).or_default().extend(domains.0);
|
||||
self.0.entry(scheme).or_default().extend(domains);
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,66 @@
|
|||
use std::{ops::{Deref, DerefMut}, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Deserializer, de::{DeserializeSeed, Error}};
|
||||
use yazi_shared::{KebabCasedKey, auth::Scheme};
|
||||
use serde::{Deserialize, Deserializer, de::{self, DeserializeSeed, Error}};
|
||||
use yazi_shared::auth::{AuthKind, Domain, Scheme};
|
||||
|
||||
use super::{Service, ServiceSftp};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Domains(pub(super) HashMap<KebabCasedKey, Service>);
|
||||
|
||||
impl Deref for Domains {
|
||||
type Target = HashMap<KebabCasedKey, Service>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl DerefMut for Domains {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
pub struct Domains {
|
||||
exact: HashMap<Domain<'static>, Service>,
|
||||
catchall: Option<Service>,
|
||||
}
|
||||
|
||||
impl Domains {
|
||||
pub fn get(&self, domain: &Domain<'_>) -> Option<&Service> {
|
||||
self.exact.get(domain.as_ref()).or(self.catchall.as_ref())
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, other: Self) {
|
||||
self.exact.extend(other.exact);
|
||||
if other.catchall.is_some() {
|
||||
self.catchall = other.catchall;
|
||||
}
|
||||
}
|
||||
|
||||
fn init(&mut self, scheme: &Scheme) {
|
||||
for (domain, service) in &mut self.0 {
|
||||
for (domain, service) in &mut self.exact {
|
||||
let kind = service.kind();
|
||||
let auth = Arc::get_mut(service.auth_mut()).expect("unique auth arc");
|
||||
|
||||
auth.kind = kind;
|
||||
auth.scheme = scheme.clone();
|
||||
auth.domain = domain.clone().into();
|
||||
auth.domain = domain.clone();
|
||||
}
|
||||
|
||||
if let Some(service) = &mut self.catchall {
|
||||
let kind = service.kind();
|
||||
let auth = Arc::get_mut(service.auth_mut()).expect("unique auth arc");
|
||||
|
||||
auth.kind = kind;
|
||||
auth.scheme = scheme.clone();
|
||||
auth.domain = Domain::CATCHALL;
|
||||
}
|
||||
}
|
||||
|
||||
fn from_map<E>(map: HashMap<Domain<'static>, Service>) -> Result<Self, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
let mut domains = Self::default();
|
||||
for (domain, service) in map {
|
||||
if domain.is_catchall() {
|
||||
domains.catchall = Some(service);
|
||||
continue;
|
||||
}
|
||||
|
||||
if service.kind() == AuthKind::Hub {
|
||||
return Err(E::custom("Hub services require a `*` catch-all domain"));
|
||||
}
|
||||
domains.exact.insert(domain, service);
|
||||
}
|
||||
Ok(domains)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,22 +71,24 @@ impl<'de> DeserializeSeed<'de> for DomainSeed<'_> {
|
|||
type Value = Domains;
|
||||
|
||||
fn deserialize<D: Deserializer<'de>>(self, deserializer: D) -> Result<Self::Value, D::Error> {
|
||||
let mut domains = Domains(match self.0 {
|
||||
let mut domains = match self.0 {
|
||||
Scheme::Regular | Scheme::Search => {
|
||||
return Err(D::Error::custom("scheme cannot be configured"));
|
||||
}
|
||||
Scheme::Sftp => {
|
||||
let map = HashMap::<KebabCasedKey, ServiceSftp>::deserialize(deserializer)?;
|
||||
map.into_iter().map(|(domain, service)| (domain, Service::Sftp(service))).collect()
|
||||
let map = HashMap::<Domain<'static>, ServiceSftp>::deserialize(deserializer)?;
|
||||
Domains::from_map(
|
||||
map.into_iter().map(|(domain, service)| (domain, Service::Sftp(service))).collect(),
|
||||
)?
|
||||
}
|
||||
Scheme::Custom(_) => {
|
||||
let map = HashMap::<KebabCasedKey, Service>::deserialize(deserializer)?;
|
||||
let map = HashMap::<Domain<'static>, Service>::deserialize(deserializer)?;
|
||||
if map.values().any(|service| matches!(service, Service::Sftp(_))) {
|
||||
return Err(D::Error::custom("SFTP services must use the `sftp` scheme"));
|
||||
}
|
||||
map
|
||||
Domains::from_map(map)?
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
domains.init(self.0);
|
||||
Ok(domains)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use super::{ServiceLua, ServiceSftp};
|
|||
pub enum Service {
|
||||
Sftp(ServiceSftp),
|
||||
Mount(ServiceLua),
|
||||
Hub(ServiceLua),
|
||||
Scope(ServiceLua),
|
||||
}
|
||||
|
||||
|
|
@ -19,7 +20,7 @@ impl TryFrom<&'static Service> for &'static ServiceSftp {
|
|||
fn try_from(value: &'static Service) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Service::Sftp(p) => Ok(p),
|
||||
Service::Mount(_) | Service::Scope(_) => {
|
||||
Service::Mount(_) | Service::Hub(_) | Service::Scope(_) => {
|
||||
Err("expected an SFTP service, got a custom VFS service")
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +33,7 @@ impl TryFrom<&'static Service> for &'static ServiceLua {
|
|||
fn try_from(value: &'static Service) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Service::Sftp(_) => Err("expected a custom VFS service, got an SFTP service"),
|
||||
Service::Mount(lua) | Service::Scope(lua) => Ok(lua),
|
||||
Service::Mount(lua) | Service::Hub(lua) | Service::Scope(lua) => Ok(lua),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,6 +43,7 @@ impl Service {
|
|||
match self {
|
||||
Self::Sftp(_) => AuthKind::Sftp,
|
||||
Self::Mount(_) => AuthKind::Mount,
|
||||
Self::Hub(_) => AuthKind::Hub,
|
||||
Self::Scope(_) => AuthKind::Scope,
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +52,7 @@ impl Service {
|
|||
match self {
|
||||
Self::Sftp(sftp) => &sftp.auth,
|
||||
Self::Mount(lua) => &lua.auth,
|
||||
Self::Hub(lua) => &lua.auth,
|
||||
Self::Scope(lua) => &lua.auth,
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +61,7 @@ impl Service {
|
|||
match self {
|
||||
Self::Sftp(sftp) => &mut sftp.auth,
|
||||
Self::Mount(lua) => &mut lua.auth,
|
||||
Self::Hub(lua) => &mut lua.auth,
|
||||
Self::Scope(lua) => &mut lua.auth,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ impl Vfs {
|
|||
where
|
||||
P: TryFrom<&'static Service, Error = &'static str>,
|
||||
{
|
||||
let Some(value) = VFS.authorities.get(&auth.scheme, &auth.domain) else {
|
||||
let Some(value) = VFS.authorities.service(&auth.scheme, &auth.domain) else {
|
||||
return Err(io::Error::other(format!("No such VFS service: {auth}")));
|
||||
};
|
||||
|
||||
|
|
@ -47,8 +47,6 @@ impl DeserializeOverWith for Vfs {
|
|||
// --- Inject
|
||||
inventory::submit! {
|
||||
AuthInventory {
|
||||
get: |scheme, domain| {
|
||||
VFS.authorities.get(scheme, domain).map(|service| service.auth().clone())
|
||||
},
|
||||
get: |scheme, domain| VFS.authorities.auth(scheme, domain),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ impl Yanked {
|
|||
}
|
||||
|
||||
pub fn apply_op(&mut self, op: &FilesOp) {
|
||||
let (removal, addition) = op.diff_recoverable(|u| self.contains(u));
|
||||
let (removal, addition) = op.diff_recoverable(self.urls());
|
||||
if !removal.is_empty() {
|
||||
let old = self.files.len();
|
||||
self.files.retain(|f| !removal.iter().any(|u| f.url.covariant(u)));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use hashbrown::HashMap;
|
||||
use yazi_fs::{Entries, Filter, FilterCase};
|
||||
use yazi_shared::{path::{AsPath, PathBufDyn}, url::UrlBuf};
|
||||
use yazi_shared::{path::{DynPath, PathBufDyn}, url::UrlBuf};
|
||||
|
||||
use crate::tab::Folder;
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ impl Finder {
|
|||
continue;
|
||||
}
|
||||
|
||||
self.matched.insert(file.urn().into(), i);
|
||||
self.matched.insert(file.entry_key().into(), i);
|
||||
if self.matched.len() > 99 {
|
||||
break;
|
||||
}
|
||||
|
|
@ -78,9 +78,9 @@ impl Finder {
|
|||
impl Finder {
|
||||
pub fn matched_idx<T>(&self, folder: &Folder, urn: T) -> Option<u8>
|
||||
where
|
||||
T: AsPath,
|
||||
T: DynPath,
|
||||
{
|
||||
if self.lock == *folder { self.matched.get(&urn.as_path()).copied() } else { None }
|
||||
if self.lock == *folder { self.matched.get(&urn.dyn_path()).copied() } else { None }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use yazi_config::{LAYOUT, YAZI};
|
|||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{Entries, FilesOp, FolderStage, cha::Cha, file::File};
|
||||
use yazi_macro::err;
|
||||
use yazi_shared::{id::Id, path::{AsPath, PathBufDyn, PathDyn}, url::UrlBuf};
|
||||
use yazi_shared::{id::Id, path::{DynPath, PathBufDyn, PathDyn}, url::UrlBuf};
|
||||
use yazi_widgets::{Scrollable, Step};
|
||||
|
||||
use crate::MgrProxy;
|
||||
|
|
@ -104,24 +104,26 @@ impl Folder {
|
|||
b
|
||||
}
|
||||
|
||||
pub fn hover(&mut self, urn: PathDyn) -> bool {
|
||||
if self.hovered().map(|h| h.urn()) == Some(urn) {
|
||||
pub fn hover(&mut self, key: PathDyn) -> bool {
|
||||
if key.is_empty() {
|
||||
return self.arrow(0);
|
||||
} else if self.hovered().map(|h| h.entry_key()) == Some(key) {
|
||||
return self.arrow(0);
|
||||
}
|
||||
|
||||
let new = self.entries.position(urn).unwrap_or(self.cursor) as isize;
|
||||
let new = self.entries.position(key).unwrap_or(self.cursor) as isize;
|
||||
let b = self.arrow(new - self.cursor as isize);
|
||||
|
||||
self.retrace();
|
||||
b
|
||||
}
|
||||
|
||||
pub fn repos(&mut self, urn: Option<PathDyn>) -> bool {
|
||||
if let Some(u) = urn {
|
||||
self.hover(u)
|
||||
} else if let Some(u) = self.trace.take() {
|
||||
let b = self.hover(u.as_path());
|
||||
self.trace = Some(u);
|
||||
pub fn repos(&mut self, key: Option<PathDyn>) -> bool {
|
||||
if let Some(k) = key {
|
||||
self.hover(k)
|
||||
} else if let Some(k) = self.trace.take() {
|
||||
let b = self.hover(k.dyn_path());
|
||||
self.trace = Some(k);
|
||||
b
|
||||
} else {
|
||||
self.arrow(0)
|
||||
|
|
@ -129,7 +131,7 @@ impl Folder {
|
|||
}
|
||||
|
||||
pub fn retrace(&mut self) {
|
||||
self.trace = self.hovered().map(|h| h.urn().into()).or(self.trace.take());
|
||||
self.trace = self.hovered().map(|h| h.entry_key().into()).or(self.trace.take());
|
||||
}
|
||||
|
||||
pub fn sync_page(&mut self, force: bool) {
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ impl Selected {
|
|||
}
|
||||
|
||||
pub fn apply_op(&mut self, op: &FilesOp) {
|
||||
let (removal, addition) = op.diff_recoverable(|u| self.contains(u));
|
||||
let (removal, addition) = op.diff_recoverable(self.urls());
|
||||
if !removal.is_empty() {
|
||||
self.remove_many(&removal);
|
||||
}
|
||||
|
|
@ -190,6 +190,12 @@ mod tests {
|
|||
assert!(!s.remove(Path::new("/a/b")));
|
||||
assert!(s.inner.is_empty());
|
||||
assert!(s.parents.is_empty());
|
||||
|
||||
// Relative path with an empty parent (root)
|
||||
assert!(s.add(f("a")));
|
||||
assert!(s.remove(Path::new("a")));
|
||||
assert!(s.inner.is_empty());
|
||||
assert!(s.parents.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ impl Tab {
|
|||
pub fn hovered_mut(&mut self) -> Option<&mut File> { self.current.hovered_mut() }
|
||||
|
||||
pub fn hovered_rect(&self) -> Option<Rect> {
|
||||
let y = self.current.entries.position(self.hovered()?.urn())? - self.current.offset;
|
||||
let y = self.current.entries.position(self.hovered()?.entry_key())? - self.current.offset;
|
||||
|
||||
let mut rect = LAYOUT.get().current;
|
||||
rect.y = rect.y.saturating_sub(1) + y as u16;
|
||||
|
|
|
|||
|
|
@ -54,7 +54,11 @@ impl Tasks {
|
|||
targets
|
||||
.iter()
|
||||
.filter(|f| {
|
||||
f.is_dir() && !targets.sizes.contains_key(&f.urn()) && !loading.contains(&f.url)
|
||||
let key = f.entry_key();
|
||||
f.is_dir()
|
||||
&& !key.is_empty()
|
||||
&& !targets.sizes.contains_key(&key)
|
||||
&& !loading.contains(&f.url)
|
||||
})
|
||||
.map(|f| &f.url)
|
||||
.collect()
|
||||
|
|
|
|||
|
|
@ -51,6 +51,6 @@ fn try_absolute_impl<'a>(url: UrlCow<'a>) -> Option<UrlCow<'a>> {
|
|||
Some(match url.as_url() {
|
||||
Url::Regular(_) => UrlBuf::Regular(loc).into(),
|
||||
Url::Search { auth, .. } => UrlBuf::Search { loc, auth: auth.clone() }.into(),
|
||||
Url::Mount { .. } | Url::Scope { .. } | Url::Sftp { .. } => None?,
|
||||
Url::Mount { .. } | Url::Hub { .. } | Url::Scope { .. } | Url::Sftp { .. } => None?,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{fs::FileTimes, io, path::Path, sync::Arc};
|
||||
|
||||
use tokio::sync::mpsc;
|
||||
use yazi_shared::{auth::AuthKind, path::{AsPath, PathBufDyn}, strand::AsStrand, url::{Url, UrlBuf, UrlCow}};
|
||||
use yazi_shared::{auth::AuthKind, path::{DynPath, PathBufDyn}, strand::AsStrand, url::{Url, UrlBuf, UrlCow}};
|
||||
|
||||
use crate::{cha::{Cha, ChaMode}, engine::{Attrs, Capabilities, Engine}};
|
||||
|
||||
|
|
@ -45,19 +45,19 @@ impl<'a> Engine for Local<'a> {
|
|||
#[inline]
|
||||
async fn copy<P>(&self, to: P, attrs: Attrs) -> io::Result<u64>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
{
|
||||
let to = to.as_path().to_os_owned()?;
|
||||
let to = to.dyn_path().to_os_owned()?;
|
||||
let from = self.path.to_owned();
|
||||
super::copy_impl(from, to, attrs).await
|
||||
}
|
||||
|
||||
fn copy_progressive<P, A>(&self, to: P, attrs: A) -> io::Result<mpsc::Receiver<io::Result<u64>>>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
A: Into<Attrs>,
|
||||
{
|
||||
let to = to.as_path().to_os_owned()?;
|
||||
let to = to.dyn_path().to_os_owned()?;
|
||||
let from = self.path.to_owned();
|
||||
Ok(super::copy_progressive_impl(from, to, attrs.into()))
|
||||
}
|
||||
|
|
@ -71,9 +71,9 @@ impl<'a> Engine for Local<'a> {
|
|||
#[inline]
|
||||
async fn hard_link<P>(&self, to: P) -> io::Result<()>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
{
|
||||
let to = to.as_path().as_os()?;
|
||||
let to = to.dyn_path().as_os()?;
|
||||
|
||||
tokio::fs::hard_link(self.path, to).await
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ impl<'a> Engine for Local<'a> {
|
|||
async fn new<'b>(url: Url<'b>) -> io::Result<Self::Me<'b>> {
|
||||
match url {
|
||||
Url::Regular(loc) | Url::Search { loc, .. } => Ok(Self::Me { url, path: loc.as_inner() }),
|
||||
Url::Mount { .. } | Url::Scope { .. } | Url::Sftp { .. } => {
|
||||
Url::Mount { .. } | Url::Hub { .. } | Url::Scope { .. } | Url::Sftp { .. } => {
|
||||
Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Not a local URL: {url:?}")))
|
||||
}
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ impl<'a> Engine for Local<'a> {
|
|||
reader: tokio::fs::read_dir(self.path).await?,
|
||||
dir: Arc::new(self.url.to_owned()),
|
||||
},
|
||||
AuthKind::Mount | AuthKind::Scope | AuthKind::Sftp => Err(io::Error::new(
|
||||
AuthKind::Mount | AuthKind::Hub | AuthKind::Scope | AuthKind::Sftp => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("Not a local URL: {:?}", self.url),
|
||||
))?,
|
||||
|
|
@ -125,9 +125,9 @@ impl<'a> Engine for Local<'a> {
|
|||
#[inline]
|
||||
async fn rename<P>(&self, to: P) -> io::Result<()>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
{
|
||||
let to = to.as_path().as_os()?;
|
||||
let to = to.dyn_path().as_os()?;
|
||||
|
||||
tokio::fs::rename(self.path, to).await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::io;
|
|||
|
||||
use tokio::{io::{AsyncRead, AsyncSeek, AsyncWrite, AsyncWriteExt}, sync::mpsc};
|
||||
use yazi_macro::ok_or_not_found;
|
||||
use yazi_shared::{path::{AsPath, PathBufDyn}, strand::{AsStrand, StrandCow}, url::{AsUrl, Url, UrlBuf}};
|
||||
use yazi_shared::{path::{DynPath, PathBufDyn}, strand::{AsStrand, StrandCow}, url::{AsUrl, Url, UrlBuf}};
|
||||
|
||||
use crate::{cha::{Cha, ChaType}, engine::{Attrs, Capabilities}};
|
||||
|
||||
|
|
@ -23,11 +23,11 @@ pub trait Engine: Sized {
|
|||
|
||||
fn copy<P>(&self, to: P, attrs: Attrs) -> impl Future<Output = io::Result<u64>>
|
||||
where
|
||||
P: AsPath;
|
||||
P: DynPath;
|
||||
|
||||
fn copy_progressive<P, A>(&self, to: P, attrs: A) -> io::Result<mpsc::Receiver<io::Result<u64>>>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
A: Into<Attrs>;
|
||||
|
||||
fn create(&self) -> impl Future<Output = io::Result<Self::File>> {
|
||||
|
|
@ -80,7 +80,7 @@ pub trait Engine: Sized {
|
|||
|
||||
fn hard_link<P>(&self, to: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsPath;
|
||||
P: DynPath;
|
||||
|
||||
fn metadata(&self) -> impl Future<Output = io::Result<Cha>>;
|
||||
|
||||
|
|
@ -157,7 +157,7 @@ pub trait Engine: Sized {
|
|||
|
||||
fn rename<P>(&self, to: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsPath;
|
||||
P: DynPath;
|
||||
|
||||
fn set_attrs(&self, attrs: Attrs) -> impl Future<Output = io::Result<()>>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{mem, ops::{Deref, DerefMut, Not}};
|
||||
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use yazi_shared::{id::Id, path::{PathBufDyn, PathDyn}};
|
||||
use yazi_shared::{id::Id, path::{PathBufDyn, PathDyn, PathLike}};
|
||||
|
||||
use super::{FilesSorter, Filter};
|
||||
use crate::{FILES_TICKET, SortBy, file::File};
|
||||
|
|
@ -69,19 +69,19 @@ impl Entries {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update_size(&mut self, mut sizes: HashMap<PathBufDyn, u64>) {
|
||||
if sizes.len() <= 50 {
|
||||
sizes.retain(|k, v| self.sizes.get(k) != Some(v));
|
||||
pub fn update_size(&mut self, sizes: HashMap<PathBufDyn, u64>) {
|
||||
self.sizes.reserve(if self.sizes.is_empty() { sizes.len() } else { sizes.len().div_ceil(2) });
|
||||
|
||||
let mut changed = false;
|
||||
for (key, size) in sizes {
|
||||
if !key.is_empty() {
|
||||
changed |= self.sizes.insert(key, size) != Some(size);
|
||||
}
|
||||
}
|
||||
|
||||
if sizes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.sorter.by == SortBy::Size {
|
||||
if changed && self.sorter.by == SortBy::Size {
|
||||
self.revision += 1;
|
||||
}
|
||||
self.sizes.extend(sizes);
|
||||
}
|
||||
|
||||
pub fn update_ioerr(&mut self) {
|
||||
|
|
@ -97,9 +97,10 @@ impl Entries {
|
|||
|
||||
macro_rules! go {
|
||||
($dist:expr, $src:expr, $inc:literal) => {
|
||||
let mut todo: HashMap<_, _> = $src.into_iter().map(|f| (f.urn().to_owned(), f)).collect();
|
||||
let mut todo: HashMap<_, _> =
|
||||
$src.into_iter().map(|f| (f.entry_key().to_owned(), f)).collect();
|
||||
for f in &$dist {
|
||||
if todo.remove(&f.urn()).is_some() && todo.is_empty() {
|
||||
if todo.remove(&f.entry_key()).is_some() && todo.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -119,48 +120,14 @@ impl Entries {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn update_deleting(&mut self, urns: HashSet<PathBufDyn>) -> Vec<usize> {
|
||||
use yazi_shared::path::PathLike;
|
||||
if urns.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
pub fn update_deleting(&mut self, mut keys: HashSet<PathBufDyn>) -> Vec<usize> {
|
||||
keys.retain(|k| !k.is_empty());
|
||||
let mut deleted = Vec::with_capacity(keys.len());
|
||||
|
||||
let (mut hidden, mut items) = if let Some(filter) = &self.filter {
|
||||
urns.into_iter().partition(|u| (!self.show_hidden && u.is_hidden()) || !filter.matches(u))
|
||||
} else if self.show_hidden {
|
||||
(HashSet::new(), urns)
|
||||
} else {
|
||||
urns.into_iter().partition(|u| u.is_hidden())
|
||||
};
|
||||
|
||||
let mut deleted = Vec::with_capacity(items.len());
|
||||
if !items.is_empty() {
|
||||
if !keys.is_empty() {
|
||||
let mut i = 0;
|
||||
self.items.retain(|f| {
|
||||
let b = items.remove(&f.urn());
|
||||
if b {
|
||||
deleted.push(i);
|
||||
}
|
||||
i += 1;
|
||||
!b
|
||||
});
|
||||
}
|
||||
if !hidden.is_empty() {
|
||||
self.hidden.retain(|f| !hidden.remove(&f.urn()));
|
||||
}
|
||||
|
||||
self.revision += deleted.is_empty().not() as u64;
|
||||
deleted
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn update_deleting(&mut self, mut urns: HashSet<PathBufDyn>) -> Vec<usize> {
|
||||
let mut deleted = Vec::with_capacity(urns.len());
|
||||
if !urns.is_empty() {
|
||||
let mut i = 0;
|
||||
self.items.retain(|f| {
|
||||
let b = urns.remove(&f.urn());
|
||||
let b = keys.remove(&f.entry_key());
|
||||
if b {
|
||||
deleted.push(i)
|
||||
}
|
||||
|
|
@ -168,8 +135,9 @@ impl Entries {
|
|||
!b
|
||||
});
|
||||
}
|
||||
if !urns.is_empty() {
|
||||
self.hidden.retain(|f| !urns.remove(&f.urn()));
|
||||
|
||||
if !keys.is_empty() {
|
||||
self.hidden.retain(|f| !keys.remove(&f.entry_key()));
|
||||
}
|
||||
|
||||
self.revision += deleted.is_empty().not() as u64;
|
||||
|
|
@ -178,8 +146,9 @@ impl Entries {
|
|||
|
||||
pub fn update_updating(
|
||||
&mut self,
|
||||
files: HashMap<PathBufDyn, File>,
|
||||
mut files: HashMap<PathBufDyn, File>,
|
||||
) -> (HashMap<PathBufDyn, File>, HashMap<PathBufDyn, File>) {
|
||||
files.retain(|k, f| !k.is_empty() && !f.entry_key().is_empty());
|
||||
if files.is_empty() {
|
||||
return Default::default();
|
||||
}
|
||||
|
|
@ -188,9 +157,9 @@ impl Entries {
|
|||
($dist:expr, $src:expr, $inc:literal) => {
|
||||
let mut b = true;
|
||||
for i in 0..$dist.len() {
|
||||
if let Some(f) = $src.remove(&$dist[i].urn()) {
|
||||
if let Some(f) = $src.remove(&$dist[i].entry_key()) {
|
||||
b = b && $dist[i].cha.hits(f.cha);
|
||||
b = b && $dist[i].urn() == f.urn();
|
||||
b = b && $dist[i].entry_key() == f.entry_key();
|
||||
|
||||
$dist[i] = f;
|
||||
if $src.is_empty() {
|
||||
|
|
@ -221,13 +190,18 @@ impl Entries {
|
|||
(hidden, items)
|
||||
}
|
||||
|
||||
pub fn update_upserting(&mut self, files: HashMap<PathBufDyn, File>) {
|
||||
pub fn update_upserting(&mut self, mut files: HashMap<PathBufDyn, File>) {
|
||||
files.retain(|k, f| !k.is_empty() && !f.entry_key().is_empty());
|
||||
if files.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.update_deleting(
|
||||
files.iter().filter(|&(u, f)| u != f.urn()).map(|(_, f)| f.urn().into()).collect(),
|
||||
files
|
||||
.iter()
|
||||
.filter(|&(k, f)| k != f.entry_key())
|
||||
.map(|(_, f)| f.entry_key().into())
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let (hidden, items) = self.update_updating(files);
|
||||
|
|
@ -255,14 +229,13 @@ impl Entries {
|
|||
}
|
||||
|
||||
fn split_files(&self, files: impl IntoIterator<Item = File>) -> (Vec<File>, Vec<File>) {
|
||||
let files = files.into_iter().filter(|f| !f.entry_key().is_empty());
|
||||
if let Some(filter) = &self.filter {
|
||||
files
|
||||
.into_iter()
|
||||
.partition(|f| (f.is_hidden() && !self.show_hidden) || !filter.matches(f.urn()))
|
||||
files.partition(|f| (f.is_hidden() && !self.show_hidden) || !filter.matches(f.urn()))
|
||||
} else if self.show_hidden {
|
||||
(vec![], files.into_iter().collect())
|
||||
(vec![], files.collect())
|
||||
} else {
|
||||
files.into_iter().partition(|f| f.is_hidden())
|
||||
files.partition(|f| f.is_hidden())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -270,7 +243,9 @@ impl Entries {
|
|||
impl Entries {
|
||||
// --- Items
|
||||
#[inline]
|
||||
pub fn position(&self, urn: PathDyn) -> Option<usize> { self.iter().position(|f| urn == f.urn()) }
|
||||
pub fn position(&self, key: PathDyn) -> Option<usize> {
|
||||
if key.is_empty() { None } else { self.iter().position(|f| f.entry_key() == key) }
|
||||
}
|
||||
|
||||
// --- Ticket
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -63,6 +63,9 @@ impl File {
|
|||
#[inline]
|
||||
pub fn urn(&self) -> PathDyn<'_> { self.url.urn() }
|
||||
|
||||
#[inline]
|
||||
pub fn entry_key(&self) -> PathDyn<'_> { self.url.entry_key() }
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<Strand<'_>> { self.url.name() }
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,8 @@ fn test_max_common_root() {
|
|||
assert_eq!(max_common_root(&[]), 0);
|
||||
assert(&[""], "");
|
||||
assert(&["a"], "");
|
||||
assert(&["search://kw:1:1/a", "search://kw:1:1/b"], "search://kw/");
|
||||
assert(&["test-hub://a1/@root/a", "test-hub://b1/@root/b"], "test-hub://root/@/");
|
||||
|
||||
assert(&["/a"], "/");
|
||||
assert(&["/a/b"], "/a");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{iter, path::Path};
|
|||
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use yazi_macro::{impl_data_any, relay};
|
||||
use yazi_shared::{id::{Id, Ids}, path::PathBufDyn, url::{UrlBuf, UrlLike, UrlMapExt}};
|
||||
use yazi_shared::{id::{Id, Ids}, path::{PathBufDyn, PathLike}, url::{UrlBuf, UrlLike, UrlMapExt}};
|
||||
|
||||
use crate::{cha::Cha, file::File};
|
||||
|
||||
|
|
@ -42,16 +42,17 @@ impl FilesOp {
|
|||
|
||||
pub fn files(&self) -> Box<dyn Iterator<Item = &File> + '_> {
|
||||
match self {
|
||||
Self::Full(_, files, _) => Box::new(files.iter()),
|
||||
Self::Part(_, files, _) => Box::new(files.iter()),
|
||||
Self::Full(_, files, _) | Self::Part(_, files, _) | Self::Creating(_, files) => {
|
||||
Box::new(files.iter().filter(|f| !f.entry_key().is_empty()))
|
||||
}
|
||||
Self::Done(..) => Box::new(iter::empty()),
|
||||
Self::Size(..) => Box::new(iter::empty()),
|
||||
Self::IOErr(..) => Box::new(iter::empty()),
|
||||
|
||||
Self::Creating(_, files) => Box::new(files.iter()),
|
||||
Self::Deleting(..) => Box::new(iter::empty()),
|
||||
Self::Updating(_, map) => Box::new(map.values()),
|
||||
Self::Upserting(_, map) => Box::new(map.values()),
|
||||
Self::Updating(_, map) | Self::Upserting(_, map) => {
|
||||
Box::new(map.values().filter(|f| !f.entry_key().is_empty()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +69,7 @@ impl FilesOp {
|
|||
pub fn create(files: Vec<File>) {
|
||||
let mut parents: HashMap<UrlBuf, Vec<_>> = Default::default();
|
||||
for file in files {
|
||||
if let Some(p) = file.url.parent() {
|
||||
if let Some((p, _)) = file.url.pair2() {
|
||||
parents.get_or_insert_default(p).push(file);
|
||||
}
|
||||
}
|
||||
|
|
@ -80,18 +81,18 @@ impl FilesOp {
|
|||
pub fn rename(map: HashMap<UrlBuf, File>) {
|
||||
let mut parents: HashMap<UrlBuf, (HashSet<_>, HashMap<_, _>)> = Default::default();
|
||||
for (o, n) in map {
|
||||
let Some(o_p) = o.parent() else { continue };
|
||||
let Some(n_p) = n.url.parent() else { continue };
|
||||
let Some((o_p, o_k)) = o.pair2() else { continue };
|
||||
let Some((n_p, n_k)) = n.url.pair2() else { continue };
|
||||
if o_p == n_p {
|
||||
parents.get_or_insert_default(o_p).1.insert(o.urn().into(), n);
|
||||
parents.get_or_insert_default(o_p).1.insert(o_k.into(), n);
|
||||
} else {
|
||||
parents.get_or_insert_default(o_p).0.insert(o.urn().into());
|
||||
parents.get_or_insert_default(n_p).1.insert(n.urn().into(), n);
|
||||
parents.get_or_insert_default(o_p).0.insert(o_k.into());
|
||||
parents.get_or_insert_default(n_p).1.insert(n_k.into(), n);
|
||||
}
|
||||
}
|
||||
for (p, (o, n)) in parents {
|
||||
match (o.is_empty(), n.is_empty()) {
|
||||
(true, true) => unreachable!(),
|
||||
(true, true) => {}
|
||||
(true, false) => Self::Upserting(p, n).emit(),
|
||||
(false, true) => Self::Deleting(p, o).emit(),
|
||||
(false, false) => {
|
||||
|
|
@ -106,14 +107,20 @@ impl FilesOp {
|
|||
let mut parents: HashMap<_, (HashMap<_, _>, HashSet<_>)> = Default::default();
|
||||
for op in ops {
|
||||
match op {
|
||||
Self::Upserting(p, map) => parents.entry(p).or_default().0.extend(map),
|
||||
Self::Deleting(p, urns) => parents.entry(p).or_default().1.extend(urns),
|
||||
Self::Upserting(p, map) => parents
|
||||
.entry(p)
|
||||
.or_default()
|
||||
.0
|
||||
.extend(map.into_iter().filter(|(k, f)| !k.is_empty() && !f.entry_key().is_empty())),
|
||||
Self::Deleting(p, keys) => {
|
||||
parents.entry(p).or_default().1.extend(keys.into_iter().filter(|k| !k.is_empty()))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
for (p, (u, d)) in parents {
|
||||
match (u.is_empty(), d.is_empty()) {
|
||||
(true, true) => unreachable!(),
|
||||
(true, true) => {}
|
||||
(true, false) => Self::Deleting(p, d).emit(),
|
||||
(false, true) => Self::Upserting(p, u).emit(),
|
||||
(false, false) => {
|
||||
|
|
@ -147,17 +154,25 @@ impl FilesOp {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn diff_recoverable(&self, contains: impl Fn(&UrlBuf) -> bool) -> (Vec<UrlBuf>, Vec<File>) {
|
||||
pub fn diff_recoverable<'a, I>(&self, urls: I) -> (Vec<UrlBuf>, Vec<File>)
|
||||
where
|
||||
I: IntoIterator<Item = &'a UrlBuf>,
|
||||
{
|
||||
let cwd = self.cwd();
|
||||
let it = urls
|
||||
.into_iter()
|
||||
.filter(|u| u.parent().is_some_and(|p| p == *cwd))
|
||||
.filter(|u| !u.entry_key().is_empty());
|
||||
|
||||
match self {
|
||||
Self::Deleting(cwd, urns) => {
|
||||
(urns.iter().filter_map(|u| cwd.try_join(u).ok()).collect(), vec![])
|
||||
Self::Deleting(_, keys) => {
|
||||
(it.filter(|u| keys.contains(&u.entry_key())).cloned().collect(), vec![])
|
||||
}
|
||||
Self::Updating(cwd, urns) | Self::Upserting(cwd, urns) => urns
|
||||
.iter()
|
||||
.filter(|&(u, f)| u != f.urn())
|
||||
.filter_map(|(u, f)| cwd.try_join(u).ok().map(|u| (u, f)))
|
||||
.filter(|(u, _)| contains(u))
|
||||
.map(|(u, f)| (u, f.clone()))
|
||||
Self::Updating(_, files) | Self::Upserting(_, files) => it
|
||||
.filter_map(|u| files.get(&u.entry_key()).map(|f| (u, f)))
|
||||
.filter(|(_, f)| !f.entry_key().is_empty())
|
||||
.filter(|&(u, f)| u != f.url)
|
||||
.map(|(u, f)| (u.clone(), f.clone()))
|
||||
.unzip(),
|
||||
_ => (vec![], vec![]),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,44 @@
|
|||
use yazi_shared::{path::{PathBufDyn, PathDyn}, url::{UrlBuf, UrlCow, UrlLike}};
|
||||
use std::{iter, sync::Arc};
|
||||
|
||||
use yazi_shared::{auth::{Auth, AuthKind}, path::{DynPath, PathBufDyn, PathDyn}, url::{UrlBuf, UrlCow, UrlLike}};
|
||||
|
||||
pub fn clean_url<'a>(url: impl Into<UrlCow<'a>>) -> UrlBuf {
|
||||
let cow: UrlCow = url.into();
|
||||
let (path, uri, urn) = clean_path_impl(
|
||||
cow.loc(),
|
||||
cow.base().components().count() - 1,
|
||||
cow.trail().components().count() - 1,
|
||||
|
||||
let depth = cow.loc().components().count();
|
||||
let base = depth - cow.uri().components().count();
|
||||
let trail = depth - cow.urn().components().count();
|
||||
|
||||
let (mut spec, path) = cow.into_pair();
|
||||
let (path, uri, urn, auth) = clean_path_impl(
|
||||
path.dyn_path(),
|
||||
base,
|
||||
trail,
|
||||
(spec.kind == AuthKind::Hub).then(|| spec.auth.clone()),
|
||||
);
|
||||
|
||||
let spec = cow.into_spec().with_ports(uri, urn);
|
||||
(spec, path).try_into().expect("UrlBuf from cleaned path")
|
||||
spec.auth = auth.unwrap_or(spec.auth);
|
||||
(spec.with_ports(uri, urn), path).try_into().expect("UrlBuf from cleaned path")
|
||||
}
|
||||
|
||||
fn clean_path_impl(path: PathDyn, base: usize, trail: usize) -> (PathBufDyn, usize, usize) {
|
||||
fn clean_path_impl(
|
||||
path: PathDyn,
|
||||
base: usize,
|
||||
trail: usize,
|
||||
auth: Option<Arc<Auth>>,
|
||||
) -> (PathBufDyn, usize, usize, Option<Arc<Auth>>) {
|
||||
use yazi_shared::path::Component::*;
|
||||
|
||||
let mut auths: Vec<_> = iter::successors(auth, |auth| auth.parent.clone()).collect();
|
||||
let root = auths.pop();
|
||||
|
||||
let mut out = vec![];
|
||||
let mut uri_count = 0;
|
||||
let mut urn_count = 0;
|
||||
|
||||
macro_rules! push {
|
||||
($i:ident, $c:ident) => {{
|
||||
out.push(($i, $c));
|
||||
($i:ident, $c:ident, $auth:ident) => {{
|
||||
out.push(($i, $c, $auth));
|
||||
if $i >= base {
|
||||
uri_count += 1;
|
||||
}
|
||||
|
|
@ -33,7 +50,7 @@ fn clean_path_impl(path: PathDyn, base: usize, trail: usize) -> (PathBufDyn, usi
|
|||
|
||||
macro_rules! pop {
|
||||
() => {{
|
||||
if let Some((i, _)) = out.pop() {
|
||||
if let Some((i, ..)) = out.pop() {
|
||||
if i >= base {
|
||||
uri_count -= 1;
|
||||
}
|
||||
|
|
@ -45,14 +62,15 @@ fn clean_path_impl(path: PathDyn, base: usize, trail: usize) -> (PathBufDyn, usi
|
|||
}
|
||||
|
||||
for (i, c) in path.components().enumerate() {
|
||||
let auth = (root.is_some() && c.has_auth()).then(|| auths.pop().unwrap());
|
||||
match c {
|
||||
CurDir => {}
|
||||
ParentDir => match out.last().map(|(_, c)| c) {
|
||||
ParentDir => match out.last().map(|(_, c, _)| c) {
|
||||
Some(RootDir) => {}
|
||||
Some(Normal(_)) => pop!(),
|
||||
None | Some(CurDir) | Some(ParentDir) | Some(Prefix(_)) => push!(i, c),
|
||||
None | Some(CurDir) | Some(ParentDir) | Some(Prefix(_)) => push!(i, c, auth),
|
||||
},
|
||||
c => push!(i, c),
|
||||
c => push!(i, c, auth),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,11 +78,18 @@ fn clean_path_impl(path: PathDyn, base: usize, trail: usize) -> (PathBufDyn, usi
|
|||
let path = if out.is_empty() {
|
||||
PathBufDyn::with_str(kind, ".")
|
||||
} else {
|
||||
PathBufDyn::from_components(kind, out.into_iter().map(|(_, c)| c))
|
||||
PathBufDyn::from_components(kind, out.iter().map(|(_, c, _)| *c))
|
||||
.expect("components with same kind")
|
||||
};
|
||||
|
||||
(path, uri_count, urn_count)
|
||||
let auth = root.map(|mut parent| {
|
||||
for mut auth in out.into_iter().filter_map(|(_, _, auth)| auth) {
|
||||
Arc::make_mut(&mut auth).parent = Some(parent);
|
||||
parent = auth;
|
||||
}
|
||||
parent
|
||||
});
|
||||
(path, uri_count, urn_count, auth)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -106,4 +131,24 @@ mod tests {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clean_hub() -> anyhow::Result<()> {
|
||||
yazi_shared::init_tests();
|
||||
|
||||
for (input, expected) in [
|
||||
("test-hub://dotdot/@foo,root/foo/..", "test-hub://root/@/."),
|
||||
("test-hub://bar/@dotdot,foo,root/foo/../bar", "test-hub://bar/@root/bar"),
|
||||
("test-hub://dotdot/@root//..", "test-hub://root/@//"),
|
||||
("test-hub://bar/@dotdot,foo,root//foo/../bar", "test-hub://bar/@root//bar"),
|
||||
("test-hub://d3/@d2,d1,root/../../..", "test-hub://d3/@d2,d1,root/../../.."),
|
||||
] {
|
||||
let input: UrlBuf = input.parse()?;
|
||||
#[cfg(unix)]
|
||||
assert_eq!(format!("{:?}", clean_url(input)), expected);
|
||||
#[cfg(windows)]
|
||||
assert_eq!(format!("{:?}", clean_url(input)).replace(r"\", "/"), expected);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ fn expand_url_impl(url: UrlCow) -> UrlCow {
|
|||
loc: LocBuf::<std::path::PathBuf>::with(path.into_os().unwrap(), uri, urn).unwrap(),
|
||||
auth: auth.clone(),
|
||||
},
|
||||
Url::Hub { auth, .. } => UrlBuf::Hub {
|
||||
auth: auth.clone().with_parent_depth(path.components().auth_depth()),
|
||||
loc: LocBuf::<std::path::PathBuf>::with(path.into_os().unwrap(), uri, urn).unwrap(),
|
||||
},
|
||||
Url::Scope { auth, .. } => UrlBuf::Scope {
|
||||
loc: LocBuf::<typed_path::UnixPathBuf>::with(path.into_unix().unwrap(), uri, urn).unwrap(),
|
||||
auth: auth.clone(),
|
||||
|
|
@ -112,6 +116,7 @@ mod tests {
|
|||
("test-mount://7z:2:1//tmp/test.zip/$BAR_BAZ", "test-mount://7z:3:2//tmp/test.zip/bar/baz"),
|
||||
("test-mount://7z:2:2//tmp/$BAR_BAZ/test.zip", "test-mount://7z:3:3//tmp/bar/baz/test.zip"),
|
||||
("test-mount://7z:2:2//$BAR_BAZ/tmp/test.zip", "test-mount://7z:2:2//bar/baz/tmp/test.zip"),
|
||||
("test-hub://a1/@root/$BAR_BAZ", "test-hub://a1:2:2/@,root/bar/baz"),
|
||||
// -1 component
|
||||
("test-mount://7z//tmp/test.zip/${BAR/BAZ}", "test-mount://7z//tmp/test.zip/bar_baz"),
|
||||
("test-mount://7z:1//tmp/test.zip/${BAR/BAZ}", "test-mount://7z:1//tmp/test.zip/${BAR/BAZ}"),
|
||||
|
|
@ -134,6 +139,7 @@ mod tests {
|
|||
),
|
||||
("test-mount://7z:3:3//tmp/test.zip/${BAR/BAZ}", "test-mount://7z:2:2//tmp/test.zip/bar_baz"),
|
||||
("test-mount://7z:3:3//tmp/${BAR/BAZ}/test.zip", "test-mount://7z:2:2//tmp/bar_baz/test.zip"),
|
||||
("test-hub://a1:2:2/@b1,root/${BAR/BAZ}", "test-hub://a1/@root/bar_baz"),
|
||||
// Zeros all components
|
||||
("test-mount://7z//${EM/PT/Y}", "test-mount://7z//"),
|
||||
("test-mount://7z:1//${EM/PT/Y}", "test-mount://7z:1//${EM/PT/Y}"),
|
||||
|
|
|
|||
|
|
@ -4,8 +4,16 @@ use anyhow::Result;
|
|||
use percent_encoding::{AsciiSet, CONTROLS, percent_decode, percent_encode};
|
||||
use yazi_shared::path::{PathCow, PathDyn, PathKind};
|
||||
|
||||
const SET: &AsciiSet =
|
||||
&CONTROLS.add(b'"').add(b'*').add(b':').add(b'<').add(b'>').add(b'?').add(b'\\').add(b'|');
|
||||
const SET: &AsciiSet = &CONTROLS
|
||||
.add(b'"')
|
||||
.add(b'*')
|
||||
.add(b':')
|
||||
.add(b'<')
|
||||
.add(b'>')
|
||||
.add(b'?')
|
||||
.add(b'\\')
|
||||
.add(b'|')
|
||||
.add(b'%');
|
||||
|
||||
pub trait PercentEncoding<'a> {
|
||||
fn percent_encode(self) -> Cow<'a, Path>;
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ impl FilesSorter {
|
|||
}),
|
||||
SortBy::Size => items.sort_unstable_by(|a, b| {
|
||||
promote!(a, b);
|
||||
let aa = if a.is_dir() { sizes.get(&a.urn()).copied() } else { None };
|
||||
let bb = if b.is_dir() { sizes.get(&b.urn()).copied() } else { None };
|
||||
let aa = if a.is_dir() { sizes.get(&a.entry_key()).copied() } else { None };
|
||||
let bb = if b.is_dir() { sizes.get(&b.entry_key()).copied() } else { None };
|
||||
self.fallback(a, b, self.cmp(aa.unwrap_or(a.len), bb.unwrap_or(b.len)))
|
||||
}),
|
||||
SortBy::Random => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use yazi_shared::{auth::{Auth, AuthKind, Encode}, path::PathBufDyn, spec::SpecInventory};
|
||||
use yazi_shared::{auth::{Auth, AuthKind, EncodeAuth}, path::PathBufDyn, spec::SpecInventory};
|
||||
use yazi_shim::{mlua::UserDataFieldsExt, strum::IntoStr};
|
||||
|
||||
use crate::Xdg;
|
||||
|
|
@ -13,12 +13,14 @@ impl FsSpec for Auth {
|
|||
fn cache(&self) -> Option<PathBuf> {
|
||||
match self.kind {
|
||||
AuthKind::Regular | AuthKind::Search => None,
|
||||
AuthKind::Mount | AuthKind::Scope | AuthKind::Sftp => Some(Xdg::temp_dir().join(format!(
|
||||
"{}_{}_{}",
|
||||
self.kind.into_str(),
|
||||
self.scheme,
|
||||
Encode::domain(&self.domain)
|
||||
))),
|
||||
AuthKind::Mount | AuthKind::Hub | AuthKind::Scope | AuthKind::Sftp => {
|
||||
Some(Xdg::temp_dir().join(format!(
|
||||
"{}_{}_{}",
|
||||
self.kind.into_str(),
|
||||
self.scheme,
|
||||
EncodeAuth::domain(&self.domain)
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}};
|
||||
|
||||
use mlua::UserDataFields;
|
||||
use yazi_shared::{path::{AsPath, PathDyn}, url::{AsUrl, Url, UrlBuf, UrlBufInventory, UrlCow, UrlLike}};
|
||||
use yazi_shared::{path::{DynPath, PathDyn}, url::{AsUrl, Url, UrlBuf, UrlBufInventory, UrlCow, UrlLike}};
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
|
||||
use crate::{FsHash128, FsSpec, path::PercentEncoding};
|
||||
|
|
@ -29,7 +29,7 @@ impl<'a> FsUrl<'a> for Url<'a> {
|
|||
fn with_loc(loc: PathDyn, mut root: PathBuf) -> PathBuf {
|
||||
let mut it = loc.components();
|
||||
if it.next() == Some(yazi_shared::path::Component::RootDir) {
|
||||
root.push(it.as_path().percent_encode());
|
||||
root.push(it.dyn_path().percent_encode());
|
||||
} else {
|
||||
root.push(".%2F");
|
||||
root.push(loc.percent_encode());
|
||||
|
|
@ -51,7 +51,7 @@ impl<'a> FsUrl<'a> for Url<'a> {
|
|||
fn unified_path(self) -> Cow<'a, Path> {
|
||||
match self {
|
||||
Self::Regular(loc) | Self::Search { loc, .. } => loc.as_inner().into(),
|
||||
Self::Mount { .. } | Self::Scope { .. } | Self::Sftp { .. } => {
|
||||
Self::Mount { .. } | Self::Hub { .. } | Self::Scope { .. } | Self::Sftp { .. } => {
|
||||
self.cache().expect("non-local URL should have a cache path").into()
|
||||
}
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ impl FsUrl<'_> for UrlBuf {
|
|||
fn unified_path(self) -> Cow<'static, Path> {
|
||||
match self {
|
||||
Self::Regular(loc) | Self::Search { loc, .. } => loc.into_inner().into(),
|
||||
Self::Mount { .. } | Self::Scope { .. } | Self::Sftp { .. } => {
|
||||
Self::Mount { .. } | Self::Hub { .. } | Self::Scope { .. } | Self::Sftp { .. } => {
|
||||
self.cache().expect("non-local URL should have a cache path").into()
|
||||
}
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@ impl<'a> FsUrl<'a> for UrlCow<'a> {
|
|||
fn unified_path(self) -> Cow<'a, Path> {
|
||||
match self {
|
||||
Self::Regular(loc) | Self::Search { loc, .. } => loc.into_inner(),
|
||||
Self::Mount { .. } | Self::Scope { .. } | Self::Sftp { .. } => {
|
||||
Self::Mount { .. } | Self::Hub { .. } | Self::Scope { .. } | Self::Sftp { .. } => {
|
||||
self.cache().expect("non-local URL should have a cache path").into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ use yazi_shared::path::PathBufDyn;
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct HoverForm {
|
||||
pub urn: Option<PathBufDyn>,
|
||||
pub key: Option<PathBufDyn>,
|
||||
}
|
||||
|
||||
impl From<Option<PathBufDyn>> for HoverForm {
|
||||
fn from(urn: Option<PathBufDyn>) -> Self { Self { urn } }
|
||||
fn from(key: Option<PathBufDyn>) -> Self { Self { key } }
|
||||
}
|
||||
|
||||
impl FromLua for HoverForm {
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ impl Drop for FileInCut {
|
|||
impl FileInCut {
|
||||
pub fn new(from: UrlBuf, to: UrlBuf, force: bool) -> Self {
|
||||
Self {
|
||||
follow: !from.auth().covariant(to.auth()),
|
||||
follow: !from.auth().same_service(to.auth()),
|
||||
id: Id::ZERO,
|
||||
from,
|
||||
to,
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ impl Scheduler {
|
|||
}
|
||||
|
||||
pub fn file_copy(&self, from: UrlBuf, to: UrlBuf, force: bool, follow: bool) {
|
||||
let follow = follow || !from.auth().covariant(to.auth());
|
||||
let follow = follow || !from.auth().same_service(to.auth());
|
||||
let mut r#in = FileInCopy { id: Id::ZERO, from, to, force, cha: None, follow, retry: 0 };
|
||||
|
||||
self.add(&mut r#in, |_| ());
|
||||
|
|
@ -109,7 +109,7 @@ impl Scheduler {
|
|||
let mut r#in = FileInHardlink { id: Id::ZERO, from, to, force, cha: None, follow };
|
||||
self.add(&mut r#in, |_| ());
|
||||
|
||||
if !r#in.from.auth().covariant(r#in.to.auth()) {
|
||||
if !r#in.from.auth().same_service(r#in.to.auth()) {
|
||||
return self
|
||||
.ops
|
||||
.out(r#in.id, FileOutHardlink::Fail("Cannot hardlink cross filesystem".to_owned()));
|
||||
|
|
|
|||
|
|
@ -35,9 +35,10 @@ impl Size {
|
|||
}
|
||||
|
||||
let parent = buf[0].0.parent().unwrap();
|
||||
// FIXME: use PathBufDyn instead of UrlBuf in SizeIn
|
||||
FilesOp::Size(
|
||||
parent.into(),
|
||||
HashMap::from_iter(buf.into_iter().map(|(u, s)| (u.urn().into(), s))),
|
||||
HashMap::from_iter(buf.into_iter().map(|(u, s)| (u.entry_key().into(), s))),
|
||||
)
|
||||
.emit();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,19 +1,37 @@
|
|||
use std::{fmt, sync::Arc};
|
||||
use std::{fmt, hash::{Hash, Hasher}, sync::Arc};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use percent_encoding::percent_decode_str;
|
||||
use serde::Deserialize;
|
||||
use yazi_shim::{SStr, cell::RoCell};
|
||||
use yazi_shim::cell::RoCell;
|
||||
|
||||
use crate::auth::{AuthInventory, AuthKind, Encode, Scheme};
|
||||
use crate::{auth::{AuthInventory, AuthKind, Domain, EncodeAuth, Scheme}, path::{Component, Components}};
|
||||
|
||||
pub(super) static DEFAULT_ARC: RoCell<Arc<Auth>> = RoCell::new();
|
||||
|
||||
#[derive(Debug, Deserialize, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Auth {
|
||||
pub kind: AuthKind,
|
||||
pub scheme: Scheme,
|
||||
pub domain: SStr,
|
||||
pub domain: Domain<'static>,
|
||||
#[serde(default)]
|
||||
pub parent: Option<Arc<Auth>>,
|
||||
}
|
||||
|
||||
impl PartialEq for Auth {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.kind == other.kind && self.scheme == other.scheme && self.domain == other.domain
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Auth {}
|
||||
|
||||
impl Hash for Auth {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.kind.hash(state);
|
||||
self.scheme.hash(state);
|
||||
self.domain.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Auth {
|
||||
|
|
@ -21,20 +39,28 @@ impl Default for Auth {
|
|||
}
|
||||
|
||||
impl fmt::Display for Auth {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Encode(self, false).fmt(f) }
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { EncodeAuth(self, false).fmt(f) }
|
||||
}
|
||||
|
||||
impl Auth {
|
||||
pub const DEFAULT: Self =
|
||||
Self { kind: AuthKind::Regular, scheme: Scheme::Regular, domain: SStr::Borrowed("") };
|
||||
pub const DEFAULT: Self = Self {
|
||||
kind: AuthKind::Regular,
|
||||
scheme: Scheme::Regular,
|
||||
domain: Domain::EMPTY,
|
||||
parent: None,
|
||||
};
|
||||
|
||||
pub fn default_arc() -> Arc<Self> { DEFAULT_ARC.clone() }
|
||||
|
||||
pub fn new(kind: AuthKind, scheme: Scheme, domain: impl Into<SStr>) -> Arc<Self> {
|
||||
Arc::new(Self { kind, scheme, domain: domain.into() })
|
||||
pub fn new<'a>(kind: AuthKind, scheme: Scheme, domain: impl Into<Domain<'a>>) -> Arc<Self> {
|
||||
Arc::new(Self { kind, scheme, domain: domain.into().into_owned(), parent: None })
|
||||
}
|
||||
|
||||
pub fn get(scheme: &Scheme, domain: &str) -> Option<Arc<Self>> {
|
||||
pub fn search<'a>(query: impl Into<Domain<'a>>) -> Arc<Self> {
|
||||
Self::new(AuthKind::Search, Scheme::Search, query)
|
||||
}
|
||||
|
||||
pub fn get(scheme: &Scheme, domain: &Domain<'_>) -> Option<Arc<Self>> {
|
||||
match scheme {
|
||||
Scheme::Regular => Some(Self::default_arc()),
|
||||
Scheme::Search => Some(Self::search(domain)),
|
||||
|
|
@ -42,22 +68,88 @@ impl Auth {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn search(query: impl Into<String>) -> Arc<Self> {
|
||||
Self::new(AuthKind::Search, Scheme::Search, query.into())
|
||||
pub fn child(self: Arc<Self>) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
kind: self.kind,
|
||||
scheme: self.scheme.clone(),
|
||||
domain: Domain::default(),
|
||||
parent: Some(self),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn root(mut self: &Arc<Self>) -> &Arc<Self> {
|
||||
while let Some(parent) = &self.parent {
|
||||
self = parent;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn descend<'a>(mut self: Arc<Self>, components: impl Into<Components<'a>>) -> Arc<Self> {
|
||||
for component in components.into() {
|
||||
match component {
|
||||
Component::RootDir => self = Self::new(self.kind, self.scheme.clone(), Domain::EMPTY),
|
||||
c if c.has_auth() => self = self.child(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn parent_at(mut self: &Arc<Self>, depth: usize) -> &Arc<Self> {
|
||||
for _ in 0..depth {
|
||||
self = self.parent.as_ref().expect("Auth parent depth out of bounds");
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_parent_depth(mut self: Arc<Self>, depth: usize) -> Arc<Self> {
|
||||
let current = self.parent_depth();
|
||||
if current == depth {
|
||||
return self;
|
||||
}
|
||||
|
||||
let mut parent = if current < depth {
|
||||
self.parent.clone()
|
||||
} else {
|
||||
self.parent_at(current - depth).parent.clone()
|
||||
};
|
||||
|
||||
for _ in current..depth {
|
||||
parent = Some(Arc::new(Self {
|
||||
kind: self.kind,
|
||||
scheme: self.scheme.clone(),
|
||||
domain: Domain::default(),
|
||||
parent,
|
||||
}));
|
||||
}
|
||||
|
||||
Arc::make_mut(&mut self).parent = parent;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn parent_depth(&self) -> usize {
|
||||
let mut depth = 0;
|
||||
let mut parent = self.parent.as_deref();
|
||||
while let Some(auth) = parent {
|
||||
depth += 1;
|
||||
parent = auth.parent.as_deref();
|
||||
}
|
||||
depth
|
||||
}
|
||||
|
||||
pub fn covariant(&self, other: &Self) -> bool {
|
||||
!self.kind.is_virtual() && !other.kind.is_virtual() || self == other
|
||||
}
|
||||
|
||||
pub fn same_service(&self, other: &Self) -> bool {
|
||||
self.covariant(other)
|
||||
|| self.kind == AuthKind::Hub && other.kind == AuthKind::Hub && self.scheme == other.scheme
|
||||
}
|
||||
|
||||
pub fn parse_cache(cache: &str) -> Result<Arc<Self>> {
|
||||
let (kind, rest) = cache.split_once('_').ok_or_else(|| anyhow!("invalid cache: {cache}"))?;
|
||||
let (scheme, domain) = rest.split_once('_').ok_or_else(|| anyhow!("invalid cache: {cache}"))?;
|
||||
|
||||
Ok(Arc::new(Self {
|
||||
kind: kind.parse()?,
|
||||
scheme: scheme.parse()?,
|
||||
domain: percent_decode_str(domain).decode_utf8()?.into_owned().into(),
|
||||
}))
|
||||
Ok(Self::new(kind.parse()?, scheme.parse()?, percent_decode_str(domain).decode_utf8()?))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
69
yazi-shared/src/auth/domain.rs
Normal file
69
yazi-shared/src/auth/domain.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
use std::{borrow::{Borrow, Cow}, fmt::{self, Display, Formatter}, ops::Deref};
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, de::{self, IntoDeserializer, value::CowStrDeserializer}};
|
||||
|
||||
#[derive(Default, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Domain<'a>(Cow<'a, str>);
|
||||
|
||||
impl Domain<'static> {
|
||||
pub const CATCHALL: Self = Self(Cow::Borrowed("*"));
|
||||
pub const EMPTY: Self = Self(Cow::Borrowed(""));
|
||||
}
|
||||
|
||||
impl Domain<'_> {
|
||||
#[inline]
|
||||
pub fn into_owned(self) -> Domain<'static> { Domain(Cow::Owned(self.0.into_owned())) }
|
||||
|
||||
#[inline]
|
||||
pub fn is_catchall(&self) -> bool { *self == Domain::CATCHALL }
|
||||
}
|
||||
|
||||
impl Deref for Domain<'_> {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl Borrow<str> for Domain<'_> {
|
||||
fn borrow(&self) -> &str { &self.0 }
|
||||
}
|
||||
|
||||
impl AsRef<str> for Domain<'_> {
|
||||
fn as_ref(&self) -> &str { &self.0 }
|
||||
}
|
||||
|
||||
impl<'a> From<String> for Domain<'a> {
|
||||
fn from(value: String) -> Self { Self(Cow::Owned(value)) }
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Domain<'a> {
|
||||
fn from(value: &'a str) -> Self { Self(Cow::Borrowed(value)) }
|
||||
}
|
||||
|
||||
impl<'a> From<Cow<'a, str>> for Domain<'a> {
|
||||
fn from(value: Cow<'a, str>) -> Self { Self(value) }
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Domain<'_>> for Domain<'a> {
|
||||
fn from(value: &'a Domain<'_>) -> Self { Self(Cow::Borrowed(&value.0)) }
|
||||
}
|
||||
|
||||
impl Display for Domain<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(&self.0, f) }
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Domain<'_> {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
Ok(Self(Cow::Owned(String::deserialize(deserializer)?)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, 'a, E> IntoDeserializer<'de, E> for Domain<'a>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
type Deserializer = CowStrDeserializer<'a, E>;
|
||||
|
||||
fn into_deserializer(self) -> Self::Deserializer { self.0.into_deserializer() }
|
||||
}
|
||||
|
|
@ -2,18 +2,18 @@ use std::fmt;
|
|||
|
||||
use percent_encoding::{AsciiSet, CONTROLS, PercentEncode, percent_encode};
|
||||
|
||||
use super::Auth;
|
||||
use super::{Auth, AuthKind, Domain};
|
||||
|
||||
pub struct Encode<'a>(pub &'a Auth, pub bool);
|
||||
pub struct EncodeAuth<'a>(pub &'a Auth, pub bool);
|
||||
|
||||
impl Encode<'_> {
|
||||
pub fn domain(s: &str) -> PercentEncode<'_> {
|
||||
const SET: &AsciiSet = &CONTROLS.add(b'/').add(b':');
|
||||
impl EncodeAuth<'_> {
|
||||
pub fn domain<'a>(s: &'a Domain<'_>) -> PercentEncode<'a> {
|
||||
const SET: &AsciiSet = &CONTROLS.add(b'/').add(b':').add(b'%');
|
||||
percent_encode(s.as_bytes(), SET)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Encode<'_> {
|
||||
impl fmt::Display for EncodeAuth<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
|
|
@ -24,3 +24,32 @@ impl fmt::Display for Encode<'_> {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
// --- EncodePrefix
|
||||
pub struct EncodePrefix<'a>(pub &'a Auth);
|
||||
|
||||
impl EncodePrefix<'_> {
|
||||
pub fn parent<'a>(s: &'a Domain<'_>) -> PercentEncode<'a> {
|
||||
const SET: &AsciiSet = &CONTROLS.add(b'/').add(b',').add(b'@').add(b'%');
|
||||
percent_encode(s.as_bytes(), SET)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EncodePrefix<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.0.kind != AuthKind::Hub {
|
||||
return f.write_str("/");
|
||||
}
|
||||
|
||||
f.write_str("/@")?;
|
||||
let (mut first, mut parent) = (true, self.0.parent.as_deref());
|
||||
while let Some(auth) = parent {
|
||||
if !first {
|
||||
f.write_str(",")?;
|
||||
}
|
||||
Self::parent(&auth.domain).fmt(f)?;
|
||||
(first, parent) = (false, auth.parent.as_deref());
|
||||
}
|
||||
f.write_str("/")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::auth::{Auth, Scheme};
|
||||
use crate::auth::{Auth, Domain, Scheme};
|
||||
|
||||
pub struct AuthInventory {
|
||||
pub get: fn(&Scheme, &str) -> Option<Arc<Auth>>,
|
||||
pub get: fn(&Scheme, &Domain<'_>) -> Option<Arc<Auth>>,
|
||||
}
|
||||
|
||||
inventory::collect!(AuthInventory);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ pub enum AuthKind {
|
|||
Regular,
|
||||
Search,
|
||||
Mount,
|
||||
Hub,
|
||||
Scope,
|
||||
Sftp,
|
||||
}
|
||||
|
|
@ -20,7 +21,7 @@ impl AuthKind {
|
|||
pub fn is_local(self) -> bool {
|
||||
match self {
|
||||
Self::Regular | Self::Search => true,
|
||||
Self::Mount | Self::Scope | Self::Sftp => false,
|
||||
Self::Mount | Self::Hub | Self::Scope | Self::Sftp => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +29,7 @@ impl AuthKind {
|
|||
pub fn is_remote(self) -> bool {
|
||||
match self {
|
||||
Self::Regular | Self::Search | Self::Mount => false,
|
||||
Self::Scope | Self::Sftp => true,
|
||||
Self::Hub | Self::Scope | Self::Sftp => true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ impl AuthKind {
|
|||
pub fn is_virtual(self) -> bool {
|
||||
match self {
|
||||
Self::Regular | Self::Search => false,
|
||||
Self::Mount | Self::Scope | Self::Sftp => true,
|
||||
Self::Mount | Self::Hub | Self::Scope | Self::Sftp => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
yazi_macro::mod_flat!(auth encode inventory kind scheme);
|
||||
yazi_macro::mod_flat!(auth domain encode inventory kind scheme);
|
||||
|
||||
pub(super) fn init() { DEFAULT_ARC.with(Default::default); }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::{ffi::{OsStr, OsString}, fmt::Debug, hash::Hash};
|
||||
|
||||
use crate::{path::{AsPath, AsPathView}, strand::AsStrandView};
|
||||
use crate::{path::{DynPath, PathView}, strand::AsStrandView};
|
||||
|
||||
// --- LocAble
|
||||
pub trait LocAble<'p>
|
||||
|
|
@ -27,14 +27,10 @@ impl<'p> LocAble<'p> for &'p typed_path::UnixPath {
|
|||
// --- LocBufAble
|
||||
pub trait LocBufAble
|
||||
where
|
||||
Self: 'static + AsPath + Default,
|
||||
Self: 'static + DynPath + Default,
|
||||
{
|
||||
type Strand<'a>: StrandAble<'a>;
|
||||
type Borrowed<'a>: LocAble<'a>
|
||||
+ LocAbleImpl<'a>
|
||||
+ AsPathView<'a, Self::Borrowed<'a>>
|
||||
+ Debug
|
||||
+ Hash;
|
||||
type Borrowed<'a>: LocAble<'a> + LocAbleImpl<'a> + PathView<'a, Self::Borrowed<'a>> + Debug + Hash;
|
||||
}
|
||||
|
||||
impl LocBufAble for std::path::PathBuf {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{cmp, ffi::OsStr, fmt::{self, Debug, Formatter}, hash::{Hash, Hasher},
|
|||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{auth::AuthKind, loc::{Loc, LocAble, LocAbleImpl, LocBufAble, LocBufAbleImpl}, path::{AsPath, AsPathView, PathDyn, SetNameError}, strand::AsStrandView};
|
||||
use crate::{auth::AuthKind, loc::{Loc, LocAble, LocAbleImpl, LocBufAble, LocBufAbleImpl}, path::{DynPath, PathDyn, PathView, SetNameError}, strand::AsStrandView};
|
||||
|
||||
#[derive(Clone, Default, Eq, PartialEq)]
|
||||
pub struct LocBuf<P = std::path::PathBuf> {
|
||||
|
|
@ -25,18 +25,18 @@ impl AsRef<std::path::Path> for LocBuf<std::path::PathBuf> {
|
|||
fn as_ref(&self) -> &std::path::Path { self.inner.as_ref() }
|
||||
}
|
||||
|
||||
impl<T> AsPath for LocBuf<T>
|
||||
impl<T> DynPath for LocBuf<T>
|
||||
where
|
||||
T: LocBufAble + AsPath,
|
||||
T: LocBufAble + DynPath,
|
||||
{
|
||||
fn as_path(&self) -> PathDyn<'_> { self.inner.as_path() }
|
||||
fn dyn_path(&self) -> PathDyn<'_> { self.inner.dyn_path() }
|
||||
}
|
||||
|
||||
impl<T> AsPath for &LocBuf<T>
|
||||
impl<T> DynPath for &LocBuf<T>
|
||||
where
|
||||
T: LocBufAble + AsPath,
|
||||
T: LocBufAble + DynPath,
|
||||
{
|
||||
fn as_path(&self) -> PathDyn<'_> { self.inner.as_path() }
|
||||
fn dyn_path(&self) -> PathDyn<'_> { self.inner.dyn_path() }
|
||||
}
|
||||
|
||||
impl<P> Ord for LocBuf<P>
|
||||
|
|
@ -59,7 +59,7 @@ where
|
|||
impl<P> Hash for LocBuf<P>
|
||||
where
|
||||
P: LocBufAble + LocBufAbleImpl,
|
||||
for<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,
|
||||
for<'a> &'a P: PathView<'a, P::Borrowed<'a>>,
|
||||
{
|
||||
fn hash<H: Hasher>(&self, state: &mut H) { self.as_loc().hash(state) }
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ where
|
|||
impl<P> Debug for LocBuf<P>
|
||||
where
|
||||
P: LocBufAble + LocBufAbleImpl + Debug,
|
||||
for<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,
|
||||
for<'a> &'a P: PathView<'a, P::Borrowed<'a>>,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.debug_struct("LocBuf")
|
||||
|
|
@ -81,7 +81,7 @@ where
|
|||
impl<P> From<P> for LocBuf<P>
|
||||
where
|
||||
P: LocBufAble + LocBufAbleImpl,
|
||||
for<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,
|
||||
for<'a> &'a P: PathView<'a, P::Borrowed<'a>>,
|
||||
{
|
||||
fn from(path: P) -> Self {
|
||||
let Loc { inner, uri, urn, _phantom } = Loc::bare(&path);
|
||||
|
|
@ -100,7 +100,7 @@ impl<T: ?Sized + AsRef<OsStr>> From<&T> for LocBuf<std::path::PathBuf> {
|
|||
impl<P> LocBuf<P>
|
||||
where
|
||||
P: LocBufAble + LocBufAbleImpl,
|
||||
for<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,
|
||||
for<'a> &'a P: PathView<'a, P::Borrowed<'a>>,
|
||||
{
|
||||
pub fn new<'a, S>(path: P, base: S, trail: S) -> Self
|
||||
where
|
||||
|
|
@ -157,7 +157,7 @@ where
|
|||
#[inline]
|
||||
pub fn as_loc<'a>(&'a self) -> Loc<'a, P::Borrowed<'a>> {
|
||||
Loc {
|
||||
inner: self.inner.as_path_view(),
|
||||
inner: self.inner.path_view(),
|
||||
uri: self.uri,
|
||||
urn: self.urn,
|
||||
_phantom: PhantomData,
|
||||
|
|
@ -223,7 +223,7 @@ where
|
|||
impl<P> LocBuf<P>
|
||||
where
|
||||
P: LocBufAble + LocBufAbleImpl,
|
||||
for<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,
|
||||
for<'a> &'a P: PathView<'a, P::Borrowed<'a>>,
|
||||
{
|
||||
#[inline]
|
||||
pub fn uri(&self) -> P::Borrowed<'_> { self.as_loc().uri() }
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::{hash::{Hash, Hasher}, marker::PhantomData, ops::Deref};
|
|||
use anyhow::{Result, bail};
|
||||
|
||||
use super::LocAbleImpl;
|
||||
use crate::{auth::AuthKind, loc::{LocAble, LocBuf, LocBufAble, StrandAbleImpl}, path::{AsPath, AsPathView, PathDyn}, strand::AsStrandView};
|
||||
use crate::{auth::AuthKind, loc::{LocAble, LocBuf, LocBufAble, StrandAbleImpl}, path::{DynPath, PathDyn, PathView}, strand::AsStrandView};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Loc<'p, P = &'p std::path::Path> {
|
||||
|
|
@ -29,11 +29,11 @@ where
|
|||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl<'p, P> AsPath for Loc<'p, P>
|
||||
impl<'p, P> DynPath for Loc<'p, P>
|
||||
where
|
||||
P: LocAble<'p> + AsPath,
|
||||
P: LocAble<'p> + DynPath,
|
||||
{
|
||||
fn as_path(&self) -> PathDyn<'_> { self.inner.as_path() }
|
||||
fn dyn_path(&self) -> PathDyn<'_> { self.inner.dyn_path() }
|
||||
}
|
||||
|
||||
// FIXME: remove
|
||||
|
|
@ -81,9 +81,9 @@ where
|
|||
|
||||
pub fn bare<T>(path: T) -> Self
|
||||
where
|
||||
T: AsPathView<'p, P>,
|
||||
T: PathView<'p, P>,
|
||||
{
|
||||
let path = path.as_path_view();
|
||||
let path = path.path_view();
|
||||
let Some(name) = path.file_name() else {
|
||||
let p = path.strip_prefix(P::empty()).unwrap();
|
||||
return Self { inner: p, uri: 0, urn: 0, _phantom: PhantomData };
|
||||
|
|
@ -114,7 +114,7 @@ where
|
|||
|
||||
pub fn floated<'a, T, S>(path: T, base: S) -> Self
|
||||
where
|
||||
T: AsPathView<'p, P>,
|
||||
T: PathView<'p, P>,
|
||||
S: AsStrandView<'a, P::Strand<'a>>,
|
||||
{
|
||||
let mut loc = Self::bare(path);
|
||||
|
|
@ -133,7 +133,7 @@ where
|
|||
|
||||
pub fn new<'a, T, S>(path: T, base: S, trail: S) -> Self
|
||||
where
|
||||
T: AsPathView<'p, P>,
|
||||
T: PathView<'p, P>,
|
||||
S: AsStrandView<'a, P::Strand<'a>>,
|
||||
{
|
||||
let mut loc = Self::bare(path);
|
||||
|
|
@ -143,18 +143,17 @@ where
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn parent(self) -> Option<P> {
|
||||
self.inner.parent().filter(|p| !p.as_encoded_bytes().is_empty())
|
||||
}
|
||||
pub fn parent(self) -> Option<P> { self.inner.parent() }
|
||||
|
||||
pub fn saturated<'a, T>(path: T, kind: AuthKind) -> Self
|
||||
where
|
||||
T: AsPathView<'p, P>,
|
||||
T: PathView<'p, P>,
|
||||
{
|
||||
match kind {
|
||||
AuthKind::Regular => Self::bare(path),
|
||||
AuthKind::Search => Self::zeroed(path),
|
||||
AuthKind::Mount => Self::zeroed(path),
|
||||
AuthKind::Hub => Self::bare(path),
|
||||
AuthKind::Scope => Self::bare(path),
|
||||
AuthKind::Sftp => Self::bare(path),
|
||||
}
|
||||
|
|
@ -206,7 +205,7 @@ where
|
|||
|
||||
pub fn with<T>(path: T, uri: usize, urn: usize) -> Result<Self>
|
||||
where
|
||||
T: AsPathView<'p, P>,
|
||||
T: PathView<'p, P>,
|
||||
{
|
||||
if urn > uri {
|
||||
bail!("URN cannot be longer than URI");
|
||||
|
|
@ -238,7 +237,7 @@ where
|
|||
|
||||
pub fn zeroed<T>(path: T) -> Self
|
||||
where
|
||||
T: AsPathView<'p, P>,
|
||||
T: PathView<'p, P>,
|
||||
{
|
||||
let mut loc = Self::bare(path);
|
||||
(loc.uri, loc.urn) = (0, 0);
|
||||
|
|
@ -248,8 +247,21 @@ where
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use typed_path::UnixPath;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parent() {
|
||||
assert_eq!(Loc::bare(Path::new("foo")).parent(), Some(Path::new("")));
|
||||
assert_eq!(Loc::bare(Path::new("")).parent(), None);
|
||||
|
||||
assert_eq!(Loc::bare(UnixPath::new("foo")).parent(), Some(UnixPath::new("")));
|
||||
assert_eq!(Loc::bare(UnixPath::new("")).parent(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with() -> Result<()> {
|
||||
let cases = [
|
||||
|
|
@ -268,11 +280,11 @@ mod tests {
|
|||
];
|
||||
|
||||
for (path, uri, urn, expect_uri, expect_urn) in cases {
|
||||
let loc = Loc::with(std::path::Path::new(path), uri, urn)?;
|
||||
let loc = Loc::with(Path::new(path), uri, urn)?;
|
||||
assert_eq!(loc.uri().to_str().unwrap(), expect_uri);
|
||||
assert_eq!(loc.urn().to_str().unwrap(), expect_urn);
|
||||
|
||||
let loc = Loc::with(typed_path::UnixPath::new(path), uri, urn)?;
|
||||
let loc = Loc::with(UnixPath::new(path), uri, urn)?;
|
||||
assert_eq!(loc.uri().to_str().unwrap(), expect_uri);
|
||||
assert_eq!(loc.urn().to_str().unwrap(), expect_urn);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
|
|||
use yazi_codegen::FromLuaOwned;
|
||||
use yazi_shim::wtf8::FromWtf8Vec;
|
||||
|
||||
use crate::{path::{AsPath, Component, PathDyn, PathDynError, PathKind, SetNameError}, strand::AsStrand};
|
||||
use crate::{path::{Component, DynPath, PathDyn, PathDynError, PathKind, SetNameError}, strand::AsStrand};
|
||||
|
||||
// --- PathBufDyn
|
||||
#[derive(Clone, Debug, Eq, FromLuaOwned)]
|
||||
|
|
@ -48,24 +48,24 @@ impl TryFrom<PathBufDyn> for typed_path::UnixPathBuf {
|
|||
|
||||
// --- Hash
|
||||
impl Hash for PathBufDyn {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) { self.as_path().hash(state) }
|
||||
fn hash<H: Hasher>(&self, state: &mut H) { self.dyn_path().hash(state) }
|
||||
}
|
||||
|
||||
// --- PartialEq
|
||||
impl PartialEq for PathBufDyn {
|
||||
fn eq(&self, other: &Self) -> bool { self.as_path() == other.as_path() }
|
||||
fn eq(&self, other: &Self) -> bool { self.dyn_path() == other.dyn_path() }
|
||||
}
|
||||
|
||||
impl PartialEq<PathDyn<'_>> for PathBufDyn {
|
||||
fn eq(&self, other: &PathDyn<'_>) -> bool { self.as_path() == *other }
|
||||
fn eq(&self, other: &PathDyn<'_>) -> bool { self.dyn_path() == *other }
|
||||
}
|
||||
|
||||
impl PartialEq<PathDyn<'_>> for &PathBufDyn {
|
||||
fn eq(&self, other: &PathDyn<'_>) -> bool { self.as_path() == *other }
|
||||
fn eq(&self, other: &PathDyn<'_>) -> bool { self.dyn_path() == *other }
|
||||
}
|
||||
|
||||
impl Equivalent<PathDyn<'_>> for PathBufDyn {
|
||||
fn equivalent(&self, key: &PathDyn<'_>) -> bool { self.as_path() == *key }
|
||||
fn equivalent(&self, key: &PathDyn<'_>) -> bool { self.dyn_path() == *key }
|
||||
}
|
||||
|
||||
impl PathBufDyn {
|
||||
|
|
@ -126,7 +126,7 @@ impl PathBufDyn {
|
|||
pub fn try_extend<T>(&mut self, paths: T) -> Result<(), PathDynError>
|
||||
where
|
||||
T: IntoIterator,
|
||||
T::Item: AsPath,
|
||||
T::Item: DynPath,
|
||||
{
|
||||
for p in paths {
|
||||
self.try_push(p)?;
|
||||
|
|
@ -136,9 +136,9 @@ impl PathBufDyn {
|
|||
|
||||
pub fn try_push<T>(&mut self, path: T) -> Result<(), PathDynError>
|
||||
where
|
||||
T: AsPath,
|
||||
T: DynPath,
|
||||
{
|
||||
let path = path.as_path();
|
||||
let path = path.dyn_path();
|
||||
Ok(match self {
|
||||
Self::Os(p) => p.push(path.as_os()?),
|
||||
Self::Unix(p) => p.push(path.encoded_bytes()),
|
||||
|
|
@ -199,7 +199,7 @@ impl Serialize for PathBufDyn {
|
|||
path: &'a [u8],
|
||||
}
|
||||
|
||||
let path = self.as_path();
|
||||
let path = self.dyn_path();
|
||||
Shadow { kind: path.kind(), path: path.encoded_bytes() }.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ impl<'a> Component<'a> {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_auth(&self) -> bool { matches!(self, Self::ParentDir | Self::Normal(_)) }
|
||||
}
|
||||
|
||||
impl<'a> From<std::path::Component<'a>> for Component<'a> {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,17 @@ pub enum Components<'a> {
|
|||
Unix(typed_path::UnixComponents<'a>),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a std::path::Path> for Components<'a> {
|
||||
fn from(value: &'a std::path::Path) -> Self { Self::Os(value.components()) }
|
||||
}
|
||||
|
||||
impl<'a> From<&'a typed_path::UnixPath> for Components<'a> {
|
||||
fn from(value: &'a typed_path::UnixPath) -> Self { Self::Unix(value.components()) }
|
||||
}
|
||||
|
||||
impl<'a> Components<'a> {
|
||||
pub fn auth_depth(self) -> usize { self.filter(Component::has_auth).count() }
|
||||
|
||||
pub fn path(&self) -> PathDyn<'a> {
|
||||
match self {
|
||||
Self::Os(c) => PathDyn::Os(c.as_path()),
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
use super::{PathBufDyn, PathDyn};
|
||||
use crate::path::PathCow;
|
||||
|
||||
// --- AsPath
|
||||
pub trait AsPath {
|
||||
fn as_path(&self) -> PathDyn<'_>;
|
||||
}
|
||||
|
||||
impl AsPath for std::path::Path {
|
||||
fn as_path(&self) -> PathDyn<'_> { PathDyn::Os(self) }
|
||||
}
|
||||
|
||||
impl AsPath for std::path::PathBuf {
|
||||
fn as_path(&self) -> PathDyn<'_> { PathDyn::Os(self) }
|
||||
}
|
||||
|
||||
impl AsPath for typed_path::UnixPath {
|
||||
fn as_path(&self) -> PathDyn<'_> { PathDyn::Unix(self) }
|
||||
}
|
||||
|
||||
impl AsPath for typed_path::UnixPathBuf {
|
||||
fn as_path(&self) -> PathDyn<'_> { PathDyn::Unix(self) }
|
||||
}
|
||||
|
||||
impl AsPath for PathDyn<'_> {
|
||||
fn as_path(&self) -> PathDyn<'_> { *self }
|
||||
}
|
||||
|
||||
impl AsPath for PathBufDyn {
|
||||
fn as_path(&self) -> PathDyn<'_> {
|
||||
match self {
|
||||
Self::Os(p) => PathDyn::Os(p),
|
||||
Self::Unix(p) => PathDyn::Unix(p),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsPath for PathCow<'_> {
|
||||
fn as_path(&self) -> PathDyn<'_> {
|
||||
match self {
|
||||
PathCow::Borrowed(p) => *p,
|
||||
PathCow::Owned(p) => p.as_path(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsPath for super::Components<'_> {
|
||||
fn as_path(&self) -> PathDyn<'_> { self.path() }
|
||||
}
|
||||
|
||||
// --- AsPathRef
|
||||
pub trait AsPathRef<'a> {
|
||||
fn as_path_ref(self) -> PathDyn<'a>;
|
||||
}
|
||||
|
||||
impl<'a> AsPathRef<'a> for PathDyn<'a> {
|
||||
fn as_path_ref(self) -> Self { self }
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ use std::borrow::Cow;
|
|||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::path::{AsPath, PathBufDyn, PathDyn, PathDynError, PathKind};
|
||||
use crate::path::{DynPath, PathBufDyn, PathDyn, PathDynError, PathKind};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PathCow<'a> {
|
||||
|
|
@ -23,7 +23,7 @@ impl<'a> From<std::path::PathBuf> for PathCow<'a> {
|
|||
}
|
||||
|
||||
impl<'a> From<&'a PathCow<'_>> for PathCow<'a> {
|
||||
fn from(value: &'a PathCow<'_>) -> Self { Self::Borrowed(value.as_path()) }
|
||||
fn from(value: &'a PathCow<'_>) -> Self { Self::Borrowed(value.dyn_path()) }
|
||||
}
|
||||
|
||||
impl From<PathCow<'_>> for PathBufDyn {
|
||||
|
|
@ -31,14 +31,14 @@ impl From<PathCow<'_>> for PathBufDyn {
|
|||
}
|
||||
|
||||
impl PartialEq for PathCow<'_> {
|
||||
fn eq(&self, other: &Self) -> bool { self.as_path() == other.as_path() }
|
||||
fn eq(&self, other: &Self) -> bool { self.dyn_path() == other.dyn_path() }
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for PathCow<'_> {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match self {
|
||||
Self::Borrowed(s) => s.as_path() == *other,
|
||||
Self::Owned(s) => s.as_path() == *other,
|
||||
Self::Borrowed(s) => s.dyn_path() == *other,
|
||||
Self::Owned(s) => s.dyn_path() == *other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
66
yazi-shared/src/path/dyn_path.rs
Normal file
66
yazi-shared/src/path/dyn_path.rs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
use super::{PathBufDyn, PathDyn};
|
||||
use crate::path::PathCow;
|
||||
|
||||
// --- DynPath
|
||||
pub trait DynPath {
|
||||
fn dyn_path(&self) -> PathDyn<'_>;
|
||||
}
|
||||
|
||||
impl DynPath for std::path::Path {
|
||||
fn dyn_path(&self) -> PathDyn<'_> { PathDyn::Os(self) }
|
||||
}
|
||||
|
||||
impl DynPath for std::path::PathBuf {
|
||||
fn dyn_path(&self) -> PathDyn<'_> { PathDyn::Os(self) }
|
||||
}
|
||||
|
||||
impl DynPath for typed_path::UnixPath {
|
||||
fn dyn_path(&self) -> PathDyn<'_> { PathDyn::Unix(self) }
|
||||
}
|
||||
|
||||
impl DynPath for typed_path::UnixPathBuf {
|
||||
fn dyn_path(&self) -> PathDyn<'_> { PathDyn::Unix(self) }
|
||||
}
|
||||
|
||||
impl DynPath for PathDyn<'_> {
|
||||
fn dyn_path(&self) -> PathDyn<'_> { *self }
|
||||
}
|
||||
|
||||
impl DynPath for PathBufDyn {
|
||||
fn dyn_path(&self) -> PathDyn<'_> {
|
||||
match self {
|
||||
Self::Os(p) => PathDyn::Os(p),
|
||||
Self::Unix(p) => PathDyn::Unix(p),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DynPath for PathCow<'_> {
|
||||
fn dyn_path(&self) -> PathDyn<'_> {
|
||||
match self {
|
||||
PathCow::Borrowed(p) => *p,
|
||||
PathCow::Owned(p) => p.dyn_path(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DynPath for super::Components<'_> {
|
||||
fn dyn_path(&self) -> PathDyn<'_> { self.path() }
|
||||
}
|
||||
|
||||
// --- DynPathRef
|
||||
pub trait DynPathRef<'a> {
|
||||
fn dyn_path_ref(self) -> PathDyn<'a>;
|
||||
}
|
||||
|
||||
impl<'a> DynPathRef<'a> for PathDyn<'a> {
|
||||
fn dyn_path_ref(self) -> Self { self }
|
||||
}
|
||||
|
||||
impl<'a> DynPathRef<'a> for &'a std::path::Path {
|
||||
fn dyn_path_ref(self) -> PathDyn<'a> { PathDyn::Os(self) }
|
||||
}
|
||||
|
||||
impl<'a> DynPathRef<'a> for &'a typed_path::UnixPath {
|
||||
fn dyn_path_ref(self) -> PathDyn<'a> { PathDyn::Unix(self) }
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ impl From<AuthKind> for PathKind {
|
|||
AuthKind::Regular => Self::Os,
|
||||
AuthKind::Search => Self::Os,
|
||||
AuthKind::Mount => Self::Os,
|
||||
AuthKind::Hub => Self::Os,
|
||||
AuthKind::Scope => Self::Unix,
|
||||
AuthKind::Sftp => Self::Unix,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,95 +2,97 @@ use std::borrow::Cow;
|
|||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{Utf8BytePredictor, path::{AsPath, Components, Display, EndsWithError, JoinError, PathBufDyn, PathCow, PathDyn, PathDynError, PathKind, RsplitOnceError, StartsWithError, StripPrefixError, StripSuffixError}, strand::{AsStrand, Strand}};
|
||||
use crate::{Utf8BytePredictor, path::{Components, Display, DynPath, EndsWithError, JoinError, PathBufDyn, PathCow, PathDyn, PathDynError, PathKind, RsplitOnceError, StartsWithError, StripPrefixError, StripSuffixError}, strand::{AsStrand, Strand}};
|
||||
|
||||
pub trait PathLike: AsPath {
|
||||
fn as_os(&self) -> Result<&std::path::Path, PathDynError> { self.as_path().as_os() }
|
||||
pub trait PathLike: DynPath {
|
||||
fn as_os(&self) -> Result<&std::path::Path, PathDynError> { self.dyn_path().as_os() }
|
||||
|
||||
fn as_unix(&self) -> Result<&typed_path::UnixPath, PathDynError> { self.as_path().as_unix() }
|
||||
fn as_unix(&self) -> Result<&typed_path::UnixPath, PathDynError> { self.dyn_path().as_unix() }
|
||||
|
||||
fn components(&self) -> Components<'_> { self.as_path().components() }
|
||||
fn components(&self) -> Components<'_> { self.dyn_path().components() }
|
||||
|
||||
fn display(&self) -> Display<'_> { self.as_path().display() }
|
||||
fn display(&self) -> Display<'_> { self.dyn_path().display() }
|
||||
|
||||
fn encoded_bytes(&self) -> &[u8] { self.as_path().encoded_bytes() }
|
||||
fn encoded_bytes(&self) -> &[u8] { self.dyn_path().encoded_bytes() }
|
||||
|
||||
fn ext(&self) -> Option<Strand<'_>> { self.as_path().ext() }
|
||||
fn ext(&self) -> Option<Strand<'_>> { self.dyn_path().ext() }
|
||||
|
||||
fn has_root(&self) -> bool { self.as_path().has_root() }
|
||||
fn has_root(&self) -> bool { self.dyn_path().has_root() }
|
||||
|
||||
fn is_absolute(&self) -> bool { self.as_path().is_absolute() }
|
||||
fn is_absolute(&self) -> bool { self.dyn_path().is_absolute() }
|
||||
|
||||
fn is_empty(&self) -> bool { self.as_path().is_empty() }
|
||||
fn is_empty(&self) -> bool { self.dyn_path().is_empty() }
|
||||
|
||||
#[cfg(unix)]
|
||||
fn is_hidden(&self) -> bool { self.as_path().is_hidden() }
|
||||
fn is_hidden(&self) -> bool { self.dyn_path().is_hidden() }
|
||||
|
||||
fn kind(&self) -> PathKind { self.as_path().kind() }
|
||||
fn kind(&self) -> PathKind { self.dyn_path().kind() }
|
||||
|
||||
fn len(&self) -> usize { self.as_path().len() }
|
||||
fn len(&self) -> usize { self.dyn_path().len() }
|
||||
|
||||
fn name(&self) -> Option<Strand<'_>> { self.as_path().name() }
|
||||
fn name(&self) -> Option<Strand<'_>> { self.dyn_path().name() }
|
||||
|
||||
fn parent(&self) -> Option<PathDyn<'_>> { self.as_path().parent() }
|
||||
fn parent(&self) -> Option<PathDyn<'_>> { self.dyn_path().parent() }
|
||||
|
||||
fn rsplit_pred<T>(&self, pred: T) -> Option<(PathDyn<'_>, PathDyn<'_>)>
|
||||
where
|
||||
T: Utf8BytePredictor,
|
||||
{
|
||||
self.as_path().rsplit_pred(pred)
|
||||
self.dyn_path().rsplit_pred(pred)
|
||||
}
|
||||
|
||||
fn stem(&self) -> Option<Strand<'_>> { self.as_path().stem() }
|
||||
fn stem(&self) -> Option<Strand<'_>> { self.dyn_path().stem() }
|
||||
|
||||
fn to_os_owned(&self) -> Result<std::path::PathBuf, PathDynError> { self.as_path().to_os_owned() }
|
||||
fn to_os_owned(&self) -> Result<std::path::PathBuf, PathDynError> {
|
||||
self.dyn_path().to_os_owned()
|
||||
}
|
||||
|
||||
fn to_owned(&self) -> PathBufDyn { self.as_path().to_owned() }
|
||||
fn to_owned(&self) -> PathBufDyn { self.dyn_path().to_owned() }
|
||||
|
||||
fn to_str(&self) -> Result<&str, std::str::Utf8Error> { self.as_path().to_str() }
|
||||
fn to_str(&self) -> Result<&str, std::str::Utf8Error> { self.dyn_path().to_str() }
|
||||
|
||||
fn to_string_lossy(&self) -> Cow<'_, str> { self.as_path().to_string_lossy() }
|
||||
fn to_string_lossy(&self) -> Cow<'_, str> { self.dyn_path().to_string_lossy() }
|
||||
|
||||
fn try_ends_with<T>(&self, child: T) -> Result<bool, EndsWithError>
|
||||
where
|
||||
T: AsStrand,
|
||||
{
|
||||
self.as_path().try_ends_with(child)
|
||||
self.dyn_path().try_ends_with(child)
|
||||
}
|
||||
|
||||
fn try_join<T>(&self, path: T) -> Result<PathBufDyn, JoinError>
|
||||
where
|
||||
T: AsStrand,
|
||||
{
|
||||
self.as_path().try_join(path)
|
||||
self.dyn_path().try_join(path)
|
||||
}
|
||||
|
||||
fn try_rsplit_seq<T>(&self, pat: T) -> Result<(PathDyn<'_>, PathDyn<'_>), RsplitOnceError>
|
||||
where
|
||||
T: AsStrand,
|
||||
{
|
||||
self.as_path().try_rsplit_seq(pat)
|
||||
self.dyn_path().try_rsplit_seq(pat)
|
||||
}
|
||||
|
||||
fn try_starts_with<T>(&self, base: T) -> Result<bool, StartsWithError>
|
||||
where
|
||||
T: AsStrand,
|
||||
{
|
||||
self.as_path().try_starts_with(base)
|
||||
self.dyn_path().try_starts_with(base)
|
||||
}
|
||||
|
||||
fn try_strip_prefix<T>(&self, base: T) -> Result<PathDyn<'_>, StripPrefixError>
|
||||
where
|
||||
T: AsStrand,
|
||||
{
|
||||
self.as_path().try_strip_prefix(base)
|
||||
self.dyn_path().try_strip_prefix(base)
|
||||
}
|
||||
|
||||
fn try_strip_suffix<T>(&self, suffix: T) -> Result<PathDyn<'_>, StripSuffixError>
|
||||
where
|
||||
T: AsStrand,
|
||||
{
|
||||
self.as_path().try_strip_suffix(suffix)
|
||||
self.dyn_path().try_strip_suffix(suffix)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(buf component components conversion cow display error kind like lua path view);
|
||||
yazi_macro::mod_flat!(buf component components cow display dyn_path error kind like lua path view);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||
use hashbrown::Equivalent;
|
||||
|
||||
use super::{RsplitOnceError, StartsWithError};
|
||||
use crate::{BytesExt, Utf8BytePredictor, path::{AsPath, Components, Display, EndsWithError, JoinError, PathBufDyn, PathDynError, PathKind, StripPrefixError, StripSuffixError}, strand::{AsStrand, Strand, StrandError}};
|
||||
use crate::{BytesExt, Utf8BytePredictor, path::{Components, Display, DynPath, EndsWithError, JoinError, PathBufDyn, PathDynError, PathKind, StripPrefixError, StripSuffixError}, strand::{AsStrand, Strand, StrandError}};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum PathDyn<'p> {
|
||||
|
|
@ -21,11 +21,11 @@ impl<'a> From<&'a typed_path::UnixPath> for PathDyn<'a> {
|
|||
}
|
||||
|
||||
impl<'a> From<&'a PathBufDyn> for PathDyn<'a> {
|
||||
fn from(value: &'a PathBufDyn) -> Self { value.as_path() }
|
||||
fn from(value: &'a PathBufDyn) -> Self { value.dyn_path() }
|
||||
}
|
||||
|
||||
impl PartialEq<PathBufDyn> for PathDyn<'_> {
|
||||
fn eq(&self, other: &PathBufDyn) -> bool { *self == other.as_path() }
|
||||
fn eq(&self, other: &PathBufDyn) -> bool { *self == other.dyn_path() }
|
||||
}
|
||||
|
||||
impl PartialEq<PathDyn<'_>> for &std::path::Path {
|
||||
|
|
@ -36,6 +36,12 @@ impl PartialEq<&std::path::Path> for PathDyn<'_> {
|
|||
fn eq(&self, other: &&std::path::Path) -> bool { matches!(*self, PathDyn::Os(p) if p == *other) }
|
||||
}
|
||||
|
||||
impl PartialEq<&typed_path::UnixPath> for PathDyn<'_> {
|
||||
fn eq(&self, other: &&typed_path::UnixPath) -> bool {
|
||||
matches!(*self, PathDyn::Unix(p) if p == *other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for PathDyn<'_> {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match *self {
|
||||
|
|
@ -46,7 +52,7 @@ impl PartialEq<&str> for PathDyn<'_> {
|
|||
}
|
||||
|
||||
impl Equivalent<PathBufDyn> for PathDyn<'_> {
|
||||
fn equivalent(&self, key: &PathBufDyn) -> bool { *self == key.as_path() }
|
||||
fn equivalent(&self, key: &PathBufDyn) -> bool { *self == key.dyn_path() }
|
||||
}
|
||||
|
||||
impl<'p> PathDyn<'p> {
|
||||
|
|
@ -139,8 +145,8 @@ impl<'p> PathDyn<'p> {
|
|||
|
||||
pub fn parent(self) -> Option<Self> {
|
||||
Some(match self {
|
||||
Self::Os(p) => Self::Os(p.parent().filter(|p| !p.as_os_str().is_empty())?),
|
||||
Self::Unix(p) => Self::Unix(p.parent().filter(|p| !p.as_bytes().is_empty())?),
|
||||
Self::Os(p) => Self::Os(p.parent()?),
|
||||
Self::Unix(p) => Self::Unix(p.parent()?),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -296,3 +302,34 @@ impl<'p> PathDyn<'p> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use typed_path::UnixPath;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parent() {
|
||||
let path = Path::new("foo").dyn_path();
|
||||
assert_eq!(path.parent(), Some(Path::new("").dyn_path()));
|
||||
assert_eq!(path.parent().unwrap().parent(), None);
|
||||
|
||||
let path = UnixPath::new("foo").dyn_path();
|
||||
assert_eq!(path.parent(), Some(UnixPath::new("").dyn_path()));
|
||||
assert_eq!(path.parent().unwrap().parent(), None);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_windows_parent() {
|
||||
let path = Path::new("foo").dyn_path();
|
||||
assert_eq!(path.parent(), Some(Path::new("").dyn_path()));
|
||||
|
||||
let path = Path::new("C:foo").dyn_path();
|
||||
assert_eq!(path.parent(), Some(Path::new("C:").dyn_path()));
|
||||
assert_eq!(path.parent().unwrap().parent(), None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
// --- AsPathView
|
||||
pub trait AsPathView<'a, T> {
|
||||
fn as_path_view(self) -> T;
|
||||
// --- PathView
|
||||
pub trait PathView<'a, T> {
|
||||
fn path_view(self) -> T;
|
||||
}
|
||||
|
||||
impl<'a> AsPathView<'a, &'a std::path::Path> for &'a std::path::Path {
|
||||
fn as_path_view(self) -> &'a std::path::Path { self }
|
||||
impl<'a> PathView<'a, &'a std::path::Path> for &'a std::path::Path {
|
||||
fn path_view(self) -> &'a std::path::Path { self }
|
||||
}
|
||||
|
||||
impl<'a> AsPathView<'a, &'a std::path::Path> for &'a std::path::PathBuf {
|
||||
fn as_path_view(self) -> &'a std::path::Path { self }
|
||||
impl<'a> PathView<'a, &'a std::path::Path> for &'a std::path::PathBuf {
|
||||
fn path_view(self) -> &'a std::path::Path { self }
|
||||
}
|
||||
|
||||
impl<'a> AsPathView<'a, &'a typed_path::UnixPath> for &'a typed_path::UnixPath {
|
||||
fn as_path_view(self) -> &'a typed_path::UnixPath { self }
|
||||
impl<'a> PathView<'a, &'a typed_path::UnixPath> for &'a typed_path::UnixPath {
|
||||
fn path_view(self) -> &'a typed_path::UnixPath { self }
|
||||
}
|
||||
|
||||
impl<'a> AsPathView<'a, &'a typed_path::UnixPath> for &'a typed_path::UnixPathBuf {
|
||||
fn as_path_view(self) -> &'a typed_path::UnixPath { self }
|
||||
impl<'a> PathView<'a, &'a typed_path::UnixPath> for &'a typed_path::UnixPathBuf {
|
||||
fn path_view(self) -> &'a typed_path::UnixPath { self }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
use std::fmt::{self, Display};
|
||||
|
||||
use crate::{auth::{AuthKind, Encode as EncodeAuth}, url::Url};
|
||||
use crate::{auth::{AuthKind, EncodeAuth, EncodePrefix}, url::Url};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Encode<'a>(pub Url<'a>);
|
||||
pub struct EncodeSpec<'a>(pub Url<'a>);
|
||||
|
||||
impl<'a> From<crate::url::Encode<'a>> for Encode<'a> {
|
||||
impl<'a> From<crate::url::Encode<'a>> for EncodeSpec<'a> {
|
||||
fn from(value: crate::url::Encode<'a>) -> Self { Self(value.0) }
|
||||
}
|
||||
|
||||
impl<'a> Encode<'a> {
|
||||
impl<'a> EncodeSpec<'a> {
|
||||
pub(crate) fn ports(self) -> impl Display {
|
||||
struct D<'a>(Encode<'a>);
|
||||
struct D<'a>(EncodeSpec<'a>);
|
||||
|
||||
impl Display for D<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
|
@ -30,7 +30,7 @@ impl<'a> Encode<'a> {
|
|||
match self.0.0.kind() {
|
||||
AuthKind::Regular => Ok(()),
|
||||
AuthKind::Search | AuthKind::Mount => w!(0, 0),
|
||||
AuthKind::Scope | AuthKind::Sftp => {
|
||||
AuthKind::Hub | AuthKind::Scope | AuthKind::Sftp => {
|
||||
w!(self.0.0.loc().name().is_some() as usize, self.0.0.loc().name().is_some() as usize)
|
||||
}
|
||||
}
|
||||
|
|
@ -41,15 +41,16 @@ impl<'a> Encode<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for Encode<'_> {
|
||||
impl Display for EncodeSpec<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0 {
|
||||
Url::Regular(_) => write!(f, "regular://"),
|
||||
Url::Search { auth, .. }
|
||||
| Url::Mount { auth, .. }
|
||||
| Url::Hub { auth, .. }
|
||||
| Url::Scope { auth, .. }
|
||||
| Url::Sftp { auth, .. } => {
|
||||
write!(f, "{}{}/", EncodeAuth(auth, false), self.ports())
|
||||
write!(f, "{}{}{}", EncodeAuth(auth, false), self.ports(), EncodePrefix(auth))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::{Result, anyhow, ensure};
|
|||
use percent_encoding::percent_decode;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{auth::{Auth, AuthKind, Encode, Scheme}, path::{PathCow, PathLike}, spec::ParsedSpec, url::Url};
|
||||
use crate::{auth::{Auth, AuthKind, Domain, EncodeAuth, Scheme}, path::{PathCow, PathLike}, spec::ParsedSpec, url::Url};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||
pub struct Spec {
|
||||
|
|
@ -28,18 +28,23 @@ impl Spec {
|
|||
// Decode domain and ports
|
||||
let mut skip = 0;
|
||||
let (domain, uri, urn) = match &parsed.scheme {
|
||||
Scheme::Regular => (Cow::Borrowed(""), None, None),
|
||||
Scheme::Regular => (Domain::default(), None, None),
|
||||
_ => Self::decode_param(rest, &mut skip)?,
|
||||
};
|
||||
|
||||
// Resolve authority
|
||||
let auth = Auth::get(&parsed.scheme, &domain)
|
||||
.ok_or_else(|| anyhow!("unknown VFS authority: {parsed}://{}", Encode::domain(&domain)))?;
|
||||
let auth = Auth::get(&parsed.scheme, &domain).ok_or_else(|| {
|
||||
anyhow!("unknown VFS authority: {parsed}://{}", EncodeAuth::domain(&domain))
|
||||
})?;
|
||||
|
||||
// Decode path
|
||||
let path = Self::decode_path(auth.kind, parsed.tilde, &rest[skip..])?;
|
||||
let (uri, urn) = Self::normalize_ports(auth.kind, uri, urn, &path)?;
|
||||
let (path, auth) = if auth.kind == AuthKind::Hub {
|
||||
Self::decode_hub(auth, parsed.tilde, &rest[skip..])?
|
||||
} else {
|
||||
(Self::decode_path(auth.kind, parsed.tilde, &rest[skip..])?, auth)
|
||||
};
|
||||
|
||||
let (uri, urn) = Self::normalize_ports(auth.kind, uri, urn, &path)?;
|
||||
Ok((Self { auth, uri, urn }, path))
|
||||
}
|
||||
|
||||
|
|
@ -54,16 +59,13 @@ impl Spec {
|
|||
fn decode_param<'a>(
|
||||
bytes: &'a [u8],
|
||||
skip: &mut usize,
|
||||
) -> Result<(Cow<'a, str>, Option<usize>, Option<usize>)> {
|
||||
) -> Result<(Domain<'a>, Option<usize>, Option<usize>)> {
|
||||
let mut len = bytes.iter().copied().take_while(|&b| b != b'/').count();
|
||||
let slash = bytes.get(len).is_some_and(|&b| b == b'/');
|
||||
*skip += len + slash as usize;
|
||||
|
||||
let (uri, urn) = Self::decode_ports(&bytes[..len], &mut len)?;
|
||||
let domain = match Cow::from(percent_decode(&bytes[..len])) {
|
||||
Cow::Borrowed(b) => str::from_utf8(b)?.into(),
|
||||
Cow::Owned(b) => String::from_utf8(b)?.into(),
|
||||
};
|
||||
let domain = percent_decode(&bytes[..len]).decode_utf8()?.into();
|
||||
|
||||
Ok((domain, uri, urn))
|
||||
}
|
||||
|
|
@ -90,6 +92,41 @@ impl Spec {
|
|||
PathCow::with(kind, bytes)
|
||||
}
|
||||
|
||||
fn decode_hub<'a>(
|
||||
mut auth: Arc<Auth>,
|
||||
tilde: bool,
|
||||
bytes: &'a [u8],
|
||||
) -> Result<(PathCow<'a>, Arc<Auth>)> {
|
||||
ensure!(bytes.first() == Some(&b'@'), "Hub URL requires an `@` parent marker");
|
||||
let end = bytes[1..]
|
||||
.iter()
|
||||
.position(|&b| b == b'/')
|
||||
.map(|i| i + 1)
|
||||
.ok_or_else(|| anyhow!("Hub URL requires a path delimiter"))?;
|
||||
|
||||
let path = Self::decode_path(AuthKind::Hub, tilde, &bytes[end + 1..])?;
|
||||
let depth = path.components().auth_depth();
|
||||
if depth == 0 {
|
||||
ensure!(bytes[1..end].is_empty(), "Hub URL has too many parent domains");
|
||||
return Ok((path, auth));
|
||||
}
|
||||
|
||||
let (mut count, mut parent) = (0, None);
|
||||
for domain in bytes[1..end].rsplit(|&b| b == b',') {
|
||||
count += 1;
|
||||
parent = Some(Arc::new(Auth {
|
||||
kind: auth.kind,
|
||||
scheme: auth.scheme.clone(),
|
||||
domain: Domain::from(percent_decode(domain).decode_utf8()?).into_owned(),
|
||||
parent,
|
||||
}));
|
||||
}
|
||||
|
||||
ensure!(count == depth, "Hub URL parent depth does not match its path");
|
||||
Arc::make_mut(&mut auth).parent = parent;
|
||||
Ok((path, auth))
|
||||
}
|
||||
|
||||
fn normalize_ports(
|
||||
kind: AuthKind,
|
||||
uri: Option<usize>,
|
||||
|
|
@ -107,7 +144,7 @@ impl Spec {
|
|||
(uri, urn)
|
||||
}
|
||||
AuthKind::Mount => (uri.unwrap_or(0), urn.unwrap_or(0)),
|
||||
AuthKind::Scope | AuthKind::Sftp => {
|
||||
AuthKind::Hub | AuthKind::Scope | AuthKind::Sftp => {
|
||||
let uri = uri.unwrap_or(path.name().is_some() as usize);
|
||||
let urn = urn.unwrap_or(path.name().is_some() as usize);
|
||||
(uri, urn)
|
||||
|
|
@ -118,7 +155,7 @@ impl Spec {
|
|||
pub fn retrieve_ports(url: Url) -> (usize, usize) {
|
||||
match url {
|
||||
Url::Regular(loc) => (loc.file_name().is_some() as usize, loc.file_name().is_some() as usize),
|
||||
Url::Search { loc, .. } | Url::Mount { loc, .. } => {
|
||||
Url::Search { loc, .. } | Url::Mount { loc, .. } | Url::Hub { loc, .. } => {
|
||||
(loc.uri().components().count(), loc.urn().components().count())
|
||||
}
|
||||
Url::Scope { loc, .. } | Url::Sftp { loc, .. } => {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ impl From<AuthKind> for StrandKind {
|
|||
AuthKind::Regular => Self::Os,
|
||||
AuthKind::Search => Self::Os,
|
||||
AuthKind::Mount => Self::Os,
|
||||
AuthKind::Hub => Self::Os,
|
||||
AuthKind::Scope => Self::Bytes,
|
||||
AuthKind::Sftp => Self::Bytes,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ pub fn init_tests() {
|
|||
|
||||
inventory::submit! {
|
||||
AuthInventory {
|
||||
get: |scheme, domain| match (scheme.as_str(), domain) {
|
||||
get: |scheme, domain| match (scheme.as_str(), domain.as_ref()) {
|
||||
("test-mount", "7z") => Some(Auth::new(AuthKind::Mount, scheme.clone(), "7z")),
|
||||
("test-hub", _) => Some(Auth::new(AuthKind::Hub, scheme.clone(), domain.clone())),
|
||||
("test-scope", "aws") => Some(Auth::new(AuthKind::Scope, scheme.clone(), "aws")),
|
||||
("sftp", "vps") => Some(Auth::new(AuthKind::Sftp, scheme.clone(), "vps")),
|
||||
_ => None,
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ use serde::{Deserialize, Serialize, de::{self, IntoDeserializer}};
|
|||
use yazi_codegen::FromLuaOwned;
|
||||
use yazi_macro::impl_data_any;
|
||||
|
||||
use crate::{auth::Auth, loc::LocBuf, path::{PathBufDyn, PathDynError, SetNameError}, spec::Spec, strand::AsStrand, url::{AsUrl, Url, UrlCow, UrlDeserializer, UrlLike}};
|
||||
use crate::{auth::{Auth, Domain}, loc::LocBuf, path::{PathBufDyn, PathDynError, SetNameError}, spec::Spec, strand::AsStrand, url::{AsUrl, Url, UrlCow, UrlDeserializer, UrlLike}};
|
||||
|
||||
#[derive(Clone, Eq, FromLuaOwned)]
|
||||
pub enum UrlBuf {
|
||||
Regular(LocBuf),
|
||||
Search { loc: LocBuf, auth: Arc<Auth> },
|
||||
Mount { loc: LocBuf, auth: Arc<Auth> },
|
||||
Hub { loc: LocBuf, auth: Arc<Auth> },
|
||||
Scope { loc: LocBuf<typed_path::UnixPathBuf>, auth: Arc<Auth> },
|
||||
Sftp { loc: LocBuf<typed_path::UnixPathBuf>, auth: Arc<Auth> },
|
||||
}
|
||||
|
|
@ -33,6 +34,7 @@ impl From<Url<'_>> for UrlBuf {
|
|||
Url::Regular(loc) => Self::Regular(loc.into()),
|
||||
Url::Search { loc, auth } => Self::Search { loc: loc.into(), auth: auth.clone() },
|
||||
Url::Mount { loc, auth } => Self::Mount { loc: loc.into(), auth: auth.clone() },
|
||||
Url::Hub { loc, auth } => Self::Hub { loc: loc.into(), auth: auth.clone() },
|
||||
Url::Scope { loc, auth } => Self::Scope { loc: loc.into(), auth: auth.clone() },
|
||||
Url::Sftp { loc, auth } => Self::Sftp { loc: loc.into(), auth: auth.clone() },
|
||||
}
|
||||
|
|
@ -140,6 +142,7 @@ impl UrlBuf {
|
|||
Self::Regular(loc) => loc.into_inner().into(),
|
||||
Self::Search { loc, .. } => loc.into_inner().into(),
|
||||
Self::Mount { loc, .. } => loc.into_inner().into(),
|
||||
Self::Hub { loc, .. } => loc.into_inner().into(),
|
||||
Self::Scope { loc, .. } => loc.into_inner().into(),
|
||||
Self::Sftp { loc, .. } => loc.into_inner().into(),
|
||||
}
|
||||
|
|
@ -156,6 +159,7 @@ impl UrlBuf {
|
|||
Self::Regular(loc) => loc.try_set_name(name.as_os()?)?,
|
||||
Self::Search { loc, .. } => loc.try_set_name(name.as_os()?)?,
|
||||
Self::Mount { loc, .. } => loc.try_set_name(name.as_os()?)?,
|
||||
Self::Hub { loc, .. } => loc.try_set_name(name.as_os()?)?,
|
||||
Self::Scope { loc, .. } => loc.try_set_name(name.encoded_bytes())?,
|
||||
Self::Sftp { loc, .. } => loc.try_set_name(name.encoded_bytes())?,
|
||||
})
|
||||
|
|
@ -166,6 +170,7 @@ impl UrlBuf {
|
|||
Self::Regular(loc) => Self::Regular(loc.rebase(base)),
|
||||
Self::Search { loc, auth } => Self::Search { loc: loc.rebase(base), auth: auth.clone() },
|
||||
Self::Mount { loc, auth } => Self::Mount { loc: loc.rebase(base), auth: auth.clone() },
|
||||
Self::Hub { .. } => todo!(),
|
||||
Self::Scope { .. } => todo!(),
|
||||
Self::Sftp { .. } => todo!(),
|
||||
}
|
||||
|
|
@ -188,6 +193,18 @@ impl UrlBuf {
|
|||
auth: Auth::search(query.as_ref()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_domain<'a>(mut self, domain: impl Into<Domain<'a>>) -> Self {
|
||||
match &mut self {
|
||||
Self::Regular(_) => {}
|
||||
Self::Search { auth, .. }
|
||||
| Self::Mount { auth, .. }
|
||||
| Self::Hub { auth, .. }
|
||||
| Self::Scope { auth, .. }
|
||||
| Self::Sftp { auth, .. } => Arc::make_mut(auth).domain = domain.into().into_owned(),
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for UrlBuf {
|
||||
|
|
@ -265,10 +282,14 @@ impl<'de> IntoDeserializer<'de, de::value::Error> for &'de UrlBuf {
|
|||
// --- Tests
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fmt::Debug;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use super::*;
|
||||
use crate::url::UrlLike;
|
||||
use crate::{path::PathKind, url::UrlLike};
|
||||
|
||||
fn debug(value: impl Debug) -> String { format!("{value:?}").replace(r"\", "/") }
|
||||
|
||||
#[test]
|
||||
fn test_join() -> anyhow::Result<()> {
|
||||
|
|
@ -293,13 +314,7 @@ mod tests {
|
|||
|
||||
for (base, path, expected) in cases {
|
||||
let base: UrlBuf = base.parse()?;
|
||||
#[cfg(unix)]
|
||||
assert_eq!(format!("{:?}", base.try_join(path)?), expected);
|
||||
#[cfg(windows)]
|
||||
assert_eq!(
|
||||
format!("{:?}", base.try_join(path)?).replace(r"\", "/"),
|
||||
expected.replace(r"\", "/")
|
||||
);
|
||||
assert_eq!(debug(base.try_join(path)?), expected);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -312,6 +327,8 @@ mod tests {
|
|||
// Regular
|
||||
("/a", Some("/")),
|
||||
("/", None),
|
||||
("a", Some("")),
|
||||
("", None),
|
||||
// Search
|
||||
("search://kw:2:2//a/b/c", Some("search://kw:1:1//a/b")),
|
||||
("search://kw:1:1//a/b", Some("search://kw//a")),
|
||||
|
|
@ -329,13 +346,16 @@ mod tests {
|
|||
("sftp://vps//", None),
|
||||
// Relative
|
||||
("search://kw:2:2/a/b", Some("search://kw:1:1/a")),
|
||||
("search://kw:1:1/a", None),
|
||||
("search://kw:1:1/a", Some("search://kw/")),
|
||||
("search://kw/", None),
|
||||
("test-mount://7z:1:1/a", Some("test-mount://7z/")),
|
||||
("test-scope://aws/a", Some("test-scope://aws/")),
|
||||
("sftp://vps/a", Some("sftp://vps/")),
|
||||
];
|
||||
|
||||
for (path, expected) in cases {
|
||||
let path: UrlBuf = path.parse()?;
|
||||
assert_eq!(path.parent().map(|u| format!("{u:?}")).as_deref(), expected);
|
||||
assert_eq!(path.parent().map(debug).as_deref(), expected);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -344,29 +364,166 @@ mod tests {
|
|||
#[test]
|
||||
fn test_into_search() -> Result<()> {
|
||||
crate::init_tests();
|
||||
const S: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
let u: UrlBuf = "/root".parse()?;
|
||||
assert_eq!(format!("{u:?}"), "/root");
|
||||
assert_eq!(debug(&u), "/root");
|
||||
|
||||
let u = u.into_search("kw")?;
|
||||
assert_eq!(format!("{u:?}"), "search://kw//root");
|
||||
assert_eq!(format!("{:?}", u.parent().unwrap()), "/");
|
||||
assert_eq!(debug(&u), "search://kw//root");
|
||||
assert_eq!(debug(u.parent().unwrap()), "/");
|
||||
|
||||
let u = u.try_join("examples")?;
|
||||
assert_eq!(format!("{u:?}"), format!("search://kw:1:1//root{S}examples"));
|
||||
assert_eq!(debug(&u), "search://kw:1:1//root/examples");
|
||||
|
||||
let u = u.try_join("README.md")?;
|
||||
assert_eq!(format!("{u:?}"), format!("search://kw:2:2//root{S}examples{S}README.md"));
|
||||
assert_eq!(debug(&u), "search://kw:2:2//root/examples/README.md");
|
||||
|
||||
let u = u.parent().unwrap();
|
||||
assert_eq!(format!("{u:?}"), format!("search://kw:1:1//root{S}examples"));
|
||||
assert_eq!(debug(u), "search://kw:1:1//root/examples");
|
||||
|
||||
let u = u.parent().unwrap();
|
||||
assert_eq!(format!("{u:?}"), "search://kw//root");
|
||||
assert_eq!(debug(u), "search://kw//root");
|
||||
|
||||
let u = u.parent().unwrap();
|
||||
assert_eq!(format!("{u:?}"), "/");
|
||||
assert_eq!(debug(u), "/");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hub_parse() -> Result<()> {
|
||||
crate::init_tests();
|
||||
|
||||
let root: UrlBuf = "test-hub://root/@/".parse()?;
|
||||
assert_eq!(debug(&root), "test-hub://root/@/");
|
||||
assert_eq!(root.loc().kind(), PathKind::Os);
|
||||
|
||||
let encoded: UrlBuf = "test-hub://%252C/@/".parse()?;
|
||||
assert_eq!(debug(encoded), "test-hub://%252C/@/");
|
||||
let encoded: UrlBuf = "test-hub://b1/@a%2Cb%40c%25d%2Fe,root/foo/bar".parse()?;
|
||||
assert_eq!(debug(encoded), "test-hub://b1/@a%2Cb%40c%25d%2Fe,root/foo/bar");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hub_domain() -> Result<()> {
|
||||
crate::init_tests();
|
||||
|
||||
let root: UrlBuf = "test-hub://root/@/".parse()?;
|
||||
let foo = root.try_join("foo")?.into_domain("a1");
|
||||
assert_eq!(debug(&foo), "test-hub://a1/@root/foo");
|
||||
|
||||
let bar = foo.try_join("bar")?.into_domain("b1");
|
||||
assert_eq!(bar.entry_key(), "b1");
|
||||
assert_eq!(debug(&bar), "test-hub://b1/@a1,root/foo/bar");
|
||||
assert_eq!(debug(bar.parent().unwrap()), "test-hub://a1/@root/foo");
|
||||
assert_eq!(debug(bar.parent().unwrap().parent().unwrap()), "test-hub://root/@/");
|
||||
|
||||
let relative = UrlCow::try_from("test-hub://a1/@/@abc")?;
|
||||
assert_eq!(debug(relative.as_url()), "test-hub://a1/@/@abc");
|
||||
assert_eq!(debug(relative.parent().unwrap().as_url()), "test-hub:///@/");
|
||||
assert_eq!(relative.entry_key(), "a1");
|
||||
assert!(!relative.is_owned());
|
||||
assert!(relative.parent().unwrap().entry_key().is_empty());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hub_join() -> Result<()> {
|
||||
crate::init_tests();
|
||||
|
||||
let root: UrlBuf = "test-hub://root/@/".parse()?;
|
||||
let bar: UrlBuf = "test-hub://b1/@a1,root/foo/bar".parse()?;
|
||||
|
||||
assert_eq!(debug(bar.try_join(".")?), "test-hub://b1/@a1,root/foo/bar");
|
||||
assert_eq!(debug(bar.try_join("..")?), "test-hub:///@b1,a1,root/foo/bar/..");
|
||||
|
||||
assert_eq!(debug(bar.try_join("/x/y")?), "test-hub:///@,//x/y");
|
||||
assert_eq!(debug(root.try_join("../../..")?), "test-hub:///@,,root/../../..");
|
||||
|
||||
let absolute = root.try_join("/foo")?;
|
||||
assert_eq!(debug(&absolute), "test-hub:///@//foo");
|
||||
let absolute = absolute.into_domain("a1");
|
||||
assert_eq!(debug(&absolute), "test-hub://a1/@//foo");
|
||||
assert_eq!(debug(absolute.parent().unwrap().try_join("..")?), "test-hub:///@//..");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hub_ports() -> Result<()> {
|
||||
crate::init_tests();
|
||||
|
||||
let ports: UrlBuf = "test-hub://b1:2:1/@a1,root/foo/bar".parse()?;
|
||||
assert_eq!(debug(ports.base()), "test-hub://root/@/");
|
||||
assert_eq!(debug(ports.trail()), "test-hub://a1/@root/foo");
|
||||
|
||||
let ports: UrlBuf = "test-hub://b1:3:1/@a1,root//foo/bar".parse()?;
|
||||
assert_eq!(debug(ports.base()), "test-hub://root/@/");
|
||||
assert_eq!(debug(ports.trail()), "test-hub://a1/@root//foo");
|
||||
|
||||
let zeroed: UrlBuf = "test-hub://b1:0:0/@a1,root/foo/bar".parse()?;
|
||||
assert_eq!(debug(zeroed.base()), "test-hub://b1/@a1,root/foo/bar");
|
||||
assert_eq!(debug(zeroed.trail()), "test-hub://b1/@a1,root/foo/bar");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hub_invalid() {
|
||||
crate::init_tests();
|
||||
|
||||
assert!("test-hub://a1/foo".parse::<UrlBuf>().is_err());
|
||||
assert!("test-hub://b1/@a1/foo/bar".parse::<UrlBuf>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hub_replace() -> Result<()> {
|
||||
crate::init_tests();
|
||||
|
||||
let url: UrlBuf = "test-hub://b1/@a1,root/foo/bar".parse()?;
|
||||
assert_eq!(
|
||||
debug(url.try_replace(2, Path::new("baz/qux"))?.as_url()),
|
||||
"test-hub://b1:2:2/@,a1,root/foo/baz/qux"
|
||||
);
|
||||
assert_eq!(debug(url.try_replace(1, Path::new("qux"))?.as_url()), "test-hub://b1/@root/qux");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_hub_windows() -> Result<()> {
|
||||
crate::init_tests();
|
||||
|
||||
let root: UrlBuf = "test-hub://root/@/".parse()?;
|
||||
let c = root.try_join(r"C:\")?.into_domain("c-root");
|
||||
assert_eq!(c.entry_key(), "c-root");
|
||||
assert_eq!(c.auth().parent_depth(), 0);
|
||||
|
||||
let drive = c.try_join(r"Users\file.txt")?.into_domain("file");
|
||||
assert_eq!(drive.loc(), Path::new(r"C:\Users\file.txt"));
|
||||
assert_eq!(drive.auth().parent_depth(), 2);
|
||||
|
||||
let parent = drive.parent().unwrap();
|
||||
assert_eq!(parent.loc(), Path::new(r"C:\Users"));
|
||||
|
||||
let parent = parent.parent().unwrap();
|
||||
assert_eq!(parent.loc(), Path::new(r"C:\"));
|
||||
assert_eq!(parent.entry_key(), "c-root");
|
||||
assert!(parent.parent().is_none());
|
||||
|
||||
let relative = root.try_join(r"C:foo")?;
|
||||
assert!(!relative.is_absolute());
|
||||
assert_eq!(relative.parent().unwrap().loc(), Path::new("C:"));
|
||||
assert!(relative.parent().unwrap().parent().is_none());
|
||||
|
||||
let unc = root.try_join(r"\\server\share\dir\file")?;
|
||||
assert!(unc.is_absolute());
|
||||
assert_eq!(unc.loc(), Path::new(r"\\server\share\dir\file"));
|
||||
assert_eq!(unc.auth().parent_depth(), 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use std::{borrow::Cow, ffi::{OsStr, OsString}, iter::FusedIterator, ops::Not};
|
||||
|
||||
use crate::{auth::Auth, loc::Loc, path, spec::{Encode as EncodeSpec, Spec}, strand::{StrandBuf, StrandCow}, url::{Component, Encode as EncodeUrl, Url}};
|
||||
use crate::{auth::Auth, loc::Loc, path, spec::{EncodeSpec, Spec}, strand::{StrandBuf, StrandCow}, url::{Component, Encode as EncodeUrl, Url}};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Components<'a> {
|
||||
inner: path::Components<'a>,
|
||||
url: Url<'a>,
|
||||
auth_yields: usize,
|
||||
back_yields: usize,
|
||||
scheme_yielded: bool,
|
||||
}
|
||||
|
|
@ -15,6 +16,7 @@ impl<'a> From<Url<'a>> for Components<'a> {
|
|||
Self {
|
||||
inner: value.loc().components(),
|
||||
url: value,
|
||||
auth_yields: 0,
|
||||
back_yields: 0,
|
||||
scheme_yielded: false,
|
||||
}
|
||||
|
|
@ -83,6 +85,10 @@ impl<'a> Components<'a> {
|
|||
Url::Mount { auth, .. } => {
|
||||
Url::Mount { loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(), auth }
|
||||
}
|
||||
Url::Hub { auth, .. } => Url::Hub {
|
||||
loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(),
|
||||
auth: auth.parent_at(self.auth_yields),
|
||||
},
|
||||
Url::Scope { auth, .. } => {
|
||||
Url::Scope { loc: Loc::with(path.as_unix().unwrap(), uri, urn).unwrap(), auth }
|
||||
}
|
||||
|
|
@ -116,6 +122,7 @@ impl<'a> Iterator for Components<'a> {
|
|||
impl<'a> DoubleEndedIterator for Components<'a> {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
if let Some(c) = self.inner.next_back() {
|
||||
self.auth_yields += c.has_auth() as usize;
|
||||
self.back_yields += 1;
|
||||
Some(c.into())
|
||||
} else if !self.scheme_yielded {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
use std::{borrow::Cow, hash::{Hash, Hasher}, path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{Result, ensure};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use typed_path::{UnixPath, UnixPathBuf};
|
||||
|
||||
use crate::{auth::{Auth, AuthKind}, loc::{Loc, LocBuf, LocCow}, path::{PathBufDyn, PathCow, PathDyn}, spec::Spec, url::{AsUrl, Url, UrlBuf}};
|
||||
use crate::{auth::{Auth, AuthKind}, loc::{Loc, LocBuf, LocCow}, path::{DynPath, PathBufDyn, PathCow, PathDyn}, spec::Spec, url::{AsUrl, Url, UrlBuf}};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum UrlCow<'a> {
|
||||
Regular(LocCow<'a>),
|
||||
Search { loc: LocCow<'a>, auth: Arc<Auth> },
|
||||
Mount { loc: LocCow<'a>, auth: Arc<Auth> },
|
||||
Hub { loc: LocCow<'a>, auth: Arc<Auth> },
|
||||
Scope { loc: LocCow<'a, &'a UnixPath, UnixPathBuf>, auth: Arc<Auth> },
|
||||
Sftp { loc: LocCow<'a, &'a UnixPath, UnixPathBuf>, auth: Arc<Auth> },
|
||||
}
|
||||
|
|
@ -21,6 +22,7 @@ impl<'a> From<Url<'a>> for UrlCow<'a> {
|
|||
Url::Regular(loc) => Self::Regular(loc.into()),
|
||||
Url::Search { loc, auth } => Self::Search { loc: loc.into(), auth: auth.clone() },
|
||||
Url::Mount { loc, auth } => Self::Mount { loc: loc.into(), auth: auth.clone() },
|
||||
Url::Hub { loc, auth } => Self::Hub { loc: loc.into(), auth: auth.clone() },
|
||||
Url::Scope { loc, auth } => Self::Scope { loc: loc.into(), auth: auth.clone() },
|
||||
Url::Sftp { loc, auth } => Self::Sftp { loc: loc.into(), auth: auth.clone() },
|
||||
}
|
||||
|
|
@ -40,6 +42,7 @@ impl From<UrlBuf> for UrlCow<'_> {
|
|||
UrlBuf::Regular(loc) => Self::Regular(loc.into()),
|
||||
UrlBuf::Search { loc, auth } => Self::Search { loc: loc.into(), auth },
|
||||
UrlBuf::Mount { loc, auth } => Self::Mount { loc: loc.into(), auth },
|
||||
UrlBuf::Hub { loc, auth } => Self::Hub { loc: loc.into(), auth },
|
||||
UrlBuf::Scope { loc, auth } => Self::Scope { loc: loc.into(), auth },
|
||||
UrlBuf::Sftp { loc, auth } => Self::Sftp { loc: loc.into(), auth },
|
||||
}
|
||||
|
|
@ -113,10 +116,13 @@ impl<'a> TryFrom<(Spec, PathDyn<'a>)> for UrlCow<'a> {
|
|||
|
||||
fn try_from((spec, path): (Spec, PathDyn<'a>)) -> Result<Self, Self::Error> {
|
||||
let Spec { auth, uri, urn } = spec;
|
||||
validate_auth_depth(&auth, path)?;
|
||||
|
||||
Ok(match auth.kind {
|
||||
AuthKind::Regular => Self::Regular(Loc::bare(path.as_os()?).into()),
|
||||
AuthKind::Search => Self::Search { loc: Loc::with(path.as_os()?, uri, urn)?.into(), auth },
|
||||
AuthKind::Mount => Self::Mount { loc: Loc::with(path.as_os()?, uri, urn)?.into(), auth },
|
||||
AuthKind::Hub => Self::Hub { loc: Loc::with(path.as_os()?, uri, urn)?.into(), auth },
|
||||
AuthKind::Scope => Self::Scope { loc: Loc::with(path.as_unix()?, uri, urn)?.into(), auth },
|
||||
AuthKind::Sftp => Self::Sftp { loc: Loc::with(path.as_unix()?, uri, urn)?.into(), auth },
|
||||
})
|
||||
|
|
@ -128,6 +134,8 @@ impl<'a> TryFrom<(Spec, PathBufDyn)> for UrlCow<'a> {
|
|||
|
||||
fn try_from((spec, path): (Spec, PathBufDyn)) -> Result<Self, Self::Error> {
|
||||
let Spec { auth, uri, urn } = spec;
|
||||
validate_auth_depth(&auth, path.dyn_path())?;
|
||||
|
||||
Ok(match auth.kind {
|
||||
AuthKind::Regular => {
|
||||
Self::Regular(LocBuf::<std::path::PathBuf>::from(path.into_os()?).into())
|
||||
|
|
@ -140,6 +148,9 @@ impl<'a> TryFrom<(Spec, PathBufDyn)> for UrlCow<'a> {
|
|||
loc: LocBuf::<std::path::PathBuf>::with(path.try_into()?, uri, urn)?.into(),
|
||||
auth,
|
||||
},
|
||||
AuthKind::Hub => {
|
||||
Self::Hub { loc: LocBuf::<PathBuf>::with(path.try_into()?, uri, urn)?.into(), auth }
|
||||
}
|
||||
AuthKind::Scope => {
|
||||
Self::Scope { loc: LocBuf::<UnixPathBuf>::with(path.try_into()?, uri, urn)?.into(), auth }
|
||||
}
|
||||
|
|
@ -172,6 +183,7 @@ impl<'a> UrlCow<'a> {
|
|||
Self::Regular(loc) => loc.is_owned(),
|
||||
Self::Search { loc, .. } => loc.is_owned(),
|
||||
Self::Mount { loc, .. } => loc.is_owned(),
|
||||
Self::Hub { loc, .. } => loc.is_owned(),
|
||||
Self::Scope { loc, .. } => loc.is_owned(),
|
||||
Self::Sftp { loc, .. } => loc.is_owned(),
|
||||
}
|
||||
|
|
@ -182,6 +194,7 @@ impl<'a> UrlCow<'a> {
|
|||
Self::Regular(loc) => UrlBuf::Regular(loc.into_owned()),
|
||||
Self::Search { loc, auth } => UrlBuf::Search { loc: loc.into_owned(), auth },
|
||||
Self::Mount { loc, auth } => UrlBuf::Mount { loc: loc.into_owned(), auth },
|
||||
Self::Hub { loc, auth } => UrlBuf::Hub { loc: loc.into_owned(), auth },
|
||||
Self::Scope { loc, auth } => UrlBuf::Scope { loc: loc.into_owned(), auth },
|
||||
Self::Sftp { loc, auth } => UrlBuf::Sftp { loc: loc.into_owned(), auth },
|
||||
}
|
||||
|
|
@ -191,7 +204,9 @@ impl<'a> UrlCow<'a> {
|
|||
let (uri, urn) = Spec::retrieve_ports(self.as_url());
|
||||
let (auth, path) = match self {
|
||||
Self::Regular(loc) => (Auth::default_arc(), loc.into_path()),
|
||||
Self::Search { loc, auth } | Self::Mount { loc, auth } => (auth, loc.into_path()),
|
||||
Self::Search { loc, auth } | Self::Mount { loc, auth } | Self::Hub { loc, auth } => {
|
||||
(auth, loc.into_path())
|
||||
}
|
||||
Self::Scope { loc, auth } | Self::Sftp { loc, auth } => (auth, loc.into_path()),
|
||||
};
|
||||
(Spec { auth, uri, urn }, path)
|
||||
|
|
@ -222,6 +237,16 @@ impl<'de> Deserialize<'de> for UrlCow<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn validate_auth_depth(auth: &Auth, path: PathDyn) -> Result<()> {
|
||||
if auth.kind == AuthKind::Hub {
|
||||
ensure!(
|
||||
auth.parent_depth() == path.components().auth_depth(),
|
||||
"Hub URL parent depth does not match its path"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::borrow::Cow;
|
|||
|
||||
use serde::{Deserializer, de::{self, IntoDeserializer, MapAccess}};
|
||||
|
||||
use crate::{data::BytesDeserializer, pool::{InternStr, Symbol}, url::UrlCow};
|
||||
use crate::{auth::Domain, data::BytesDeserializer, pool::{InternStr, Symbol}, url::UrlCow};
|
||||
|
||||
pub struct UrlDeserializer<'a>(pub(super) UrlCow<'a>);
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ impl<'de, 'a: 'de> Deserializer<'de> for UrlDeserializer<'a> {
|
|||
struct MapDeserializer<'a> {
|
||||
kind: Option<&'static str>,
|
||||
scheme: Option<Symbol<str>>,
|
||||
domain: Option<Symbol<str>>,
|
||||
domain: Option<Domain<'static>>,
|
||||
uri: Option<usize>,
|
||||
urn: Option<usize>,
|
||||
path: Option<Cow<'a, [u8]>>,
|
||||
|
|
@ -47,7 +47,7 @@ impl<'a> MapDeserializer<'a> {
|
|||
Self {
|
||||
kind: Some(spec.kind.into()),
|
||||
scheme: Some(spec.scheme.intern()),
|
||||
domain: Some(spec.domain.intern()),
|
||||
domain: Some(spec.domain.clone()),
|
||||
uri: Some(uri),
|
||||
urn: Some(urn),
|
||||
path: Some(path.into_encoded_bytes()),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{spec::Encode, url::Url};
|
||||
use crate::{spec::EncodeSpec, url::Url};
|
||||
|
||||
pub struct Display<'a>(pub Url<'a>);
|
||||
|
||||
|
|
@ -6,7 +6,7 @@ impl std::fmt::Display for Display<'_> {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let (kind, loc) = (self.0.kind(), self.0.loc());
|
||||
if kind.is_virtual() {
|
||||
Encode(self.0).fmt(f)?;
|
||||
EncodeSpec(self.0).fmt(f)?;
|
||||
}
|
||||
loc.display().fmt(f)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,36 @@
|
|||
use std::fmt::{self, Display};
|
||||
|
||||
use percent_encoding::{CONTROLS, percent_encode};
|
||||
use percent_encoding::{AsciiSet, CONTROLS, PercentEncode, percent_encode};
|
||||
|
||||
use crate::{auth::Encode as EncodeAuth, url::Url};
|
||||
use crate::{auth::{EncodeAuth, EncodePrefix}, spec::EncodeSpec, url::Url};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Encode<'a>(pub Url<'a>);
|
||||
|
||||
impl Encode<'_> {
|
||||
pub fn loc(b: &[u8]) -> PercentEncode<'_> {
|
||||
const SET: &AsciiSet = &CONTROLS.add(b'%');
|
||||
percent_encode(b, SET)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Encode<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use crate::spec::Encode as E;
|
||||
|
||||
let loc = percent_encode(self.0.loc().encoded_bytes(), CONTROLS);
|
||||
let loc = Self::loc(self.0.loc().encoded_bytes());
|
||||
match self.0 {
|
||||
Url::Regular(_) => write!(f, "regular~://{loc}"),
|
||||
Url::Search { auth, .. }
|
||||
| Url::Mount { auth, .. }
|
||||
| Url::Hub { auth, .. }
|
||||
| Url::Scope { auth, .. }
|
||||
| Url::Sftp { auth, .. } => {
|
||||
write!(f, "{}{}/{loc}", EncodeAuth(auth, true), E::ports((*self).into()))
|
||||
write!(
|
||||
f,
|
||||
"{}{}{}{loc}",
|
||||
EncodeAuth(auth, true),
|
||||
EncodeSpec::ports((*self).into()),
|
||||
EncodePrefix(auth)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{borrow::Cow, ffi::OsStr, path::Path};
|
|||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{auth::{Auth, AuthKind}, path::{AsPathRef, EndsWithError, JoinError, PathDyn, StartsWithError, StripPrefixError}, spec::Spec, strand::{AsStrand, Strand}, url::{AsUrl, Components, Display, Url, UrlBuf, UrlCow}};
|
||||
use crate::{auth::{Auth, AuthKind}, path::{DynPathRef, EndsWithError, JoinError, PathDyn, StartsWithError, StripPrefixError}, spec::Spec, strand::{AsStrand, Strand}, url::{AsUrl, Components, Display, Url, UrlBuf, UrlCow}};
|
||||
|
||||
pub trait UrlLike
|
||||
where
|
||||
|
|
@ -22,6 +22,8 @@ where
|
|||
|
||||
fn ext(&self) -> Option<Strand<'_>> { self.as_url().ext() }
|
||||
|
||||
fn entry_key(&self) -> PathDyn<'_> { self.as_url().entry_key() }
|
||||
|
||||
fn has_base(&self) -> bool { self.as_url().has_base() }
|
||||
|
||||
fn has_root(&self) -> bool { self.as_url().has_root() }
|
||||
|
|
@ -44,6 +46,8 @@ where
|
|||
|
||||
fn pair(&self) -> Option<(Url<'_>, PathDyn<'_>)> { self.as_url().pair() }
|
||||
|
||||
fn pair2(&self) -> Option<(Url<'_>, PathDyn<'_>)> { self.as_url().pair2() }
|
||||
|
||||
fn parent(&self) -> Option<Url<'_>> { self.as_url().parent() }
|
||||
|
||||
fn spec(&self) -> Spec { self.as_url().spec() }
|
||||
|
|
@ -62,8 +66,8 @@ where
|
|||
self.as_url().try_join(path)
|
||||
}
|
||||
|
||||
fn try_replace<'a>(&self, take: usize, path: impl AsPathRef<'a>) -> Result<UrlCow<'a>> {
|
||||
self.as_url().try_replace(take, path)
|
||||
fn try_replace<'a>(&self, take: usize, to: impl DynPathRef<'a>) -> Result<UrlCow<'a>> {
|
||||
self.as_url().try_replace(take, to)
|
||||
}
|
||||
|
||||
fn try_starts_with(&self, base: impl AsUrl) -> Result<bool, StartsWithError> {
|
||||
|
|
|
|||
|
|
@ -121,6 +121,9 @@ impl UserData for UrlBuf {
|
|||
methods.add_method_once("into_search", |_, me, domain: LuaString| {
|
||||
me.into_search(domain.to_str()?).into_lua_err()
|
||||
});
|
||||
methods.add_method_once("into_domain", |_, me, domain: LuaString| {
|
||||
Ok(me.into_domain(domain.to_str()?.to_owned()))
|
||||
});
|
||||
|
||||
methods.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(*me == *other));
|
||||
methods.add_meta_method(MetaMethod::ToString, |lua, me, ()| {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ impl AsUrl for UrlBuf {
|
|||
Self::Regular(loc) => Url::Regular(loc.as_loc()),
|
||||
Self::Search { loc, auth } => Url::Search { loc: loc.as_loc(), auth },
|
||||
Self::Mount { loc, auth } => Url::Mount { loc: loc.as_loc(), auth },
|
||||
Self::Hub { loc, auth } => Url::Hub { loc: loc.as_loc(), auth },
|
||||
Self::Scope { loc, auth } => Url::Scope { loc: loc.as_loc(), auth },
|
||||
Self::Sftp { loc, auth } => Url::Sftp { loc: loc.as_loc(), auth },
|
||||
}
|
||||
|
|
@ -64,6 +65,7 @@ impl AsUrl for UrlCow<'_> {
|
|||
Self::Regular(loc) => Url::Regular(loc.as_loc()),
|
||||
Self::Search { loc, auth } => Url::Search { loc: loc.as_loc(), auth },
|
||||
Self::Mount { loc, auth } => Url::Mount { loc: loc.as_loc(), auth },
|
||||
Self::Hub { loc, auth } => Url::Hub { loc: loc.as_loc(), auth },
|
||||
Self::Scope { loc, auth } => Url::Scope { loc: loc.as_loc(), auth },
|
||||
Self::Sftp { loc, auth } => Url::Sftp { loc: loc.as_loc(), auth },
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ use hashbrown::Equivalent;
|
|||
use serde::Serialize;
|
||||
|
||||
use super::Encode as EncodeUrl;
|
||||
use crate::{auth::{Auth, AuthKind}, loc::{Loc, LocBuf}, path::{AsPath, AsPathRef, EndsWithError, JoinError, PathBufDyn, PathDyn, PathDynError, PathLike, StartsWithError, StripPrefixError, StripSuffixError}, spec::{Encode as EncodeSpec, ParsedSpec, Spec}, strand::{AsStrand, Strand}, url::{AsUrl, Components, UrlBuf, UrlCow}};
|
||||
use crate::{auth::{Auth, AuthKind}, loc::{Loc, LocBuf}, path::{DynPath, DynPathRef, EndsWithError, JoinError, PathBufDyn, PathDyn, PathDynError, PathLike, StartsWithError, StripPrefixError, StripSuffixError}, spec::{EncodeSpec, ParsedSpec, Spec}, strand::{AsStrand, Strand}, url::{AsUrl, Components, UrlBuf, UrlCow}};
|
||||
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||
pub enum Url<'a> {
|
||||
Regular(Loc<'a>),
|
||||
Search { loc: Loc<'a>, auth: &'a Arc<Auth> },
|
||||
Mount { loc: Loc<'a>, auth: &'a Arc<Auth> },
|
||||
Hub { loc: Loc<'a>, auth: &'a Arc<Auth> },
|
||||
Scope { loc: Loc<'a, &'a typed_path::UnixPath>, auth: &'a Arc<Auth> },
|
||||
Sftp { loc: Loc<'a, &'a typed_path::UnixPath>, auth: &'a Arc<Auth> },
|
||||
}
|
||||
|
|
@ -60,11 +61,22 @@ impl<'a> Url<'a> {
|
|||
Self::Regular(_) => &Auth::DEFAULT,
|
||||
Self::Search { auth, .. }
|
||||
| Self::Mount { auth, .. }
|
||||
| Self::Hub { auth, .. }
|
||||
| Self::Scope { auth, .. }
|
||||
| Self::Sftp { auth, .. } => auth,
|
||||
}
|
||||
}
|
||||
|
||||
fn auth_at(self, base: Self) -> Option<&'a Auth> {
|
||||
let (Self::Hub { auth, .. }, Self::Hub { .. }) = (self, base) else {
|
||||
return self.auth().covariant(base.auth()).then_some(self.auth());
|
||||
};
|
||||
|
||||
let depth =
|
||||
self.loc().components().auth_depth().checked_sub(base.loc().components().auth_depth())?;
|
||||
Some(auth.parent_at(depth))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_regular(self) -> Result<Self, PathDynError> {
|
||||
Ok(Self::Regular(Loc::bare(self.loc().as_os()?)))
|
||||
|
|
@ -75,6 +87,10 @@ impl<'a> Url<'a> {
|
|||
Self::Regular(loc) => Self::Regular(Loc::bare(loc.base())),
|
||||
Self::Search { loc, auth } => Self::Search { loc: Loc::zeroed(loc.base()), auth },
|
||||
Self::Mount { loc, auth } => Self::Mount { loc: Loc::zeroed(loc.base()), auth },
|
||||
Self::Hub { loc, auth } => Self::Hub {
|
||||
loc: Loc::bare(loc.base()),
|
||||
auth: auth.parent_at(loc.uri().dyn_path().components().auth_depth()),
|
||||
},
|
||||
Self::Scope { loc, auth } => Self::Scope { loc: Loc::bare(loc.base()), auth },
|
||||
Self::Sftp { loc, auth } => Self::Sftp { loc: Loc::bare(loc.base()), auth },
|
||||
}
|
||||
|
|
@ -95,6 +111,7 @@ impl<'a> Url<'a> {
|
|||
Self::Regular(loc) => loc.extension()?.as_strand(),
|
||||
Self::Search { loc, .. } => loc.extension()?.as_strand(),
|
||||
Self::Mount { loc, .. } => loc.extension()?.as_strand(),
|
||||
Self::Hub { loc, .. } => loc.extension()?.as_strand(),
|
||||
Self::Scope { loc, .. } => loc.extension()?.as_strand(),
|
||||
Self::Sftp { loc, .. } => loc.extension()?.as_strand(),
|
||||
})
|
||||
|
|
@ -106,6 +123,7 @@ impl<'a> Url<'a> {
|
|||
Self::Regular(loc) => loc.has_base(),
|
||||
Self::Search { loc, .. } => loc.has_base(),
|
||||
Self::Mount { loc, .. } => loc.has_base(),
|
||||
Self::Hub { loc, .. } => loc.has_base(),
|
||||
Self::Scope { loc, .. } => loc.has_base(),
|
||||
Self::Sftp { loc, .. } => loc.has_base(),
|
||||
}
|
||||
|
|
@ -120,6 +138,7 @@ impl<'a> Url<'a> {
|
|||
Self::Regular(loc) => loc.has_trail(),
|
||||
Self::Search { loc, .. } => loc.has_trail(),
|
||||
Self::Mount { loc, .. } => loc.has_trail(),
|
||||
Self::Hub { loc, .. } => loc.has_trail(),
|
||||
Self::Scope { loc, .. } => loc.has_trail(),
|
||||
Self::Sftp { loc, .. } => loc.has_trail(),
|
||||
}
|
||||
|
|
@ -140,11 +159,12 @@ impl<'a> Url<'a> {
|
|||
#[inline]
|
||||
pub fn loc(self) -> PathDyn<'a> {
|
||||
match self {
|
||||
Self::Regular(loc) => loc.as_path(),
|
||||
Self::Search { loc, .. } => loc.as_path(),
|
||||
Self::Mount { loc, .. } => loc.as_path(),
|
||||
Self::Scope { loc, .. } => loc.as_path(),
|
||||
Self::Sftp { loc, .. } => loc.as_path(),
|
||||
Self::Regular(loc) => loc.dyn_path(),
|
||||
Self::Search { loc, .. } => loc.dyn_path(),
|
||||
Self::Mount { loc, .. } => loc.dyn_path(),
|
||||
Self::Hub { loc, .. } => loc.dyn_path(),
|
||||
Self::Scope { loc, .. } => loc.dyn_path(),
|
||||
Self::Sftp { loc, .. } => loc.dyn_path(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -154,6 +174,7 @@ impl<'a> Url<'a> {
|
|||
Self::Regular(loc) => loc.file_name()?.as_strand(),
|
||||
Self::Search { loc, .. } => loc.file_name()?.as_strand(),
|
||||
Self::Mount { loc, .. } => loc.file_name()?.as_strand(),
|
||||
Self::Hub { loc, .. } => loc.file_name()?.as_strand(),
|
||||
Self::Scope { loc, .. } => loc.file_name()?.as_strand(),
|
||||
Self::Sftp { loc, .. } => loc.file_name()?.as_strand(),
|
||||
})
|
||||
|
|
@ -165,6 +186,19 @@ impl<'a> Url<'a> {
|
|||
#[inline]
|
||||
pub fn pair(self) -> Option<(Self, PathDyn<'a>)> { Some((self.parent()?, self.urn())) }
|
||||
|
||||
#[inline]
|
||||
pub fn pair2(self) -> Option<(Self, PathDyn<'a>)> {
|
||||
Some((self.parent()?, Some(self.entry_key()).filter(|k| !k.is_empty())?))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn entry_key(self) -> PathDyn<'a> {
|
||||
match self {
|
||||
Self::Hub { auth, .. } => PathDyn::Unix(typed_path::UnixPath::new(auth.domain.as_bytes())),
|
||||
_ => self.urn(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parent(self) -> Option<Self> {
|
||||
let uri = self.uri();
|
||||
|
||||
|
|
@ -187,6 +221,11 @@ impl<'a> Url<'a> {
|
|||
Self::Mount { loc: Loc::floated(loc.parent()?, loc.base()), auth }
|
||||
}
|
||||
|
||||
// Hub
|
||||
Self::Hub { loc, auth } => {
|
||||
Self::Hub { loc: Loc::bare(loc.parent()?), auth: auth.parent.as_ref()? }
|
||||
}
|
||||
|
||||
// Scope
|
||||
Self::Scope { loc, auth } => Self::Scope { loc: Loc::bare(loc.parent()?), auth },
|
||||
|
||||
|
|
@ -205,6 +244,7 @@ impl<'a> Url<'a> {
|
|||
Self::Regular(_) => Auth::default_arc(),
|
||||
Self::Search { auth, .. }
|
||||
| Self::Mount { auth, .. }
|
||||
| Self::Hub { auth, .. }
|
||||
| Self::Scope { auth, .. }
|
||||
| Self::Sftp { auth, .. } => auth.clone(),
|
||||
};
|
||||
|
|
@ -219,6 +259,7 @@ impl<'a> Url<'a> {
|
|||
Self::Regular(loc) => loc.file_stem()?.as_strand(),
|
||||
Self::Search { loc, .. } => loc.file_stem()?.as_strand(),
|
||||
Self::Mount { loc, .. } => loc.file_stem()?.as_strand(),
|
||||
Self::Hub { loc, .. } => loc.file_stem()?.as_strand(),
|
||||
Self::Scope { loc, .. } => loc.file_stem()?.as_strand(),
|
||||
Self::Sftp { loc, .. } => loc.file_stem()?.as_strand(),
|
||||
})
|
||||
|
|
@ -253,6 +294,11 @@ impl<'a> Url<'a> {
|
|||
Self::Mount { loc: Loc::new(loc.trail(), loc.base(), loc.base()), auth }
|
||||
}
|
||||
|
||||
Self::Hub { loc, auth } => Self::Hub {
|
||||
loc: Loc::bare(loc.trail()),
|
||||
auth: auth.parent_at(loc.urn().dyn_path().components().auth_depth()),
|
||||
},
|
||||
|
||||
Self::Scope { loc, auth } => Self::Scope { loc: Loc::bare(loc.trail()), auth },
|
||||
|
||||
Self::Sftp { loc, auth } => Self::Sftp { loc: Loc::bare(loc.trail()), auth },
|
||||
|
|
@ -261,13 +307,16 @@ impl<'a> Url<'a> {
|
|||
|
||||
pub fn triple(self) -> (PathDyn<'a>, PathDyn<'a>, PathDyn<'a>) {
|
||||
match self {
|
||||
Self::Regular(loc) | Self::Search { loc, .. } | Self::Mount { loc, .. } => {
|
||||
Self::Regular(loc)
|
||||
| Self::Search { loc, .. }
|
||||
| Self::Mount { loc, .. }
|
||||
| Self::Hub { loc, .. } => {
|
||||
let (base, rest, urn) = loc.triple();
|
||||
(base.as_path(), rest.as_path(), urn.as_path())
|
||||
(base.dyn_path(), rest.dyn_path(), urn.dyn_path())
|
||||
}
|
||||
Self::Scope { loc, .. } | Self::Sftp { loc, .. } => {
|
||||
let (base, rest, urn) = loc.triple();
|
||||
(base.as_path(), rest.as_path(), urn.as_path())
|
||||
(base.dyn_path(), rest.dyn_path(), urn.dyn_path())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -279,6 +328,7 @@ impl<'a> Url<'a> {
|
|||
}
|
||||
|
||||
pub fn try_join(self, path: impl AsStrand) -> Result<UrlBuf, JoinError> {
|
||||
let path = path.as_strand();
|
||||
let joined = self.loc().try_join(path)?;
|
||||
|
||||
Ok(match self {
|
||||
|
|
@ -300,6 +350,11 @@ impl<'a> Url<'a> {
|
|||
UrlBuf::Mount { loc: LocBuf::<PathBuf>::zeroed(joined.into_os()?), auth: auth.clone() }
|
||||
}
|
||||
|
||||
Self::Hub { auth, .. } => UrlBuf::Hub {
|
||||
loc: joined.into_os()?.into(),
|
||||
auth: auth.clone().descend(path.as_os_path()?),
|
||||
},
|
||||
|
||||
Self::Scope { auth, .. } => {
|
||||
UrlBuf::Scope { loc: joined.into_unix()?.into(), auth: auth.clone() }
|
||||
}
|
||||
|
|
@ -310,8 +365,8 @@ impl<'a> Url<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn try_replace<'b>(self, take: usize, to: impl AsPathRef<'b>) -> Result<UrlCow<'b>> {
|
||||
self.try_replace_impl(take, to.as_path_ref())
|
||||
pub fn try_replace<'b>(self, take: usize, to: impl DynPathRef<'b>) -> Result<UrlCow<'b>> {
|
||||
self.try_replace_impl(take, to.dyn_path_ref())
|
||||
}
|
||||
|
||||
fn try_replace_impl<'b>(self, take: usize, rep: PathDyn<'b>) -> Result<UrlCow<'b>> {
|
||||
|
|
@ -337,6 +392,10 @@ impl<'a> Url<'a> {
|
|||
loc: LocBuf::<std::path::PathBuf>::new(path.into_os()?, loc.base(), loc.trail()),
|
||||
auth: auth.clone(),
|
||||
},
|
||||
Self::Hub { loc, auth } if path.try_starts_with(loc.trail())? => UrlBuf::Hub {
|
||||
auth: auth.clone().with_parent_depth(path.components().auth_depth()),
|
||||
loc: LocBuf::<PathBuf>::new(path.into_os()?, loc.base(), loc.trail()),
|
||||
},
|
||||
Self::Scope { loc, auth } if path.try_starts_with(loc.trail())? => UrlBuf::Scope {
|
||||
loc: LocBuf::<typed_path::UnixPathBuf>::new(path.into_unix()?, loc.base(), loc.trail()),
|
||||
auth: auth.clone(),
|
||||
|
|
@ -354,6 +413,10 @@ impl<'a> Url<'a> {
|
|||
loc: LocBuf::<std::path::PathBuf>::saturated(path.into_os()?, self.kind()),
|
||||
auth: auth.clone(),
|
||||
},
|
||||
Self::Hub { auth, .. } => UrlBuf::Hub {
|
||||
auth: auth.clone().with_parent_depth(path.components().auth_depth()),
|
||||
loc: LocBuf::<PathBuf>::saturated(path.into_os()?, self.kind()),
|
||||
},
|
||||
Self::Scope { auth, .. } => UrlBuf::Scope {
|
||||
loc: LocBuf::<typed_path::UnixPathBuf>::saturated(path.into_unix()?, self.kind()),
|
||||
auth: auth.clone(),
|
||||
|
|
@ -367,10 +430,12 @@ impl<'a> Url<'a> {
|
|||
Ok(url.into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn try_starts_with(self, base: impl AsUrl) -> Result<bool, StartsWithError> {
|
||||
let base = base.as_url();
|
||||
Ok(self.loc().try_starts_with(base.loc())? && self.auth().covariant(base.auth()))
|
||||
Ok(
|
||||
self.loc().try_starts_with(base.loc())?
|
||||
&& self.auth_at(base).is_some_and(|a| a == base.auth()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn try_strip_prefix(self, base: impl AsUrl) -> Result<PathDyn<'a>, StripPrefixError> {
|
||||
|
|
@ -379,7 +444,7 @@ impl<'a> Url<'a> {
|
|||
|
||||
let base = base.as_url();
|
||||
let prefix = self.loc().try_strip_prefix(base.loc())?;
|
||||
if self.auth().covariant(base.auth()) {
|
||||
if self.auth_at(base).is_some_and(|a| a == base.auth()) {
|
||||
return Ok(prefix);
|
||||
}
|
||||
|
||||
|
|
@ -420,22 +485,24 @@ impl<'a> Url<'a> {
|
|||
#[inline]
|
||||
pub fn uri(self) -> PathDyn<'a> {
|
||||
match self {
|
||||
Self::Regular(loc) => loc.uri().as_path(),
|
||||
Self::Search { loc, .. } => loc.uri().as_path(),
|
||||
Self::Mount { loc, .. } => loc.uri().as_path(),
|
||||
Self::Scope { loc, .. } => loc.uri().as_path(),
|
||||
Self::Sftp { loc, .. } => loc.uri().as_path(),
|
||||
Self::Regular(loc) => loc.uri().dyn_path(),
|
||||
Self::Search { loc, .. } => loc.uri().dyn_path(),
|
||||
Self::Mount { loc, .. } => loc.uri().dyn_path(),
|
||||
Self::Hub { loc, .. } => loc.uri().dyn_path(),
|
||||
Self::Scope { loc, .. } => loc.uri().dyn_path(),
|
||||
Self::Sftp { loc, .. } => loc.uri().dyn_path(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn urn(self) -> PathDyn<'a> {
|
||||
match self {
|
||||
Self::Regular(loc) => loc.urn().as_path(),
|
||||
Self::Search { loc, .. } => loc.urn().as_path(),
|
||||
Self::Mount { loc, .. } => loc.urn().as_path(),
|
||||
Self::Scope { loc, .. } => loc.urn().as_path(),
|
||||
Self::Sftp { loc, .. } => loc.urn().as_path(),
|
||||
Self::Regular(loc) => loc.urn().dyn_path(),
|
||||
Self::Search { loc, .. } => loc.urn().dyn_path(),
|
||||
Self::Mount { loc, .. } => loc.urn().dyn_path(),
|
||||
Self::Hub { loc, .. } => loc.urn().dyn_path(),
|
||||
Self::Scope { loc, .. } => loc.urn().dyn_path(),
|
||||
Self::Sftp { loc, .. } => loc.urn().dyn_path(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ impl FileBuilder for Demand {
|
|||
AuthKind::Regular | AuthKind::Search => {
|
||||
self.0.build::<yazi_fs::engine::local::Demand>().open(url).await?.into()
|
||||
}
|
||||
AuthKind::Mount | AuthKind::Scope => {
|
||||
AuthKind::Mount | AuthKind::Hub | AuthKind::Scope => {
|
||||
self.0.build::<super::lua::Demand>().open(url).await?.into()
|
||||
}
|
||||
AuthKind::Sftp => self.0.build::<super::sftp::Demand>().open(url).await?.into(),
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ where
|
|||
|
||||
match (from.kind().is_local(), to.kind().is_local()) {
|
||||
(true, true) => Local::new(from).await?.copy(to.loc(), attrs).await,
|
||||
(false, false) if from.auth().covariant(to.auth()) => {
|
||||
(false, false) if from.auth().same_service(to.auth()) => {
|
||||
Engines::new(from).await?.copy(to.loc(), attrs).await
|
||||
}
|
||||
(true, false) | (false, true) | (false, false) => super::copy_impl(from, to, attrs).await,
|
||||
|
|
@ -75,7 +75,7 @@ where
|
|||
let (from, to) = (from.as_url(), to.as_url());
|
||||
let attrs = attrs.into();
|
||||
|
||||
if from.auth().covariant(to.auth()) {
|
||||
if from.auth().same_service(to.auth()) {
|
||||
let engine = Engines::new(from).await?;
|
||||
if engine.capabilities().await?.copy_progressive {
|
||||
return engine.copy_progressive(to.loc(), attrs);
|
||||
|
|
@ -119,7 +119,7 @@ where
|
|||
V: AsUrl,
|
||||
{
|
||||
let (original, link) = (original.as_url(), link.as_url());
|
||||
if original.auth().covariant(link.auth()) {
|
||||
if original.auth().same_service(link.auth()) {
|
||||
Engines::new(original).await?.hard_link(link.loc()).await
|
||||
} else {
|
||||
Err(io::Error::from(io::ErrorKind::CrossesDevices))
|
||||
|
|
@ -208,7 +208,7 @@ where
|
|||
V: AsUrl,
|
||||
{
|
||||
let (from, to) = (from.as_url(), to.as_url());
|
||||
if from.auth().covariant(to.auth()) {
|
||||
if from.auth().same_service(to.auth()) {
|
||||
Engines::new(from).await?.rename(to.loc()).await
|
||||
} else {
|
||||
Err(io::Error::from(io::ErrorKind::CrossesDevices))
|
||||
|
|
@ -268,7 +268,9 @@ where
|
|||
let url = url.into();
|
||||
match url.as_url() {
|
||||
Url::Regular(_) | Url::Search { .. } => yazi_fs::engine::local::try_absolute(url),
|
||||
Url::Mount { .. } | Url::Scope { .. } | Url::Sftp { .. } => super::try_absolute_impl(url),
|
||||
Url::Mount { .. } | Url::Hub { .. } | Url::Scope { .. } | Url::Sftp { .. } => {
|
||||
super::try_absolute_impl(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::io;
|
|||
|
||||
use tokio::sync::mpsc;
|
||||
use yazi_fs::{cha::Cha, engine::{Attrs, Capabilities, Engine}};
|
||||
use yazi_shared::{path::{AsPath, PathBufDyn}, strand::AsStrand, url::{Url, UrlBuf, UrlCow}};
|
||||
use yazi_shared::{path::{DynPath, PathBufDyn}, strand::AsStrand, url::{Url, UrlBuf, UrlCow}};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) enum Engines<'a> {
|
||||
|
|
@ -52,7 +52,7 @@ impl<'a> Engine for Engines<'a> {
|
|||
|
||||
async fn copy<P>(&self, to: P, attrs: Attrs) -> io::Result<u64>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
{
|
||||
match self {
|
||||
Self::Local(p) => p.copy(to, attrs).await,
|
||||
|
|
@ -63,7 +63,7 @@ impl<'a> Engine for Engines<'a> {
|
|||
|
||||
fn copy_progressive<P, A>(&self, to: P, attrs: A) -> io::Result<mpsc::Receiver<io::Result<u64>>>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
A: Into<Attrs>,
|
||||
{
|
||||
match self {
|
||||
|
|
@ -107,7 +107,7 @@ impl<'a> Engine for Engines<'a> {
|
|||
|
||||
async fn hard_link<P>(&self, to: P) -> io::Result<()>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
{
|
||||
match self {
|
||||
Self::Local(p) => p.hard_link(to).await,
|
||||
|
|
@ -129,7 +129,7 @@ impl<'a> Engine for Engines<'a> {
|
|||
|
||||
Ok(match url.kind() {
|
||||
K::Regular | K::Search => Self::Me::Local(yazi_fs::engine::local::Local::new(url).await?),
|
||||
K::Mount | K::Scope => Self::Me::Lua(super::lua::Lua::new(url).await?),
|
||||
K::Mount | K::Hub | K::Scope => Self::Me::Lua(super::lua::Lua::new(url).await?),
|
||||
K::Sftp => Self::Me::Sftp(super::sftp::Sftp::new(url).await?),
|
||||
})
|
||||
}
|
||||
|
|
@ -184,7 +184,7 @@ impl<'a> Engine for Engines<'a> {
|
|||
|
||||
async fn rename<P>(&self, to: P) -> io::Result<()>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
{
|
||||
match self {
|
||||
Self::Local(p) => p.rename(to).await,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use yazi_binding::MpscTx;
|
|||
use yazi_config::vfs::{ServiceLua, Vfs};
|
||||
use yazi_fs::{cha::Cha, engine::{Attrs, Capabilities, Engine}, file::Files};
|
||||
use yazi_runner::{RUNNER, provider::{ProvideResult, ProviderJob}};
|
||||
use yazi_shared::{event::Cmd, path::{AsPath, PathBufDyn}, strand::AsStrand, url::{AsUrl, Url, UrlBuf, UrlCow}};
|
||||
use yazi_shared::{event::Cmd, path::{DynPath, PathBufDyn}, strand::AsStrand, url::{AsUrl, Url, UrlBuf, UrlCow}};
|
||||
|
||||
use crate::engine::lua::ReadDir;
|
||||
|
||||
|
|
@ -48,23 +48,23 @@ impl<'a> Engine for Lua<'a> {
|
|||
|
||||
async fn copy<P>(&self, to: P, attrs: Attrs) -> io::Result<u64>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
{
|
||||
let from = self.url.to_owned();
|
||||
let to = to.as_path().to_owned();
|
||||
let to = to.dyn_path().to_owned();
|
||||
|
||||
Ok(self.call(ProviderJob::Copy { from, to, attrs }).await?.0?)
|
||||
}
|
||||
|
||||
fn copy_progressive<P, A>(&self, to: P, attrs: A) -> io::Result<mpsc::Receiver<io::Result<u64>>>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
A: Into<Attrs>,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel(20);
|
||||
let job = ProviderJob::CopyProgressive {
|
||||
from: self.url.to_owned(),
|
||||
to: to.as_path().to_owned(),
|
||||
to: to.dyn_path().to_owned(),
|
||||
attrs: attrs.into(),
|
||||
tx: MpscTx::map(tx.clone(), Ok),
|
||||
};
|
||||
|
|
@ -88,10 +88,10 @@ impl<'a> Engine for Lua<'a> {
|
|||
|
||||
async fn hard_link<P>(&self, to: P) -> io::Result<()>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
{
|
||||
let from = self.url.to_owned();
|
||||
let to = to.as_path().to_owned();
|
||||
let to = to.dyn_path().to_owned();
|
||||
|
||||
Ok(self.call(ProviderJob::HardLink { from, to }).await?.ok()?)
|
||||
}
|
||||
|
|
@ -103,7 +103,7 @@ impl<'a> Engine for Lua<'a> {
|
|||
}
|
||||
|
||||
async fn new<'b>(url: Url<'b>) -> io::Result<Self::Me<'b>> {
|
||||
let (Url::Mount { auth, .. } | Url::Scope { auth, .. }) = url else {
|
||||
let (Url::Mount { auth, .. } | Url::Hub { auth, .. } | Url::Scope { auth, .. }) = url else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("Not a custom VFS URL: {url:?}"),
|
||||
|
|
@ -141,10 +141,10 @@ impl<'a> Engine for Lua<'a> {
|
|||
|
||||
async fn rename<P>(&self, to: P) -> io::Result<()>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
{
|
||||
let from = self.url.to_owned();
|
||||
let to = to.as_path().to_owned();
|
||||
let to = to.dyn_path().to_owned();
|
||||
|
||||
Ok(self.call(ProviderJob::Rename { from, to }).await?.ok()?)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use tokio::{io::{AsyncWriteExt, BufReader, BufWriter}, sync::mpsc::Receiver};
|
|||
use yazi_config::vfs::{ServiceSftp, Vfs};
|
||||
use yazi_fs::engine::{Capabilities, DirReader, Engine, FileHolder};
|
||||
use yazi_sftp::fs::{Attrs, Flags};
|
||||
use yazi_shared::{auth::AuthKind, loc::LocBuf, path::{AsPath, PathBufDyn}, strand::AsStrand, url::{Url, UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_shared::{auth::AuthKind, loc::LocBuf, path::{DynPath, PathBufDyn}, strand::AsStrand, url::{Url, UrlBuf, UrlCow, UrlLike}};
|
||||
|
||||
use super::Cha;
|
||||
use crate::engine::sftp::Conn;
|
||||
|
|
@ -83,9 +83,9 @@ impl<'a> Engine for Sftp<'a> {
|
|||
|
||||
async fn copy<P>(&self, to: P, attrs: yazi_fs::engine::Attrs) -> io::Result<u64>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
{
|
||||
let to = to.as_path().as_unix()?;
|
||||
let to = to.dyn_path().as_unix()?;
|
||||
let attrs = super::Attrs(attrs).try_into().unwrap_or_default();
|
||||
|
||||
let op = self.op().await?;
|
||||
|
|
@ -107,12 +107,12 @@ impl<'a> Engine for Sftp<'a> {
|
|||
|
||||
fn copy_progressive<P, A>(&self, to: P, attrs: A) -> io::Result<Receiver<io::Result<u64>>>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
A: Into<yazi_fs::engine::Attrs>,
|
||||
{
|
||||
let to = UrlBuf::Sftp {
|
||||
loc: LocBuf::<typed_path::UnixPathBuf>::saturated(
|
||||
to.as_path().to_unix_owned()?,
|
||||
to.dyn_path().to_unix_owned()?,
|
||||
AuthKind::Sftp,
|
||||
),
|
||||
auth: self.config.auth.clone(),
|
||||
|
|
@ -138,9 +138,9 @@ impl<'a> Engine for Sftp<'a> {
|
|||
|
||||
async fn hard_link<P>(&self, to: P) -> io::Result<()>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
{
|
||||
let to = to.as_path().as_unix()?;
|
||||
let to = to.dyn_path().as_unix()?;
|
||||
|
||||
Ok(self.op().await?.hardlink(self.path, to).await?)
|
||||
}
|
||||
|
|
@ -176,9 +176,9 @@ impl<'a> Engine for Sftp<'a> {
|
|||
|
||||
async fn rename<P>(&self, to: P) -> io::Result<()>
|
||||
where
|
||||
P: AsPath,
|
||||
P: DynPath,
|
||||
{
|
||||
let to = to.as_path().as_unix()?;
|
||||
let to = to.dyn_path().as_unix()?;
|
||||
let op = self.op().await?;
|
||||
|
||||
match op.rename_posix(self.path, &to).await {
|
||||
|
|
|
|||
|
|
@ -97,20 +97,20 @@ impl Local {
|
|||
let mut ops = Vec::with_capacity(urls.len());
|
||||
|
||||
for u in urls {
|
||||
let Some((parent, urn)) = u.pair() else { continue };
|
||||
let Some((parent, key)) = u.pair2() else { continue };
|
||||
let Ok(file) = File::new(&u).await else {
|
||||
ops.push(FilesOp::Deleting(parent.into(), [urn.into()].into()));
|
||||
ops.push(FilesOp::Deleting(parent.into(), [key.into()].into()));
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(p) = file.url.as_local()
|
||||
&& !engine::local::match_name_case(p).await
|
||||
{
|
||||
ops.push(FilesOp::Deleting(parent.into(), [urn.into()].into()));
|
||||
ops.push(FilesOp::Deleting(parent.into(), [key.into()].into()));
|
||||
continue;
|
||||
}
|
||||
|
||||
ops.push(FilesOp::Upserting(parent.into(), [(urn.into(), file)].into()));
|
||||
ops.push(FilesOp::Upserting(parent.into(), [(key.into(), file)].into()));
|
||||
}
|
||||
|
||||
FilesOp::mutate(ops);
|
||||
|
|
|
|||
|
|
@ -39,16 +39,16 @@ impl Remote {
|
|||
let mut ups = Vec::with_capacity(urls.len());
|
||||
|
||||
for (u, upload) in urls {
|
||||
let Some((parent, urn)) = u.pair() else { continue };
|
||||
let Some((parent, key)) = u.pair2() else { continue };
|
||||
let Ok(mut file) = File::new(&u).await else {
|
||||
ops.push(FilesOp::Deleting(parent.into(), [urn.into()].into()));
|
||||
ops.push(FilesOp::Deleting(parent.into(), [key.into()].into()));
|
||||
continue;
|
||||
};
|
||||
|
||||
let is_file = file.is_file();
|
||||
file.cha.ctime = Some(SystemTime::now());
|
||||
|
||||
ops.push(FilesOp::Upserting(parent.into(), [(urn.into(), file)].into()));
|
||||
ops.push(FilesOp::Upserting(parent.into(), [(key.into(), file)].into()));
|
||||
if upload && is_file {
|
||||
ups.push(u);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ impl Reporter {
|
|||
for url in urls.into_iter().map(Into::into) {
|
||||
match url.as_url().kind() {
|
||||
AuthKind::Regular | AuthKind::Search => self.report_local(url),
|
||||
AuthKind::Mount => {} // TODO: mounted VFS cache invalidation
|
||||
AuthKind::Mount | AuthKind::Hub => {} // TODO: mounted VFS cache invalidation
|
||||
AuthKind::Scope | AuthKind::Sftp => self.report_remote(url),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ impl Watched {
|
|||
AuthKind::Mount => UrlBuf::Mount { loc: path.into_os().ok()?.into(), auth },
|
||||
AuthKind::Scope => UrlBuf::Scope { loc: path.into_unix().ok()?.into(), auth },
|
||||
AuthKind::Sftp => UrlBuf::Sftp { loc: path.into_unix().ok()?.into(), auth },
|
||||
AuthKind::Regular | AuthKind::Search => return None,
|
||||
AuthKind::Regular | AuthKind::Search | AuthKind::Hub => return None,
|
||||
};
|
||||
if self.contains_url(&url) { Some(url) } else { None }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue