JWS C++ Library
C++ language utility library
option.cpp
Go to the documentation of this file.
00001 /*
00002  * This work is licensed under a Creative Commons 
00003  * Attribution-Noncommercial-Share Alike 3.0 United States License.
00004  * 
00005  *    http://creativecommons.org/licenses/by-nc-sa/3.0/us/
00006  * 
00007  * You are free:
00008  * 
00009  *    to Share - to copy, distribute, display, and perform the work
00010  *    to Remix - to make derivative works
00011  * 
00012  * Under the following conditions:
00013  * 
00014  *    Attribution. You must attribute the work in the manner specified by the
00015  *    author or licensor (but not in any way that suggests that they endorse you
00016  *    or your use of the work).
00017  * 
00018  *    Noncommercial. You may not use this work for commercial purposes.
00019  * 
00020  *    Share Alike. If you alter, transform, or build upon this work, you may
00021  *    distribute the resulting work only under the same or similar license to
00022  *    this one.
00023  * 
00024  * For any reuse or distribution, you must make clear to others the license
00025  * terms of this work. The best way to do this is by including this header.
00026  * 
00027  * Any of the above conditions can be waived if you get permission from the
00028  * copyright holder.
00029  * 
00030  * Apart from the remix rights granted under this license, nothing in this
00031  * license impairs or restricts the author's moral rights.
00032  */
00033 
00034 
00054 #include <jwsc++/config.h>
00055 
00056 #include <cstdlib>
00057 #include <cstring>
00058 #include <cctype>
00059 #include <iostream>
00060 #include <fstream>
00061 #include <sstream>
00062 #include <list>
00063 #include <vector>
00064 
00065 #include "jwsc++/base/exception.h"
00066 #include "jwsc++/base/option.h"
00067 
00068 #ifdef JWSCXX_HAVE_DMALLOC
00069 #include <dmalloc.h>
00070 #endif
00071 
00072 
00073 using std::ios_base;
00074 using std::istream;
00075 using std::ostream;
00076 using std::ifstream;
00077 using std::ostringstream;
00078 using std::list;
00079 using std::vector;
00080 using std::strlen;
00081 using std::strcmp;
00082 using std::strncmp;
00083 using std::isspace;
00084 
00085 using namespace jwscxx::base;
00086 
00087 
00088 /* ----------------------------  Option  ------------------------------------ */
00089 
00095 Option::Option(char s_name, const char* l_name, const char* desc) 
00096     throw (Arg_error)
00097 {
00098     if (!s_name && !l_name)
00099     {
00100         throw Arg_error("Option must have a short and/or long name");
00101     }
00102 
00103     this->s_name = s_name;
00104     this->l_name = l_name;
00105     this->desc   = desc;
00106 }
00107 
00108 
00114 bool Option::is_s_name_option(const char* arg) const
00115 {
00116     if (strlen(arg) != 2)
00117     {
00118         return false;
00119     }
00120 
00121     char str[3] = {'-', s_name, 0};
00122 
00123     return (strcmp(arg, str) == 0);
00124 }
00125 
00126 
00132 bool Option::is_l_name_option(const char* arg) const
00133 {
00134     char str[3]  = {'-', '-', 0};
00135 
00136     if (strncmp(arg, str, 2) != 0)
00137     {
00138         return false;
00139     }
00140 
00141     arg += 2;
00142 
00143     const char* eq = arg;
00144     while (*eq != '=' && *eq != 0)
00145     {
00146         eq++;
00147     }
00148 
00149     size_t arg_len = eq - arg;
00150 
00151     if (arg_len != strlen(l_name))
00152     {
00153         return false;
00154     }
00155 
00156     return (strncmp(arg, l_name, arg_len) == 0);
00157 }
00158 
00159 /* -------------------------------------------------------------------------- */
00160 
00161 
00162 
00163 
00164 
00165 
00166 
00167 
00168 /* --------------------------  Option_with_arg  ----------------------------- */
00169 
00176 Option_with_arg::Option_with_arg
00177 (
00178     char        s_name, 
00179     const char* l_name, 
00180     const char* desc,
00181     void (*func)(const char* arg) throw (Arg_error)
00182 ) 
00183 throw (Arg_error)
00184     : Option(s_name, l_name, desc)
00185 {
00186     this->func = func;
00187 }
00188 
00189 
00190 
00200 int Option_with_arg::process(int argc, char** argv) const 
00201     throw (Arg_error)
00202 {
00203     for (int i = 0; i < argc; i++)
00204     {
00205         if (l_name && is_l_name_option(argv[ i ]))
00206         {
00207             func(get_l_name_arg(argv[ i ]));
00208             return i;
00209         }
00210         if (s_name && is_s_name_option(argv[ i ]))
00211         {
00212             func(((i+1) < argc) ? argv[ i+1 ] : 0);
00213             return i;
00214         }
00215     }
00216 
00217     return -1;
00218 }
00219 
00220 
00237 void Option_with_arg::print(ostream& out, int width) const throw (Arg_error)
00238 {
00239     if (width > 60)
00240     {
00241         throw Arg_error("Option name field width > 60");
00242     }
00243 
00244     int s_name_len = 2;
00245     int l_name_len = 2 + ((l_name) ? strlen(l_name)+4 : 0);
00246     int opt_len    = 1 + s_name_len + 2 + l_name_len + 2;
00247 
00248     int opt_padding = (opt_len < width) ? width - opt_len : 0;
00249 
00250     out << " ";
00251 
00252     if (s_name)
00253         out << "-" << s_name;
00254     else
00255         out << "  ";
00256 
00257     if (s_name && l_name)
00258         out << ",";
00259     else
00260         out << " ";
00261 
00262     out << " ";
00263 
00264     if (l_name)
00265         out << "--" << l_name << "=ARG";
00266     else
00267         out << "      ";
00268 
00269     out << "  ";
00270 
00271     if (opt_padding > 0)
00272     {
00273         out.width(opt_padding);
00274         out << "";
00275         out.width(0);
00276     }
00277 
00278     if (!desc || strlen(desc) == 0)
00279     {
00280         out << "\n";
00281         return;
00282     }
00283 
00284     char        desc_buff[81]      = {0};
00285     char*       desc_buff_word_ptr = desc_buff;
00286     char*       desc_buff_ptr      = desc_buff;
00287     const char* desc_char_word_ptr = desc;
00288     const char* desc_char_ptr      = desc;
00289 
00290     int num_lines = 1;
00291     int len       = 0;
00292 
00293     while (*desc_char_ptr != '\0')
00294     {
00295         int desc_max_len;
00296 
00297         if (num_lines == 1)
00298         {
00299             desc_max_len = 80 - (opt_len + opt_padding);
00300         }
00301         else
00302         {
00303             desc_max_len = 80 - width;
00304         }
00305 
00306         if (len == desc_max_len)
00307         {
00308             if (desc_buff == desc_buff_word_ptr)
00309             {
00310                 desc_char_word_ptr = desc_char_ptr;
00311             }
00312             else
00313             {
00314                 desc_char_ptr = desc_char_word_ptr;
00315                 *desc_buff_word_ptr = '\0';
00316             }
00317 
00318             if (num_lines > 1)
00319             {
00320                 out.width(width);
00321                 out << "";
00322                 out.width(0);
00323             }
00324 
00325             desc_buff_ptr = desc_buff;
00326             while (*desc_buff_ptr == ' ')
00327                 desc_buff_ptr++;
00328             out << desc_buff_ptr << "\n";
00329 
00330             desc_buff_word_ptr = desc_buff;
00331             desc_buff_ptr      = desc_buff;
00332 
00333             len = 0;
00334             num_lines++;
00335         }
00336 
00337         *desc_buff_ptr = *desc_char_ptr;
00338         desc_char_ptr++;
00339         desc_buff_ptr++;
00340         len++;
00341 
00342         if (*desc_char_ptr == ' ')
00343         {
00344             desc_buff_word_ptr = desc_buff_ptr;
00345             desc_char_word_ptr = desc_char_ptr;
00346         }
00347     }
00348 
00349     *desc_buff_ptr = *desc_char_ptr;
00350 
00351     if (*desc_buff != '\0')
00352     {
00353         if (num_lines > 1)
00354         {
00355             out.width(width);
00356             out << "";
00357             out.width(0);
00358         }
00359 
00360         desc_buff_ptr = desc_buff;
00361         while (*desc_buff_ptr == ' ')
00362             desc_buff_ptr++;
00363         out << desc_buff_ptr << "\n";
00364     }
00365 }
00366 
00367 
00374 const char* Option_with_arg::get_l_name_arg(const char* str) const
00375 {
00376     while (*str && *str != '=')
00377     {
00378         str++;
00379     }
00380 
00381     if (*str)
00382     {
00383         return str+1;
00384     }
00385 
00386     return 0;
00387 }
00388 
00389 /* -------------------------------------------------------------------------- */
00390 
00391 
00392 
00393 
00394 
00395 
00396 
00397 
00398 /* --------------------------  Option_no_arg  ------------------------------- */
00399 
00406 Option_no_arg::Option_no_arg
00407 (
00408     char        s_name, 
00409     const char* l_name, 
00410     const char* desc,
00411     void (*func)(void)
00412 ) 
00413 throw (Arg_error)
00414     : Option(s_name, l_name, desc)
00415 {
00416     this->func = func;
00417 }
00418 
00419 
00431 int Option_no_arg::process(int argc, char** argv) const throw (Arg_error)
00432 {
00433     for (int i = 0; i < argc; i++)
00434     {
00435         if ((l_name && is_l_name_option(argv[ i ])) ||
00436             (s_name && is_s_name_option(argv[ i ])))
00437         {
00438             func();
00439             return i;
00440         }
00441     }
00442 
00443     return -1;
00444 }
00445 
00446 
00463 void Option_no_arg::print(ostream& out, int width) const throw (Arg_error)
00464 {
00465     if (width > 60)
00466     {
00467         throw Arg_error("Option name field width > 60");
00468     }
00469 
00470     int s_name_len = 2;
00471     int l_name_len = 2 + ((l_name) ? strlen(l_name) : 0);
00472     int opt_len    = 1 + s_name_len + 2 + l_name_len + 2;
00473 
00474     int opt_padding = (opt_len < width) ? width - opt_len : 0;
00475 
00476 
00477     out << " ";
00478 
00479     if (s_name)
00480         out << "-" << s_name;
00481     else
00482         out << "  ";
00483 
00484     if (s_name && l_name)
00485         out << ",";
00486     else
00487         out << " ";
00488 
00489     out << " ";
00490 
00491     if (l_name)
00492         out << "--" << l_name;
00493     else
00494         out << "  ";
00495 
00496     out << "  ";
00497 
00498     if (opt_padding > 0)
00499     {
00500         out.width(opt_padding);
00501         out << "";
00502         out.width(0);
00503     }
00504 
00505     if (!desc || strlen(desc) == 0)
00506     {
00507         out << "\n";
00508         return;
00509     }
00510 
00511     char        desc_buff[81]      = {0};
00512     char*       desc_buff_word_ptr = desc_buff;
00513     char*       desc_buff_ptr      = desc_buff;
00514     const char* desc_char_word_ptr = desc;
00515     const char* desc_char_ptr      = desc;
00516 
00517     int num_lines = 1;
00518     int len       = 0;
00519 
00520     while (*desc_char_ptr != '\0')
00521     {
00522         int desc_max_len;
00523 
00524         if (num_lines == 1)
00525         {
00526             desc_max_len = 80 - (opt_len + opt_padding);
00527         }
00528         else
00529         {
00530             desc_max_len = 80 - width;
00531         }
00532 
00533         if (len == desc_max_len)
00534         {
00535             if (desc_buff == desc_buff_word_ptr)
00536             {
00537                 desc_char_word_ptr = desc_char_ptr;
00538             }
00539             else
00540             {
00541                 desc_char_ptr = desc_char_word_ptr;
00542                 *desc_buff_word_ptr = '\0';
00543             }
00544 
00545             if (num_lines > 1)
00546             {
00547                 out.width(width);
00548                 out << "";
00549                 out.width(0);
00550             }
00551 
00552             desc_buff_ptr = desc_buff;
00553             while (*desc_buff_ptr == ' ')
00554                 desc_buff_ptr++;
00555             out << desc_buff_ptr << "\n";
00556 
00557             desc_buff_word_ptr = desc_buff;
00558             desc_buff_ptr      = desc_buff;
00559 
00560             len = 0;
00561             num_lines++;
00562         }
00563 
00564         *desc_buff_ptr = *desc_char_ptr;
00565         desc_char_ptr++;
00566         desc_buff_ptr++;
00567         len++;
00568 
00569         if (*desc_char_ptr == ' ')
00570         {
00571             desc_buff_word_ptr = desc_buff_ptr;
00572             desc_char_word_ptr = desc_char_ptr;
00573         }
00574     }
00575 
00576     *desc_buff_ptr = *desc_char_ptr;
00577 
00578     if (*desc_buff != '\0')
00579     {
00580         if (num_lines > 1)
00581         {
00582             out.width(width);
00583             out << "";
00584             out.width(0);
00585         }
00586 
00587         desc_buff_ptr = desc_buff;
00588         while (*desc_buff_ptr == ' ')
00589             desc_buff_ptr++;
00590         out << desc_buff_ptr << "\n";
00591     }
00592 }
00593 
00594 /* -------------------------------------------------------------------------- */
00595 
00596 
00597 
00598 
00599 
00600 
00601 
00602 
00603 /* ----------------------------  Options  ----------------------------------- */
00604 
00606 Options::~Options()
00607 {
00608     for (list<Option*>::iterator it = opts.begin(); it != opts.end(); it++)
00609     {
00610         delete *it;
00611     }
00612 }
00613 
00614 
00621 void Options::add(Option* opt)
00622 {
00623     opts.push_back(opt);
00624 }
00625 
00626 
00635 int Options::process(int argc, char** argv) const throw (Arg_error)
00636 {
00637     int n = 0;
00638 
00639     for (int i = 0; i < argc; i++)
00640     {
00641         bool processed = false;
00642 
00643         for (list<Option*>::const_iterator it = opts.begin(); 
00644                 it != opts.end(); it++)
00645         {
00646             if ((*it)->process(1, &(argv[ i ])) >= 0)
00647             {
00648                 processed = true;
00649                 break;
00650             }
00651         }
00652 
00653         if (!processed && is_an_option(argv[ i ]))
00654         {
00655             ostringstream ost;
00656             ost << "Unknown option '" << argv[ i ] << '\'';
00657             throw Arg_error(ost.str());
00658         }
00659         else if (!processed)
00660         {
00661             n++;
00662         }
00663     }
00664 
00665     return n;
00666 }
00667 
00668 
00675 void Options::process(const char* fname) const throw (IO_error, Arg_error)
00676 {
00677     ostringstream ost;
00678     ifstream in;
00679 
00680     in.open(fname);
00681     if (in.fail())
00682     {
00683         ost << fname << ": Could not open file";
00684         throw IO_error(ost.str());
00685     }
00686 
00687     try
00688     {
00689         process(in);
00690     }
00691     catch (Arg_error e)
00692     {
00693         ost << fname << ": " << e.get_msg();
00694         throw Arg_error(ost.str());
00695     }
00696 
00697     in.close();
00698     if (in.fail())
00699     {
00700         ost << fname << ": Could not close file";
00701         throw IO_error(ost.str());
00702     }
00703 }
00704 
00705 
00711 void Options::process(istream& in) const throw (Arg_error)
00712 {
00713     char  c;
00714 
00715     while (in.peek() != EOF)
00716     {
00717         c = in.get();
00718         if (in.fail())
00719         {
00720             throw Arg_error("Could not get character from input");
00721         }
00722 
00723         if (c == '#')
00724         {
00725             in.ignore(255, '\n');
00726             if (in.fail())
00727             {
00728                 throw Arg_error("Could not ignore line");
00729             }
00730         }
00731         else if (!isspace(c))
00732         {
00733             // NEED TO ALLOCATE A CONSTANT STRING FOR EACH LINE READ
00734             char* buf = (char*) calloc(256, sizeof(char));
00735             buf[0] = '-';
00736             buf[1] = '-';
00737             buf[255] = 0;
00738 
00739             in.putback(c);
00740             in.getline(&(buf[2]), 253);
00741             if (in.fail())
00742             {
00743                 throw Arg_error("Could not read line");
00744             }
00745 
00746             bool processed = false;
00747 
00748             for (list<Option*>::const_iterator it = opts.begin(); 
00749                     it != opts.end(); it++)
00750             {
00751                 if ((*it)->process(1, &buf) >= 0)
00752                 {
00753                     processed = true;
00754                     break;
00755                 }
00756             }
00757             if (!processed && is_an_option(buf))
00758             {
00759                 ostringstream ost;
00760                 ost << "Unknown option '" << &(buf[2]) << '\'';
00761                 throw Arg_error(ost.str());
00762             }
00763         }
00764     }
00765 }
00766 
00767 
00784 void Options::print(ostream& out, int width) const
00785 {
00786     for (list<Option*>::const_iterator it = opts.begin(); 
00787             it != opts.end(); it++)
00788     {
00789         (*it)->print(out, width);
00790     }
00791 }
00792 
00793 
00795 bool Options::is_an_option(const char* arg) const
00796 {
00797     int len = strlen(arg);
00798 
00799     if (len < 2)
00800     {
00801         return false;
00802     }
00803 
00804     if (len == 2 && arg[0] == '-')
00805     {
00806         return true;
00807     }
00808 
00809     if (arg[0] == '-' && arg[1] == '-')
00810     {
00811         return true;
00812     }
00813 
00814     return false;
00815 }
00816 
00817 
00818 /* -------------------------------------------------------------------------- */