use std::{
ffi::CString,
ops::Deref,
sync::atomic::{AtomicBool, Ordering},
};
#[cfg(windows)]
use rb_sys::rb_w32_sysinit;
use rb_sys::{
ruby_cleanup, ruby_exec_node, ruby_init_stack, ruby_process_options, ruby_set_script_name,
ruby_setup, VALUE,
};
use crate::{
error::{protect, Error},
r_string::IntoRString,
value::private::ReprValue,
Ruby,
};
pub struct Cleanup(Ruby);
impl Drop for Cleanup {
fn drop(&mut self) {
unsafe {
ruby_cleanup(0);
}
}
}
impl Deref for Cleanup {
type Target = Ruby;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[inline(always)]
pub unsafe fn setup() -> Cleanup {
static INIT: AtomicBool = AtomicBool::new(false);
let mut variable_in_this_stack_frame: VALUE = 0;
ruby_init_stack(&mut variable_in_this_stack_frame as *mut VALUE as *mut _);
match INIT.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) {
Ok(false) => {
#[cfg(windows)]
{
let mut argc = 0;
let mut argv: [*mut std::os::raw::c_char; 0] = [];
let mut argv = argv.as_mut_ptr();
rb_w32_sysinit(&mut argc, &mut argv);
}
if ruby_setup() != 0 {
panic!("Failed to setup Ruby");
};
Cleanup(Ruby::get_unchecked())
}
Err(true) => panic!("Ruby already initialized"),
r => panic!("unexpected INIT state {:?}", r),
}
}
#[inline(always)]
pub unsafe fn init() -> Cleanup {
let cleanup = setup();
init_options(&["-e", ""]);
cleanup
}
#[inline(always)]
unsafe fn init_options(opts: &[&str]) {
let mut argv = vec![CString::new("ruby").unwrap()];
argv.extend(opts.iter().map(|s| CString::new(*s).unwrap()));
let mut argv = argv
.iter()
.map(|cs| cs.as_ptr() as *mut _)
.collect::<Vec<_>>();
let mut node = 0 as _;
protect(|| {
node = ruby_process_options(argv.len() as i32, argv.as_mut_ptr());
Ruby::get_unchecked().qnil()
})
.unwrap();
if ruby_exec_node(node) != 0 {
panic!("Ruby init code failed");
};
}
impl Ruby {
pub fn init(func: fn(&Ruby) -> Result<(), Error>) -> Result<(), String> {
func(unsafe { &init() }).map_err(|e| e.to_string())
}
pub fn script<T>(&self, name: T)
where
T: IntoRString,
{
let name = name.into_r_string_with(self);
unsafe { ruby_set_script_name(name.as_rb_value()) };
}
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::script` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn ruby_script<T>(name: T)
where
T: IntoRString,
{
get_ruby!().script(name)
}