1use anyhow::{anyhow, Result};
2use fsevent::EventStream;
3use futures::{Stream, StreamExt};
4use smol::io::{AsyncReadExt, AsyncWriteExt};
5use std::{
6 io,
7 os::unix::fs::MetadataExt,
8 path::{Component, Path, PathBuf},
9 pin::Pin,
10 time::{Duration, SystemTime},
11};
12use text::Rope;
13
14#[async_trait::async_trait]
15pub trait Fs: Send + Sync {
16 async fn create_dir(&self, path: &Path) -> Result<()>;
17 async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;
18 async fn copy(
19 &self,
20 source: &Path,
21 target: &Path,
22 options: CopyOptions,
23 ) -> Result<Vec<PathBuf>>;
24 async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>;
25 async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>;
26 async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
27 async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
28 async fn load(&self, path: &Path) -> Result<String>;
29 async fn save(&self, path: &Path, text: &Rope) -> Result<()>;
30 async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
31 async fn is_file(&self, path: &Path) -> bool;
32 async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>;
33 async fn read_dir(
34 &self,
35 path: &Path,
36 ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;
37 async fn watch(
38 &self,
39 path: &Path,
40 latency: Duration,
41 ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>;
42 fn is_fake(&self) -> bool;
43 #[cfg(any(test, feature = "test-support"))]
44 fn as_fake(&self) -> &FakeFs;
45}
46
47#[derive(Copy, Clone, Default)]
48pub struct CreateOptions {
49 pub overwrite: bool,
50 pub ignore_if_exists: bool,
51}
52
53#[derive(Copy, Clone, Default)]
54pub struct CopyOptions {
55 pub overwrite: bool,
56 pub ignore_if_exists: bool,
57}
58
59#[derive(Copy, Clone, Default)]
60pub struct RenameOptions {
61 pub overwrite: bool,
62 pub ignore_if_exists: bool,
63}
64
65#[derive(Copy, Clone, Default)]
66pub struct RemoveOptions {
67 pub recursive: bool,
68 pub ignore_if_not_exists: bool,
69}
70
71#[derive(Clone, Debug)]
72pub struct Metadata {
73 pub inode: u64,
74 pub mtime: SystemTime,
75 pub is_symlink: bool,
76 pub is_dir: bool,
77}
78
79pub struct RealFs;
80
81#[async_trait::async_trait]
82impl Fs for RealFs {
83 async fn create_dir(&self, path: &Path) -> Result<()> {
84 Ok(smol::fs::create_dir_all(path).await?)
85 }
86
87 async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
88 let mut open_options = smol::fs::OpenOptions::new();
89 open_options.write(true).create(true);
90 if options.overwrite {
91 open_options.truncate(true);
92 } else if !options.ignore_if_exists {
93 open_options.create_new(true);
94 }
95 open_options.open(path).await?;
96 Ok(())
97 }
98
99 async fn copy(
100 &self,
101 source: &Path,
102 target: &Path,
103 options: CopyOptions,
104 ) -> Result<Vec<PathBuf>> {
105 if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
106 if options.ignore_if_exists {
107 return Ok(Default::default());
108 } else {
109 return Err(anyhow!("{target:?} already exists"));
110 }
111 }
112
113 let mut paths = vec![target.to_path_buf()];
114 let metadata = smol::fs::metadata(source).await?;
115 let _ = smol::fs::remove_dir_all(target).await;
116 if metadata.is_dir() {
117 self.create_dir(target).await?;
118 let mut children = smol::fs::read_dir(source).await?;
119 while let Some(child) = children.next().await {
120 if let Ok(child) = child {
121 let child_source_path = child.path();
122 let child_target_path = target.join(child.file_name());
123 paths.extend(
124 self.copy(&child_source_path, &child_target_path, options)
125 .await?,
126 );
127 }
128 }
129 } else {
130 smol::fs::copy(source, target).await?;
131 }
132
133 Ok(paths)
134 }
135
136 async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
137 if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
138 if options.ignore_if_exists {
139 return Ok(());
140 } else {
141 return Err(anyhow!("{target:?} already exists"));
142 }
143 }
144
145 smol::fs::rename(source, target).await?;
146 Ok(())
147 }
148
149 async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
150 let result = if options.recursive {
151 smol::fs::remove_dir_all(path).await
152 } else {
153 smol::fs::remove_dir(path).await
154 };
155 match result {
156 Ok(()) => Ok(()),
157 Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
158 Ok(())
159 }
160 Err(err) => Err(err)?,
161 }
162 }
163
164 async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
165 match smol::fs::remove_file(path).await {
166 Ok(()) => Ok(()),
167 Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
168 Ok(())
169 }
170 Err(err) => Err(err)?,
171 }
172 }
173
174 async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
175 Ok(Box::new(std::fs::File::open(path)?))
176 }
177
178 async fn load(&self, path: &Path) -> Result<String> {
179 let mut file = smol::fs::File::open(path).await?;
180 let mut text = String::new();
181 file.read_to_string(&mut text).await?;
182 Ok(text)
183 }
184
185 async fn save(&self, path: &Path, text: &Rope) -> Result<()> {
186 let buffer_size = text.summary().bytes.min(10 * 1024);
187 let file = smol::fs::File::create(path).await?;
188 let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
189 for chunk in text.chunks() {
190 writer.write_all(chunk.as_bytes()).await?;
191 }
192 writer.flush().await?;
193 Ok(())
194 }
195
196 async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
197 Ok(smol::fs::canonicalize(path).await?)
198 }
199
200 async fn is_file(&self, path: &Path) -> bool {
201 smol::fs::metadata(path)
202 .await
203 .map_or(false, |metadata| metadata.is_file())
204 }
205
206 async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
207 let symlink_metadata = match smol::fs::symlink_metadata(path).await {
208 Ok(metadata) => metadata,
209 Err(err) => {
210 return match (err.kind(), err.raw_os_error()) {
211 (io::ErrorKind::NotFound, _) => Ok(None),
212 (io::ErrorKind::Other, Some(libc::ENOTDIR)) => Ok(None),
213 _ => Err(anyhow::Error::new(err)),
214 }
215 }
216 };
217
218 let is_symlink = symlink_metadata.file_type().is_symlink();
219 let metadata = if is_symlink {
220 smol::fs::metadata(path).await?
221 } else {
222 symlink_metadata
223 };
224 Ok(Some(Metadata {
225 inode: metadata.ino(),
226 mtime: metadata.modified().unwrap(),
227 is_symlink,
228 is_dir: metadata.file_type().is_dir(),
229 }))
230 }
231
232 async fn read_dir(
233 &self,
234 path: &Path,
235 ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
236 let result = smol::fs::read_dir(path).await?.map(|entry| match entry {
237 Ok(entry) => Ok(entry.path()),
238 Err(error) => Err(anyhow!("failed to read dir entry {:?}", error)),
239 });
240 Ok(Box::pin(result))
241 }
242
243 async fn watch(
244 &self,
245 path: &Path,
246 latency: Duration,
247 ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
248 let (tx, rx) = smol::channel::unbounded();
249 let (stream, handle) = EventStream::new(&[path], latency);
250 std::mem::forget(handle);
251 std::thread::spawn(move || {
252 stream.run(move |events| smol::block_on(tx.send(events)).is_ok());
253 });
254 Box::pin(rx)
255 }
256
257 fn is_fake(&self) -> bool {
258 false
259 }
260 #[cfg(any(test, feature = "test-support"))]
261 fn as_fake(&self) -> &FakeFs {
262 panic!("called `RealFs::as_fake`")
263 }
264}
265
266#[cfg(any(test, feature = "test-support"))]
267#[derive(Clone, Debug)]
268struct FakeFsEntry {
269 metadata: Metadata,
270 content: Option<String>,
271}
272
273#[cfg(any(test, feature = "test-support"))]
274struct FakeFsState {
275 entries: std::collections::BTreeMap<PathBuf, FakeFsEntry>,
276 next_inode: u64,
277 event_txs: Vec<smol::channel::Sender<Vec<fsevent::Event>>>,
278}
279
280#[cfg(any(test, feature = "test-support"))]
281impl FakeFsState {
282 fn validate_path(&self, path: &Path) -> Result<()> {
283 if path.is_absolute()
284 && path
285 .parent()
286 .and_then(|path| self.entries.get(path))
287 .map_or(false, |e| e.metadata.is_dir)
288 {
289 Ok(())
290 } else {
291 Err(anyhow!("invalid path {:?}", path))
292 }
293 }
294
295 async fn emit_event<I, T>(&mut self, paths: I)
296 where
297 I: IntoIterator<Item = T>,
298 T: Into<PathBuf>,
299 {
300 let events = paths
301 .into_iter()
302 .map(|path| fsevent::Event {
303 event_id: 0,
304 flags: fsevent::StreamFlags::empty(),
305 path: path.into(),
306 })
307 .collect::<Vec<_>>();
308
309 self.event_txs.retain(|tx| {
310 let _ = tx.try_send(events.clone());
311 !tx.is_closed()
312 });
313 }
314}
315
316#[cfg(any(test, feature = "test-support"))]
317pub struct FakeFs {
318 // Use an unfair lock to ensure tests are deterministic.
319 state: futures::lock::Mutex<FakeFsState>,
320 executor: std::sync::Weak<gpui::executor::Background>,
321}
322
323#[cfg(any(test, feature = "test-support"))]
324impl FakeFs {
325 pub fn new(executor: std::sync::Arc<gpui::executor::Background>) -> std::sync::Arc<Self> {
326 let mut entries = std::collections::BTreeMap::new();
327 entries.insert(
328 Path::new("/").to_path_buf(),
329 FakeFsEntry {
330 metadata: Metadata {
331 inode: 0,
332 mtime: SystemTime::now(),
333 is_dir: true,
334 is_symlink: false,
335 },
336 content: None,
337 },
338 );
339 std::sync::Arc::new(Self {
340 executor: std::sync::Arc::downgrade(&executor),
341 state: futures::lock::Mutex::new(FakeFsState {
342 entries,
343 next_inode: 1,
344 event_txs: Default::default(),
345 }),
346 })
347 }
348
349 pub async fn insert_dir(&self, path: impl AsRef<Path>) {
350 let mut state = self.state.lock().await;
351 let path = path.as_ref();
352 state.validate_path(path).unwrap();
353
354 let inode = state.next_inode;
355 state.next_inode += 1;
356 state.entries.insert(
357 path.to_path_buf(),
358 FakeFsEntry {
359 metadata: Metadata {
360 inode,
361 mtime: SystemTime::now(),
362 is_dir: true,
363 is_symlink: false,
364 },
365 content: None,
366 },
367 );
368 state.emit_event(&[path]).await;
369 }
370
371 pub async fn insert_file(&self, path: impl AsRef<Path>, content: String) {
372 let mut state = self.state.lock().await;
373 let path = path.as_ref();
374 state.validate_path(path).unwrap();
375
376 let inode = state.next_inode;
377 state.next_inode += 1;
378 state.entries.insert(
379 path.to_path_buf(),
380 FakeFsEntry {
381 metadata: Metadata {
382 inode,
383 mtime: SystemTime::now(),
384 is_dir: false,
385 is_symlink: false,
386 },
387 content: Some(content),
388 },
389 );
390 state.emit_event(&[path]).await;
391 }
392
393 #[must_use]
394 pub fn insert_tree<'a>(
395 &'a self,
396 path: impl 'a + AsRef<Path> + Send,
397 tree: serde_json::Value,
398 ) -> futures::future::BoxFuture<'a, ()> {
399 use futures::FutureExt as _;
400 use serde_json::Value::*;
401
402 async move {
403 let path = path.as_ref();
404
405 match tree {
406 Object(map) => {
407 self.insert_dir(path).await;
408 for (name, contents) in map {
409 let mut path = PathBuf::from(path);
410 path.push(name);
411 self.insert_tree(&path, contents).await;
412 }
413 }
414 Null => {
415 self.insert_dir(&path).await;
416 }
417 String(contents) => {
418 self.insert_file(&path, contents).await;
419 }
420 _ => {
421 panic!("JSON object must contain only objects, strings, or null");
422 }
423 }
424 }
425 .boxed()
426 }
427
428 pub async fn files(&self) -> Vec<PathBuf> {
429 self.state
430 .lock()
431 .await
432 .entries
433 .iter()
434 .filter_map(|(path, entry)| entry.content.as_ref().map(|_| path.clone()))
435 .collect()
436 }
437
438 async fn simulate_random_delay(&self) {
439 self.executor
440 .upgrade()
441 .expect("executor has been dropped")
442 .simulate_random_delay()
443 .await;
444 }
445}
446
447#[cfg(any(test, feature = "test-support"))]
448#[async_trait::async_trait]
449impl Fs for FakeFs {
450 async fn create_dir(&self, path: &Path) -> Result<()> {
451 self.simulate_random_delay().await;
452 let state = &mut *self.state.lock().await;
453 let path = normalize_path(path);
454 let mut ancestor_path = PathBuf::new();
455 let mut created_dir_paths = Vec::new();
456 for component in path.components() {
457 ancestor_path.push(component);
458 let entry = state
459 .entries
460 .entry(ancestor_path.clone())
461 .or_insert_with(|| {
462 let inode = state.next_inode;
463 state.next_inode += 1;
464 created_dir_paths.push(ancestor_path.clone());
465 FakeFsEntry {
466 metadata: Metadata {
467 inode,
468 mtime: SystemTime::now(),
469 is_dir: true,
470 is_symlink: false,
471 },
472 content: None,
473 }
474 });
475 if !entry.metadata.is_dir {
476 return Err(anyhow!(
477 "cannot create directory because {:?} is a file",
478 ancestor_path
479 ));
480 }
481 }
482 state.emit_event(&created_dir_paths).await;
483
484 Ok(())
485 }
486
487 async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
488 self.simulate_random_delay().await;
489 let mut state = self.state.lock().await;
490 let path = normalize_path(path);
491 state.validate_path(&path)?;
492 if let Some(entry) = state.entries.get_mut(&path) {
493 if entry.metadata.is_dir || entry.metadata.is_symlink {
494 return Err(anyhow!(
495 "cannot create file because {:?} is a dir or a symlink",
496 path
497 ));
498 }
499
500 if options.overwrite {
501 entry.metadata.mtime = SystemTime::now();
502 entry.content = Some(Default::default());
503 } else if !options.ignore_if_exists {
504 return Err(anyhow!(
505 "cannot create file because {:?} already exists",
506 &path
507 ));
508 }
509 } else {
510 let inode = state.next_inode;
511 state.next_inode += 1;
512 let entry = FakeFsEntry {
513 metadata: Metadata {
514 inode,
515 mtime: SystemTime::now(),
516 is_dir: false,
517 is_symlink: false,
518 },
519 content: Some(Default::default()),
520 };
521 state.entries.insert(path.to_path_buf(), entry);
522 }
523 state.emit_event(&[path]).await;
524
525 Ok(())
526 }
527
528 async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
529 let source = normalize_path(source);
530 let target = normalize_path(target);
531
532 let mut state = self.state.lock().await;
533 state.validate_path(&source)?;
534 state.validate_path(&target)?;
535
536 if !options.overwrite && state.entries.contains_key(&target) {
537 if options.ignore_if_exists {
538 return Ok(());
539 } else {
540 return Err(anyhow!("{target:?} already exists"));
541 }
542 }
543
544 let mut removed = Vec::new();
545 state.entries.retain(|path, entry| {
546 if let Ok(relative_path) = path.strip_prefix(&source) {
547 removed.push((relative_path.to_path_buf(), entry.clone()));
548 false
549 } else {
550 true
551 }
552 });
553
554 for (relative_path, entry) in removed {
555 let new_path = normalize_path(&target.join(relative_path));
556 state.entries.insert(new_path, entry);
557 }
558
559 state.emit_event(&[source, target]).await;
560 Ok(())
561 }
562
563 async fn copy(
564 &self,
565 source: &Path,
566 target: &Path,
567 options: CopyOptions,
568 ) -> Result<Vec<PathBuf>> {
569 let source = normalize_path(source);
570 let target = normalize_path(target);
571
572 let mut state = self.state.lock().await;
573 state.validate_path(&source)?;
574 state.validate_path(&target)?;
575
576 if !options.overwrite && state.entries.contains_key(&target) {
577 if options.ignore_if_exists {
578 return Ok(Default::default());
579 } else {
580 return Err(anyhow!("{target:?} already exists"));
581 }
582 }
583
584 let mut new_entries = Vec::new();
585 for (path, entry) in &state.entries {
586 if let Ok(relative_path) = path.strip_prefix(&source) {
587 new_entries.push((relative_path.to_path_buf(), entry.clone()));
588 }
589 }
590
591 let mut paths = Vec::new();
592 for (relative_path, entry) in new_entries {
593 let new_path = normalize_path(&target.join(relative_path));
594 paths.push(new_path.clone());
595 state.entries.insert(new_path, entry);
596 }
597
598 state.emit_event(&paths).await;
599 Ok(paths)
600 }
601
602 async fn remove_dir(&self, dir_path: &Path, options: RemoveOptions) -> Result<()> {
603 let dir_path = normalize_path(dir_path);
604 let mut state = self.state.lock().await;
605 state.validate_path(&dir_path)?;
606 if let Some(entry) = state.entries.get(&dir_path) {
607 if !entry.metadata.is_dir {
608 return Err(anyhow!(
609 "cannot remove {dir_path:?} because it is not a dir"
610 ));
611 }
612
613 if !options.recursive {
614 let descendants = state
615 .entries
616 .keys()
617 .filter(|path| path.starts_with(path))
618 .count();
619 if descendants > 1 {
620 return Err(anyhow!("{dir_path:?} is not empty"));
621 }
622 }
623
624 state.entries.retain(|path, _| !path.starts_with(&dir_path));
625 state.emit_event(&[dir_path]).await;
626 } else if !options.ignore_if_not_exists {
627 return Err(anyhow!("{dir_path:?} does not exist"));
628 }
629
630 Ok(())
631 }
632
633 async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
634 let path = normalize_path(path);
635 let mut state = self.state.lock().await;
636 state.validate_path(&path)?;
637 if let Some(entry) = state.entries.get(&path) {
638 if entry.metadata.is_dir {
639 return Err(anyhow!("cannot remove {path:?} because it is not a file"));
640 }
641
642 state.entries.remove(&path);
643 state.emit_event(&[path]).await;
644 } else if !options.ignore_if_not_exists {
645 return Err(anyhow!("{path:?} does not exist"));
646 }
647 Ok(())
648 }
649
650 async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
651 let text = self.load(path).await?;
652 Ok(Box::new(io::Cursor::new(text)))
653 }
654
655 async fn load(&self, path: &Path) -> Result<String> {
656 let path = normalize_path(path);
657 self.simulate_random_delay().await;
658 let state = self.state.lock().await;
659 let text = state
660 .entries
661 .get(&path)
662 .and_then(|e| e.content.as_ref())
663 .ok_or_else(|| anyhow!("file {:?} does not exist", path))?;
664 Ok(text.clone())
665 }
666
667 async fn save(&self, path: &Path, text: &Rope) -> Result<()> {
668 self.simulate_random_delay().await;
669 let mut state = self.state.lock().await;
670 let path = normalize_path(path);
671 state.validate_path(&path)?;
672 if let Some(entry) = state.entries.get_mut(&path) {
673 if entry.metadata.is_dir {
674 Err(anyhow!("cannot overwrite a directory with a file"))
675 } else {
676 entry.content = Some(text.chunks().collect());
677 entry.metadata.mtime = SystemTime::now();
678 state.emit_event(&[path]).await;
679 Ok(())
680 }
681 } else {
682 let inode = state.next_inode;
683 state.next_inode += 1;
684 let entry = FakeFsEntry {
685 metadata: Metadata {
686 inode,
687 mtime: SystemTime::now(),
688 is_dir: false,
689 is_symlink: false,
690 },
691 content: Some(text.chunks().collect()),
692 };
693 state.entries.insert(path.to_path_buf(), entry);
694 state.emit_event(&[path]).await;
695 Ok(())
696 }
697 }
698
699 async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
700 self.simulate_random_delay().await;
701 Ok(normalize_path(path))
702 }
703
704 async fn is_file(&self, path: &Path) -> bool {
705 let path = normalize_path(path);
706 self.simulate_random_delay().await;
707 let state = self.state.lock().await;
708 state
709 .entries
710 .get(&path)
711 .map_or(false, |entry| !entry.metadata.is_dir)
712 }
713
714 async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
715 self.simulate_random_delay().await;
716 let state = self.state.lock().await;
717 let path = normalize_path(path);
718 Ok(state.entries.get(&path).map(|entry| entry.metadata.clone()))
719 }
720
721 async fn read_dir(
722 &self,
723 abs_path: &Path,
724 ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
725 use futures::{future, stream};
726 self.simulate_random_delay().await;
727 let state = self.state.lock().await;
728 let abs_path = normalize_path(abs_path);
729 Ok(Box::pin(stream::iter(state.entries.clone()).filter_map(
730 move |(child_path, _)| {
731 future::ready(if child_path.parent() == Some(&abs_path) {
732 Some(Ok(child_path))
733 } else {
734 None
735 })
736 },
737 )))
738 }
739
740 async fn watch(
741 &self,
742 path: &Path,
743 _: Duration,
744 ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
745 let mut state = self.state.lock().await;
746 self.simulate_random_delay().await;
747 let (tx, rx) = smol::channel::unbounded();
748 state.event_txs.push(tx);
749 let path = path.to_path_buf();
750 let executor = self.executor.clone();
751 Box::pin(futures::StreamExt::filter(rx, move |events| {
752 let result = events.iter().any(|event| event.path.starts_with(&path));
753 let executor = executor.clone();
754 async move {
755 if let Some(executor) = executor.clone().upgrade() {
756 executor.simulate_random_delay().await;
757 }
758 result
759 }
760 }))
761 }
762
763 fn is_fake(&self) -> bool {
764 true
765 }
766
767 #[cfg(any(test, feature = "test-support"))]
768 fn as_fake(&self) -> &FakeFs {
769 self
770 }
771}
772
773pub fn normalize_path(path: &Path) -> PathBuf {
774 let mut components = path.components().peekable();
775 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
776 components.next();
777 PathBuf::from(c.as_os_str())
778 } else {
779 PathBuf::new()
780 };
781
782 for component in components {
783 match component {
784 Component::Prefix(..) => unreachable!(),
785 Component::RootDir => {
786 ret.push(component.as_os_str());
787 }
788 Component::CurDir => {}
789 Component::ParentDir => {
790 ret.pop();
791 }
792 Component::Normal(c) => {
793 ret.push(c);
794 }
795 }
796 }
797 ret
798}