diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5fb9060..218b038 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - ruby: ['3.2', '3.3', '3.4', '4.0'] + ruby: ['3.2', '3.3', '3.4', '4.0', 'truffleruby'] runs-on: ${{ matrix.os }} steps: diff --git a/Cargo.lock b/Cargo.lock index 224d6b7..f74b036 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -187,29 +187,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "magnus" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b36a5b126bbe97eb0d02d07acfeb327036c6319fd816139a49824a83b7f9012" -dependencies = [ - "magnus-macros", - "rb-sys", - "rb-sys-env", - "seq-macro", -] - -[[package]] -name = "magnus-macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47607461fd8e1513cb4f2076c197d8092d921a1ea75bd08af97398f593751892" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "memchr" version = "2.7.6" @@ -226,8 +203,8 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" name = "mrml" version = "0.1.0" dependencies = [ - "magnus", "mrml 5.1.0", + "rb-sys", "serde", "serde_json", ] @@ -307,12 +284,6 @@ dependencies = [ "syn", ] -[[package]] -name = "rb-sys-env" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a" - [[package]] name = "regex" version = "1.12.2" @@ -354,12 +325,6 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" -[[package]] -name = "seq-macro" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" - [[package]] name = "serde" version = "1.0.228" diff --git a/ext/mrml/Cargo.toml b/ext/mrml/Cargo.toml index bb3b81b..f5deaab 100644 --- a/ext/mrml/Cargo.toml +++ b/ext/mrml/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" [dependencies] mrml = "5.1" -magnus = "0.8" +rb-sys = "0.9" [dependencies.serde] version = "1.0" diff --git a/ext/mrml/src/lib.rs b/ext/mrml/src/lib.rs index bdd063b..744b21b 100644 --- a/ext/mrml/src/lib.rs +++ b/ext/mrml/src/lib.rs @@ -1,46 +1,40 @@ -use magnus::{ - function, method, prelude::*, value::Lazy, Ruby, - Error, ExceptionClass, RModule -}; +use std::ffi::c_void; +use std::os::raw::{c_char, c_int, c_long}; +use std::panic::{self, AssertUnwindSafe}; +use std::ptr; +use std::slice; +use std::{mem, result}; use mrml::mjml::Mjml; use mrml::prelude::print::Printable; use mrml::prelude::render::RenderOptions; -static MODULE: Lazy = - Lazy::new(|ruby| ruby.class_object().const_get("MRML").unwrap()); - -static ERROR: Lazy = - Lazy::new(|ruby| ruby.get_inner(&MODULE).const_get("Error").unwrap()); - -fn mrml_error() -> ExceptionClass { - Ruby::get().unwrap().get_inner(&ERROR) -} - -macro_rules! error { - ($ex:ident) => { - Error::new(mrml_error(), $ex.to_string()) - }; -} +use rb_sys::{ + rb_cObject, rb_check_string_type, rb_const_get, + rb_data_type_struct__bindgen_ty_1, rb_data_type_t, + rb_data_typed_object_wrap, rb_define_class_under, rb_define_method, + rb_define_module, rb_define_singleton_method, rb_eTypeError, rb_exc_new, + rb_exc_raise, rb_intern, rb_jump_tag, rb_obj_class, rb_protect, + rb_undef_alloc_func, rb_utf8_str_new, size_t, VALUE, Qnil, RSTRING_LEN, + RSTRING_PTR, RTYPEDDATA_GET_DATA, RTYPEDDATA_P, RTYPEDDATA_TYPE +}; -#[magnus::wrap(class = "MRML::Template", free_immediately, size)] +#[derive(Clone)] struct Template { res: Mjml } impl Template { - fn new(input: String) -> Result { - match mrml::parse(&input) { - Ok(output) => Ok(Self { res: output.element }), - Err(ex) => Err(error!(ex)) - } + fn new(input: &str) -> Result { + mrml::parse(input) + .map(|output| Self { res: output.element }) + .map_err(|ex| ex.to_string()) } - fn from_json(input: String) -> Result { - match serde_json::from_str::(&input) { - Ok(res) => Ok(Self { res }), - Err(ex) => Err(error!(ex)) - } + fn from_json(input: &str) -> Result { + serde_json::from_str::(input) + .map(|res| Self { res }) + .map_err(|ex| ex.to_string()) } fn get_title(&self) -> Option { @@ -51,48 +45,277 @@ impl Template { self.res.get_preview() } - fn to_mjml(&self) -> String { - self.res.print_dense().unwrap() + fn to_mjml(&self) -> Result { + self.res.print_dense().map_err(|ex| ex.to_string()) + } + + fn to_json(&self) -> Result { + serde_json::to_string(&self.res).map_err(|ex| ex.to_string()) + } + + fn to_html(&self) -> Result { + self.res.render(&RenderOptions::default()).map_err(|ex| ex.to_string()) + } +} + +const TEMPLATE_TYPE_NAME: &[u8] = b"MRML::Template\0"; + +enum AppError { + Mrml(String), + Type(String), + RubyJump(c_int) +} + +impl From for AppError { + fn from(error: String) -> Self { + Self::Mrml(error) + } +} + +impl From for AppError { + fn from(_error: std::str::Utf8Error) -> Self { + Self::Type("input string must be valid UTF-8".to_string()) + } +} + +struct TemplateDataType(rb_data_type_t); + +// Ruby treats rb_data_type_t as immutable process-wide metadata after init. +unsafe impl Sync for TemplateDataType {} + +static TEMPLATE_TYPE: TemplateDataType = TemplateDataType( + rb_data_type_t { + wrap_struct_name: TEMPLATE_TYPE_NAME.as_ptr() as *const c_char, + function: rb_data_type_struct__bindgen_ty_1 { + dmark: None, + dfree: Some(template_free), + dsize: Some(template_size), + dcompact: None, + reserved: [ptr::null_mut(); 1] + }, + parent: ptr::null(), + data: ptr::null_mut(), + flags: 0 + } +); + +unsafe extern "C" fn template_free(ptr: *mut c_void) { + if !ptr.is_null() { + drop(Box::from_raw(ptr as *mut Template)); + } +} + +unsafe extern "C" fn template_size(_ptr: *const c_void) -> size_t { + mem::size_of::