rustpython_vm/
ospath.rs

1use crate::{
2    builtins::PyBaseExceptionRef,
3    convert::{ToPyException, TryFromObject},
4    function::FsPath,
5    object::AsObject,
6    PyObjectRef, PyResult, VirtualMachine,
7};
8use std::path::{Path, PathBuf};
9
10// path_ without allow_fd in CPython
11#[derive(Clone)]
12pub struct OsPath {
13    pub path: std::ffi::OsString,
14    pub(super) mode: OutputMode,
15}
16
17#[derive(Debug, Copy, Clone)]
18pub(super) enum OutputMode {
19    String,
20    Bytes,
21}
22
23impl OutputMode {
24    pub(super) fn process_path(self, path: impl Into<PathBuf>, vm: &VirtualMachine) -> PyResult {
25        fn inner(mode: OutputMode, path: PathBuf, vm: &VirtualMachine) -> PyResult {
26            let path_as_string = |p: PathBuf| {
27                p.into_os_string().into_string().map_err(|_| {
28                    vm.new_unicode_decode_error(
29                        "Can't convert OS path to valid UTF-8 string".into(),
30                    )
31                })
32            };
33            match mode {
34                OutputMode::String => path_as_string(path).map(|s| vm.ctx.new_str(s).into()),
35                OutputMode::Bytes => {
36                    #[cfg(any(unix, target_os = "wasi"))]
37                    {
38                        use rustpython_common::os::ffi::OsStringExt;
39                        Ok(vm.ctx.new_bytes(path.into_os_string().into_vec()).into())
40                    }
41                    #[cfg(windows)]
42                    {
43                        path_as_string(path).map(|s| vm.ctx.new_bytes(s.into_bytes()).into())
44                    }
45                }
46            }
47        }
48        inner(self, path.into(), vm)
49    }
50}
51
52impl OsPath {
53    pub fn new_str(path: impl Into<std::ffi::OsString>) -> Self {
54        let path = path.into();
55        Self {
56            path,
57            mode: OutputMode::String,
58        }
59    }
60
61    pub(crate) fn from_fspath(fspath: FsPath, vm: &VirtualMachine) -> PyResult<OsPath> {
62        let path = fspath.as_os_str(vm)?.to_owned();
63        let mode = match fspath {
64            FsPath::Str(_) => OutputMode::String,
65            FsPath::Bytes(_) => OutputMode::Bytes,
66        };
67        Ok(OsPath { path, mode })
68    }
69
70    pub fn as_path(&self) -> &Path {
71        Path::new(&self.path)
72    }
73
74    pub fn into_bytes(self) -> Vec<u8> {
75        self.path.into_encoded_bytes()
76    }
77
78    pub fn to_string_lossy(&self) -> std::borrow::Cow<'_, str> {
79        self.path.to_string_lossy()
80    }
81
82    pub fn into_cstring(self, vm: &VirtualMachine) -> PyResult<std::ffi::CString> {
83        std::ffi::CString::new(self.into_bytes()).map_err(|err| err.to_pyexception(vm))
84    }
85
86    #[cfg(windows)]
87    pub fn to_widecstring(&self, vm: &VirtualMachine) -> PyResult<widestring::WideCString> {
88        widestring::WideCString::from_os_str(&self.path).map_err(|err| err.to_pyexception(vm))
89    }
90
91    pub fn filename(&self, vm: &VirtualMachine) -> PyResult {
92        self.mode.process_path(self.path.clone(), vm)
93    }
94}
95
96impl AsRef<Path> for OsPath {
97    fn as_ref(&self) -> &Path {
98        self.as_path()
99    }
100}
101
102impl TryFromObject for OsPath {
103    // TODO: path_converter with allow_fd=0 in CPython
104    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
105        let fspath = FsPath::try_from(obj, true, vm)?;
106        Self::from_fspath(fspath, vm)
107    }
108}
109
110// path_t with allow_fd in CPython
111#[derive(Clone)]
112pub(crate) enum OsPathOrFd {
113    Path(OsPath),
114    Fd(i32),
115}
116
117impl TryFromObject for OsPathOrFd {
118    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
119        let r = match obj.try_index_opt(vm) {
120            Some(int) => Self::Fd(int?.try_to_primitive(vm)?),
121            None => Self::Path(obj.try_into_value(vm)?),
122        };
123        Ok(r)
124    }
125}
126
127impl From<OsPath> for OsPathOrFd {
128    fn from(path: OsPath) -> Self {
129        Self::Path(path)
130    }
131}
132
133impl OsPathOrFd {
134    pub fn filename(&self, vm: &VirtualMachine) -> PyObjectRef {
135        match self {
136            OsPathOrFd::Path(path) => path.filename(vm).unwrap_or_else(|_| vm.ctx.none()),
137            OsPathOrFd::Fd(fd) => vm.ctx.new_int(*fd).into(),
138        }
139    }
140}
141
142// TODO: preserve the input `PyObjectRef` of filename and filename2 (Failing check `self.assertIs(err.filename, name, str(func)`)
143pub struct IOErrorBuilder<'a> {
144    error: &'a std::io::Error,
145    filename: Option<OsPathOrFd>,
146    filename2: Option<OsPathOrFd>,
147}
148
149impl<'a> IOErrorBuilder<'a> {
150    pub fn new(error: &'a std::io::Error) -> Self {
151        Self {
152            error,
153            filename: None,
154            filename2: None,
155        }
156    }
157    pub(crate) fn filename(mut self, filename: impl Into<OsPathOrFd>) -> Self {
158        let filename = filename.into();
159        self.filename.replace(filename);
160        self
161    }
162    pub(crate) fn filename2(mut self, filename: impl Into<OsPathOrFd>) -> Self {
163        let filename = filename.into();
164        self.filename2.replace(filename);
165        self
166    }
167
168    pub(crate) fn with_filename(
169        error: &'a std::io::Error,
170        filename: impl Into<OsPathOrFd>,
171        vm: &VirtualMachine,
172    ) -> PyBaseExceptionRef {
173        let zelf = Self {
174            error,
175            filename: Some(filename.into()),
176            filename2: None,
177        };
178        zelf.to_pyexception(vm)
179    }
180}
181
182impl<'a> ToPyException for IOErrorBuilder<'a> {
183    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
184        let excp = self.error.to_pyexception(vm);
185
186        if let Some(filename) = &self.filename {
187            excp.as_object()
188                .set_attr("filename", filename.filename(vm), vm)
189                .unwrap();
190        }
191        if let Some(filename2) = &self.filename2 {
192            excp.as_object()
193                .set_attr("filename2", filename2.filename(vm), vm)
194                .unwrap();
195        }
196        excp
197    }
198}