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#[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}