conch_runtime_pshaw/env/
cur_dir.rs

1use crate::env::SubEnvironment;
2use crate::path::NormalizedPath;
3use std::borrow::Cow;
4use std::env;
5use std::io;
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8
9/// An interface for working with the shell's current working directory.
10pub trait WorkingDirectoryEnvironment {
11    /// Converts the specified path to one relative to the environment's working directory.
12    fn path_relative_to_working_dir<'a>(&self, path: Cow<'a, Path>) -> Cow<'a, Path>;
13
14    /// Retrieves the current working directory of this environment.
15    fn current_working_dir(&self) -> &Path;
16}
17
18impl<'b, T: ?Sized + WorkingDirectoryEnvironment> WorkingDirectoryEnvironment for &'b T {
19    fn path_relative_to_working_dir<'a>(&self, path: Cow<'a, Path>) -> Cow<'a, Path> {
20        (**self).path_relative_to_working_dir(path)
21    }
22
23    fn current_working_dir(&self) -> &Path {
24        (**self).current_working_dir()
25    }
26}
27
28impl<'b, T: ?Sized + WorkingDirectoryEnvironment> WorkingDirectoryEnvironment for &'b mut T {
29    fn path_relative_to_working_dir<'a>(&self, path: Cow<'a, Path>) -> Cow<'a, Path> {
30        (**self).path_relative_to_working_dir(path)
31    }
32
33    fn current_working_dir(&self) -> &Path {
34        (**self).current_working_dir()
35    }
36}
37
38/// An interface for changing the shell's current working directory.
39pub trait ChangeWorkingDirectoryEnvironment: WorkingDirectoryEnvironment {
40    /// Changes the environment's current working directory to the following path.
41    ///
42    /// The provided `path` can either be an absolute path, or one which will be
43    /// treated as relative to the current working directory.
44    fn change_working_dir<'a>(&mut self, path: Cow<'a, Path>) -> io::Result<()>;
45}
46
47impl<'b, T: ?Sized> ChangeWorkingDirectoryEnvironment for &'b mut T
48where
49    T: ChangeWorkingDirectoryEnvironment,
50{
51    fn change_working_dir<'a>(&mut self, path: Cow<'a, Path>) -> io::Result<()> {
52        (**self).change_working_dir(path)
53    }
54}
55
56/// An environment module for keeping track of the current working directory.
57///
58/// This is a "virtual" implementation because changing the working directory
59/// through this environment will not affect the working directory of the
60/// entire process.
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct VirtualWorkingDirEnv {
63    cwd: Arc<NormalizedPath>,
64}
65
66impl VirtualWorkingDirEnv {
67    /// Constructs a new environment with a provided working directory.
68    ///
69    /// The specified `path` *must* be an absolute path or an error will result.
70    pub fn new<P: AsRef<Path>>(path: P) -> io::Result<Self> {
71        Self::with_path_buf(path.as_ref().to_path_buf())
72    }
73
74    /// Constructs a new environment with a provided `PathBuf` as a working directory.
75    ///
76    /// The specified `path` *must* be an absolute path or an error will result.
77    pub fn with_path_buf(path: PathBuf) -> io::Result<Self> {
78        if path.is_absolute() {
79            let normalized = NormalizedPath::new_normalized_logical(path);
80            if normalized.is_dir() {
81                Ok(Self {
82                    cwd: Arc::new(normalized),
83                })
84            } else {
85                let msg = format!("not a directory: {}", normalized.display());
86                Err(io::Error::new(io::ErrorKind::NotFound, msg))
87            }
88        } else {
89            let msg = format!("specified path not absolute: {}", path.display());
90            Err(io::Error::new(io::ErrorKind::InvalidInput, msg))
91        }
92    }
93
94    /// Constructs a new environment and initializes it with the current
95    /// working directory of the current process.
96    pub fn with_process_working_dir() -> io::Result<Self> {
97        env::current_dir().and_then(Self::with_path_buf)
98    }
99}
100
101impl WorkingDirectoryEnvironment for VirtualWorkingDirEnv {
102    fn path_relative_to_working_dir<'a>(&self, path: Cow<'a, Path>) -> Cow<'a, Path> {
103        if path.is_absolute() {
104            path
105        } else {
106            let mut new_cwd = (*self.cwd).clone();
107            new_cwd.join_normalized_logial(&path);
108
109            Cow::Owned(new_cwd.into_inner())
110        }
111    }
112
113    fn current_working_dir(&self) -> &Path {
114        &*self.cwd
115    }
116}
117
118impl ChangeWorkingDirectoryEnvironment for VirtualWorkingDirEnv {
119    fn change_working_dir<'a>(&mut self, path: Cow<'a, Path>) -> io::Result<()> {
120        let mut new_cwd = (*self.cwd).clone();
121        // NB: use logical normalization here for maximum flexibility.
122        // If physical normalization is needed, it can always be done
123        // by the caller (logical normalization is a no-op if `path`
124        // has already been canonicalized/symlinks resolved)
125        new_cwd.join_normalized_logial(&path);
126
127        if new_cwd.is_dir() {
128            self.cwd = Arc::new(new_cwd);
129            Ok(())
130        } else {
131            let msg = format!("not a directory: {}", new_cwd.display());
132            Err(io::Error::new(io::ErrorKind::NotFound, msg))
133        }
134    }
135}
136
137impl SubEnvironment for VirtualWorkingDirEnv {
138    fn sub_env(&self) -> Self {
139        self.clone()
140    }
141}