rice 4.1.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f03a8ee866743d992f90b1584740922c09cc495b114e1416e3391e43ff70dd9
4
- data.tar.gz: 1fd60da5718c311a7ea4ad1443442bfe70c151f6a8ec0e84c024991114ae4741
3
+ metadata.gz: 1971d90148d0c032df2e58c7e251b57c69ad2a4ddd76d6835048b266f243e966
4
+ data.tar.gz: '0933de85d9bb135813adb756ac365dfbb333bc4ddf4575614f2200af1a78c1f4'
5
5
  SHA512:
6
- metadata.gz: ab7089e5fae0ab6e5c11408f84ec86d1124570ec51740ed7e047df3971dcb03b36a04c0662c8b18b7030e9c44b4ec3eebdf9a7f29199888b1e3c2d1f5c8256b7
7
- data.tar.gz: f3714fc7ae13d64bd56103dc7680536aa42305daa6ef8332220f48f74c8dbdabcae95ec57035d653e416977c0096f309014a467cc2d590c5f3b6375c16bbabbd
6
+ metadata.gz: 47bbbb4ddfacf6f67fc47fdf3b1bc81b2b5a1d8c889238ef1fba751b3866598a41e82609637c294609ceb0f7d3728bde3328cc3b4110af5f676b7b293fded1e9
7
+ data.tar.gz: 609458d567c204fa4156520523715e9415ac2e79bee6ddf266568408b1cb7203be68a3e216022c2dab0988d73a06c757ec63a8169d37eb0c14d80883785a708b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## Unreleased (4.2)
2
+
3
+ * Support Ruby 3.3.0.
4
+ * Split Object.call to an explicit Object.call_kw for calling methods expecting keyword arguments.
5
+ * Previously, if a wrapper used `keepAlive` on an argument or return value that was itself a Rice type, calling said method would segfault. We've now added an explicit exception to be thrown in this case, prevending the segfault and providing guidance on what was wrong and how to fix it. See [#193](https://github.com/jasonroelofs/rice/pull/193) and [#194](https://github.com/jasonroelofs/rice/pull/194)
6
+ * Fix wrapping of std::shared_ptr to properly take default arguments into account.
7
+
1
8
  ## 4.1
2
9
 
3
10
  Rice 4.1 builds on the 4.0 release and has a number of improvements that both polish Rice and extend its functionality. However, there are three incompatibilities to know about:
data/CONTRIBUTORS.md CHANGED
@@ -17,3 +17,5 @@ I'd like to thank the following people for their help in making Rice what it is
17
17
  * [Charlie Savage (cfis)](https://github.com/cfis) for multiple improvements and modernizations: [#130](https://github.com/jasonroelofs/rice/pull/130), [#131](https://github.com/jasonroelofs/rice/pull/131), [#133](https://github.com/jasonroelofs/rice/pull/133), [#134](https://github.com/jasonroelofs/rice/pull/134), [#136](https://github.com/jasonroelofs/rice/pull/136), [#137](https://github.com/jasonroelofs/rice/pull/137), [#140](https://github.com/jasonroelofs/rice/pull/140), [#141](https://github.com/jasonroelofs/rice/pull/141) and many others, including the work to make Rice header-only.
18
18
  * [Atsushi Tatsuma (yoshoku)](https://github.com/yoshoku) for [#135](https://github.com/jasonroelofs/rice/pull/135)
19
19
  * [Andrew Kane (ankane)](https://github.com/ankane) for helping [test Rice 4](https://github.com/jasonroelofs/rice/issues/149).
20
+ * [Maxim Samsonov (maxirmx)](https://github.com/maxirmx) for [#193](https://github.com/jasonroelofs/rice/issues/193) and [#194](https://github.com/jasonroelofs/rice/pull/194)
21
+ * [kvtb](https://github.com/kvtb) for [#191](https://github.com/jasonroelofs/rice/issues/191)
@@ -1531,7 +1531,7 @@ namespace Rice::detail
1531
1531
  #pragma GCC diagnostic push
1532
1532
  #pragma GCC diagnostic ignored "-Warray-bounds"
1533
1533
  #endif
1534
- return static_cast<Wrapper*>(RTYPEDDATA_DATA(value));
1534
+ return RTYPEDDATA_P(value) ? static_cast<Wrapper*>(RTYPEDDATA_DATA(value)) : nullptr;
1535
1535
  #ifdef __GNUC__
1536
1536
  #pragma GCC diagnostic pop
1537
1537
  #endif
@@ -4052,6 +4052,9 @@ namespace Rice::detail
4052
4052
  // Figure out what self is
4053
4053
  Class_T getReceiver(VALUE self);
4054
4054
 
4055
+ // Throw an exception when wrapper cannot be extracted
4056
+ [[noreturn]] void noWrapper(const VALUE klass, const std::string& wrapper);
4057
+
4055
4058
  // Do we need to keep alive any arguments?
4056
4059
  void checkKeepAlive(VALUE self, VALUE returnValue, std::vector<VALUE>& rubyValues);
4057
4060
 
@@ -4073,6 +4076,7 @@ namespace Rice::detail
4073
4076
  #include <array>
4074
4077
  #include <algorithm>
4075
4078
  #include <stdexcept>
4079
+ #include <sstream>
4076
4080
 
4077
4081
 
4078
4082
  namespace Rice::detail
@@ -4107,7 +4111,7 @@ namespace Rice::detail
4107
4111
  NativeFunction<From_Ruby_T, Function_T, IsMethod>::NativeFunction(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo)
4108
4112
  : klass_(klass), method_name_(method_name), function_(function), methodInfo_(methodInfo)
4109
4113
  {
4110
- // Create a tuple of NativeArgs that will convert the Ruby values to native values. For
4114
+ // Create a tuple of NativeArgs that will convert the Ruby values to native values. For
4111
4115
  // builtin types NativeArgs will keep a copy of the native value so that it
4112
4116
  // can be passed by reference or pointer to the native function. For non-builtin types
4113
4117
  // it will just pass the value through.
@@ -4233,7 +4237,7 @@ namespace Rice::detail
4233
4237
  {
4234
4238
  // Call the native method and get the result
4235
4239
  Return_T nativeResult = std::apply(this->function_, nativeArgs);
4236
-
4240
+
4237
4241
  // Return the result
4238
4242
  return this->toRuby_.convert(nativeResult);
4239
4243
  }
@@ -4284,15 +4288,38 @@ namespace Rice::detail
4284
4288
  }
4285
4289
  }
4286
4290
 
4291
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
4292
+ void NativeFunction<From_Ruby_T, Function_T, IsMethod>::noWrapper(const VALUE klass, const std::string& wrapper)
4293
+ {
4294
+ std::stringstream message;
4295
+
4296
+ message << "When calling the method `";
4297
+ message << this->method_name_;
4298
+ message << "' we could not find the wrapper for the '";
4299
+ message << rb_obj_classname(klass);
4300
+ message << "' ";
4301
+ message << wrapper;
4302
+ message << " type. You should not use keepAlive() on a Return or Arg that is a builtin Rice type.";
4303
+
4304
+ throw std::runtime_error(message.str());
4305
+ }
4306
+
4287
4307
  template<typename From_Ruby_T, typename Function_T, bool IsMethod>
4288
4308
  void NativeFunction<From_Ruby_T, Function_T, IsMethod>::checkKeepAlive(VALUE self, VALUE returnValue, std::vector<VALUE>& rubyValues)
4289
4309
  {
4290
- // Check function arguments
4310
+ // selfWrapper will be nullptr if this(self) is a builtin type and not an external(wrapped) type
4311
+ // it is highly unlikely that keepAlive is used in this case but we check anyway
4291
4312
  Wrapper* selfWrapper = getWrapper(self);
4313
+
4314
+ // Check function arguments
4292
4315
  for (const Arg& arg : (*this->methodInfo_))
4293
4316
  {
4294
4317
  if (arg.isKeepAlive())
4295
4318
  {
4319
+ if (selfWrapper == nullptr)
4320
+ {
4321
+ noWrapper(self, "self");
4322
+ }
4296
4323
  selfWrapper->addKeepAlive(rubyValues[arg.position]);
4297
4324
  }
4298
4325
  }
@@ -4300,7 +4327,17 @@ namespace Rice::detail
4300
4327
  // Check return value
4301
4328
  if (this->methodInfo_->returnInfo.isKeepAlive())
4302
4329
  {
4330
+ if (selfWrapper == nullptr)
4331
+ {
4332
+ noWrapper(self, "self");
4333
+ }
4334
+
4335
+ // returnWrapper will be nullptr if returnValue is a built-in type and not an external(wrapped) type
4303
4336
  Wrapper* returnWrapper = getWrapper(returnValue);
4337
+ if (returnWrapper == nullptr)
4338
+ {
4339
+ noWrapper(returnValue, "return");
4340
+ }
4304
4341
  returnWrapper->addKeepAlive(self);
4305
4342
  }
4306
4343
  }
@@ -4672,7 +4709,8 @@ namespace Rice
4672
4709
 
4673
4710
  //! Call the Ruby method specified by 'id' on object 'obj'.
4674
4711
  /*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to
4675
- * Ruby objects with to_ruby<>.
4712
+ * Ruby objects with to_ruby<>. To call methods expecting keyword arguments,
4713
+ * use call_kw.
4676
4714
  *
4677
4715
  * E.g.:
4678
4716
  * \code
@@ -4690,6 +4728,29 @@ namespace Rice
4690
4728
  template<typename ...Arg_Ts>
4691
4729
  Object call(Identifier id, Arg_Ts... args) const;
4692
4730
 
4731
+ //! Call the Ruby method specified by 'id' on object 'obj'.
4732
+ /*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to
4733
+ * Ruby objects with to_ruby<>. The final argument must be a Hash and will be treated
4734
+ * as keyword arguments to the function.
4735
+ *
4736
+ * E.g.:
4737
+ * \code
4738
+ * Rice::Hash kw;
4739
+ * kw[":argument"] = String("one")
4740
+ * Rice::Object obj = x.call_kw("foo", kw);
4741
+ * \endcode
4742
+ *
4743
+ * If a return type is specified, the return value will automatically be
4744
+ * converted to that type as long as 'from_ruby' exists for that type.
4745
+ *
4746
+ * E.g.:
4747
+ * \code
4748
+ * float ret = x.call_kw<float>("foo", kw);
4749
+ * \endcode
4750
+ */
4751
+ template<typename ...Arg_Ts>
4752
+ Object call_kw(Identifier id, Arg_Ts... args) const;
4753
+
4693
4754
  //! Vectorized call.
4694
4755
  /*! Calls the method identified by id with the list of arguments
4695
4756
  * identified by args.
@@ -4753,6 +4814,7 @@ namespace Rice
4753
4814
  } // namespace Rice
4754
4815
 
4755
4816
  #endif // Rice__Object_defn__hpp_
4817
+
4756
4818
  // --------- Object.ipp ---------
4757
4819
  #ifndef Rice__Object__ipp_
4758
4820
  #define Rice__Object__ipp_
@@ -4797,7 +4859,15 @@ namespace Rice
4797
4859
  easy to duplicate by setting GC.stress to true and calling a constructor
4798
4860
  that takes multiple values like a std::pair wrapper. */
4799
4861
  std::array<VALUE, sizeof...(Arg_Ts)> values = { detail::To_Ruby<detail::remove_cv_recursive_t<Arg_Ts>>().convert(args)... };
4800
- return detail::protect(rb_funcallv_kw, value(), id.id(), (int)values.size(), (const VALUE*)values.data(), RB_PASS_CALLED_KEYWORDS);
4862
+ return detail::protect(rb_funcallv, value(), id.id(), (int)values.size(), (const VALUE*)values.data());
4863
+ }
4864
+
4865
+ template<typename ...Arg_Ts>
4866
+ inline Object Object::call_kw(Identifier id, Arg_Ts... args) const
4867
+ {
4868
+ /* IMPORTANT - See call() above */
4869
+ std::array<VALUE, sizeof...(Arg_Ts)> values = { detail::To_Ruby<detail::remove_cv_recursive_t<Arg_Ts>>().convert(args)... };
4870
+ return detail::protect(rb_funcallv_kw, value(), id.id(), (int)values.size(), (const VALUE*)values.data(), RB_PASS_KEYWORDS);
4801
4871
  }
4802
4872
 
4803
4873
  template<typename T>
@@ -4975,6 +5045,7 @@ namespace Rice::detail
4975
5045
  #endif // Rice__Object__ipp_
4976
5046
 
4977
5047
 
5048
+
4978
5049
  // ========= Builtin_Object.hpp =========
4979
5050
 
4980
5051
 
data/include/rice/stl.hpp CHANGED
@@ -168,6 +168,11 @@ namespace Rice::detail
168
168
 
169
169
  return std::complex<T>(From_Ruby<T>().convert(real), From_Ruby<T>().convert(imaginary));
170
170
  }
171
+
172
+ bool is_convertible(VALUE value)
173
+ {
174
+ return rb_type(value) == RUBY_T_COMPLEX;
175
+ }
171
176
  };
172
177
 
173
178
  template<typename T>
@@ -183,11 +188,17 @@ namespace Rice::detail
183
188
  return this->converted_;
184
189
  }
185
190
 
191
+ bool is_convertible(VALUE value)
192
+ {
193
+ return rb_type(value) == RUBY_T_COMPLEX;
194
+ }
195
+
186
196
  private:
187
197
  std::complex<T> converted_;
188
198
  };
189
199
  }
190
200
 
201
+
191
202
  // ========= optional.hpp =========
192
203
 
193
204
 
@@ -219,7 +230,7 @@ namespace Rice::detail
219
230
  class To_Ruby<std::optional<T>>
220
231
  {
221
232
  public:
222
- static VALUE convert(std::optional<T>& data, bool takeOwnership = false)
233
+ static VALUE convert(const std::optional<T>& data, bool takeOwnership = false)
223
234
  {
224
235
  if (data.has_value())
225
236
  {
@@ -236,7 +247,7 @@ namespace Rice::detail
236
247
  class To_Ruby<std::optional<T>&>
237
248
  {
238
249
  public:
239
- static VALUE convert(std::optional<T>& data, bool takeOwnership = false)
250
+ static VALUE convert(const std::optional<T>& data, bool takeOwnership = false)
240
251
  {
241
252
  if (data.has_value())
242
253
  {
@@ -288,6 +299,7 @@ namespace Rice::detail
288
299
  };
289
300
  }
290
301
 
302
+
291
303
  // ========= reference_wrapper.hpp =========
292
304
 
293
305
 
@@ -309,7 +321,7 @@ namespace Rice::detail
309
321
  class To_Ruby<std::reference_wrapper<T>>
310
322
  {
311
323
  public:
312
- VALUE convert(std::reference_wrapper<T>& data, bool takeOwnership = false)
324
+ VALUE convert(const std::reference_wrapper<T>& data, bool takeOwnership = false)
313
325
  {
314
326
  return To_Ruby<T&>().convert(data.get());
315
327
  }
@@ -334,6 +346,7 @@ namespace Rice::detail
334
346
  };
335
347
  }
336
348
 
349
+
337
350
  // ========= smart_ptr.hpp =========
338
351
 
339
352
 
@@ -448,8 +461,18 @@ namespace Rice::detail
448
461
  class From_Ruby<std::shared_ptr<T>>
449
462
  {
450
463
  public:
464
+ From_Ruby() = default;
465
+
466
+ explicit From_Ruby(Arg * arg) : arg_(arg)
467
+ {
468
+ }
469
+
451
470
  std::shared_ptr<T> convert(VALUE value)
452
471
  {
472
+ if(value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) {
473
+ return this->arg_->template defaultValue<std::shared_ptr<T>>();
474
+ }
475
+
453
476
  Wrapper* wrapper = detail::getWrapper(value, Data_Type<T>::ruby_data_type());
454
477
 
455
478
  using Wrapper_T = WrapperSmartPointer<std::shared_ptr, T>;
@@ -461,14 +484,27 @@ namespace Rice::detail
461
484
  }
462
485
  return smartWrapper->data();
463
486
  }
487
+
488
+ private:
489
+ Arg* arg_ = nullptr;
464
490
  };
465
491
 
466
492
  template <typename T>
467
493
  class From_Ruby<std::shared_ptr<T>&>
468
494
  {
469
495
  public:
496
+ From_Ruby() = default;
497
+
498
+ explicit From_Ruby(Arg * arg) : arg_(arg)
499
+ {
500
+ }
501
+
470
502
  std::shared_ptr<T>& convert(VALUE value)
471
503
  {
504
+ if(value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) {
505
+ return this->arg_->template defaultValue<std::shared_ptr<T>>();
506
+ }
507
+
472
508
  Wrapper* wrapper = detail::getWrapper(value, Data_Type<T>::ruby_data_type());
473
509
 
474
510
  using Wrapper_T = WrapperSmartPointer<std::shared_ptr, T>;
@@ -480,6 +516,9 @@ namespace Rice::detail
480
516
  }
481
517
  return smartWrapper->data();
482
518
  }
519
+
520
+ private:
521
+ Arg* arg_ = nullptr;
483
522
  };
484
523
 
485
524
  template<typename T>
@@ -492,6 +531,7 @@ namespace Rice::detail
492
531
  };
493
532
  }
494
533
 
534
+
495
535
  // ========= monostate.hpp =========
496
536
 
497
537
 
@@ -513,7 +553,7 @@ namespace Rice::detail
513
553
  class To_Ruby<std::monostate>
514
554
  {
515
555
  public:
516
- VALUE convert(std::monostate& _)
556
+ VALUE convert(const std::monostate& _)
517
557
  {
518
558
  return Qnil;
519
559
  }
@@ -523,7 +563,7 @@ namespace Rice::detail
523
563
  class To_Ruby<std::monostate&>
524
564
  {
525
565
  public:
526
- static VALUE convert(std::monostate& data, bool takeOwnership = false)
566
+ static VALUE convert(const std::monostate& data, bool takeOwnership = false)
527
567
  {
528
568
  return Qnil;
529
569
  }
@@ -563,6 +603,7 @@ namespace Rice::detail
563
603
  };
564
604
  }
565
605
 
606
+
566
607
  // ========= variant.hpp =========
567
608
 
568
609
 
@@ -596,13 +637,13 @@ namespace Rice::detail
596
637
  public:
597
638
 
598
639
  template<typename T>
599
- static VALUE convertElement(std::variant<Types...>& data, bool takeOwnership)
640
+ static VALUE convertElement(const std::variant<Types...>& data, bool takeOwnership)
600
641
  {
601
642
  return To_Ruby<T>().convert(std::get<T>(data));
602
643
  }
603
644
 
604
645
  template<std::size_t... I>
605
- static VALUE convertIterator(std::variant<Types...>& data, bool takeOwnership, std::index_sequence<I...>& indices)
646
+ static VALUE convertIterator(const std::variant<Types...>& data, bool takeOwnership, std::index_sequence<I...>& indices)
606
647
  {
607
648
  // Create a tuple of the variant types so we can look over the tuple's types
608
649
  using Tuple_T = std::tuple<Types...>;
@@ -634,7 +675,7 @@ namespace Rice::detail
634
675
  return result;
635
676
  }
636
677
 
637
- static VALUE convert(std::variant<Types...>& data, bool takeOwnership = false)
678
+ static VALUE convert(const std::variant<Types...>& data, bool takeOwnership = false)
638
679
  {
639
680
  auto indices = std::make_index_sequence<std::variant_size_v<std::variant<Types...>>>{};
640
681
  return convertIterator(data, takeOwnership, indices);
@@ -646,13 +687,13 @@ namespace Rice::detail
646
687
  {
647
688
  public:
648
689
  template<typename T>
649
- static VALUE convertElement(std::variant<Types...>& data, bool takeOwnership)
690
+ static VALUE convertElement(const std::variant<Types...>& data, bool takeOwnership)
650
691
  {
651
692
  return To_Ruby<T>().convert(std::get<T>(data));
652
693
  }
653
694
 
654
695
  template<std::size_t... I>
655
- static VALUE convertIterator(std::variant<Types...>& data, bool takeOwnership, std::index_sequence<I...>& indices)
696
+ static VALUE convertIterator(const std::variant<Types...>& data, bool takeOwnership, std::index_sequence<I...>& indices)
656
697
  {
657
698
  // Create a tuple of the variant types so we can look over the tuple's types
658
699
  using Tuple_T = std::tuple<Types...>;
@@ -665,7 +706,7 @@ namespace Rice::detail
665
706
  return result;
666
707
  }
667
708
 
668
- static VALUE convert(std::variant<Types...>& data, bool takeOwnership = false)
709
+ static VALUE convert(const std::variant<Types...>& data, bool takeOwnership = false)
669
710
  {
670
711
  auto indices = std::make_index_sequence<std::variant_size_v<std::variant<Types...>>>{};
671
712
  return convertIterator(data, takeOwnership, indices);
@@ -755,6 +796,7 @@ namespace Rice::detail
755
796
  };
756
797
  }
757
798
 
799
+
758
800
  // ========= pair.hpp =========
759
801
 
760
802
 
@@ -2331,6 +2373,11 @@ namespace Rice
2331
2373
  }
2332
2374
  }
2333
2375
 
2376
+ bool is_convertible(VALUE value)
2377
+ {
2378
+ return rb_type(value) == RUBY_T_ARRAY;
2379
+ }
2380
+
2334
2381
  private:
2335
2382
  Arg* arg_ = nullptr;
2336
2383
  };
@@ -2378,6 +2425,11 @@ namespace Rice
2378
2425
  }
2379
2426
  }
2380
2427
 
2428
+ bool is_convertible(VALUE value)
2429
+ {
2430
+ return rb_type(value) == RUBY_T_ARRAY;
2431
+ }
2432
+
2381
2433
  private:
2382
2434
  Arg* arg_ = nullptr;
2383
2435
  std::vector<T> converted_;
@@ -2413,10 +2465,16 @@ namespace Rice
2413
2465
  }
2414
2466
  }
2415
2467
 
2468
+ bool is_convertible(VALUE value)
2469
+ {
2470
+ return rb_type(value) == RUBY_T_ARRAY;
2471
+ }
2472
+
2416
2473
  private:
2417
2474
  std::vector<T> converted_;
2418
2475
  };
2419
2476
  }
2420
2477
  }
2421
2478
 
2479
+
2422
2480
  #endif // Rice__stl__hpp_
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rice
2
- VERSION = "4.1.0"
2
+ VERSION = "4.2.0"
3
3
  end
@@ -95,6 +95,9 @@ namespace Rice::detail
95
95
  // Figure out what self is
96
96
  Class_T getReceiver(VALUE self);
97
97
 
98
+ // Throw an exception when wrapper cannot be extracted
99
+ [[noreturn]] void noWrapper(const VALUE klass, const std::string& wrapper);
100
+
98
101
  // Do we need to keep alive any arguments?
99
102
  void checkKeepAlive(VALUE self, VALUE returnValue, std::vector<VALUE>& rubyValues);
100
103
 
@@ -113,4 +116,4 @@ namespace Rice::detail
113
116
  }
114
117
  #include "NativeFunction.ipp"
115
118
 
116
- #endif // Rice__detail__Native_Function__hpp_
119
+ #endif // Rice__detail__Native_Function__hpp_
@@ -1,6 +1,7 @@
1
1
  #include <array>
2
2
  #include <algorithm>
3
3
  #include <stdexcept>
4
+ #include <sstream>
4
5
 
5
6
  #include "cpp_protect.hpp"
6
7
  #include "to_ruby_defn.hpp"
@@ -38,7 +39,7 @@ namespace Rice::detail
38
39
  NativeFunction<From_Ruby_T, Function_T, IsMethod>::NativeFunction(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo)
39
40
  : klass_(klass), method_name_(method_name), function_(function), methodInfo_(methodInfo)
40
41
  {
41
- // Create a tuple of NativeArgs that will convert the Ruby values to native values. For
42
+ // Create a tuple of NativeArgs that will convert the Ruby values to native values. For
42
43
  // builtin types NativeArgs will keep a copy of the native value so that it
43
44
  // can be passed by reference or pointer to the native function. For non-builtin types
44
45
  // it will just pass the value through.
@@ -164,7 +165,7 @@ namespace Rice::detail
164
165
  {
165
166
  // Call the native method and get the result
166
167
  Return_T nativeResult = std::apply(this->function_, nativeArgs);
167
-
168
+
168
169
  // Return the result
169
170
  return this->toRuby_.convert(nativeResult);
170
171
  }
@@ -215,15 +216,38 @@ namespace Rice::detail
215
216
  }
216
217
  }
217
218
 
219
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
220
+ void NativeFunction<From_Ruby_T, Function_T, IsMethod>::noWrapper(const VALUE klass, const std::string& wrapper)
221
+ {
222
+ std::stringstream message;
223
+
224
+ message << "When calling the method `";
225
+ message << this->method_name_;
226
+ message << "' we could not find the wrapper for the '";
227
+ message << rb_obj_classname(klass);
228
+ message << "' ";
229
+ message << wrapper;
230
+ message << " type. You should not use keepAlive() on a Return or Arg that is a builtin Rice type.";
231
+
232
+ throw std::runtime_error(message.str());
233
+ }
234
+
218
235
  template<typename From_Ruby_T, typename Function_T, bool IsMethod>
219
236
  void NativeFunction<From_Ruby_T, Function_T, IsMethod>::checkKeepAlive(VALUE self, VALUE returnValue, std::vector<VALUE>& rubyValues)
220
237
  {
221
- // Check function arguments
238
+ // selfWrapper will be nullptr if this(self) is a builtin type and not an external(wrapped) type
239
+ // it is highly unlikely that keepAlive is used in this case but we check anyway
222
240
  Wrapper* selfWrapper = getWrapper(self);
241
+
242
+ // Check function arguments
223
243
  for (const Arg& arg : (*this->methodInfo_))
224
244
  {
225
245
  if (arg.isKeepAlive())
226
246
  {
247
+ if (selfWrapper == nullptr)
248
+ {
249
+ noWrapper(self, "self");
250
+ }
227
251
  selfWrapper->addKeepAlive(rubyValues[arg.position]);
228
252
  }
229
253
  }
@@ -231,7 +255,17 @@ namespace Rice::detail
231
255
  // Check return value
232
256
  if (this->methodInfo_->returnInfo.isKeepAlive())
233
257
  {
258
+ if (selfWrapper == nullptr)
259
+ {
260
+ noWrapper(self, "self");
261
+ }
262
+
263
+ // returnWrapper will be nullptr if returnValue is a built-in type and not an external(wrapped) type
234
264
  Wrapper* returnWrapper = getWrapper(returnValue);
265
+ if (returnWrapper == nullptr)
266
+ {
267
+ noWrapper(returnValue, "return");
268
+ }
235
269
  returnWrapper->addKeepAlive(self);
236
270
  }
237
271
  }
@@ -193,7 +193,7 @@ namespace Rice::detail
193
193
  #pragma GCC diagnostic push
194
194
  #pragma GCC diagnostic ignored "-Warray-bounds"
195
195
  #endif
196
- return static_cast<Wrapper*>(RTYPEDDATA_DATA(value));
196
+ return RTYPEDDATA_P(value) ? static_cast<Wrapper*>(RTYPEDDATA_DATA(value)) : nullptr;
197
197
  #ifdef __GNUC__
198
198
  #pragma GCC diagnostic pop
199
199
  #endif
@@ -1,5 +1,4 @@
1
1
  require 'bundler/setup'
2
- require 'rice'
3
2
  require 'mkmf-rice'
4
3
 
5
4
  create_makefile('sample_callbacks')
@@ -1,5 +1,4 @@
1
1
  require 'bundler/setup'
2
- require 'rice'
3
2
  require 'mkmf-rice'
4
3
 
5
4
  create_makefile('sample_enum')
@@ -1,5 +1,4 @@
1
1
  require 'bundler/setup'
2
- require 'rice'
3
2
  require 'mkmf-rice'
4
3
 
5
4
  create_makefile('animals')
@@ -1,5 +1,4 @@
1
1
  require 'bundler/setup'
2
- require 'rice'
3
2
  require 'mkmf-rice'
4
3
 
5
4
  create_makefile('map')
data/test/embed_ruby.cpp CHANGED
@@ -15,6 +15,12 @@ void embed_ruby()
15
15
  ruby_init();
16
16
  ruby_init_loadpath();
17
17
 
18
+ #if RUBY_API_VERSION_MAJOR == 3 && RUBY_API_VERSION_MINOR >= 3
19
+ // Force the prelude / builtins
20
+ char *opts[] = { "ruby", "-e;" };
21
+ ruby_options(2, opts);
22
+ #endif
23
+
18
24
  initialized__ = true;
19
25
  }
20
26
  }
@@ -1,5 +1,4 @@
1
1
  require 'bundler/setup'
2
- require 'rice'
3
2
  require 'mkmf-rice'
4
3
 
5
4
  create_makefile('t1')
@@ -1,5 +1,4 @@
1
1
  require 'bundler/setup'
2
- require 'rice'
3
2
  require 'mkmf-rice'
4
3
 
5
4
  create_makefile('t2')
data/test/extconf.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'bundler/setup'
2
- require 'rice'
3
2
  require 'mkmf-rice'
4
3
  require 'rbconfig'
5
4
 
@@ -1,4 +1,4 @@
1
- #include <assert.h>
1
+ #include <assert.h>
2
2
 
3
3
  #include "unittest.hpp"
4
4
  #include "embed_ruby.hpp"
@@ -60,9 +60,9 @@ TESTCASE(attributes)
60
60
  ASSERT_EXCEPTION_CHECK(
61
61
  Exception,
62
62
  o.call("read_char=", "some text"),
63
- ASSERT_EQUAL("undefined method `read_char=' for :DataStruct", ex.what())
63
+ ASSERT(std::string(ex.what()).find("undefined method `read_char='") == 0)
64
64
  );
65
-
65
+
66
66
  // Test writeonly attribute
67
67
  result = o.call("write_int=", 5);
68
68
  ASSERT_EQUAL(5, detail::From_Ruby<int>().convert(result.value()));
@@ -70,7 +70,7 @@ TESTCASE(attributes)
70
70
  ASSERT_EXCEPTION_CHECK(
71
71
  Exception,
72
72
  o.call("write_int", 3),
73
- ASSERT_EQUAL("undefined method `write_int' for :DataStruct", ex.what())
73
+ ASSERT(std::string(ex.what()).find("undefined method `write_int'") == 0)
74
74
  );
75
75
 
76
76
  // Test readwrite attribute
@@ -101,7 +101,7 @@ TESTCASE(static_attributes)
101
101
  ASSERT_EXCEPTION_CHECK(
102
102
  Exception,
103
103
  c.call("static_string=", true),
104
- ASSERT_EQUAL("undefined method `static_string=' for DataStruct:Class", ex.what())
104
+ ASSERT(std::string(ex.what()).find("undefined method `static_string='") == 0)
105
105
  );
106
106
  }
107
107
 
@@ -127,7 +127,7 @@ TESTCASE(not_defined)
127
127
  {
128
128
  Data_Type<DataStruct> c = define_class<DataStruct>("DataStruct");
129
129
 
130
- #ifdef _MSC_VER
130
+ #ifdef _MSC_VER
131
131
  const char* message = "Type is not defined with Rice: class `anonymous namespace'::SomeClass";
132
132
  #else
133
133
  const char* message = "Type is not defined with Rice: (anonymous namespace)::SomeClass";
@@ -0,0 +1,80 @@
1
+ #include "unittest.hpp"
2
+ #include "embed_ruby.hpp"
3
+ #include <rice/rice.hpp>
4
+ #include <rice/stl.hpp>
5
+
6
+ using namespace Rice;
7
+
8
+ TESTSUITE(Keep_Alive_No_Wrapper);
9
+
10
+ namespace
11
+ {
12
+ class Animal
13
+ {
14
+ public:
15
+ Animal(char const * name) : name_(name) {}
16
+ char const * getName() { return name_; }
17
+ virtual ~Animal() = default;
18
+ private:
19
+ char const * name_;
20
+ };
21
+
22
+ class Zoo
23
+ {
24
+ public:
25
+ Zoo(void)
26
+ {
27
+ pets_.push_back(new Animal("Bear"));
28
+ pets_.push_back(new Animal("Tiger"));
29
+ pets_.push_back(new Animal("Lion"));
30
+ }
31
+
32
+ ~Zoo()
33
+ {
34
+ for(auto pet : pets_)
35
+ {
36
+ delete pet;
37
+ }
38
+ pets_.clear();
39
+ }
40
+
41
+ Object getPets(void) {
42
+ Array pets;
43
+ for(auto p: pets_) {
44
+ pets.push(p);
45
+ }
46
+ return pets;
47
+ }
48
+
49
+ private:
50
+ std::vector<Animal*> pets_;
51
+ };
52
+ }
53
+
54
+ SETUP(Keep_Alive_No_Wrapper)
55
+ {
56
+ embed_ruby();
57
+ }
58
+
59
+ TESTCASE(test_keep_alive_no_wrapper)
60
+ {
61
+ define_class<Animal>("Animal")
62
+ .define_constructor(Constructor<Animal, char const *>())
63
+ .define_method("get_name", &Animal::getName);
64
+
65
+ define_class<Zoo>("Zoo")
66
+ .define_constructor(Constructor<Zoo>())
67
+ .define_method("get_pets", &Zoo::getPets, Return().keepAlive());
68
+
69
+ Module m = define_module("TestingModule");
70
+ Object zoo = m.module_eval("@zoo = Zoo.new");
71
+
72
+ // get_pets returns an Array (builtin type) so Return().keepAlive()
73
+ // shall result in std::runtime_error
74
+ ASSERT_EXCEPTION_CHECK(
75
+ Exception,
76
+ m.module_eval("@zoo.get_pets.each do |pet| puts pet.name; end"),
77
+ ASSERT_EQUAL("When calling the method `get_pets' we could not find the wrapper for the 'Array' return type. You should not use keepAlive() on a Return or Arg that is a builtin Rice type.",
78
+ ex.what())
79
+ );
80
+ }
data/test/test_Object.cpp CHANGED
@@ -174,20 +174,29 @@ TESTCASE(call_return_rice_object)
174
174
 
175
175
  TESTCASE(call_with_keywords)
176
176
  {
177
- Module kernel = Module("Kernel");
177
+ Module m(anonymous_module());
178
+
179
+ m.module_eval(R"(
180
+ def self.keywords_test(value, exception:)
181
+ if exception
182
+ raise "An exception!"
183
+ end
178
184
 
185
+ value
186
+ end
187
+ )");
179
188
 
180
189
  Hash keywords;
181
190
  keywords[":exception"] = false;
182
- Object result = kernel.call("Integer", "charlie", keywords);
183
- ASSERT_EQUAL(Qnil, result.value());
191
+ Object result = m.call_kw("keywords_test", "charlie", keywords);
192
+ ASSERT_EQUAL("charlie", detail::From_Ruby<const char*>().convert(result.value()));
184
193
 
185
194
  keywords[":exception"] = true;
186
195
 
187
196
  ASSERT_EXCEPTION_CHECK(
188
197
  Exception,
189
- kernel.call("Integer", "charlie", keywords),
190
- ASSERT_EQUAL("invalid value for Integer(): \"charlie\"", ex.what())
198
+ m.call_kw("keywords_test", "charlie", keywords),
199
+ ASSERT_EQUAL("An exception!", ex.what())
191
200
  );
192
201
  }
193
202
 
@@ -240,4 +249,4 @@ TESTCASE(test_mark)
240
249
  Object o(INT2NUM(42));
241
250
  rb_gc_start();
242
251
  ASSERT_EQUAL(42, detail::From_Ruby<int>().convert(o.value()));
243
- }
252
+ }
@@ -109,6 +109,11 @@ SETUP(SmartPointer)
109
109
  define_global_function("extract_flag_unique_ptr_ref", &extractFlagUniquePtrRef);
110
110
  define_global_function("extract_flag_shared_ptr", &extractFlagSharedPtr);
111
111
  define_global_function("extract_flag_shared_ptr_ref", &extractFlagSharedPtrRef);
112
+
113
+ define_global_function("extract_flag_shared_ptr_with_default", &extractFlagSharedPtr,
114
+ Arg("myClass") = std::make_shared<MyClass>());
115
+ define_global_function("extract_flag_shared_ptr_ref_with_default", &extractFlagSharedPtrRef,
116
+ Arg("myClass") = std::make_shared<MyClass>());
112
117
  }
113
118
 
114
119
  TESTCASE(TransferOwnership)
@@ -169,6 +174,7 @@ TESTCASE(UniquePtrRefParameter)
169
174
  extract_flag_unique_ptr_ref(my_class))";
170
175
 
171
176
  Object result = m.module_eval(code);
177
+ ASSERT_EQUAL(7, detail::From_Ruby<int>().convert(result));
172
178
  }
173
179
 
174
180
  TESTCASE(SharedPtrParameter)
@@ -179,10 +185,11 @@ TESTCASE(SharedPtrParameter)
179
185
 
180
186
  std::string code = R"(factory = Factory.new
181
187
  my_class = factory.share
182
- my_class.set_flag(7)
188
+ my_class.set_flag(8)
183
189
  extract_flag_shared_ptr(my_class))";
184
-
190
+
185
191
  Object result = m.module_eval(code);
192
+ ASSERT_EQUAL(8, detail::From_Ruby<int>().convert(result));
186
193
  }
187
194
 
188
195
  TESTCASE(SharedPtrRefParameter)
@@ -193,8 +200,41 @@ TESTCASE(SharedPtrRefParameter)
193
200
 
194
201
  std::string code = R"(factory = Factory.new
195
202
  my_class = factory.share
196
- my_class.set_flag(7)
203
+ my_class.set_flag(9)
197
204
  extract_flag_shared_ptr_ref(my_class))";
198
205
 
199
206
  Object result = m.module_eval(code);
200
- }
207
+ ASSERT_EQUAL(9, detail::From_Ruby<int>().convert(result));
208
+ }
209
+
210
+ TESTCASE(SharedPtrDefaultParameter)
211
+ {
212
+ MyClass::reset();
213
+
214
+ Module m = define_module("TestingModule");
215
+
216
+ std::string code = R"(factory = Factory.new
217
+ my_class = factory.share
218
+ my_class.set_flag(7)
219
+ extract_flag_shared_ptr_with_default())";
220
+
221
+ Object result = m.module_eval(code);
222
+ // The default value kicks in and ignores any previous pointer
223
+ ASSERT_EQUAL(0, detail::From_Ruby<int>().convert(result));
224
+ }
225
+
226
+ TESTCASE(SharedPtrRefDefaultParameter)
227
+ {
228
+ MyClass::reset();
229
+
230
+ Module m = define_module("TestingModule");
231
+
232
+ std::string code = R"(factory = Factory.new
233
+ my_class = factory.share
234
+ my_class.set_flag(7)
235
+ extract_flag_shared_ptr_ref_with_default())";
236
+
237
+ Object result = m.module_eval(code);
238
+ // The default value kicks in and ignores any previous pointer
239
+ ASSERT_EQUAL(0, detail::From_Ruby<int>().convert(result));
240
+ }
@@ -27,7 +27,10 @@ TESTCASE(std_string_to_ruby_encoding)
27
27
  Object object(value);
28
28
  Object encoding = object.call("encoding");
29
29
  Object encodingName = encoding.call("name");
30
- ASSERT_EQUAL("ASCII-8BIT", detail::From_Ruby<std::string>().convert(encodingName));
30
+ std::string result = detail::From_Ruby<std::string>().convert(encodingName);
31
+ if(result != "ASCII-8BIT" && result != "US-ASCII" && result != "UTF-8") {
32
+ FAIL("Encoding incorrect", "ASCII-8BIT, US-ASCII, or UTF-8 (Windows)", result);
33
+ }
31
34
  }
32
35
 
33
36
  TESTCASE(std_string_to_ruby_encoding_utf8)
@@ -71,4 +74,4 @@ TESTCASE(std_string_from_ruby_with_binary)
71
74
  std::string got = detail::From_Ruby<std::string>().convert(rb_str_new("\000test", 5));
72
75
  ASSERT_EQUAL(5ul, got.length());
73
76
  ASSERT_EQUAL(std::string("\000test", 5), got);
74
- }
77
+ }
@@ -4,6 +4,7 @@
4
4
  #include <rice/stl.hpp>
5
5
 
6
6
  #include <variant>
7
+ #include <complex>
7
8
 
8
9
  using namespace Rice;
9
10
 
@@ -11,7 +12,14 @@ TESTSUITE(Variant);
11
12
 
12
13
  namespace
13
14
  {
14
- using Intrinsic_Variant_T = std::variant<std::string, double, bool, int>;
15
+ using Intrinsic_Variant_T = std::variant<
16
+ std::string,
17
+ std::complex<double>,
18
+ std::vector<int>,
19
+ double,
20
+ bool,
21
+ int
22
+ >;
15
23
 
16
24
  inline std::ostream& operator<<(std::ostream& stream, Intrinsic_Variant_T const& variant)
17
25
  {
@@ -30,6 +38,19 @@ namespace
30
38
  return result;
31
39
  }
32
40
 
41
+ Intrinsic_Variant_T variantComplex()
42
+ {
43
+ using namespace std::complex_literals;
44
+ Intrinsic_Variant_T result { 1i };
45
+ return result;
46
+ }
47
+
48
+ Intrinsic_Variant_T variantVector()
49
+ {
50
+ Intrinsic_Variant_T result { std::vector<int>{1, 2, 3} };
51
+ return result;
52
+ }
53
+
33
54
  Intrinsic_Variant_T variantDouble()
34
55
  {
35
56
  Intrinsic_Variant_T result { 3.3 };
@@ -73,6 +94,8 @@ void makeIntrinsicVariant()
73
94
  define_class<MyClass>("MyClass").
74
95
  define_constructor(Constructor<MyClass>()).
75
96
  define_method("variant_string", &MyClass::variantString).
97
+ define_method("variant_complex", &MyClass::variantComplex).
98
+ define_method("variant_vector", &MyClass::variantVector).
76
99
  define_method("variant_double", &MyClass::variantDouble).
77
100
  define_method("variant_bool_true", &MyClass::variantBoolTrue).
78
101
  define_method("variant_bool_false", &MyClass::variantBoolFalse).
@@ -83,12 +106,21 @@ void makeIntrinsicVariant()
83
106
 
84
107
  TESTCASE(IntrinsicReturns)
85
108
  {
109
+ using namespace std::complex_literals;
110
+
86
111
  Module m = define_module("Testing");
87
112
  Object myClass = m.module_eval("MyClass.new");
88
113
 
89
114
  Object result = myClass.call("variant_string");
90
115
  ASSERT_EQUAL("a string", detail::From_Ruby<std::string>().convert(result));
91
-
116
+
117
+ result = myClass.call("variant_complex");
118
+ ASSERT_EQUAL(1i, detail::From_Ruby<std::complex<double>>().convert(result));
119
+
120
+ result = myClass.call("variant_vector");
121
+ std::vector<int> converted = detail::From_Ruby<std::vector<int>>().convert(result);
122
+ ASSERT_EQUAL(3, converted.size());
123
+
92
124
  result = myClass.call("variant_double");
93
125
  ASSERT_EQUAL(3.3, detail::From_Ruby<double>().convert(result));
94
126
 
@@ -104,6 +136,8 @@ TESTCASE(IntrinsicReturns)
104
136
 
105
137
  TESTCASE(IntrinsicRoundtrip)
106
138
  {
139
+ using namespace std::complex_literals;
140
+
107
141
  Module m = define_module("Testing");
108
142
  Object myClass = m.module_eval("MyClass.new");
109
143
 
@@ -112,6 +146,17 @@ TESTCASE(IntrinsicRoundtrip)
112
146
  Object result = m.module_eval(code);
113
147
  ASSERT_EQUAL("roundtrip string", detail::From_Ruby<std::string>().convert(result));
114
148
 
149
+ code = R"(my_class = MyClass.new
150
+ my_class.roundtrip(Complex(2, 3)))";
151
+ result = m.module_eval(code);
152
+ ASSERT_EQUAL((2.0 + 3i), detail::From_Ruby<std::complex<double>>().convert(result));
153
+
154
+ code = R"(my_class = MyClass.new
155
+ my_class.roundtrip([1, 2, 3]))";
156
+ result = m.module_eval(code);
157
+ std::vector<int> expected = {1, 2, 3};
158
+ ASSERT_EQUAL(expected, detail::From_Ruby<std::vector<int>>().convert(result));
159
+
115
160
  code = R"(my_class = MyClass.new
116
161
  my_class.roundtrip(44.4))";
117
162
  result = m.module_eval(code);
@@ -150,7 +195,7 @@ TESTCASE(VariantAttribute)
150
195
  ASSERT_EQUAL(77.7, detail::From_Ruby<double>().convert(result));
151
196
  result = myClass.call("variant_attr");
152
197
  ASSERT_EQUAL(77.7, detail::From_Ruby<double>().convert(result));
153
-
198
+
154
199
  result = myClass.call("variant_attr=", true);
155
200
  ASSERT(detail::From_Ruby<bool>().convert(result));
156
201
  result = myClass.call("variant_attr");
@@ -298,4 +343,4 @@ TESTCASE(ClassRoundtripRef)
298
343
  hello = instance2.call("say_hello");
299
344
  ASSERT_EQUAL("Hi from MyClass2", detail::From_Ruby<std::string>().convert(hello));
300
345
  }
301
- #endif
346
+ #endif
@@ -229,7 +229,7 @@ TESTCASE(unsigned_long_long_from_ruby)
229
229
  ASSERT_EXCEPTION_CHECK(
230
230
  Exception,
231
231
  detail::From_Ruby<unsigned long long>().convert(rb_str_new2("bad value")),
232
- ASSERT_EQUAL("no implicit conversion from string", ex.what())
232
+ ASSERT(std::string(ex.what()).find("no implicit conversion") == 0)
233
233
  );
234
234
  }
235
235
 
@@ -396,4 +396,4 @@ TESTCASE(char_star_from_ruby)
396
396
  detail::From_Ruby<const char*>().convert(rb_float_new(11.11)),
397
397
  ASSERT_EQUAL("wrong argument type Float (expected String)", ex.what())
398
398
  );
399
- }
399
+ }
data/test/unittest.hpp CHANGED
@@ -236,7 +236,7 @@ void assert_equal(
236
236
 
237
237
  if constexpr (is_streamable<std::stringstream, T>::value && is_streamable<std::stringstream, U>::value)
238
238
  {
239
- strm << s_t << " != " << s_u;
239
+ strm << s_t << " != " << s_u << " (" << u << ") ";
240
240
  }
241
241
  strm << " at " << file << ":" << line;
242
242
  throw Assertion_Failed(strm.str());
@@ -263,6 +263,14 @@ void assert_not_equal(
263
263
  }
264
264
  }
265
265
 
266
+ #define FAIL(message, expect, got) \
267
+ do \
268
+ { \
269
+ std::stringstream strm; \
270
+ strm << message << " expected: " << (expect) << " got: " << (got); \
271
+ throw Assertion_Failed(strm.str()); \
272
+ } while(0)
273
+
266
274
  #define ASSERT_EQUAL(x, y) \
267
275
  do \
268
276
  { \
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rice
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Brannan
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-04-23 00:00:00.000000000 Z
13
+ date: 2024-01-10 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -185,6 +185,7 @@ files:
185
185
  - test/test_Iterator.cpp
186
186
  - test/test_Jump_Tag.cpp
187
187
  - test/test_Keep_Alive.cpp
188
+ - test/test_Keep_Alive_No_Wrapper.cpp
188
189
  - test/test_Memory_Management.cpp
189
190
  - test/test_Module.cpp
190
191
  - test/test_Object.cpp
@@ -230,7 +231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
230
231
  - !ruby/object:Gem::Version
231
232
  version: '0'
232
233
  requirements: []
233
- rubygems_version: 3.4.12
234
+ rubygems_version: 3.5.3
234
235
  signing_key:
235
236
  specification_version: 4
236
237
  summary: Ruby Interface for C++ Extensions