use std::{ffi::CString, fmt, mem::transmute, os::raw::c_int};
use rb_sys::{
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, VALUE,
};
use crate::{
class::{Class, RClass},
error::{protect, Error},
exception::ExceptionClass,
into_value::IntoValue,
method::Method,
object::Object,
r_array::RArray,
try_convert::TryConvert,
value::{
private::{self, ReprValue as _},
IntoId, NonZeroValue, ReprValue, Value,
},
Ruby,
};
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 {
Self(NonZeroValue::new_unchecked(Value::new(val)))
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::module_new` instead")
)]
#[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(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(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(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(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) }
}
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::module_comparable` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn comparable() -> RModule {
get_ruby!().module_comparable()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::module_enumerable` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn enumerable() -> RModule {
get_ruby!().module_enumerable()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::module_errno` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn errno() -> RModule {
get_ruby!().module_errno()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::module_file_test` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn file_test() -> RModule {
get_ruby!().module_file_test()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::module_gc` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn gc() -> RModule {
get_ruby!().module_gc()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::module_kernel` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn kernel() -> RModule {
get_ruby!().module_kernel()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::module_math` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn math() -> RModule {
get_ruby!().module_math()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::module_process` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn process() -> RModule {
get_ruby!().module_process()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::module_wait_readable` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn wait_readable() -> RModule {
get_ruby!().module_wait_readable()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::module_wait_writable` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn wait_writable() -> RModule {
get_ruby!().module_wait_writable()
}