options.C

00001 /*
00002  * This file is part of the "Archon" framework.
00003  * (http://files3d.sourceforge.net)
00004  *
00005  * Copyright © 2002 by Kristian Spangsege and Brian Kristiansen.
00006  *
00007  * Permission to use, copy, modify, and distribute this software and
00008  * its documentation under the terms of the GNU General Public License is
00009  * hereby granted. No representations are made about the suitability of
00010  * this software for any purpose. It is provided "as is" without express
00011  * or implied warranty. See the GNU General Public License
00012  * (http://www.gnu.org/copyleft/gpl.html) for more details.
00013  *
00014  * The characters in this file are ISO8859-1 encoded.
00015  *
00016  * The documentation in this file is in "Doxygen" style
00017  * (http://www.doxygen.org).
00018  */
00019 
00020 #include <ctype.h>
00021 #include <string.h>
00022 #include <stdio.h>
00023 #include <string>
00024 #include <iostream>
00025 
00026 #include <archon/math/vector.H>
00027 #include <archon/util/parse_values.H>
00028 #include <archon/util/text.H>
00029 #include <archon/util/options.H>
00030 #include <archon/util/stream.H>
00031 #include <archon/util/color.H>
00032 
00033 using namespace std;
00034 
00035 namespace Archon
00036 {
00037   using namespace Math;
00038 
00039   namespace Utilities
00040   {
00041     template<>
00042     bool Options::Parser<bool>::parse(const char *s) throw(ParseException)
00043     {
00044       return Utilities::ParseValues::parseBoolean(true, s);
00045     }
00046 
00047     template<>
00048     string Options::Parser<bool>::format(bool v)
00049     {
00050       return v ? "yes" : "no";
00051     }
00052 
00053     template<>
00054     int Options::Parser<int>::parse(const char *s) throw(ParseException)
00055     {
00056       return Utilities::ParseValues::parseInteger(true, s);
00057     }
00058 
00059     template<>
00060     string Options::Parser<int>::format(int v)
00061     {
00062       return Text::toString(v);
00063     }
00064 
00065     template<>
00066     double Options::Parser<double>::parse(const char *s) throw(ParseException)
00067     {
00068       return Utilities::ParseValues::parseDouble(s);
00069     }
00070 
00071     template<>
00072     string Options::Parser<double>::format(double v)
00073     {
00074       return Text::toString(v);
00075     }
00076 
00077     template<>
00078     string Options::Parser<string>::parse(const char *s) throw(ParseException)
00079     {
00080       return s;
00081     }
00082 
00083     template<>
00084     string Options::Parser<string>::format(string v)
00085     {
00086       return v;
00087     }
00088 
00089 
00090     template<>
00091     Options::IntByInt Options::Parser<Options::IntByInt>::parse(const char *s)
00092       throw(ParseException)
00093     {
00094       try
00095       {
00096         IntByInt r;
00097         int i=0;
00098         while(s[i] && s[i] != 'x' && s[i] != 'X') ++i;
00099         r.x = ParseValues::parseInteger(false, s, s+i);
00100         if(s[i])
00101           r.y = ParseValues::parseInteger(false, s+i+1);
00102         else r.y = r.x;
00103         return r;
00104       }
00105       catch(ParseException &e)
00106       {
00107         ARCHON_THROW1(ParseException,
00108                       "Invalid resolution '" + string(s) + "'");
00109       }
00110     }
00111 
00112     template<>
00113     string Options::Parser<Options::IntByInt>::format(IntByInt v)
00114     {
00115       char buf[100];
00116       sprintf(buf, "%dx%d", v.x, v.y);
00117       return buf;
00118     }
00119 
00120 
00121     template<>
00122     Vector3 Options::Parser<Vector3>::parse(const char *s)
00123       throw(ParseException)
00124     {
00125       try
00126       {
00127         Vector3 v;
00128         int i=0;
00129         while(s[i] && s[i] != ',') ++i;
00130         if(!s[i]) ARCHON_THROW1(ParseException,
00131                                 "Invalid vector '" + string(s) + "'");
00132         v[0] = ParseValues::parseDouble(s, s+i);
00133         ++i;
00134         int j=i;
00135         while(s[i] && s[i] != ',') ++i;
00136         if(!s[i]) ARCHON_THROW1(ParseException,
00137                                 "Invalid vector '" + string(s) + "'");
00138         v[1] = ParseValues::parseDouble(s+j, s+i);
00139         ++i;
00140         v[2] = ParseValues::parseDouble(s+i);
00141         return v;
00142       }
00143       catch(ParseException &e)
00144       {
00145         ARCHON_THROW1(ParseException,
00146                       "Invalid vector '" + string(s) + "'");
00147       }
00148     }
00149 
00150     template<>
00151     string Options::Parser<Vector3>::format(Vector3 v)
00152     {
00153       char buf[100];
00154       sprintf(buf, "%g,%g,%g", v[0], v[1], v[2]);
00155       return buf;
00156     }
00157 
00158 
00159     template<>
00160     ColorRGBA Options::Parser<ColorRGBA>::parse(const char *s)
00161       throw(ParseException)
00162     {
00163       try
00164       {
00165         ColorRGBA c;
00166         int j, i=0;
00167         while(s[i] && s[i] != ',') ++i;
00168         c[0] = ParseValues::parseDouble(s, s+i);
00169         if(!s[i]) ARCHON_THROW1(ParseException,
00170                                 "Invalid color '" + string(s) + "'");
00171         ++i;
00172         j=i;
00173         while(s[i] && s[i] != ',') ++i;
00174         c[1] = ParseValues::parseDouble(s+j, s+i);
00175         if(!s[i]) ARCHON_THROW1(ParseException,
00176                                 "Invalid color '" + string(s) + "'");
00177         ++i;
00178         j=i;
00179         while(s[i] && s[i] != ',') ++i;
00180         c[2] = ParseValues::parseDouble(s+j, s+i);
00181         if(!s[i]) c[3] = 1;
00182         else
00183         {
00184           ++i;
00185           c[3] = ParseValues::parseDouble(s+i);
00186         }
00187         return c;
00188       }
00189       catch(ParseException &e)
00190       {
00191         ARCHON_THROW1(ParseException,
00192                       "Invalid color '" + string(s) + "'");
00193       }
00194     }
00195 
00196     template<>
00197     string Options::Parser<ColorRGBA>::format(ColorRGBA c)
00198     {
00199       char buf[200];
00200       sprintf(buf, "%g,%g,%g,%g", c[0], c[1], c[2], c[3]);
00201       return buf;
00202     }
00203 
00204 
00205     void Options::verify(string shortName, string longName)
00206       throw(DefinitionException)
00207     {
00208       if(!shortName.size() && !longName.size())
00209         ARCHON_THROW1(DefinitionException,
00210                       "You may not omit both the short "
00211                       "and the long name");
00212       if(shortName.size() > 1)
00213         ARCHON_THROW1(DefinitionException,
00214                       "Illegal length of short name \"" +
00215                       shortName + "\"");
00216 
00217       if(shortName.size())
00218         for(unsigned i=0; i<defs.size(); ++i)
00219           if(shortName == defs[i]->shortName)
00220             ARCHON_THROW1(DefinitionException,
00221                           "Redefinition of short name \"" +
00222                           shortName + "\"");
00223       if(longName.size())
00224         for(unsigned i=0; i<defs.size(); ++i)
00225           if(longName == defs[i]->longName)
00226             ARCHON_THROW1(DefinitionException,
00227                           "Redefinition of long name \"" +
00228                           longName + "\"");
00229     }
00230 
00231     Options::Options(bool treatNegativeNumbersAsOptions, Logger *l):
00232       treatNegativeNumbersAsOptions(treatNegativeNumbersAsOptions),
00233       logger(l)
00234     {
00235       if(!logger) logger = Logger::get();
00236     }
00237 
00238     Options::~Options()
00239     {{ // The extra scope is needed to work around gcc3.2 bug #8287
00240       for(unsigned i=0; i<defs.size(); ++i) delete defs[i];
00241     }}
00242 
00243     Options::ArgType Options::examineArg(string arg)
00244     {
00245       return
00246         !arg.size() || arg[0] != '-' ? argType_normal :
00247         arg.size() > 1 && arg[1] == '-' ? argType_longOption :
00248         !treatNegativeNumbersAsOptions &&
00249         arg.size() > 1 && isdigit(arg[1]) ? argType_normal :
00250         argType_shortOption;
00251     }
00252 
00253     bool Options::processCommandLine(int &argc, const char *argv[],
00254                                      bool switchesOnly)
00255     {
00256       bool error = false;
00257       unsigned j = 1;
00258       for(unsigned i=1; i<static_cast<unsigned>(argc); ++i)
00259       {
00260         string arg = argv[j] = argv[i];
00261         ArgType argType = examineArg(arg);
00262 
00263         // Skip non-switch arguments
00264         if(argType == argType_normal)
00265         {
00266           ++j;
00267           continue;
00268         }
00269 
00270         // Lookup the switch
00271         const Def *d = 0;
00272         if(argType == argType_shortOption)
00273         {
00274           for(unsigned k=0; k<defs.size(); ++k)
00275             if(defs[k]->shortName.size() &&
00276                defs[k]->shortName == arg.substr(1))
00277             {
00278               d = defs[k];
00279               break;
00280             }
00281         }
00282         else
00283         {
00284           for(unsigned k=0; k<defs.size(); ++k)
00285             if(defs[k]->longName.size() &&
00286                defs[k]->longName == arg.substr(2))
00287             {
00288               d = defs[k];
00289               break;
00290             }
00291         }
00292 
00293         if(!d)
00294         {
00295           if(switchesOnly) ++j;
00296           else
00297           {
00298             logger->log("Commandline error: Unrecogninzed option '" +
00299                         arg + "'");
00300             error = true;
00301           }
00302           continue;
00303         }
00304 
00305         // Check for switch arguments
00306         const char *optionArg = 0;
00307         if(d->wantArg != wantArg_never)
00308         {
00309           if(i+1<static_cast<unsigned>(argc) && examineArg(argv[i+1]) == argType_normal)
00310             optionArg = argv[++i];
00311           else if(d->wantArg == wantArg_always)
00312           {
00313             if(switchesOnly) ++j;
00314             else
00315             {
00316               logger->log("Commandline error: Missing mandatory argument for option '" +
00317                           arg + "'");
00318               error = true;
00319             }
00320             continue;
00321           }
00322         }
00323 
00324         // Evaluate switch
00325         if((!switchesOnly || d->switchOnly)) try
00326         {
00327           d->execute(this, optionArg);
00328         }
00329         catch(ParseException &e)
00330         {
00331           logger->log("Commandline error: " + e.getMessage());
00332           error = true;
00333         }
00334         else
00335         {
00336           ++j;
00337           if(optionArg) argv[j++] = optionArg;
00338         }
00339       }
00340 
00341       argc = j;
00342 
00343       return error;
00344     }
00345 
00346     namespace
00347     {
00348       struct LineReader
00349       {
00350         Ref<Stream::Reader> subReader;
00351         string buffer;
00352 
00353         LineReader(Ref<Stream::Reader> r): subReader(r) {}
00354 
00359         string readLine()
00360           throw(Stream::ReadException, UnexpectedException)
00361         {
00362           string::size_type i = 0;
00363 
00364           for(;;)
00365           {
00366             string::size_type j = buffer.find('\n', i);
00367             if(j != string::npos)
00368             {
00369               i = j+1;
00370               break;
00371             }
00372 
00373             i = buffer.size();
00374 
00375             char b[1024];
00376             int n = subReader->read(b, sizeof(b));
00377             if(!n) break;
00378             buffer.append(b, n);
00379           }
00380 
00381           string l(buffer, 0, i);
00382           buffer.erase(0, i);
00383           return l;
00384         }
00385       };
00386 
00387       void stripLeadSpace(string &s)
00388       {
00389         string::size_type i = 0;
00390         while(i<s.size() && isspace(s[i])) ++i;
00391         s.erase(0, i);
00392       }
00393 
00394       struct EatException: virtual Utilities::Exception
00395       {
00396         EatException(string l, string m): Utilities::Exception(l, m) {}
00397       };
00398 
00407       string eatQuoteWord(string &_s) throw(EatException)
00408       {
00409         string s = _s;
00410         stripLeadSpace(s);
00411         if(s.empty()) return "";
00412         string t;
00413         string::size_type i = 0;
00414         while(i<s.size())
00415         {
00416           if(isspace(s[i])) break;
00417           if(s[i] == '"')
00418           {
00419             ++i;
00420             for(;;)
00421             {
00422               if(i == s.size())
00423                 ARCHON_THROW1(EatException,
00424                               "Unterminated double-quoted part");
00425 
00426               if(s[i] == '"')
00427               {
00428                 ++i;
00429                 break;
00430               }
00431 
00432               if(s[i] == '\\')
00433               {
00434                 ++i;
00435                 if(i == s.size())
00436                   ARCHON_THROW1(EatException,
00437                                 "Unterminated escape sequence '\\'");
00438                 switch(s[i])
00439                 {
00440                 case 'n':  t += "\n"; break;
00441                 case 't':  t += "\t"; break;
00442                 case 'v':  t += "\v"; break;
00443                 case 'b':  t += "\b"; break;
00444                 case 'r':  t += "\r"; break;
00445                 case 'f':  t += "\f"; break;
00446                 case 'a':  t += "\a"; break;
00447                 case '\\': t += "\\"; break;
00448                 case '"':  t += "\""; break;
00449 
00450                 case 'x':
00451                   {
00452                     if(i+2 >= s.size())
00453                       ARCHON_THROW1(EatException,
00454                                     "Unterminated escape sequence '\\x'");
00455 
00456                     ++i;
00457                     int c = static_cast<unsigned char>(s[i]);
00458                     ++i;
00459                     int d = static_cast<unsigned char>(s[i]);
00460 
00461                     if(!isxdigit(c) || !isxdigit(d))
00462                       ARCHON_THROW1(EatException,
00463                                     "Invalid escape sequence '\\x" +
00464                                     s.substr(i, 2) + "'");
00465 
00466                     if(isdigit(c)) c -= '0';
00467                     else if(isupper(c)) c -= 'A';
00468                     else  c -= 'a';
00469 
00470                     if(isdigit(d)) d -= '0';
00471                     else if(isupper(d)) d -= 'A';
00472                     else  d -= 'a';
00473 
00474                     t += static_cast<char>(static_cast<unsigned char>(16*c+d));
00475                   }
00476                   break;
00477 
00478                 default:
00479                       ARCHON_THROW1(EatException,
00480                                     "Unrecognized escape sequence '\\" +
00481                                     s.substr(i) + "'");
00482                 }
00483 
00484                 ++i;
00485               }
00486               else
00487               {
00488                 t += s[i];
00489                 ++i;
00490               }
00491             }
00492           }
00493           else
00494           {
00495             t += s[i];
00496             ++i;
00497           }
00498         }
00499 
00500         _s = s.substr(i);
00501         return t;
00502       }
00503 
00504       string printQuoteWord(string s)
00505       {
00506         bool quote = s.empty();
00507         for(string::size_type i=0; i<s.size(); ++i)
00508         {
00509           int c = s[i];
00510           if(!isgraph(c) || c == '"')
00511           {
00512             quote = true;
00513             break;
00514           }
00515         }
00516         return quote ? Text::escapeNonprintable(s) : s;
00517       }
00518     }
00519 
00520     bool Options::processConfigFile(string path) throw(FileAccessException)
00521     {
00522       Ref<Stream::Reader> r;
00523       try
00524       {
00525         r = Stream::makeFileReader(path);
00526       }
00527       catch(Stream::FileOpenException &e)
00528       {
00529         ARCHON_THROW1(FileAccessException, e.getMessage());
00530       }
00531 
00532       bool error = false;
00533       LineReader lineReader(r);
00534       unsigned long n = 0;
00535       for(;;)
00536       {
00537         string l = lineReader.readLine();
00538         if(l.empty()) break;
00539         ++n;
00540         stripLeadSpace(l);
00541         if(l.empty() || l[0] == '#') continue;
00542         try
00543         {
00544           string name = eatQuoteWord(l);
00545           if(name.empty())
00546           {
00547             logger->log(path + ":" + Text::toString(n) +
00548                         ": Empty option name");
00549             error = true;
00550             continue;
00551           }
00552           stripLeadSpace(l);
00553           const char *optionArg = 0;
00554           string value;
00555           if(!l.empty())
00556           {
00557             for(;;)
00558             {
00559               string v = eatQuoteWord(l);
00560               if(v.empty()) break;
00561               if(!value.empty()) value += " ";
00562               value += v;
00563             }
00564             optionArg = value.c_str();
00565           }
00566 
00567           const Def *d = 0;
00568           for(unsigned i=0; i<defs.size(); ++i)
00569             if(defs[i]->longName.size() &&
00570                defs[i]->longName == name)
00571             {
00572               d = defs[i];
00573               break;
00574             }
00575 
00576           if(!d)
00577           {
00578             logger->log(path + ":" + Text::toString(n) +
00579                         ": Unrecogninzed option '" + name + "'");
00580             error = true;
00581             continue;
00582           }
00583 
00584           if(d->wantArg == wantArg_never && optionArg)
00585           {
00586             logger->log(path + ":" + Text::toString(n) +
00587                         ": Option '" + name + "' accepts no value");
00588             error = true;
00589             continue;
00590           }
00591 
00592           if(d->wantArg == wantArg_always && !optionArg)
00593           {
00594             logger->log(path + ":" + Text::toString(n) +
00595                         ": Option '" + name + "' requires a value");
00596             error = true;
00597             continue;
00598           }
00599 
00600           try
00601           {
00602             d->execute(this, optionArg);
00603           }
00604           catch(ParseException &e)
00605           {
00606             logger->log(path + ":" + Text::toString(n) +
00607                         ": " + e.getMessage());
00608             error = true;
00609           }
00610         }
00611         catch(EatException &e)
00612         {
00613           logger->log(path + ":" + Text::toString(n) + ": " + e.getMessage());
00614           error = true;
00615         }
00616       }
00617       return error;
00618     }
00619 
00620     void Options::saveConfigFile(string path) throw(FileAccessException)
00621     {
00622       Ref<Stream::Writer> w;
00623       try
00624       {
00625         w = Stream::makeFileWriter(path);
00626       }
00627       catch(Stream::FileOpenException &e)
00628       {
00629         ARCHON_THROW1(FileAccessException, e.getMessage());
00630       }
00631       bool first = true;
00632       for(unsigned i=0; i<defs.size(); ++i)
00633       {
00634         const Def *d = defs[i];
00635         if(d->switchOnly || d->longName.empty() ||
00636            !dynamic_cast<const DefVarBase *>(d) &&
00637            d->wantArg != wantArg_never) continue;
00638         if(!first) w->writeAll("\n");
00639         first = false;
00640         w->writeAll("# " + d->description + "\n");
00641         string s;
00642         if(d->wantArg != wantArg_never)
00643         {
00644           s += printQuoteWord(d->longName);
00645           if(const DefVarBase *_d = dynamic_cast<const DefVarBase *>(d))
00646             s += " " + printQuoteWord(_d->getValue());
00647         }
00648         else
00649         {
00650           if(const DefVarBase *_d = dynamic_cast<const DefVarBase *>(d))
00651             if(!_d->isDefaultSet()) s += "#";
00652           s += printQuoteWord(d->longName);
00653         }
00654         w->writeAll(s + "\n");
00655       }
00656     }
00657 
00658     string Options::list(unsigned width)
00659     {
00660       const string::size_type maxValueLength = 12; // Must be at least 5
00661       vector<double> columnWidthFractions;
00662       columnWidthFractions.push_back(0.25);
00663       columnWidthFractions.push_back(0.25);
00664       columnWidthFractions.push_back(0.5);
00665       Text::Table<string> table(defs.size(), columnWidthFractions);
00666       for(unsigned i=0; i<defs.size(); ++i)
00667       {
00668         const Def *d = defs[i];
00669         if(d->shortName.size()) table(i, 0) = "-" + d->shortName;
00670         if(d->longName.size() && d->shortName.size()) table(i, 0) += ", ";
00671         if(d->longName.size()) table(i, 0) += "--" + d->longName;
00672         if(const DefVarBase *_d = dynamic_cast<const DefVarBase *>(d))
00673         {
00674           string s = _d->getValue();
00675           string t = printQuoteWord(s);
00676           if(t.size() > maxValueLength)
00677           {
00678             if(s.size()+3 >= maxValueLength)
00679               s = s.substr(s.size()+3-maxValueLength);
00680             for(;;)
00681             {
00682               t = "..." + printQuoteWord(s);
00683               if(t.size() <= maxValueLength) break;
00684               s.erase(0, 1);
00685             }
00686           }
00687           table(i, 1) = t;
00688         }
00689         table(i, 2) = d->description;
00690       }
00691 
00692       return Text::format(table, width);
00693     }
00694   }
00695 }

Generated on Sun Jul 30 22:55:41 2006 for Archon by  doxygen 1.4.4