use std::{
ffi::{CString, c_int, c_void},
fmt,
mem::transmute,
};
use rb_sys::{
VALUE, rb_alias, rb_attr, rb_class_inherited_p, rb_const_get, rb_const_set,
rb_define_class_id_under, rb_define_method_id, rb_define_module_function,
rb_define_module_id_under, rb_define_private_method, rb_define_protected_method,
rb_include_module, rb_mComparable, rb_mEnumerable, rb_mErrno, rb_mFileTest, rb_mGC, rb_mKernel,
rb_mMath, rb_mProcess, rb_mWaitReadable, rb_mWaitWritable, rb_mod_ancestors, rb_module_new,
rb_prepend_module, ruby_value_type,
};
use crate::{
Ruby,
class::{Class, RClass},
error::{Error, protect},
exception::ExceptionClass,
into_value::IntoValue,
method::Method,
object::Object,
r_array::RArray,
try_convert::TryConvert,
value::{
IntoId, NonZeroValue, ReprValue, Value,
private::{self, ReprValue as _},
},
};
impl Ruby {
pub fn module_new(&self) -> RModule {
unsafe { RModule::from_rb_value_unchecked(rb_module_new()) }
}
}
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct RModule(NonZeroValue);
impl RModule {
#[inline]
pub fn from_value(val: Value) -> Option<Self> {
unsafe {
(val.rb_type() == ruby_value_type::RUBY_T_MODULE)
.then(|| Self(NonZeroValue::new_unchecked(val)))
}
}
#[inline]
pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self {
unsafe { Self(NonZeroValue::new_unchecked(Value::new(val))) }
}
#[allow(clippy::new_without_default)]
#[deprecated(note = "please use `Ruby::module_new` instead")]
#[cfg(feature = "old-api")]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn new() -> Self {
get_ruby!().module_new()
}
pub fn define_module_function<M>(self, name: &str, func: M) -> Result<(), Error>
where
M: Method,
{
debug_assert_value!(self);
let name = CString::new(name).unwrap();
protect(|| {
unsafe {
rb_define_module_function(
self.as_rb_value(),
name.as_ptr(),
transmute::<*mut c_void, Option<unsafe extern "C" fn() -> VALUE>>(
func.as_ptr(),
),
M::arity().into(),
)
};
Ruby::get_with(self).qnil()
})?;
Ok(())
}
}
impl fmt::Display for RModule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", unsafe { self.to_s_infallible() })
}
}
impl fmt::Debug for RModule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.inspect())
}
}
impl IntoValue for RModule {
#[inline]
fn into_value_with(self, _: &Ruby) -> Value {
self.0.get()
}
}
impl Object for RModule {}
impl Module for RModule {}
unsafe impl private::ReprValue for RModule {}
impl ReprValue for RModule {}
impl TryConvert for RModule {
fn try_convert(val: Value) -> Result<Self, Error> {
Self::from_value(val).ok_or_else(|| {
Error::new(
Ruby::get_with(val).exception_type_error(),
format!("no implicit conversion of {} into Module", unsafe {
val.classname()
},),
)
})
}
}
pub trait Module: Object + ReprValue + Copy {
fn define_class<T>(self, name: T, superclass: RClass) -> Result<RClass, Error>
where
T: IntoId,
{
debug_assert_value!(self);
debug_assert_value!(superclass);
let id = name.into_id_with(&Ruby::get_with(self));
let superclass = superclass.as_rb_value();
protect(|| unsafe {
RClass::from_rb_value_unchecked(rb_define_class_id_under(
self.as_rb_value(),
id.as_rb_id(),
superclass,
))
})
}
fn define_module<T>(self, name: T) -> Result<RModule, Error>
where
T: IntoId,
{
let id = name.into_id_with(&Ruby::get_with(self));
protect(|| unsafe {
RModule::from_rb_value_unchecked(rb_define_module_id_under(
self.as_rb_value(),
id.as_rb_id(),
))
})
}
fn define_error<T>(self, name: T, superclass: ExceptionClass) -> Result<ExceptionClass, Error>
where
T: IntoId,
{
self.define_class(name, superclass.as_r_class())
.map(|c| unsafe { ExceptionClass::from_value_unchecked(c.as_value()) })
}
fn include_module(self, module: RModule) -> Result<(), Error> {
protect(|| {
unsafe { rb_include_module(self.as_rb_value(), module.as_rb_value()) };
Ruby::get_with(self).qnil()
})?;
Ok(())
}
fn prepend_module(self, module: RModule) -> Result<(), Error> {
protect(|| {
unsafe { rb_prepend_module(self.as_rb_value(), module.as_rb_value()) };
Ruby::get_with(self).qnil()
})?;
Ok(())
}
fn const_set<T, U>(self, name: T, value: U) -> Result<(), Error>
where
T: IntoId,
U: IntoValue,
{
let handle = Ruby::get_with(self);
let id = name.into_id_with(&handle);
let val = value.into_value_with(&handle);
protect(|| {
unsafe { rb_const_set(self.as_rb_value(), id.as_rb_id(), val.as_rb_value()) };
handle.qnil()
})?;
Ok(())
}
fn const_get<T, U>(self, name: T) -> Result<U, Error>
where
T: IntoId,
U: TryConvert,
{
debug_assert_value!(self);
let id = name.into_id_with(&Ruby::get_with(self));
let res =
unsafe { protect(|| Value::new(rb_const_get(self.as_rb_value(), id.as_rb_id()))) };
res.and_then(TryConvert::try_convert)
}
fn is_inherited<T>(self, other: T) -> bool
where
T: ReprValue + Module,
{
unsafe {
Value::new(rb_class_inherited_p(
self.as_rb_value(),
other.as_rb_value(),
))
.to_bool()
}
}
fn ancestors(self) -> RArray {
unsafe { RArray::from_rb_value_unchecked(rb_mod_ancestors(self.as_rb_value())) }
}
fn define_method<T, M>(self, name: T, func: M) -> Result<(), Error>
where
T: IntoId,
M: Method,
{
debug_assert_value!(self);
let handle = Ruby::get_with(self);
let id = name.into_id_with(&handle);
protect(|| {
unsafe {
rb_define_method_id(
self.as_rb_value(),
id.as_rb_id(),
transmute::<*mut c_void, Option<unsafe extern "C" fn() -> VALUE>>(
func.as_ptr(),
),
M::arity().into(),
)
};
handle.qnil()
})?;
Ok(())
}
fn define_private_method<M>(self, name: &str, func: M) -> Result<(), Error>
where
M: Method,
{
debug_assert_value!(self);
let name = CString::new(name).unwrap();
protect(|| {
unsafe {
rb_define_private_method(
self.as_rb_value(),
name.as_ptr(),
transmute::<*mut c_void, Option<unsafe extern "C" fn() -> VALUE>>(
func.as_ptr(),
),
M::arity().into(),
)
};
Ruby::get_with(self).qnil()
})?;
Ok(())
}
fn define_protected_method<M>(self, name: &str, func: M) -> Result<(), Error>
where
M: Method,
{
debug_assert_value!(self);
let name = CString::new(name).unwrap();
protect(|| {
unsafe {
rb_define_protected_method(
self.as_rb_value(),
name.as_ptr(),
transmute::<*mut c_void, Option<unsafe extern "C" fn() -> VALUE>>(
func.as_ptr(),
),
M::arity().into(),
)
};
Ruby::get_with(self).qnil()
})?;
Ok(())
}
fn define_attr<T>(self, name: T, rw: Attr) -> Result<(), Error>
where
T: IntoId,
{
let handle = Ruby::get_with(self);
let id = name.into_id_with(&handle);
protect(|| {
unsafe {
rb_attr(
self.as_rb_value(),
id.as_rb_id(),
rw.is_read() as c_int,
rw.is_write() as c_int,
0,
)
};
handle.qnil()
})?;
Ok(())
}
fn define_alias<T, U>(self, dst: T, src: U) -> Result<(), Error>
where
T: IntoId,
U: IntoId,
{
let handle = Ruby::get_with(self);
let d_id = dst.into_id_with(&handle);
let s_id = src.into_id_with(&handle);
protect(|| {
unsafe { rb_alias(self.as_rb_value(), d_id.as_rb_id(), s_id.as_rb_id()) };
handle.qnil()
})?;
Ok(())
}
}
#[derive(Clone, Copy, Debug)]
pub enum Attr {
Read,
Write,
ReadWrite,
}
impl Attr {
fn is_read(self) -> bool {
match self {
Attr::Read | Attr::ReadWrite => true,
Attr::Write => false,
}
}
fn is_write(self) -> bool {
match self {
Attr::Write | Attr::ReadWrite => true,
Attr::Read => false,
}
}
}
impl Ruby {
#[inline]
pub fn module_comparable(&self) -> RModule {
unsafe { RModule::from_rb_value_unchecked(rb_mComparable) }
}
#[inline]
pub fn module_enumerable(&self) -> RModule {
unsafe { RModule::from_rb_value_unchecked(rb_mEnumerable) }
}
#[inline]
pub fn module_errno(&self) -> RModule {
unsafe { RModule::from_rb_value_unchecked(rb_mErrno) }
}
#[inline]
pub fn module_file_test(&self) -> RModule {
unsafe { RModule::from_rb_value_unchecked(rb_mFileTest) }
}
#[inline]
pub fn module_gc(&self) -> RModule {
unsafe { RModule::from_rb_value_unchecked(rb_mGC) }
}
#[inline]
pub fn module_kernel(&self) -> RModule {
unsafe { RModule::from_rb_value_unchecked(rb_mKernel) }
}
#[inline]
pub fn module_math(&self) -> RModule {
unsafe { RModule::from_rb_value_unchecked(rb_mMath) }
}
#[inline]
pub fn module_process(&self) -> RModule {
unsafe { RModule::from_rb_value_unchecked(rb_mProcess) }
}
#[inline]
pub fn module_wait_readable(&self) -> RModule {
unsafe { RModule::from_rb_value_unchecked(rb_mWaitReadable) }
}
#[inline]
pub fn module_wait_writable(&self) -> RModule {
unsafe { RModule::from_rb_value_unchecked(rb_mWaitWritable) }
}
}
#[deprecated(note = "please use `Ruby::module_comparable` instead")]
#[cfg(feature = "old-api")]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn comparable() -> RModule {
get_ruby!().module_comparable()
}
#[deprecated(note = "please use `Ruby::module_enumerable` instead")]
#[cfg(feature = "old-api")]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn enumerable() -> RModule {
get_ruby!().module_enumerable()
}
#[deprecated(note = "please use `Ruby::module_errno` instead")]
#[cfg(feature = "old-api")]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn errno() -> RModule {
get_ruby!().module_errno()
}
#[deprecated(note = "please use `Ruby::module_file_test` instead")]
#[cfg(feature = "old-api")]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn file_test() -> RModule {
get_ruby!().module_file_test()
}
#[deprecated(note = "please use `Ruby::module_gc` instead")]
#[cfg(feature = "old-api")]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn gc() -> RModule {
get_ruby!().module_gc()
}
#[deprecated(note = "please use `Ruby::module_kernel` instead")]
#[cfg(feature = "old-api")]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn kernel() -> RModule {
get_ruby!().module_kernel()
}
#[deprecated(note = "please use `Ruby::module_math` instead")]
#[cfg(feature = "old-api")]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn math() -> RModule {
get_ruby!().module_math()
}
#[deprecated(note = "please use `Ruby::module_process` instead")]
#[cfg(feature = "old-api")]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn process() -> RModule {
get_ruby!().module_process()
}
#[deprecated(note = "please use `Ruby::module_wait_readable` instead")]
#[cfg(feature = "old-api")]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn wait_readable() -> RModule {
get_ruby!().module_wait_readable()
}
#[deprecated(note = "please use `Ruby::module_wait_writable` instead")]
#[cfg(feature = "old-api")]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn wait_writable() -> RModule {
get_ruby!().module_wait_writable()
}