rustpython_vm/function/
fspath.rs1use crate::{
2 PyObjectRef, PyResult, TryFromObject, VirtualMachine,
3 builtins::{PyBytes, PyBytesRef, PyStrRef},
4 convert::{IntoPyException, ToPyObject},
5 function::PyStr,
6};
7use alloc::borrow::Cow;
8use core::hint::cold_path;
9use std::{ffi::OsStr, path::PathBuf};
10
11#[derive(Clone)]
13pub enum FsPath {
14 Str(PyStrRef),
15 Bytes(PyBytesRef),
16}
17
18impl FsPath {
19 pub fn try_from_path_like(
20 obj: PyObjectRef,
21 check_for_nul: bool,
22 vm: &VirtualMachine,
23 ) -> PyResult<Self> {
24 Self::try_from(
25 obj,
26 check_for_nul,
27 "expected str, bytes or os.PathLike object",
28 vm,
29 )
30 }
31
32 pub fn try_from(
34 obj: PyObjectRef,
35 check_for_nul: bool,
36 msg: &'static str,
37 vm: &VirtualMachine,
38 ) -> PyResult<Self> {
39 let match1 = |obj: PyObjectRef| {
40 let pathlike = match_class!(match obj {
41 s @ PyStr => {
42 if check_for_nul && s.contains_nuls() {
43 cold_path();
44 return Err(crate::exceptions::nul_char_error(vm));
45 }
46 Self::Str(s)
47 }
48 b @ PyBytes => {
49 if check_for_nul && b.payload.contains_nuls() {
50 cold_path();
51 return Err(crate::exceptions::nul_char_error(vm));
52 }
53 Self::Bytes(b)
54 }
55 obj => return Ok(Err(obj)),
56 });
57 Ok(Ok(pathlike))
58 };
59 let obj = match match1(obj)? {
60 Ok(pathlike) => return Ok(pathlike),
61 Err(obj) => obj,
62 };
63 let not_pathlike_error = || format!("{msg}, not {}", obj.class().name());
64 let method = vm.get_method_or_type_error(
65 obj.clone(),
66 identifier!(vm, __fspath__),
67 not_pathlike_error,
68 )?;
69 if vm.is_none(&method) {
71 return Err(vm.new_type_error(not_pathlike_error()));
72 }
73 let result = method.call((), vm)?;
74 match1(result)?.map_err(|result| {
75 vm.new_type_error(format!(
76 "expected {}.__fspath__() to return str or bytes, not {}",
77 obj.class().name(),
78 result.class().name(),
79 ))
80 })
81 }
82
83 pub fn as_os_str(&self, vm: &VirtualMachine) -> PyResult<Cow<'_, OsStr>> {
84 match self {
86 Self::Str(s) => vm.fsencode(s),
87 Self::Bytes(b) => Self::bytes_as_os_str(b.as_bytes(), vm).map(Cow::Borrowed),
88 }
89 }
90
91 #[must_use]
92 pub fn as_bytes(&self) -> &[u8] {
93 match self {
95 Self::Str(s) => s.as_bytes(),
96 Self::Bytes(b) => b.as_bytes(),
97 }
98 }
99
100 #[must_use]
101 pub fn to_string_lossy(&self) -> Cow<'_, str> {
102 match self {
103 Self::Str(s) => s.to_string_lossy(),
104 Self::Bytes(s) => String::from_utf8_lossy(s.as_bytes()),
105 }
106 }
107
108 pub fn to_path_buf(&self, vm: &VirtualMachine) -> PyResult<PathBuf> {
109 let path = match self {
110 Self::Str(s) => PathBuf::from(vm.fsencode(s)?.as_ref() as &OsStr),
111 Self::Bytes(b) => PathBuf::from(Self::bytes_as_os_str(b.as_bytes(), vm)?),
112 };
113 Ok(path)
114 }
115
116 pub fn to_cstring(&self, vm: &VirtualMachine) -> PyResult<alloc::ffi::CString> {
117 alloc::ffi::CString::new(self.as_bytes()).map_err(|e| e.into_pyexception(vm))
118 }
119
120 #[cfg(windows)]
121 pub fn to_wide_cstring(&self, vm: &VirtualMachine) -> PyResult<widestring::WideCString> {
122 widestring::WideCString::from_os_str(self.as_os_str(vm)?)
123 .map_err(|err| err.into_pyexception(vm))
124 }
125
126 pub fn bytes_as_os_str<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a std::ffi::OsStr> {
127 rustpython_host_env::os::bytes_as_os_str(b).map_err(|e| {
128 vm.new_unicode_decode_error(
129 vm.ctx.new_str("utf-8"),
130 vm.ctx.new_bytes(b.to_vec()),
131 e.valid_up_to(),
132 e.error_len().map_or(b.len(), |n| e.valid_up_to() + n),
133 vm.ctx.new_str("can't decode path for utf-8"),
134 )
135 })
136 }
137}
138
139impl ToPyObject for FsPath {
140 fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
141 match self {
142 Self::Str(s) => s.into(),
143 Self::Bytes(b) => b.into(),
144 }
145 }
146}
147
148impl TryFromObject for FsPath {
149 fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
152 Self::try_from_path_like(obj, true, vm)
153 }
154}