use std::path::Path;
use libc::wchar_t;
use pyo3::ffi as pyffi;
mod error {
use {
pyo3::{ffi as pyffi, prelude::*},
std::{
ffi::CStr,
fmt::{Display, Formatter},
},
};
fn format_pyerr(py: Python, err: PyErr) -> Result<String, &'static str> {
let type_repr = err
.get_type(py)
.repr()
.map_err(|_| "unable to get repr of error type")?;
let value_repr = err
.value(py)
.repr()
.map_err(|_| "unable to get repr of error value")?;
let mut traceback = None;
if let Some(tb) = err.traceback(py) {
if let Ok(tb) = tb.format() {
traceback = Some(tb)
}
}
let value = if let Some(tb) = traceback {
format!(
"{}: {}\n{}",
type_repr.to_string_lossy(),
value_repr.to_string_lossy(),
tb
)
} else {
format!(
"{}: {}",
type_repr.to_string_lossy(),
value_repr.to_string_lossy()
)
};
Ok(value)
}
#[derive(Debug)]
pub enum NewInterpreterError {
Simple(&'static str),
Dynamic(String),
}
impl From<&'static str> for NewInterpreterError {
fn from(v: &'static str) -> Self {
NewInterpreterError::Simple(v)
}
}
impl Display for NewInterpreterError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self {
NewInterpreterError::Simple(value) => value.fmt(f),
NewInterpreterError::Dynamic(value) => value.fmt(f),
}
}
}
impl std::error::Error for NewInterpreterError {}
impl NewInterpreterError {
pub(crate) fn new_from_pyerr(py: Python, err: PyErr, context: &str) -> Self {
match format_pyerr(py, err) {
Ok(value) => NewInterpreterError::Dynamic(format!("during {context}: {value}")),
Err(msg) => NewInterpreterError::Dynamic(format!("during {context}: {msg}")),
}
}
pub(crate) fn new_from_pystatus(status: &pyffi::PyStatus, context: &str) -> Self {
if !status.func.is_null() && !status.err_msg.is_null() {
let func = unsafe { CStr::from_ptr(status.func) };
let msg = unsafe { CStr::from_ptr(status.err_msg) };
NewInterpreterError::Dynamic(format!(
"during {}: {}: {}",
context,
func.to_string_lossy(),
msg.to_string_lossy()
))
} else if !status.err_msg.is_null() {
let msg = unsafe { CStr::from_ptr(status.err_msg) };
NewInterpreterError::Dynamic(format!(
"during {}: {}",
context,
msg.to_string_lossy()
))
} else {
NewInterpreterError::Dynamic(format!("during {context}: could not format PyStatus"))
}
}
}
pub type NewInterpreterResult<T> = Result<T, NewInterpreterError>;
}
pub use error::{NewInterpreterError, NewInterpreterResult};
pub(crate) mod utils {
use super::*;
use std::ffi::{CString, OsString};
#[cfg(target_family = "unix")]
use std::{ffi::NulError, os::unix::ffi::OsStrExt};
#[cfg(target_family = "windows")]
use std::os::windows::prelude::OsStrExt;
pub(crate) unsafe fn set_config_string_from_str(
config: &pyffi::PyConfig,
dest: &*mut wchar_t,
value: &str,
context: &str,
) -> Result<(), NewInterpreterError> {
match CString::new(value) {
Ok(value) => unsafe {
let status = pyffi::PyConfig_SetBytesString(
config as *const _ as *mut _,
dest as *const *mut _ as *mut *mut _,
value.as_ptr(),
);
if pyffi::PyStatus_Exception(status) != 0 {
Err(NewInterpreterError::new_from_pystatus(&status, context))
} else {
Ok(())
}
},
Err(_) => Err(NewInterpreterError::Dynamic(format!(
"during {context}: unable to convert {value} to C string"
))),
}
}
#[cfg(unix)]
pub(crate) unsafe fn set_config_string_from_path(
config: &pyffi::PyConfig,
dest: &*mut wchar_t,
path: &Path,
context: &str,
) -> Result<(), NewInterpreterError> {
let value = CString::new(path.as_os_str().as_bytes())
.map_err(|_| NewInterpreterError::Simple("cannot convert path to C string"))?;
let status = unsafe {
pyffi::PyConfig_SetBytesString(
config as *const _ as *mut _,
dest as *const *mut _ as *mut *mut _,
value.as_ptr() as *const _,
)
};
if unsafe { pyffi::PyStatus_Exception(status) } != 0 {
Err(NewInterpreterError::new_from_pystatus(&status, context))
} else {
Ok(())
}
}
#[cfg(windows)]
pub(crate) unsafe fn set_config_string_from_path(
config: &pyffi::PyConfig,
dest: &*mut wchar_t,
path: &Path,
context: &str,
) -> Result<(), NewInterpreterError> {
let status = unsafe {
let mut value: Vec<wchar_t> = path.as_os_str().encode_wide().collect();
value.push(0);
pyffi::PyConfig_SetString(
config as *const _ as *mut _,
dest as *const *mut _ as *mut *mut _,
value.as_ptr() as *const _,
)
};
if unsafe { pyffi::PyStatus_Exception(status) } != 0 {
Err(NewInterpreterError::new_from_pystatus(&status, context))
} else {
Ok(())
}
}
#[cfg(target_family = "unix")]
pub fn set_argv(
config: &mut pyffi::PyConfig,
args: &[OsString],
) -> Result<(), NewInterpreterError> {
let argc = args.len() as isize;
let argv = args
.iter()
.map(|x| CString::new(x.as_bytes()))
.collect::<Result<Vec<_>, NulError>>()
.map_err(|_| {
NewInterpreterError::Simple("unable to construct C string from OsString")
})?;
let argvp = argv
.iter()
.map(|x| x.as_ptr() as *mut i8)
.collect::<Vec<_>>();
let status = unsafe {
pyffi::PyConfig_SetBytesArgv(config as *mut _, argc, argvp.as_ptr() as *mut _)
};
if unsafe { pyffi::PyStatus_Exception(status) } != 0 {
Err(NewInterpreterError::new_from_pystatus(
&status,
"setting argv",
))
} else {
Ok(())
}
}
#[cfg(target_family = "windows")]
pub fn set_argv(
config: &mut pyffi::PyConfig,
args: &[OsString],
) -> Result<(), NewInterpreterError> {
let argc = args.len() as isize;
let argv = args
.iter()
.map(|x| {
let mut buffer = x.encode_wide().collect::<Vec<u16>>();
buffer.push(0);
buffer
})
.collect::<Vec<_>>();
let argvp = argv
.iter()
.map(|x| x.as_ptr() as *mut u16)
.collect::<Vec<_>>();
let status =
unsafe { pyffi::PyConfig_SetArgv(config as *mut _, argc, argvp.as_ptr() as *mut _) };
if unsafe { pyffi::PyStatus_Exception(status) } != 0 {
Err(NewInterpreterError::new_from_pystatus(
&status,
"setting argv",
))
} else {
Ok(())
}
}
}