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

Last change on this file since 4346 was 3737, checked in by ldelgass, 11 years ago

Fix for building with Ruby 1.8 (define RSTRING_PTR/LEN if they aren't defined)

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