use crate::{
builtins::PyBaseExceptionRef,
convert::{ToPyException, TryFromObject},
function::FsPath,
object::AsObject,
PyObjectRef, PyResult, VirtualMachine,
};
use std::path::{Path, PathBuf};
#[derive(Clone)]
pub struct OsPath {
pub path: std::ffi::OsString,
pub(super) mode: OutputMode,
}
#[derive(Debug, Copy, Clone)]
pub(super) enum OutputMode {
String,
Bytes,
}
impl OutputMode {
pub(super) fn process_path(self, path: impl Into<PathBuf>, vm: &VirtualMachine) -> PyResult {
fn inner(mode: OutputMode, path: PathBuf, vm: &VirtualMachine) -> PyResult {
let path_as_string = |p: PathBuf| {
p.into_os_string().into_string().map_err(|_| {
vm.new_unicode_decode_error(
"Can't convert OS path to valid UTF-8 string".into(),
)
})
};
match mode {
OutputMode::String => path_as_string(path).map(|s| vm.ctx.new_str(s).into()),
OutputMode::Bytes => {
#[cfg(any(unix, target_os = "wasi"))]
{
use rustpython_common::os::ffi::OsStringExt;
Ok(vm.ctx.new_bytes(path.into_os_string().into_vec()).into())
}
#[cfg(windows)]
{
path_as_string(path).map(|s| vm.ctx.new_bytes(s.into_bytes()).into())
}
}
}
}
inner(self, path.into(), vm)
}
}
impl OsPath {
pub fn new_str(path: impl Into<std::ffi::OsString>) -> Self {
let path = path.into();
Self {
path,
mode: OutputMode::String,
}
}
pub(crate) fn from_fspath(fspath: FsPath, vm: &VirtualMachine) -> PyResult<OsPath> {
let path = fspath.as_os_str(vm)?.to_owned();
let mode = match fspath {
FsPath::Str(_) => OutputMode::String,
FsPath::Bytes(_) => OutputMode::Bytes,
};
Ok(OsPath { path, mode })
}
pub fn as_path(&self) -> &Path {
Path::new(&self.path)
}
pub fn into_bytes(self) -> Vec<u8> {
self.path.into_encoded_bytes()
}
pub fn to_string_lossy(&self) -> std::borrow::Cow<'_, str> {
self.path.to_string_lossy()
}
pub fn into_cstring(self, vm: &VirtualMachine) -> PyResult<std::ffi::CString> {
std::ffi::CString::new(self.into_bytes()).map_err(|err| err.to_pyexception(vm))
}
#[cfg(windows)]
pub fn to_widecstring(&self, vm: &VirtualMachine) -> PyResult<widestring::WideCString> {
widestring::WideCString::from_os_str(&self.path).map_err(|err| err.to_pyexception(vm))
}
pub fn filename(&self, vm: &VirtualMachine) -> PyResult {
self.mode.process_path(self.path.clone(), vm)
}
}
impl AsRef<Path> for OsPath {
fn as_ref(&self) -> &Path {
self.as_path()
}
}
impl TryFromObject for OsPath {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
let fspath = FsPath::try_from(obj, true, vm)?;
Self::from_fspath(fspath, vm)
}
}
#[derive(Clone)]
pub(crate) enum OsPathOrFd {
Path(OsPath),
Fd(i32),
}
impl TryFromObject for OsPathOrFd {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
let r = match obj.try_index_opt(vm) {
Some(int) => Self::Fd(int?.try_to_primitive(vm)?),
None => Self::Path(obj.try_into_value(vm)?),
};
Ok(r)
}
}
impl From<OsPath> for OsPathOrFd {
fn from(path: OsPath) -> Self {
Self::Path(path)
}
}
impl OsPathOrFd {
pub fn filename(&self, vm: &VirtualMachine) -> PyObjectRef {
match self {
OsPathOrFd::Path(path) => path.filename(vm).unwrap_or_else(|_| vm.ctx.none()),
OsPathOrFd::Fd(fd) => vm.ctx.new_int(*fd).into(),
}
}
}
pub struct IOErrorBuilder<'a> {
error: &'a std::io::Error,
filename: Option<OsPathOrFd>,
filename2: Option<OsPathOrFd>,
}
impl<'a> IOErrorBuilder<'a> {
pub fn new(error: &'a std::io::Error) -> Self {
Self {
error,
filename: None,
filename2: None,
}
}
pub(crate) fn filename(mut self, filename: impl Into<OsPathOrFd>) -> Self {
let filename = filename.into();
self.filename.replace(filename);
self
}
pub(crate) fn filename2(mut self, filename: impl Into<OsPathOrFd>) -> Self {
let filename = filename.into();
self.filename2.replace(filename);
self
}
pub(crate) fn with_filename(
error: &'a std::io::Error,
filename: impl Into<OsPathOrFd>,
vm: &VirtualMachine,
) -> PyBaseExceptionRef {
let zelf = Self {
error,
filename: Some(filename.into()),
filename2: None,
};
zelf.to_pyexception(vm)
}
}
impl<'a> ToPyException for IOErrorBuilder<'a> {
fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
let excp = self.error.to_pyexception(vm);
if let Some(filename) = &self.filename {
excp.as_object()
.set_attr("filename", filename.filename(vm), vm)
.unwrap();
}
if let Some(filename2) = &self.filename2 {
excp.as_object()
.set_attr("filename2", filename2.filename(vm), vm)
.unwrap();
}
excp
}
}