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