00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
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 {{
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
00264 if(argType == argType_normal)
00265 {
00266 ++j;
00267 continue;
00268 }
00269
00270
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
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
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;
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 }