use std::mem;
use monty_types::{
ExcType, MkdirCallArgs, MontyObject, MontyPath, OsFunctionCall, PathBytesDataArgs, PathStringDataArgs,
RenameCallArgs,
};
use crate::{
args::{ArgValues, FromArgs, LaxBool},
bytecode::VM,
exception_private::{ExcTypeExt, RunError, RunResult, SimpleException},
heap::{ContainsHeap, DropWithContext, Heap, HeapData, HeapId},
intern::{Interns, StaticStrings},
value::Value,
};
impl<C: ContainsHeap> DropWithContext<C> for OsFunctionCall {
fn drop_with(self, _heap: &mut C) {
drop(self);
}
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub(crate) enum PendingOsEffect {
BufferStore { file_id: HeapId },
WritePosition {
file_id: HeapId,
previous_position: u64,
previous_length: u64,
},
ListdirNames,
}
impl PendingOsEffect {
pub(crate) fn pinned_file(self) -> Option<HeapId> {
match self {
Self::BufferStore { file_id } | Self::WritePosition { file_id, .. } => Some(file_id),
Self::ListdirNames => None,
}
}
}
pub(crate) fn listdir_names(obj: MontyObject) -> Result<MontyObject, RunError> {
let invalid = |type_name: &str| -> RunError {
SimpleException::new_msg(
ExcType::RuntimeError,
format!("invalid return type: os.listdir requires the host to return a list of paths, got {type_name}"),
)
.into()
};
let MontyObject::List(mut items) = obj else {
return Err(invalid(obj.type_name()));
};
for item in &mut items {
match item {
MontyObject::Path(path) | MontyObject::String(path) => {
if let Some(sep) = path.rfind('/') {
path.drain(..=sep);
}
*item = MontyObject::String(mem::take(path));
}
other => return Err(invalid(other.type_name())),
}
}
Ok(MontyObject::List(items))
}
#[must_use]
pub(crate) fn is_path_os_method(method: StaticStrings) -> bool {
matches!(
method,
StaticStrings::Exists
| StaticStrings::IsFile
| StaticStrings::IsDir
| StaticStrings::IsSymlink
| StaticStrings::ReadText
| StaticStrings::ReadBytes
| StaticStrings::StatMethod
| StaticStrings::Iterdir
| StaticStrings::Resolve
| StaticStrings::Absolute
| StaticStrings::Unlink
| StaticStrings::Rmdir
| StaticStrings::WriteText
| StaticStrings::AppendText
| StaticStrings::WriteBytes
| StaticStrings::AppendBytes
| StaticStrings::Mkdir
| StaticStrings::Rename
)
}
pub(crate) fn build_path_os_call(
method: StaticStrings,
path: MontyPath,
args: ArgValues,
vm: &mut VM<'_>,
) -> RunResult<Option<OsFunctionCall>> {
macro_rules! path_only {
($name:literal, $variant:ident) => {{
args.check_zero_args($name, vm.heap)?;
OsFunctionCall::$variant(path)
}};
}
let call = match method {
StaticStrings::Exists => path_only!("exists", Exists),
StaticStrings::IsFile => path_only!("is_file", IsFile),
StaticStrings::IsDir => path_only!("is_dir", IsDir),
StaticStrings::IsSymlink => path_only!("is_symlink", IsSymlink),
StaticStrings::ReadText => path_only!("read_text", ReadText),
StaticStrings::ReadBytes => path_only!("read_bytes", ReadBytes),
StaticStrings::StatMethod => path_only!("stat", Stat),
StaticStrings::Iterdir => path_only!("iterdir", Iterdir),
StaticStrings::Resolve => path_only!("resolve", Resolve),
StaticStrings::Absolute => path_only!("absolute", Absolute),
StaticStrings::Unlink => path_only!("unlink", Unlink),
StaticStrings::Rmdir => path_only!("rmdir", Rmdir),
StaticStrings::WriteText => {
OsFunctionCall::WriteText(extract_str_data("write_text", path, args, vm.heap, vm.interns)?)
}
StaticStrings::AppendText => {
OsFunctionCall::AppendText(extract_str_data("append_text", path, args, vm.heap, vm.interns)?)
}
StaticStrings::WriteBytes => {
OsFunctionCall::WriteBytes(extract_bytes_data("write_bytes", path, args, vm.heap, vm.interns)?)
}
StaticStrings::AppendBytes => {
OsFunctionCall::AppendBytes(extract_bytes_data("append_bytes", path, args, vm.heap, vm.interns)?)
}
StaticStrings::Mkdir => OsFunctionCall::Mkdir(extract_mkdir_args(path, args, vm)?),
StaticStrings::Rename => OsFunctionCall::Rename(extract_rename_args(path, args, vm.heap, vm.interns)?),
_ => {
let _ = path;
args.drop_with(vm.heap);
return Ok(None);
}
};
Ok(Some(call))
}
fn extract_str_data(
method: &'static str,
path: MontyPath,
args: ArgValues,
heap: &mut Heap,
interns: &Interns,
) -> RunResult<PathStringDataArgs> {
let data = arg_or_missing_data(method, args, heap)?;
let data_str = value_to_owned_string(&data, heap, interns);
let py_type = data.py_type_name_heap(heap, interns);
data.drop_with(heap);
match data_str {
Some(data) => Ok(PathStringDataArgs { path, data }),
None => Err(ExcType::type_error(format!("data must be str, not {py_type}"))),
}
}
fn extract_bytes_data(
method: &'static str,
path: MontyPath,
args: ArgValues,
heap: &mut Heap,
interns: &Interns,
) -> RunResult<PathBytesDataArgs> {
let data = arg_or_missing_data(method, args, heap)?;
let bytes = value_to_owned_bytes(&data, heap, interns);
let py_type = data.py_type_name_heap(heap, interns);
data.drop_with(heap);
match bytes {
Some(data) => Ok(PathBytesDataArgs { path, data }),
None => Err(ExcType::type_error(format!(
"memoryview: a bytes-like object is required, not '{py_type}'"
))),
}
}
#[derive(FromArgs)]
#[from_args(name = "Path.mkdir", style = def)]
struct PathMkdirArgs {
#[from_args(default = 0o777_i64)]
mode: i64,
#[from_args(default = LaxBool::new(false))]
parents: LaxBool,
#[from_args(default = LaxBool::new(false))]
exist_ok: LaxBool,
}
fn extract_mkdir_args(path: MontyPath, args: ArgValues, vm: &mut VM<'_>) -> RunResult<MkdirCallArgs> {
let PathMkdirArgs {
mode,
parents,
exist_ok,
} = PathMkdirArgs::from_args(args, vm)?;
let _ = mode;
Ok(MkdirCallArgs {
path,
parents: parents.bool(),
exist_ok: exist_ok.bool(),
})
}
fn extract_rename_args(
src: MontyPath,
args: ArgValues,
heap: &mut Heap,
interns: &Interns,
) -> RunResult<RenameCallArgs> {
let target = args.get_one_arg("rename", heap)?;
let dst_str = value_to_owned_string(&target, heap, interns);
target.drop_with(heap);
match dst_str {
Some(dst) => Ok(RenameCallArgs {
src,
dst: MontyPath::new(dst),
}),
None => Err(ExcType::type_error(
"Path.rename() argument 'target' must be str or Path".to_owned(),
)),
}
}
fn arg_or_missing_data(method: &'static str, args: ArgValues, heap: &mut Heap) -> RunResult<Value> {
if matches!(args, ArgValues::Empty) {
return Err(ExcType::type_error(format!(
"Path.{method}() missing 1 required positional argument: 'data'"
)));
}
args.get_one_arg(method, heap)
}
pub(crate) fn value_to_owned_string(value: &Value, heap: &Heap, interns: &Interns) -> Option<String> {
match value {
Value::InternString(id) => Some(interns.get_str(*id).to_owned()),
Value::Ref(id) => match heap.get(*id) {
HeapData::Str(s) => Some(s.as_str().to_owned()),
HeapData::Path(p) => Some(p.as_str().to_owned()),
_ => None,
},
_ => None,
}
}
fn value_to_owned_bytes(value: &Value, heap: &Heap, interns: &Interns) -> Option<Vec<u8>> {
match value {
Value::InternBytes(id) => Some(interns.get_bytes(*id).to_owned()),
Value::Ref(id) => match heap.get(*id) {
HeapData::Bytes(b) => Some(b.as_slice().to_owned()),
_ => None,
},
_ => None,
}
}