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