Skip to main content

yazi_shared/url/
components.rs

1use std::{borrow::Cow, ffi::{OsStr, OsString}, iter::FusedIterator, ops::Not};
2
3use crate::{loc::Loc, path, scheme::{Encode as EncodeScheme, SchemeCow, SchemeRef}, strand::{StrandBuf, StrandCow}, url::{Component, Encode as EncodeUrl, Url}};
4
5#[derive(Clone)]
6pub struct Components<'a> {
7	inner:          path::Components<'a>,
8	url:            Url<'a>,
9	back_yields:    usize,
10	scheme_yielded: bool,
11}
12
13impl<'a> From<Url<'a>> for Components<'a> {
14	fn from(value: Url<'a>) -> Self {
15		Self {
16			inner:          value.loc().components(),
17			url:            value,
18			back_yields:    0,
19			scheme_yielded: false,
20		}
21	}
22}
23
24impl<'a> Components<'a> {
25	pub fn covariant(&self, other: &Self) -> bool {
26		match (self.scheme_yielded, other.scheme_yielded) {
27			(true, true) => {}
28			(false, false) if self.scheme().covariant(other.scheme()) => {}
29			_ => return false,
30		}
31		self.inner == other.inner
32	}
33
34	pub fn os_str(&self) -> Cow<'a, OsStr> {
35		let Ok(os) = self.inner.strand().as_os() else {
36			return OsString::from(EncodeUrl(self.url()).to_string()).into();
37		};
38
39		if self.url.is_regular() || self.scheme_yielded {
40			return os.into();
41		}
42
43		let mut s = OsString::from(EncodeScheme(self.url()).to_string());
44		s.reserve_exact(os.len());
45		s.push(os);
46		s.into()
47	}
48
49	pub fn scheme(&self) -> SchemeRef<'a> {
50		let left = self.inner.clone().count();
51
52		let (uri, urn) = SchemeCow::retrieve_ports(self.url);
53		let (uri, urn) = (
54			uri.saturating_sub(self.back_yields).min(left),
55			urn.saturating_sub(self.back_yields).min(left),
56		);
57
58		match self.url {
59			Url::Regular(_) => SchemeRef::Regular { uri, urn },
60			Url::Search { domain, .. } => SchemeRef::Search { domain, uri, urn },
61			Url::Archive { domain, .. } => SchemeRef::Archive { domain, uri, urn },
62			Url::Sftp { domain, .. } => SchemeRef::Sftp { domain, uri, urn },
63		}
64	}
65
66	pub fn strand(&self) -> StrandCow<'a> {
67		let s = self.inner.strand();
68		if self.url.is_regular() || self.scheme_yielded {
69			return s.into();
70		}
71
72		let mut buf = StrandBuf::with_str(s.kind(), EncodeScheme(self.url()).to_string());
73		buf.reserve_exact(s.len());
74		buf.try_push(s).expect("strand with same kind");
75		buf.into()
76	}
77
78	pub fn url(&self) -> Url<'a> {
79		let path = self.inner.path();
80		let (uri, urn) = self.scheme().ports();
81		match self.url {
82			Url::Regular(_) => Url::Regular(Loc::with(path.as_os().unwrap(), uri, urn).unwrap()),
83			Url::Search { domain, .. } => {
84				Url::Search { loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(), domain }
85			}
86			Url::Archive { domain, .. } => {
87				Url::Archive { loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(), domain }
88			}
89			Url::Sftp { domain, .. } => {
90				Url::Sftp { loc: Loc::with(path.as_unix().unwrap(), uri, urn).unwrap(), domain }
91			}
92		}
93	}
94}
95
96impl<'a> Iterator for Components<'a> {
97	type Item = Component<'a>;
98
99	fn next(&mut self) -> Option<Self::Item> {
100		if !self.scheme_yielded {
101			self.scheme_yielded = true;
102			Some(Component::Scheme(self.scheme()))
103		} else {
104			self.inner.next().map(Into::into)
105		}
106	}
107
108	fn size_hint(&self) -> (usize, Option<usize>) {
109		let (min, max) = self.inner.size_hint();
110		let scheme = self.scheme_yielded.not() as usize;
111
112		(min + scheme, max.map(|n| n + scheme))
113	}
114}
115
116impl<'a> DoubleEndedIterator for Components<'a> {
117	fn next_back(&mut self) -> Option<Self::Item> {
118		if let Some(c) = self.inner.next_back() {
119			self.back_yields += 1;
120			Some(c.into())
121		} else if !self.scheme_yielded {
122			self.scheme_yielded = true;
123			Some(Component::Scheme(self.scheme()))
124		} else {
125			None
126		}
127	}
128}
129
130impl<'a> FusedIterator for Components<'a> {}
131
132impl<'a> PartialEq for Components<'a> {
133	fn eq(&self, other: &Self) -> bool {
134		if self.inner != other.inner {
135			return false;
136		}
137		match (self.scheme_yielded, other.scheme_yielded) {
138			(true, true) => true,
139			(false, false) if self.scheme() == other.scheme() => true,
140			_ => false,
141		}
142	}
143}
144
145// --- Tests
146#[cfg(test)]
147mod tests {
148	use anyhow::Result;
149
150	use crate::{scheme::SchemeRef, url::{Component, UrlBuf, UrlLike}};
151
152	#[test]
153	fn test_url() -> Result<()> {
154		use Component::*;
155		use SchemeRef as S;
156
157		crate::init_tests();
158
159		let search: UrlBuf = "search://keyword//root/projects/yazi".parse()?;
160		assert_eq!(search.uri(), "");
161		assert_eq!(search.scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 });
162
163		let src = search.try_join("src")?;
164		assert_eq!(src.uri(), "src");
165		assert_eq!(src.scheme(), S::Search { domain: "keyword", uri: 1, urn: 1 });
166
167		let main = src.try_join("main.rs")?;
168		assert_eq!(main.urn(), "src/main.rs");
169		assert_eq!(main.scheme(), S::Search { domain: "keyword", uri: 2, urn: 2 });
170
171		let mut it = main.components();
172		assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 2, urn: 2 });
173		assert_eq!(it.next_back(), Some(Normal("main.rs".into())));
174		assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 1, urn: 1 });
175		assert_eq!(it.next_back(), Some(Normal("src".into())));
176		assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 });
177		assert_eq!(it.next_back(), Some(Normal("yazi".into())));
178		assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 });
179
180		let mut it = main.components();
181		assert_eq!(it.next(), Some(Scheme(S::Search { domain: "keyword", uri: 2, urn: 2 })));
182		assert_eq!(it.next(), Some(RootDir));
183		assert_eq!(it.next(), Some(Normal("root".into())));
184		assert_eq!(it.next(), Some(Normal("projects".into())));
185		assert_eq!(it.next(), Some(Normal("yazi".into())));
186		assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 2, urn: 2 });
187		assert_eq!(it.next(), Some(Normal("src".into())));
188		assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 1, urn: 1 });
189		assert_eq!(it.next_back(), Some(Normal("main.rs".into())));
190		assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 });
191		assert_eq!(it.next(), None);
192		assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 });
193
194		Ok(())
195	}
196}