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

Last change on this file since 3177 was 3177, checked in by mmc, 9 years ago

Updated all of the copyright notices to reference the transfer to
the new HUBzero Foundation, LLC.

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