use monty_types::{GetenvArgs, MkdirCallArgs, MontyObject, MontyPath, OsFunctionCall, RenameCallArgs};
use crate::{
args::{ArgValues, FromArgs, LaxBool},
bytecode::{CallResult, VM},
defer_drop,
exception_private::{ExcType, ExcTypeExt, RunError, RunResult},
heap::{HeapData, HeapId},
intern::{StaticStrings, StringId},
modules::ModuleFunctions,
object_bridge::MontyObjectExt,
os_dispatch::{PendingOsEffect, value_to_owned_string},
types::{Module, Property, Type, property::ZeroArgOsProperty, str::allocate_string},
value::Value,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, serde::Serialize, serde::Deserialize)]
#[strum(serialize_all = "lowercase")]
pub(crate) enum OsFunctions {
Getenv,
Listdir,
Stat,
Mkdir,
Makedirs,
Remove,
Unlink,
Rmdir,
Rename,
Replace,
Fspath,
}
pub fn create_module(vm: &mut VM<'_>) -> HeapId {
fn function(f: OsFunctions) -> Value {
Value::ModuleFunction(ModuleFunctions::Os(f))
}
let attrs = [
(StaticStrings::Getenv, function(OsFunctions::Getenv)),
(StaticStrings::Listdir, function(OsFunctions::Listdir)),
(StaticStrings::StatMethod, function(OsFunctions::Stat)),
(StaticStrings::Mkdir, function(OsFunctions::Mkdir)),
(StaticStrings::Makedirs, function(OsFunctions::Makedirs)),
(StaticStrings::Remove, function(OsFunctions::Remove)),
(StaticStrings::Unlink, function(OsFunctions::Unlink)),
(StaticStrings::Rmdir, function(OsFunctions::Rmdir)),
(StaticStrings::Rename, function(OsFunctions::Rename)),
(StaticStrings::Replace, function(OsFunctions::Replace)),
(StaticStrings::OsFspath, function(OsFunctions::Fspath)),
(
StaticStrings::Environ,
Value::Property(Property::Os(ZeroArgOsProperty::GetEnviron)),
),
(StaticStrings::Sep, Value::InternString(StringId::from_ascii(b'/'))),
(StaticStrings::Altsep, Value::None),
(StaticStrings::Extsep, Value::InternString(StringId::from_ascii(b'.'))),
(StaticStrings::Curdir, Value::InternString(StringId::from_ascii(b'.'))),
(StaticStrings::Pardir, StaticStrings::ParentDirString.into()),
(StaticStrings::Linesep, Value::InternString(StringId::from_ascii(b'\n'))),
(StaticStrings::Name, StaticStrings::Posix.into()),
(StaticStrings::Devnull, StaticStrings::DevNullString.into()),
];
let mut module = Module::new(StaticStrings::Os);
for (attr, value) in attrs {
module.set_attr(attr, value, vm);
}
vm.heap.allocate(HeapData::Module(Box::new(module)))
}
pub(super) fn call(vm: &mut VM<'_>, functions: OsFunctions, args: ArgValues) -> RunResult<CallResult> {
match functions {
OsFunctions::Getenv => getenv(vm, args),
OsFunctions::Listdir => listdir(vm, args),
OsFunctions::Stat => stat(vm, args),
OsFunctions::Mkdir => mkdir(vm, args),
OsFunctions::Makedirs => makedirs(vm, args),
OsFunctions::Remove => remove(vm, args),
OsFunctions::Unlink => unlink(vm, args),
OsFunctions::Rmdir => rmdir(vm, args),
OsFunctions::Rename => rename(vm, args),
OsFunctions::Replace => replace(vm, args),
OsFunctions::Fspath => fspath(vm, args),
}
}
fn getenv(vm: &mut VM<'_>, args: ArgValues) -> RunResult<CallResult> {
let (key_value, default_value) = args.get_one_two_args("os.getenv", vm.heap)?;
if let Some(key) = key_value.as_either_str(vm.heap) {
key_value.drop_with(vm.heap);
Ok(CallResult::OsCall(OsFunctionCall::Getenv(GetenvArgs {
key: key.into_string(vm.interns),
default: MontyObject::new(default_value.unwrap_or(Value::None), vm),
})))
} else {
let type_name = key_value.py_type_name_heap(vm.heap, vm.interns);
key_value.drop_with(vm.heap);
if let Some(d) = default_value {
d.drop_with(vm.heap);
}
Err(ExcType::type_error(format!("str expected, not {type_name}")))
}
}
#[derive(FromArgs)]
#[from_args(name = "listdir", at_most_total)]
struct ListdirArgs {
#[from_args(default = Value::None)]
path: Value,
}
fn listdir(vm: &mut VM<'_>, args: ArgValues) -> RunResult<CallResult> {
let ListdirArgs { path } = ListdirArgs::from_args(args, vm)?;
defer_drop!(path, vm);
let path = if matches!(path, Value::None) {
MontyPath::from(".")
} else {
extract_os_path(path, "listdir", "path", PathAccepts::FdOrNone, vm)?
};
Ok(CallResult::OsCallWithEffect {
call: OsFunctionCall::Iterdir(path),
effect: PendingOsEffect::ListdirNames,
})
}
#[derive(FromArgs)]
#[from_args(name = "stat", style = c_named)]
struct StatArgs {
path: Value,
#[from_args(kw_only, default = Value::None)]
dir_fd: Value,
#[from_args(kw_only, default = LaxBool::new(true))]
follow_symlinks: LaxBool,
}
fn stat(vm: &mut VM<'_>, args: ArgValues) -> RunResult<CallResult> {
let StatArgs {
path,
dir_fd,
follow_symlinks,
} = StatArgs::from_args(args, vm)?;
defer_drop!(path, vm);
defer_drop!(dir_fd, vm);
let path = extract_os_path(path, "stat", "path", PathAccepts::Fd, vm)?;
check_dir_fd(dir_fd, vm)?;
if follow_symlinks.bool() {
Ok(CallResult::OsCall(OsFunctionCall::Stat(path)))
} else {
Err(ExcType::not_implemented_os_arg(Some("stat"), "follow_symlinks"))
}
}
#[derive(FromArgs)]
#[from_args(name = "mkdir", style = c_named)]
struct MkdirArgs {
path: Value,
#[from_args(default = Value::Int(0o777))]
mode: Value,
#[from_args(kw_only, default = Value::None)]
dir_fd: Value,
}
fn mkdir(vm: &mut VM<'_>, args: ArgValues) -> RunResult<CallResult> {
let MkdirArgs { path, mode, dir_fd } = MkdirArgs::from_args(args, vm)?;
defer_drop!(path, vm);
defer_drop!(mode, vm);
defer_drop!(dir_fd, vm);
let path = extract_os_path(path, "mkdir", "path", PathAccepts::NoFd, vm)?;
check_mode(mode, vm)?;
check_dir_fd(dir_fd, vm)?;
Ok(CallResult::OsCall(OsFunctionCall::Mkdir(MkdirCallArgs {
path,
parents: false,
exist_ok: false,
})))
}
#[derive(FromArgs)]
#[from_args(name = "makedirs", style = def)]
struct MakedirsArgs {
name: Value,
#[from_args(default = Value::Int(0o777))]
mode: Value,
#[from_args(default = LaxBool::new(false))]
exist_ok: LaxBool,
}
fn makedirs(vm: &mut VM<'_>, args: ArgValues) -> RunResult<CallResult> {
let MakedirsArgs { name, mode, exist_ok } = MakedirsArgs::from_args(args, vm)?;
defer_drop!(name, vm);
defer_drop!(mode, vm);
let path = extract_path(name, vm, ExcType::type_error_fspath)?;
check_mode(mode, vm)?;
Ok(CallResult::OsCall(OsFunctionCall::Mkdir(MkdirCallArgs {
path,
parents: true,
exist_ok: exist_ok.bool(),
})))
}
#[derive(FromArgs)]
#[from_args(name = "remove", style = c_named)]
struct RemoveArgs {
path: Value,
#[from_args(kw_only, default = Value::None)]
dir_fd: Value,
}
fn remove(vm: &mut VM<'_>, args: ArgValues) -> RunResult<CallResult> {
let RemoveArgs { path, dir_fd } = RemoveArgs::from_args(args, vm)?;
single_path_call(path, dir_fd, "remove", OsFunctionCall::Unlink, vm)
}
#[derive(FromArgs)]
#[from_args(name = "unlink", style = c_named)]
struct UnlinkArgs {
path: Value,
#[from_args(kw_only, default = Value::None)]
dir_fd: Value,
}
fn unlink(vm: &mut VM<'_>, args: ArgValues) -> RunResult<CallResult> {
let UnlinkArgs { path, dir_fd } = UnlinkArgs::from_args(args, vm)?;
single_path_call(path, dir_fd, "unlink", OsFunctionCall::Unlink, vm)
}
fn single_path_call(
path: Value,
dir_fd: Value,
func: &'static str,
make_call: impl FnOnce(MontyPath) -> OsFunctionCall,
vm: &mut VM<'_>,
) -> RunResult<CallResult> {
defer_drop!(path, vm);
defer_drop!(dir_fd, vm);
let path = extract_os_path(path, func, "path", PathAccepts::NoFd, vm)?;
check_dir_fd(dir_fd, vm)?;
Ok(CallResult::OsCall(make_call(path)))
}
#[derive(FromArgs)]
#[from_args(name = "rmdir", style = c_named)]
struct RmdirArgs {
path: Value,
#[from_args(kw_only, default = Value::None)]
dir_fd: Value,
}
fn rmdir(vm: &mut VM<'_>, args: ArgValues) -> RunResult<CallResult> {
let RmdirArgs { path, dir_fd } = RmdirArgs::from_args(args, vm)?;
single_path_call(path, dir_fd, "rmdir", OsFunctionCall::Rmdir, vm)
}
#[derive(FromArgs)]
#[from_args(name = "rename", style = c_named)]
struct RenameArgs {
src: Value,
dst: Value,
#[from_args(kw_only, default = Value::None)]
src_dir_fd: Value,
#[from_args(kw_only, default = Value::None)]
dst_dir_fd: Value,
}
fn rename(vm: &mut VM<'_>, args: ArgValues) -> RunResult<CallResult> {
let RenameArgs {
src,
dst,
src_dir_fd,
dst_dir_fd,
} = RenameArgs::from_args(args, vm)?;
rename_like(src, dst, src_dir_fd, dst_dir_fd, "rename", vm)
}
#[derive(FromArgs)]
#[from_args(name = "replace", style = c_named)]
struct ReplaceArgs {
src: Value,
dst: Value,
#[from_args(kw_only, default = Value::None)]
src_dir_fd: Value,
#[from_args(kw_only, default = Value::None)]
dst_dir_fd: Value,
}
fn replace(vm: &mut VM<'_>, args: ArgValues) -> RunResult<CallResult> {
let ReplaceArgs {
src,
dst,
src_dir_fd,
dst_dir_fd,
} = ReplaceArgs::from_args(args, vm)?;
rename_like(src, dst, src_dir_fd, dst_dir_fd, "replace", vm)
}
fn rename_like(
src: Value,
dst: Value,
src_dir_fd: Value,
dst_dir_fd: Value,
func: &'static str,
vm: &mut VM<'_>,
) -> RunResult<CallResult> {
defer_drop!(src, vm);
defer_drop!(dst, vm);
defer_drop!(src_dir_fd, vm);
defer_drop!(dst_dir_fd, vm);
let src = extract_os_path(src, func, "src", PathAccepts::NoFd, vm)?;
let dst = extract_os_path(dst, func, "dst", PathAccepts::NoFd, vm)?;
if dir_fd_specified(src_dir_fd, vm)? | dir_fd_specified(dst_dir_fd, vm)? {
Err(ExcType::not_implemented_os_arg(Some(func), "src_dir_fd and dst_dir_fd"))
} else {
Ok(CallResult::OsCall(OsFunctionCall::Rename(RenameCallArgs { src, dst })))
}
}
#[derive(FromArgs)]
#[from_args(name = "fspath", style = c_named, at_most_total)]
struct FspathArgs {
path: Value,
}
fn fspath(vm: &mut VM<'_>, args: ArgValues) -> RunResult<CallResult> {
let FspathArgs { path } = FspathArgs::from_args(args, vm)?;
match path.py_type_heap(vm.heap) {
Type::Str | Type::Bytes => Ok(CallResult::Value(path)),
Type::Path => {
let text = value_to_owned_string(&path, vm.heap, vm.interns).expect("Path always yields a string");
path.drop_with(vm.heap);
Ok(CallResult::Value(allocate_string(text, vm.heap)))
}
_ => {
let type_name = path.py_type_name_heap(vm.heap, vm.interns).into_owned();
path.drop_with(vm.heap);
Err(ExcType::type_error_fspath(&type_name))
}
}
}
fn extract_os_path(
value: &Value,
func: &'static str,
arg: &'static str,
accepts: PathAccepts,
vm: &VM<'_>,
) -> RunResult<MontyPath> {
extract_path(value, vm, |type_name| {
ExcType::type_error_os_path(func, arg, accepts.phrase_for(value, vm), type_name)
})
}
#[derive(Clone, Copy)]
enum PathAccepts {
NoFd,
Fd,
FdOrNone,
}
impl PathAccepts {
fn phrase_for(self, value: &Value, vm: &VM<'_>) -> &'static str {
let refused = match value.py_type_heap(vm.heap) {
Type::Bytes => true,
Type::Int | Type::Bool => matches!(self, Self::Fd | Self::FdOrNone),
_ => false,
};
match (self, refused) {
(Self::NoFd, false) => "string, bytes or os.PathLike",
(Self::Fd, false) => "string, bytes, os.PathLike or integer",
(Self::FdOrNone, false) => "string, bytes, os.PathLike, integer or None",
(Self::NoFd | Self::Fd, true) => "string or os.PathLike",
(Self::FdOrNone, true) => "string, os.PathLike or None",
}
}
}
fn extract_path(value: &Value, vm: &VM<'_>, type_error: impl FnOnce(&str) -> RunError) -> RunResult<MontyPath> {
value_to_owned_string(value, vm.heap, vm.interns).map_or_else(
|| Err(type_error(&value.py_type_name_heap(vm.heap, vm.interns))),
|path| Ok(MontyPath::new(path)),
)
}
fn check_dir_fd(value: &Value, vm: &VM<'_>) -> RunResult<()> {
if dir_fd_specified(value, vm)? {
Err(ExcType::not_implemented_os_arg(None, "dir_fd"))
} else {
Ok(())
}
}
fn dir_fd_specified(value: &Value, vm: &VM<'_>) -> RunResult<bool> {
match value {
Value::None => Ok(false),
Value::Bool(_) => Ok(true),
Value::Int(fd) => match i32::try_from(*fd) {
Ok(_) => Ok(true),
Err(_) if *fd > 0 => Err(ExcType::overflow_fd_maximum()),
Err(_) => Err(ExcType::overflow_fd_minimum()),
},
_ => match value.py_type_heap(vm.heap) {
Type::Int if value.long_int_is_negative(vm) => Err(ExcType::overflow_fd_minimum()),
Type::Int => Err(ExcType::overflow_fd_maximum()),
other => Err(ExcType::type_error_dir_fd(&other.name(vm.heap, vm.interns))),
},
}
}
fn check_mode(value: &Value, vm: &VM<'_>) -> RunResult<()> {
match value {
Value::Bool(_) => Ok(()),
Value::Int(mode) => i32::try_from(*mode).map(|_| ()).map_err(|_| ExcType::overflow_c_int()),
_ => match value.py_type_heap(vm.heap) {
Type::Int => Err(ExcType::overflow_c_int()),
other => Err(ExcType::type_error_not_integer(&other.name(vm.heap, vm.interns))),
},
}
}