source: trunk/lang/ruby/Ruby_Rappture.cc @ 3280

Last change on this file since 3280 was 3280, checked in by gah, 9 years ago

fixes for 1.9 ruby build

File size: 17.2 KB
Line 
1/*
2 * ----------------------------------------------------------------------
3 *  Ruby Rappture Extension Source
4 *
5 * ======================================================================
6 *  AUTHOR:  Benjamin Haley, Purdue University
7 *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
8 *
9 *  See the file "license.terms" for information on usage and
10 *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 * ======================================================================
12 */
13
14/* This file uses Ruby's C API to create a new Ruby class named Rappture
15 *  as a wrapper around the Rappture library C++ API.
16 *
17 * Define RAISE_EXCEPTIONS (see Makefile) to raise Ruby Exceptions on errors.
18 * Without this, those methods which return a Ruby object on success return
19 *  Qnil on errors.  For those methods which return Qnil on either success or
20 *  failure, there is no way to check for errors without an Exception.
21 */
22
23#include <string>        /* std::string */
24#include <stdlib.h>      /* atof() */
25#include "RpLibrary.h"   /* RpLibrary, Rappture::Buffer */
26#include "RpUtils.h"     /* Rappture::Utils::progress() */
27#include "RpUnits.h"     /* RpUnits::convert() */
28#include "ruby.h"        /* VALUE, rb_*(), Data_*_Struct(), NUM2INT(),
29                            INT2NUM(), NUM2DBL(), Str2Cstr(), ANYARGS */
30
31/******************************************************************************
32 * File scope variables
33 ******************************************************************************/
34
35
36/* The new Rappture class */
37VALUE classRappture;
38
39
40extern "C" void Init_Rappture(void);
41
42static std::string
43GetStdString(VALUE value)
44{
45    VALUE strValue;
46
47    strValue = StringValue(value);
48    return std::string(RSTRING_PTR(strValue), RSTRING_LEN(strValue));
49}
50
51static const char *
52GetString(VALUE value)
53{
54    return StringValuePtr(value);
55}
56
57/******************************************************************************
58 * RbRp_GetString()
59 *
60 * Implement the get() public method of the Rappture class.
61 * Return a Ruby String from the location path in the Rappture object self.
62 *
63 * The original plan was to convert the returned std::string to a Ruby String,
64 *  then try calling the to_f and to_i methods on the Ruby String, to see if
65 *  we should return a Float (double) or a Fixnum (integer).  The problem with
66 *  this approach is that these methods (to_f and to_i) return 0.0 and 0,
67 *  respectively, on error,  **without raising an Exception**, so there is no
68 *  way to  distinguish between an error and legitimate zero values.  The user
69 *  will have to call the appropriate Ruby conversion functions on the returned
70 *  String, for now.
71 ******************************************************************************/
72
73static VALUE
74RbRp_GetString(VALUE self, VALUE path)
75{
76   RpLibrary *lib;
77   std::string result;
78
79   /* Extract the pointer to the Rappture object, lib, from the Ruby object,
80      self */
81   Data_Get_Struct(self, RpLibrary, lib);
82
83
84   /* Read the data from path in lib as a C++ std::string. */
85   result = lib->getString(GetStdString(path));
86     
87   /* Return a Ruby VALUE */
88   return rb_str_new2(result.c_str());
89
90}  /* end RbRp_GetString */
91
92
93/******************************************************************************
94 * RbRp_GetData()
95 *
96 * Implement the getdata() public method of the Rappture class.
97 * Return a Ruby String from the location path in the Rappture object self.
98 ******************************************************************************/
99
100static VALUE
101RbRp_GetData(VALUE self, VALUE path)
102{
103   RpLibrary *lib;
104   Rappture::Buffer buf;
105
106   /* Extract the pointer to the Rappture object, lib, from the Ruby object,
107      self */
108   Data_Get_Struct(self, RpLibrary, lib);
109
110   /* Read the data from path in lib as a C++ std::string. */
111   buf = lib->getData(GetStdString(path));
112     
113   /* Return a Ruby VALUE */
114   return rb_str_new2(buf.bytes());
115
116}  /* end RbRp_GetData */
117
118
119/******************************************************************************
120 * RbRp_PutObject()
121 *
122 * Implement the put() public method of the Rappture class.
123 * Put a Ruby String, Float (double), or Fixnum (integer) object into the
124 *  location path in the Rappture object self.
125 * Return Qnil.
126 * On error, if RAISE_EXCEPTIONS is defined, raise a Ruby RuntimeError
127 *  Exception.
128 ******************************************************************************/
129
130static VALUE
131RbRp_PutObject(VALUE self, VALUE path, VALUE value, VALUE append)
132{
133#ifdef RAISE_EXCEPTIONS
134   /* Get the Ruby ID of the "to_s" method. */
135   ID id_to_s = rb_intern("to_s");
136
137   /* Call the "to_s" method on value to get a Ruby String representation of
138      the value, in case we need to compose an error message below.  The final
139      argument 0 indicates no further arguments.*/
140   VALUE rbStrName = rb_funcall(value, id_to_s, 0);
141#endif
142
143   RpLibrary *lib;
144   int intVal;
145
146   /* Extract the pointer to the Rappture object, lib, from the Ruby
147      object, self */
148   Data_Get_Struct(self, RpLibrary, lib);
149
150   /* Check the type of the Ruby object value.  If the type if one we are
151      prepared to handle, call the appropriate put() function.  If the type
152      is unexpected, raise a RuntimeError Exception, if RAISE_EXCEPTIONS is
153      defined.*/
154   switch (TYPE(value))
155   {
156      case T_STRING:
157          /* Read the data from path in lib as a C++ std::string. */
158          lib->put(GetStdString(path), GetStdString(value), "",
159                NUM2INT(append));
160         break;
161      case T_FIXNUM:
162         intVal = NUM2INT(value);
163         lib->putData(GetStdString(path), (const char *)&intVal, sizeof(int),
164                      NUM2INT(append));
165         break;
166      case T_FLOAT:
167         lib->put(GetStdString(path), NUM2DBL(value), "", NUM2INT(append));
168         break;
169      default:
170#ifdef RAISE_EXCEPTIONS
171         rb_raise(rb_eRuntimeError,
172                  "Unable to put object %s to Rappture: unknown type",
173                  GetString(rbStrName));
174#endif
175         break;
176   }  /* end switch */
177
178   /* Return a Ruby VALUE */
179   return Qnil;
180
181}  /* end RbRp_PutObject */
182
183
184/******************************************************************************
185 * RbRp_PutData()
186 *
187 * Implement the putdata() public method of the Rappture class.
188 * Put a Ruby String into the location path in the Rappture object self.
189 * Return Qnil.
190 * On error, if RAISE_EXCEPTIONS is defined, raise a Ruby RuntimeError
191 *  Exception.
192 ******************************************************************************/
193
194static VALUE
195RbRp_PutData(VALUE self, VALUE path, VALUE value, VALUE append)
196{
197#ifdef RAISE_EXCEPTIONS
198   /* Get the Ruby ID of the "to_s" method. */
199   ID id_to_s = rb_intern("to_s");
200
201   /* Call the "to_s" method on value to get a Ruby String representation of
202      the value, in case we need to compose an error message below.  The final
203      argument 0 indicates no further arguments.*/
204   VALUE rbStrName = rb_funcall(value, id_to_s, 0);
205#endif
206
207   RpLibrary *lib;
208
209   /* Extract the pointer to the Rappture object, lib, from the Ruby
210      object, self */
211   Data_Get_Struct(self, RpLibrary, lib);
212
213   if (T_STRING == TYPE(value))
214   {
215       VALUE strValue;
216
217       strValue = StringValue(value);
218       lib->putData(GetStdString(path),
219                    RSTRING_PTR(strValue), RSTRING_LEN(strValue),
220                    NUM2INT(append));
221   }
222#ifdef RAISE_EXCEPTIONS
223   else
224   {
225      rb_raise(rb_eRuntimeError,
226               "Unable to put data \"%s\" to Rappture: unknown type",
227               GetString(rbStrName));
228   }
229#endif
230
231   /* Return a Ruby VALUE */
232   return Qnil;
233
234}  /* end RbRp_PutData */
235
236
237/******************************************************************************
238 * RbRp_PutFile()
239 *
240 * Implement the putfile() public method of the Rappture class.
241 * Put a file into the location path in the Rappture object self.
242 * Return Qnil.
243 * On error, if RAISE_EXCEPTIONS is defined, raise a Ruby RuntimeError
244 *  Exception.
245 ******************************************************************************/
246
247static VALUE
248RbRp_PutFile(VALUE self, VALUE path, VALUE filename, VALUE append,
249             VALUE compress)
250{
251#ifdef RAISE_EXCEPTIONS
252   /* Get the Ruby ID of the "to_s" method. */
253   ID id_to_s = rb_intern("to_s");
254
255   /* Call the "to_s" method on value to get a Ruby String representation of
256      the value, in case we need to compose an error message below.  The final
257      argument 0 indicates no further arguments.*/
258   VALUE rbStrName = rb_funcall(filename, id_to_s, 0);
259#endif
260
261   RpLibrary *lib;
262
263   /* Extract the pointer to the Rappture object, lib, from the Ruby
264      object, self */
265   Data_Get_Struct(self, RpLibrary, lib);
266
267   if (T_STRING == TYPE(filename))
268   {
269      VALUE ft = rb_const_get(rb_cObject, rb_intern("FileTest"));
270      ID id_filetest = rb_intern("file?");
271
272      if (Qtrue == rb_funcall(ft, id_filetest, 1, filename))  /* valid filename */
273      {
274         lib->putFile(GetStdString(path), GetStdString(filename),
275                NUM2INT(compress), NUM2INT(append));
276      }
277#ifdef RAISE_EXCEPTIONS
278      else
279      {
280         rb_raise(rb_eRuntimeError, "%s is not a valid file",
281                  GetString(rbStrName));
282      }
283#endif
284   }
285#ifdef RAISE_EXCEPTIONS
286   else
287       rb_raise(rb_eRuntimeError, "Bad file name: %s",
288                GetString(rbStrName));
289#endif
290
291   /* Return a Ruby VALUE */
292   return Qnil;
293
294}  /* end RbRp_PutFile */
295
296
297/******************************************************************************
298 * RbRp_Result()
299 *
300 * Implement the result() public method of the Rappture class.
301 * Write the XML of the Rappture object self to disk.
302 * Return Qnil.
303 ******************************************************************************/
304
305static VALUE
306RbRp_Result(VALUE self, VALUE status)
307{
308   RpLibrary *lib;
309
310   /* Extract the pointer to the Rappture object, lib, from the Ruby
311      object, self */
312   Data_Get_Struct(self, RpLibrary, lib);
313
314   /* Write the Rappture XML to disk. */
315   lib->result(NUM2INT(status));
316
317   /* Return a Ruby VALUE */
318   return Qnil;
319
320}  /* end RbRp_Result */
321
322
323/******************************************************************************
324 * RbRp_Xml()
325 *
326 * Impelement the xml() public method of the Rappture class.
327 * Return a Ruby String with the XML of the Rappture object self.
328 * Return Qnil on error, or, if RAISE_EXCEPTIONS is defined, raise a
329 *  RuntimeError Exception.
330 ******************************************************************************/
331   
332static VALUE
333RbRp_Xml(VALUE self)
334{
335   RpLibrary *lib;
336   std::string str;
337
338   /* Extract the pointer to the Rappture object, lib, from the Ruby
339      object, self */
340   Data_Get_Struct(self, RpLibrary, lib);
341
342   /* Get the Rappture object's XML */
343   str = lib->xml();
344
345   /* Return a Ruby VALUE */
346   if (str.empty())
347   {
348#ifdef RAISE_EXCEPTIONS
349      rb_raise(rb_eRuntimeError, "Unable to retrieve XML");
350#else
351      return Qnil;
352#endif
353   }
354   else
355      return rb_str_new2(str.c_str());
356
357}  /* end RbRp_Xml */
358
359
360/******************************************************************************
361 * RbRp_Progress()
362 *
363 * Implement the progress() public method for the Rappture class.
364 * Write a progress message to stdout, using the Ruby Fixnum percent and the
365 *  Ruby String message.
366 * Return Qnil.
367 ******************************************************************************/
368
369static VALUE
370RbRp_Progress(VALUE self, VALUE percent, VALUE message)
371{
372   /* Note: self is a dummy here */
373
374   /* Write the message */
375    (void)Rappture::Utils::progress(NUM2INT(percent),
376                                    GetString(message));
377
378   /* Return a Ruby VALUE */
379   return Qnil;
380
381}  /* end RbRp_Progress */
382
383
384/******************************************************************************
385 * RbRp_Convert()
386 *
387 * Implement the convert() public method for the Rappture class.
388 * Convert the Ruby String fromVal, containing a numeric value and optional
389 *  units, to the units specified by the Ruby String toUnitsName.
390 * If the Ruby Fixnum showUnits is 1, return a Ruby String with units appended,
391 *  else return a Ruby Float (double).
392 * On error, return Qnil, or, if RAISE_EXCEPTIONS is defined, raise a
393 *  RuntimeError Exception.
394 ******************************************************************************/
395
396static VALUE
397RbRp_Convert(VALUE self, VALUE fromVal, VALUE toUnitsName, VALUE showUnits)
398{
399   VALUE retVal = Qnil;
400   std::string strRetVal;
401   int result;
402
403   /* Convert */
404   strRetVal = RpUnits::convert(GetStdString(fromVal),
405                GetStdString(toUnitsName), NUM2INT(showUnits), &result);
406   /* Return value */
407   if (0 == result)
408   {
409      const char *retCStr = strRetVal.c_str();
410
411      if (NUM2INT(showUnits))  /* Ruby String */
412         retVal = rb_str_new2(retCStr);
413      else                     /* Ruby Float */
414         retVal = rb_float_new(atof(retCStr));
415   }
416#ifdef RAISE_EXCEPTIONS
417   else
418      rb_raise(rb_eRuntimeError, "Unable to convert \"%s\" to \"%s\"",
419               GetString(fromVal), GetString(toUnitsName));
420#endif
421   return retVal;
422
423}  /* end RbRp_Convert */
424
425
426/******************************************************************************
427 * RbRp_Delete()
428 *
429 * Implement the destructor for the Rappture class.
430 * This function is registered with Ruby's garbage collector, which requires
431 *  the void * argument.
432 ******************************************************************************/
433   
434static void
435RbRp_Delete(void *ptr)
436{
437   delete ((RpLibrary *)ptr);
438
439}  /* end RbRp_Delete */
440
441
442/******************************************************************************
443 * RbRp_Init()
444 *
445 * Implement the initialize method, called from the new() method, for the
446 *  Rappture class.
447 * Set the instance variable @driver (the name of the driver XML file).
448 * Return self.
449 ******************************************************************************/
450
451static VALUE
452RbRp_Init(VALUE self, VALUE driver)
453{
454   /* Set instance variable */
455   rb_iv_set(self, "@driver", driver);
456
457   /* Return a Ruby VALUE */
458   return self;
459
460}  /* end RbRp_Init */
461
462
463/******************************************************************************
464 * RbRp_New()
465 *
466 * Implement the new() method for the Rappture class.
467 * Create a Rappture I/O object from the XML file driver.
468 * Create a new Ruby object.
469 * Associate the Rappture object and the Ruby object with the class value
470 *  classval, and garbage collection functions (mark and sweep).
471 * Pass driver to the initialize method.
472 * Return the new Ruby object.
473 * Note this method is not static.
474 ******************************************************************************/
475
476VALUE
477RbRp_New(VALUE classval, VALUE driver)
478{
479   /* Create the Rappture object from the XML driver file. */
480   RpLibrary *lib = new RpLibrary(GetStdString(driver));
481     
482   /* Data_Wrap_Struct() creates a new Ruby object which associates the
483      Rappture object, lib, with the class type classval, and registers 0
484      as the marking function and RbRp_Delete() as the freeing function for
485      Ruby's mark and sweep garbage collector. */
486   VALUE rbLib = Data_Wrap_Struct(classval, 0, RbRp_Delete, lib);
487
488   /* Call the initialize method; rb_obj_call_init() requires an argument
489      array. */
490   VALUE argv[1] = {driver};
491   rb_obj_call_init(rbLib, 1, argv);
492
493   /* Return a Ruby VALUE */
494   return rbLib;
495
496}  /* end RbRp_New */
497
498
499/******************************************************************************
500 * Init_Rappture()
501 *
502 * This is the first function called when Ruby loads the Rappture extension.
503 * Create the Rappture class, add constants, and register methods.
504 ******************************************************************************/
505
506void
507Init_Rappture(void)
508{
509   /* Create the Rappture class as a sublcass of Ruby's Object class. */
510   classRappture = rb_define_class("Rappture", rb_cObject);
511
512   /* Add constants to the Rappture class, accessible via, e.g.,
513      "Rappture::APPEND"*/
514   rb_define_const(classRappture, "APPEND", INT2NUM(RPLIB_APPEND));
515   rb_define_const(classRappture, "OVERWRITE", INT2NUM(RPLIB_OVERWRITE));
516   rb_define_const(classRappture, "UNITS_ON", INT2NUM(RPUNITS_UNITS_ON));
517   rb_define_const(classRappture, "UNITS_OFF", INT2NUM(RPUNITS_UNITS_OFF));
518   rb_define_const(classRappture, "COMPRESS", INT2NUM(RPLIB_COMPRESS));
519   rb_define_const(classRappture, "NO_COMPRESS", INT2NUM(RPLIB_NO_COMPRESS));
520
521   /* Function pointer cast necessary for C++ (but not C) */
522#  define RB_FUNC(func)  (VALUE (*)(ANYARGS))func
523
524   /* Register methods; last argument gives the expected number of arguments,
525      not counting self (i.e. number of arguments from Ruby code) */
526   rb_define_singleton_method(classRappture, "new", RB_FUNC(RbRp_New),    1);
527   rb_define_method(classRappture, "initialize", RB_FUNC(RbRp_Init),      1);
528   rb_define_method(classRappture, "get",        RB_FUNC(RbRp_GetString), 1);
529   rb_define_method(classRappture, "getdata",    RB_FUNC(RbRp_GetData),   1);
530   rb_define_method(classRappture, "put",        RB_FUNC(RbRp_PutObject), 3);
531   rb_define_method(classRappture, "putdata",    RB_FUNC(RbRp_PutData),   3);
532   rb_define_method(classRappture, "putfile",    RB_FUNC(RbRp_PutFile),   4);
533   rb_define_method(classRappture, "result",     RB_FUNC(RbRp_Result),    1);
534   rb_define_method(classRappture, "xml",        RB_FUNC(RbRp_Xml),       0);
535   rb_define_method(classRappture, "convert",    RB_FUNC(RbRp_Convert),   3);
536   rb_define_method(classRappture, "progress",   RB_FUNC(RbRp_Progress),  2);
537
538}  /* end Init_Rappture */
539
540/* TODO rpElement*(), rpChildren*() */
541
Note: See TracBrowser for help on using the repository browser.