source: branches/blt4/lang/tcl/src/RpUnitsTclInterface.cc

Last change on this file was 3949, checked in by gah, 11 years ago

sync with trunk

File size: 17.9 KB
Line 
1/*
2 * ----------------------------------------------------------------------
3 *  Rappture::units
4 *
5 *  This is an interface to the rappture units module.
6 *  It allows you to convert between units and format values.
7 * ======================================================================
8 *  AUTHOR:  Derrick Kearney, Purdue University
9 *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
10 *
11 *  See the file "license.terms" for information on usage and
12 *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13 * ======================================================================
14 */
15#include <tcl.h>
16#include "RpUnits.h"
17
18extern "C" Tcl_AppInitProc RpUnits_Init;
19
20static Tcl_CmdProc RpTclUnitsConvert;
21static Tcl_CmdProc RpTclUnitsDesc;
22static Tcl_CmdProc RpTclUnitsSysFor;
23static Tcl_CmdProc RpTclUnitsSysAll;
24static Tcl_CmdProc RpTclUnitsSearchFor;
25
26
27/**********************************************************************/
28// FUNCTION: RpUnits_Init()
29/// Initializes the Rappture Units module and commands defined below
30/**
31 * Called in Rappture_Init() to initialize the Rappture Units module.
32 * Initialized commands include:
33 * ::Rappture::Units::convert
34 * ::Rappture::Units::description
35 * ::Rappture::Units::System::for
36 * ::Rappture::Units::System::all
37 * ::Rappture::Units::Search::for
38 */
39
40#include <algorithm>
41#include <functional>
42#include <cctype>
43
44// Trim from start
45static inline std::string &ltrim(std::string &s)
46{
47    s.erase(s.begin(),
48        std::find_if(s.begin(), s.end(),
49                     std::not1(std::ptr_fun<int, int>(std::isspace))));
50    return s;
51}
52
53// Trim from end
54static inline std::string &rtrim(std::string &s)
55{
56    s.erase(std::find_if(s.rbegin(), s.rend(),
57        std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
58    return s;
59}
60
61// Trim from both ends
62static inline std::string &trim(std::string &s)
63{
64    return ltrim(rtrim(s));
65}
66
67extern "C" int
68RpUnits_Init(Tcl_Interp *interp)
69{
70
71    Tcl_CreateCommand(interp, "::Rappture::Units::convert",
72        RpTclUnitsConvert, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL);
73
74    Tcl_CreateCommand(interp, "::Rappture::Units::description",
75        RpTclUnitsDesc, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL);
76
77    Tcl_CreateCommand(interp, "::Rappture::Units::System::for",
78        RpTclUnitsSysFor, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL);
79
80    Tcl_CreateCommand(interp, "::Rappture::Units::System::all",
81        RpTclUnitsSysAll, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL);
82
83    Tcl_CreateCommand(interp, "::Rappture::Units::Search::for",
84        RpTclUnitsSearchFor, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL);
85
86    return TCL_OK;
87}
88
89/**********************************************************************/
90// FUNCTION: RpTclUnitsConvert()
91/// Rappture::Units::convert function in Tcl, used to convert unit.
92/**
93 * Converts values between recognized units in Rappture.
94 * Full function call:
95 * ::Rappture::Units::convert <value> ?-context units? ?-to units? ?-units on/off?
96 *
97 * units attached to <value> take precedence over units
98 * provided in -context option.
99 */
100
101
102int
103RpTclUnitsConvert   (   ClientData cdata,
104                        Tcl_Interp *interp,
105                        int argc,
106                        const char *argv[]  )
107{
108    std::string inValue       = ""; // value provided by the user
109    std::string origUnitsName = "";
110    std::string fromUnitsName = ""; // name of the units for user's value
111    std::string toUnitsName   = ""; // name of the units to convert to
112    std::string option        = ""; // tmp var for parsing command line options
113    std::string val           = ""; // inValue + fromUnitsName as one string
114    std::string convertedVal  = ""; // result of conversion
115    int showUnits             = 1;  // flag if we should show units in result
116    int result                = 0;  // flag if the conversion was successful
117
118    int nextarg          = 1; // start parsing using the '2'th argument
119    int argsLeft         = 0; // temp variable for calculation
120    int retVal           = 0; // TCL_OK or TCL_ERROR depending on result val
121    char *endptr         = NULL;
122
123    int err                   = 0;  // err code for validate()
124    std::string type          = ""; // junk variable that validate() needs
125    std::list<std::string> compatList;
126    std::list<std::string>::iterator compatListIter;
127    std::string listStr       = ""; // string version of compatList
128    std::string mesg          = ""; // error mesg text
129
130    Tcl_ResetResult(interp);
131
132    // parse through command line options
133    if (argc < 2) {
134        Tcl_AppendResult(interp,
135            "wrong # args: should be \"",
136            // argv[0]," value args\"",
137            argv[0],
138            " <value> ?-context units? ?-to units? ?-units on/off?\"",
139            (char*)NULL);
140        return TCL_ERROR;
141    }
142
143    inValue = std::string(argv[nextarg++]);
144
145    argsLeft = (argc-nextarg);
146    while (argsLeft > 0 ) {
147        if (*argv[nextarg] == '-') {
148            option = std::string(argv[nextarg]);
149
150            if ( option == "-context" ) {
151                nextarg++;
152                if (argv[nextarg] != NULL) {
153                    fromUnitsName = std::string(argv[nextarg]);
154                    err = 0;
155                    origUnitsName = fromUnitsName;
156                    err = RpUnits::validate(fromUnitsName,type,&compatList);
157                    if ( err != 0) {
158                        Tcl_AppendResult(interp,
159                            "bad value \"", origUnitsName.c_str(),
160                            "\": should be a recognized unit for Rappture",
161                            (char*)NULL);
162                        return TCL_ERROR;
163                    }
164                } else {
165                    // if user does not specify wishes for this option,
166                    // set fromUnitsName to an empty string.
167                    fromUnitsName = "";
168                }
169            } else if ( option == "-to" ) {
170                nextarg++;
171                if (argv[nextarg] != NULL) {
172                    toUnitsName = std::string(argv[nextarg]);
173                    err = 0;
174                    origUnitsName = toUnitsName;
175                    err = RpUnits::validate(toUnitsName,type);
176                    if (err != 0) {
177                        Tcl_AppendResult(interp,
178                            "bad value \"", origUnitsName.c_str(),
179                            "\": should be a recognized unit for Rappture",
180                            (char*)NULL);
181                        return TCL_ERROR;
182                    }
183                } else {
184                    // if user does not specify wishes for this option,
185                    // set toUnitsName to an empty string.
186                    toUnitsName = "";
187                }
188            } else if ( option == "-units" ) {
189                nextarg++;
190                if (argv[nextarg] != NULL) {
191                    if (Tcl_GetBoolean(interp, argv[nextarg], &showUnits)) {
192                        // unrecognized value for -units option
193                        // Tcl_GetBoolean fills in error message
194                        // Tcl_AppendResult(interp,
195                        //     "expected boolean value but got \"",
196                        //     argv[nextarg], "\"", (char*)NULL);
197                        return TCL_ERROR;
198                    }
199                } else {
200                    // if user does not specify wishes for this option,
201                    // return error.
202                    // unrecognized value for -units option
203                    Tcl_AppendResult(interp,
204                        "expected boolean value but got \"\"", (char*)NULL);
205                    return TCL_ERROR;
206                }
207            } else {
208                // unrecognized option
209                Tcl_AppendResult(interp, "bad option \"", argv[nextarg],
210                        "\": should be -context, -to, -units",
211                        (char*)NULL);
212                return TCL_ERROR;
213            }
214
215            nextarg++;
216        } else {
217            // unrecognized input
218            Tcl_AppendResult(interp, "bad option \"", argv[nextarg], "\": ",
219                "should be -context, -to, -units",
220                (char*)NULL);
221            return TCL_ERROR;
222
223        }
224
225        argsLeft = (argc-nextarg);
226    }
227
228    // check the inValue to see if it has units
229    // or if we should use those provided in -context option
230
231
232    // Trim away white space from the value. 
233    trim(inValue);
234
235    strtod(inValue.c_str(), &endptr);
236    if (endptr == inValue.c_str()) {
237        // there was no numeric value that could be pulled from inValue
238        // return error
239
240        mesg =  "\": should be a real number with units";
241
242        if (!fromUnitsName.empty()) {
243            list2str(compatList,listStr);
244            mesg = mesg + " of (" + listStr + ")";
245        }
246 
247        Tcl_AppendResult(interp, "bad value \"",
248                inValue.c_str(), mesg.c_str(), (char*)NULL);
249        return TCL_ERROR;
250    } else if ( ((unsigned)(endptr - inValue.c_str())) == inValue.length() ) {
251        // add 1 because we are subtracting indicies
252        // there were no units at the end of the inValue string
253        // rappture units convert expects the val variable to be
254        // the quantity and units in one string
255
256        if (!fromUnitsName.empty()) {
257            val = inValue + fromUnitsName;
258        } else {
259            Tcl_AppendResult(interp, "value: \"", inValue.c_str(),
260                    "\" has unrecognized units", (char*)NULL);
261            return TCL_ERROR;
262        }
263    } else {
264        // there seemed to be units at the end of the inValue string
265        // we will ignore the -context flag and use the units in inValue
266        val = inValue;
267    }
268
269    // call the rappture units convert function
270    convertedVal = RpUnits::convert(val, toUnitsName, showUnits, &result);
271
272    if ( (!convertedVal.empty()) && (result == 0) ) {
273        // store the new result in the interpreter
274        Tcl_AppendResult(interp, convertedVal.c_str(), (char*)NULL);
275        retVal = TCL_OK;
276    } else {
277        // error while converting
278        Tcl_AppendResult(interp,
279                convertedVal.c_str(),
280                (char*)NULL);
281        retVal = TCL_ERROR;
282    }
283
284    return retVal;
285}
286
287/**********************************************************************/
288// FUNCTION: RpTclUnitsDesc()
289/// Rappture::Units::description function in Tcl, returns description of units
290/**
291 * Returns a description for the specified system of units.
292 * The description includes the abstract type (length, temperature, etc.)
293 * along with a list of all compatible systems.
294 *
295 * Full function call:
296 * ::Rappture::Units::description <units>
297 */
298
299int
300RpTclUnitsDesc      (   ClientData cdata,
301                        Tcl_Interp *interp,
302                        int argc,
303                        const char *argv[]  )
304{
305    std::string unitsName     = ""; // name of the units provided by user
306    std::string type          = ""; // name of the units provided by user
307    std::string listStr       = ""; // name of the units provided by user
308    // const RpUnits* unitsObj   = NULL;
309    std::list<std::string> compatList;
310
311    int nextarg               = 1; // start parsing using the '2'th argument
312    int err                   = 0; // err code for validate()
313
314    Tcl_ResetResult(interp);
315
316    // parse through command line options
317    if (argc != 2) {
318        Tcl_AppendResult(interp,
319                "wrong # args: should be \"", argv[0],
320                " units\"", (char*)NULL);
321        return TCL_ERROR;
322    }
323
324    unitsName = std::string(argv[nextarg]);
325
326    err = RpUnits::validate(unitsName,type,&compatList);
327    if (err) {
328        /*
329         * according to tcl version, in this case we
330         * should return an empty string. i happen to disagree.
331         * the next few lines is what i think the user should see.
332        Tcl_AppendResult(interp,
333            "bad value \"", unitsName.c_str(),
334            "\": should be a recognized unit for Rappture",
335            (char*)NULL);
336        return TCL_ERROR;
337        */
338        return TCL_OK;
339    }
340
341    Tcl_AppendResult(interp, type.c_str(), (char*)NULL);
342
343    list2str(compatList,listStr);
344
345    Tcl_AppendResult(interp, " (", listStr.c_str() ,")", (char*)NULL);
346
347    return TCL_OK;
348}
349
350/**********************************************************************/
351// FUNCTION: RpTclUnitsSysFor()
352/// Rappture::Units::System::for fxn in Tcl, returns system for given units
353/**
354 * Returns the system, as a string, for the given system of units, or ""
355 * if there is no system that matches the units string.
356 *
357 * Full function call:
358 * ::Rappture::Units::System::for <units>
359 */
360
361int
362RpTclUnitsSysFor    (   ClientData cdata,
363                        Tcl_Interp *interp,
364                        int argc,
365                        const char *argv[]  )
366{
367    std::string unitsName     = ""; // name of the units provided by user
368    std::string type          = ""; // type/system of units to be returned to user
369    int nextarg               = 1; // start parsing using the '2'th argument
370    int err                   = 0;
371
372    Tcl_ResetResult(interp);
373
374    // parse through command line options
375    if (argc != 2) {
376        Tcl_AppendResult(interp, "wrong # args: should be \"",
377                argv[0], " units\"", (char*)NULL);
378        return TCL_ERROR;
379    }
380
381    unitsName = std::string(argv[nextarg]);
382
383    // look in our dictionary of units to see if 'unitsName' is a valid unit
384    // if so, return its type (or system) in the variable 'type'.
385    err = RpUnits::validate(unitsName,type);
386    if (err) {
387        /*
388         * according to tcl version, in this case we
389         * should return an empty string. i happen to disagree.
390         * the next few lines is what i think the user should see.
391        Tcl_AppendResult(interp,
392            "The units named: \"", unitsName.c_str(),
393            "\" is not a recognized unit for rappture",
394            (char*)NULL);
395        return TCL_ERROR;
396        */
397        return TCL_OK;
398    }
399
400    Tcl_AppendResult(interp, type.c_str(), (char*)NULL);
401    return TCL_OK;
402
403}
404
405/**********************************************************************/
406// FUNCTION: RpTclUnitsSysAll()
407/// Rappture::Units::System::all fxn in Tcl, returns list of compatible units
408/**
409 * Returns a list of all units compatible with the given units string.
410 * Compatible units are determined by following all conversion
411 * relationships that lead to the same base system.
412 *
413 * Full function call:
414 * ::Rappture::Units::System::all <units>
415 */
416
417int
418RpTclUnitsSysAll    (   ClientData cdata,
419                        Tcl_Interp *interp,
420                        int argc,
421                        const char *argv[]  )
422{
423    std::string unitsName     = ""; // name of the units provided by user
424    std::string type          = ""; // junk variable that validate() needs
425    // const RpUnits* unitsObj   = NULL;
426    std::list<std::string> compatList;
427    std::list<std::string>::iterator compatListIter;
428    int nextarg               = 1; // start parsing using the '2'th argument
429    int err                   = 0; // err code for validate
430
431    Tcl_ResetResult(interp);
432
433    // parse through command line options
434    if (argc != 2) {
435        Tcl_AppendResult(interp, "wrong # args: should be \"",
436                argv[0], " units\"", (char*)NULL);
437        return TCL_ERROR;
438    }
439
440    unitsName = std::string(argv[nextarg]);
441
442    err = RpUnits::validate(unitsName,type,&compatList);
443    if (err) {
444        /*
445         * according to tcl version, in this case we
446         * should return an empty string. i happen to disagree.
447         * the next few lines is what i think the user should see.
448        Tcl_AppendResult(interp,
449            "The units named: \"", unitsName.c_str(),
450            "\" is not a recognized unit for rappture",
451            (char*)NULL);
452        return TCL_ERROR;
453        */
454        return TCL_OK;
455    }
456
457    compatListIter = compatList.begin();
458
459    while (compatListIter != compatList.end()) {
460        Tcl_AppendElement(interp,(*compatListIter).c_str());
461        // increment the iterator
462        compatListIter++;
463    }
464
465    return TCL_OK;
466}
467
468/**********************************************************************/
469// FUNCTION: RpTclUnitsSearchfor()
470/// Rappture::Units::Search::for fxn in Tcl, returns string of found units
471/**
472 * Returns a list of all units from the given units string that
473 * were found within the units dictionary. This function takes in a
474 * string with or without a value. The string at the very least should
475 * contain the units you are searching for in the dictionary. If the
476 * string contains a value as well, the value will be ignored. A value
477 * is considered any numeric sequence as defined by the function
478 * strtod().
479 *
480 * Full function call:
481 * ::Rappture::Units::Search::for <units>
482 */
483
484int
485RpTclUnitsSearchFor (  ClientData cdata,
486                        Tcl_Interp *interp,
487                        int argc,
488                        const char *argv[]  )
489{
490    std::string unitsName     = ""; // name of the units provided by user
491    std::string origUnitsName = ""; // name of the units provided by user
492    std::string type          = ""; // junk variable that validate() needs
493    int nextarg               = 1; // start parsing using the '2'th argument
494    int err                   = 0; // err code for validate
495    double val                = 0;
496
497    Tcl_ResetResult(interp);
498
499    // parse through command line options
500    if (argc != 2) {
501        Tcl_AppendResult(interp, "wrong # args: should be \"",
502                argv[0], " units\"", (char*)NULL);
503        return TCL_ERROR;
504    }
505
506    // find where the unitsName begins
507    unitSlice(std::string(argv[nextarg]),unitsName,val);
508
509    err = RpUnits::validate(unitsName,type);
510    if (err) {
511        /*
512         * according to tcl version, in this case we
513         * should return an empty string. i happen to disagree.
514         * the next few lines is what i think the user should see.
515        Tcl_AppendResult(interp,
516            "Unrecognized units: \"", origUnitsName.c_str(), "\"", (char*)NULL);
517        return TCL_ERROR;
518        */
519        return TCL_OK;
520    }
521
522    Tcl_AppendResult(interp, unitsName.c_str(), (char*)NULL);
523
524    return TCL_OK;
525}
Note: See TracBrowser for help on using the repository browser.