command_line.cpp

Go to the documentation of this file.
00001 #ifndef COMMAND_LINE_IMPLEMENTATION_FILE
00002 #define COMMAND_LINE_IMPLEMENTATION_FILE
00003 
00004 /*****************************************************************************\
00005 *                                                                             *
00006 *  Name   : command_line                                                      *
00007 *  Author : Chris Koeritz                                                     *
00008 *                                                                             *
00009 *******************************************************************************
00010 * Copyright (c) 1992-$now By Author.  This program is free software; you can  *
00011 * redistribute it and/or modify it under the terms of the GNU General Public  *
00012 * License as published by the Free Software Foundation; either version 2 of   *
00013 * the License or (at your option) any later version.  This is online at:      *
00014 *     http://www.fsf.org/copyleft/gpl.html                                    *
00015 * Please send any updates to: fred@gruntose.com                               *
00016 \*****************************************************************************/
00017 
00018 #include "command_line.h"
00019 #include "directory.h"
00020 #include "filename.h"
00021 
00022 #include <basis/function.h>
00023 #include <basis/istring.h>
00024 #include <basis/log_base.h>
00025 #include <basis/mutex.h>
00026 #include <basis/portable.h>
00027 #include <basis/string_array.h>
00028 #include <data_struct/static_memory_gremlin.h>
00029 #include <textual/parser_bits.h>
00030 
00031 #undef LOG
00032 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger(), s)
00033 
00035 
00036 command_parameter::command_parameter(parameter_types type)
00037 : _type(type), _text(new istring) {}
00038 
00039 command_parameter::command_parameter(parameter_types type, const istring &text)
00040 : _type(type), _text(new istring(text)) {}
00041 
00042 command_parameter::command_parameter(const command_parameter &to_copy)
00043 : object_base(), _type(VALUE), _text(new istring)
00044 { *this = to_copy; }
00045 
00046 command_parameter::~command_parameter() { WHACK(_text); }
00047 
00048 const istring &command_parameter::text() const { return *_text; }
00049 
00050 void command_parameter::text(const istring &new_text) { *_text = new_text; }
00051 
00052 command_parameter &command_parameter::operator =
00053     (const command_parameter &to_copy)
00054 {
00055   if (this == &to_copy) return *this;
00056   _type = to_copy._type;
00057   *_text = *to_copy._text;
00058   return *this;
00059 }
00060 
00062 
00063 // option_prefixes: the list of valid prefixes for options on a command line.
00064 // these are the characters that precede command line arguments.  For Unix,
00065 // the default is a dash (-), while for DOS most programs use forward-slash
00066 // (/).  Adding more characters is trivial; just add a character to the list
00067 // before the sentinel of '\0'.
00068 #if defined(_MSC_VER) || defined(__MINGW32__)
00069   static char option_prefixes[] = { '-', '/', '\0' };
00070 #elif defined(__UNIX__)
00071   static char option_prefixes[] = { '-', '\0' };
00072 #else
00073   #error "I don't know what kind of operating system this is."
00074 #endif
00075 
00076 bool it_is_a_prefix_char(char to_test)
00077 {
00078   for (int i = 0; option_prefixes[i]; i++)
00079     if (to_test == option_prefixes[i]) return true;
00080   return false;
00081 }
00082 
00084 
00085 class internal_cmd_line_array_of_parms : public array<command_parameter> {};
00086 
00088 
00089 SAFE_STATIC_CONST(command_parameter, command_line::cmdline_blank_parm, )
00090   // our default return for erroneous indices.
00091 
00092 command_line::command_line(int argc, char *argv[])
00093 : _implementation(new internal_cmd_line_array_of_parms),
00094   _program_name(new filename(directory::absolute_path(argv[0])))
00095 {
00096   argv++;  // skip command name in argv.
00097 
00098   // loop over the rest of the fields and examine them.
00099   string_array string_list;  // accumulated below.
00100   while (--argc > 0) {
00101     istring to_store = argv[0];  // retrieve the current string.
00102     string_list += to_store;  // put the string in our list.
00103     argv++;  // next string.
00104   }
00105   parse_string_array(string_list);
00106 }
00107 
00108 command_line::command_line(const istring &full_line)
00109 : _implementation(new internal_cmd_line_array_of_parms),
00110   _program_name(new filename)
00111 {
00112   istring accumulator;
00113   string_array string_list;
00114   bool in_quote = false;
00115 //hmmm: this is not quote right yet.
00116 //      use the separate command line method, but get it to run iteratively
00117 //      so we can keep pulling them apart?  maybe it already does!
00118 //      separate is better because it handles escaped quotes.
00119   for (int i = 0; i < full_line.length(); i++) {
00120     char to_examine = full_line.get(i);
00121     if (to_examine == '"') {
00122       // it's a quote character, so maybe we can start eating spaces.
00123       if (!in_quote) {
00124         in_quote = true;
00125         continue;  // eat the quote character but change modes.
00126       }
00127       // nope, we're closing a quote.  we assume that the quotes are
00128       // around the whole argument.  that's the best win32 can do at least.
00129       in_quote = false;
00130       to_examine = ' ';  // trick parser into logging the accumulated string.
00131       // intentional fall-through to space case.
00132     }
00133 
00134     if (parser_bits::white_space(to_examine)) {
00135       // if this is a white space, then we start a new string.
00136       if (!in_quote && accumulator.t()) {
00137         // only grab the accumulator if there are some contents.
00138         string_list += accumulator;
00139         accumulator = "";
00140       } else if (in_quote) {
00141         // we're stuffing the spaces into the string since we're quoted.
00142         accumulator += to_examine;
00143       }
00144     } else {
00145       // not white space, so save it in the accumulator.
00146       accumulator += to_examine;
00147     }
00148   }
00149   if (accumulator.t()) string_list += accumulator;
00150     // that partial string wasn't snarfed during the loop.
00151   // grab the program name off the list so the parsing occurs as expected.
00152   *_program_name = directory::absolute_path(string_list[0]);
00153   string_list.zap(0, 0);
00154   parse_string_array(string_list);
00155 }
00156 
00157 command_line::~command_line()
00158 {
00159   WHACK(_program_name);
00160   WHACK(_implementation);
00161 }
00162 
00163 int command_line::entries() const { return _implementation->length(); }
00164 
00165 filename command_line::program_name() const { return *_program_name; }
00166 
00167 const command_parameter &command_line::get(int field) const
00168 {
00169   bounds_return(field, 0, entries() - 1, cmdline_blank_parm());
00170   return _implementation->get(field);
00171 }
00172 
00173 void command_line::separate_command_line(const istring &cmd_line,
00174     istring &app, istring &parms)
00175 {
00176   char to_find = ' ';  // the command separator.
00177   if (cmd_line[0] == '\"') to_find = '\"';
00178     // if the first character is a quote, then we are seeing a quoted phrase
00179     // and need to look for its completing quote.  otherwise, we'll just look
00180     // for the next space.
00181 
00182   int seek_posn = 1;  // skip the first character.  we have accounted for it.
00183   // skim down the string, looking for the ending of the first phrase.
00184   while (seek_posn < cmd_line.length()) {
00185     // look for our parameter separator.  this will signify the end of the
00186     // first phrase / chunk.  if we don't find it, then it should just mean
00187     // there was only one item on the command line.
00188     int indy = cmd_line.find(to_find, seek_posn);
00189     if (negative(indy)) {
00190       // yep, there wasn't a matching separator, so we think this is just
00191       // one chunk--the app name.
00192       app = cmd_line;
00193       break;
00194     } else {
00195       // now that we know where our separator is, we need to find the right
00196       // two parts (app and parms) based on the separator character in use.
00197       if (to_find == '\"') {
00198         // we are looking for a quote character to complete the app name.
00199         if (cmd_line[indy - 1] == '\\') {
00200           // we have a backslash escaping this quote!  keep seeking.
00201           seek_posn = indy + 1;
00202           continue;
00203         }
00204         app = cmd_line.substring(0, indy);
00205         parms = cmd_line.substring(indy + 2, cmd_line.end());
00206           // skip the quote and the obligatory space character after it.
00207         break;
00208       } else {
00209         // simple space handling here; no escapes to worry about.
00210         app = cmd_line.substring(0, indy - 1);
00211         parms = cmd_line.substring(indy + 1, cmd_line.end());
00212         break;
00213       }
00214     }
00215   }
00216 }
00217 
00218 bool command_line::zap(int field)
00219 {
00220   bounds_return(field, 0, entries() - 1, false);
00221   _implementation->zap(field, field);
00222   return true;
00223 }
00224 
00225 // makes a complaint about a failure and sets the hidden commands to have a
00226 // bogus entry so they aren't queried again.
00227 #define COMPLAIN_CMDS(s) \
00228   listo_cmds += "unknown"; \
00229   COMPLAIN(s)
00230 
00231 string_array command_line::get_command_line()
00232 {
00233   FUNCDEF("get_command_line");
00234   string_array listo_cmds;
00235   // the temporary string below can be given a flat formatting of the commands
00236   // and it will be popped out into a list of arguments.
00237   istring temporary;
00238 #ifdef __UNIX__
00239   if (!__argc || !__argv) {
00240     // our global parameters have not been set, so we must calculate them.
00241     temporary = portable::get_cmdline_from_proc();
00242   } else {
00243     // we have easy access to command line arguments supposedly, so use them.
00244     for (int i = 0; i < __argc; i++) {
00245       // add a string entry for each argument.
00246       listo_cmds += __argv[i];
00247     }
00248     // we don't need a long string to be parsed; the list is ready.
00249     return listo_cmds;
00250   }
00251 #elif defined(__WIN32__)
00252   // we have easy access to the original list of commands.
00253   for (int i = 0; i < __argc; i++) {
00254     // add a string entry for each argument.
00255     listo_cmds += __argv[i];
00256   }
00257   return listo_cmds;
00258 #elif defined(EMBEDDED_BUILD)
00259   listo_cmds += "unknown";
00260   return listo_cmds;
00261 #else
00262   COMPLAIN_CMDS("this OS doesn't support getting the command line.");
00263   return listo_cmds;
00264 #endif
00265 
00266   // now that we have our best guess at a flat representation of the command
00267   // line arguments, we'll chop it up.
00268 
00269 //hmmm: this algorithm doesn't support spaces in filenames currently.
00270 //hmmm: for windows, we can parse the quotes that should be around cmd name.
00271 //hmmm: but for unix, the ps command doesn't support spaces either.  how to
00272 //      get around that to support programs with spaces in the name?
00273   int posn = 0;
00274   int last_posn = -1;
00275   while (posn < temporary.length()) {
00276     posn = temporary.find(' ', posn);
00277     if (non_negative(posn)) {
00278       // found another space to turn into a portion of the command line.
00279       listo_cmds += temporary.substring(last_posn + 1, posn - 1);
00280         // grab the piece of string between the point just beyond where we
00281         // last saw a space and the position just before the space.
00282       last_posn = posn;  // save the last space position.
00283       posn++;  // push the pointer past the space.
00284     } else {
00285       // no more spaces in the string.  grab what we can from the last bit
00286       // of the string that we see.
00287       if (last_posn < temporary.length() - 1) {
00288         // there's something worthwhile grabbing after the last place we
00289         // saw a space.
00290         listo_cmds += temporary.substring(last_posn + 1,
00291             temporary.length() - 1);
00292       }
00293       break;  // we're done finding spaces.
00294     }
00295   }
00296 
00297   return listo_cmds;
00298 }
00299 
00300 istring command_line::text_form() const
00301 {
00302   istring to_return;
00303   const istring EOL = log_base::platform_ending();
00304   for (int i = 0; i < entries(); i++) {
00305     const command_parameter &curr = get(i);
00306     to_return += isprintf("%d: ", i + 1);
00307     switch (curr.type()) {
00308       case command_parameter::CHAR_FLAG:
00309         to_return += istring("<char flag> ") + curr.text() + EOL;
00310         break;
00311       case command_parameter::STRING_FLAG:
00312         to_return += istring("<string flag> ") + curr.text() + EOL;
00313         break;
00314       case command_parameter::VALUE:  // pass through to default.
00315       default:
00316         to_return += istring("<value> ") + curr.text() + EOL;
00317         break;
00318     }
00319   }
00320   return to_return;
00321 }
00322 
00323 bool command_line::find(char option_character, int &index,
00324     bool case_sense) const
00325 {
00326   istring opt(option_character, 1);  // convert to a string once here.
00327   if (!case_sense) opt.to_lower();  // no case-sensitivity.
00328   for (int i = index; i < entries(); i++) {
00329 //hmmm: optimize this too.
00330     if (get(i).type() == command_parameter::CHAR_FLAG) {
00331       bool success = (!case_sense && get(i).text().iequals(opt))
00332           || (case_sense && (get(i).text() == opt));
00333       if (success) {
00334         // the type is appropriate and the value is correct as well...
00335         index = i;
00336         return true;
00337       }
00338     }
00339   }
00340   return false;
00341 }
00342 
00343 bool command_line::find(const istring &option_string, int &index,
00344     bool case_sense) const
00345 {
00346   FUNCDEF("find");
00347 if (option_string.length() && (option_string[0] == '-') )
00348 LOG(istring("found option string with dash!  string is: ") + option_string);
00349 
00350   for (int i = index; i < entries(); i++) {
00351     if (get(i).type() == command_parameter::STRING_FLAG) {
00352       bool success = (!case_sense && get(i).text().iequals(option_string))
00353           || (case_sense && (get(i).text() == option_string));
00354       if (success) {
00355         // the type is appropriate and the value is correct as well...
00356         index = i;
00357         return true;
00358       }
00359     }
00360   }
00361   return false;
00362 }
00363 
00364 bool command_line::get_value(char option_character, istring &value,
00365     bool case_sense) const
00366 {
00367   value = "";
00368   int posn = 0;  // where we find the flag.
00369   if (!find(option_character, posn, case_sense)) return false;
00370 
00371   // get the value after the flag, if there is such.
00372   posn++;  // this is where we think our flag's value lives.
00373   if (posn >= entries()) return false;
00374 
00375   // there's still an entry after where we found our flag; grab it.
00376   command_parameter cp = get(posn);
00377   if (cp.type() != command_parameter::VALUE) return false;
00378 
00379   // finally; we've found an appropriate text value.
00380   value = cp.text();
00381   return true;
00382 }
00383 
00384 bool command_line::get_value(const istring &option_string, istring &value,
00385     bool case_sense) const
00386 {
00387   FUNCDEF("get_value");
00388 if (option_string.length() && (option_string[0] == '-') )
00389 LOG(istring("found option string with dash!  string is: ") + option_string);
00390 
00391   value = "";
00392   int posn = 0;  // where we find the flag.
00393   if (!find(option_string, posn, case_sense)) return false;
00394 
00395   // get the value after the flag, if there is such.
00396   posn++;  // this is where we think our flag's value lives.
00397   if (posn >= entries()) return false;
00398 
00399   // there's still an entry after where we found our flag; grab it.
00400   command_parameter cp = get(posn);
00401   if (cp.type() != command_parameter::VALUE) return false;
00402 
00403   // finally; we've found an appropriate text value.
00404   value = cp.text();
00405   return true;
00406 }
00407 
00408 void command_line::parse_string_array(const string_array &to_parse)
00409 {
00410   bool still_looking_for_flags = true;  // goes to false when only values left.
00411   // loop over the fields and examine them.
00412   for (int i = 0; i < to_parse.length(); i++) {
00413     // retrieve a character from the current string.
00414     int index = 0;
00415     char c = to_parse[i].get(index++);
00416     // we check whether it's a prefix character, and if so, what kind.
00417     if (still_looking_for_flags && it_is_a_prefix_char(c)) {
00418       // at least one prefix is there, so treat this as a flag.
00419       bool gnu_type_of_flag = false;
00420       if (it_is_a_prefix_char(to_parse[i].get(index))) {
00421         // there's a special GNU double flag beginner.
00422         index++;  // skip that extra one.
00423         if ( (index >= to_parse[i].length())
00424             || parser_bits::white_space(to_parse[i].get(index))) {
00425           // special case of '--' (or '//' i suppose) with white space or
00426           // nothing else afterwards; indicates that the rest of the items
00427           // should just be values, not flags.
00428           still_looking_for_flags = false;
00429           continue;  // we ate that item.
00430         }
00431         gnu_type_of_flag = true;
00432       }
00433       // everything after the prefixes is considered part of the flag; they're
00434       // either individual flag characters (on a single prefix) or they're the
00435       // full name for the flag (gnu style).
00436       c = 1;  // reset to a true bool value.
00437       istring gnu_accumulator;  // if processing a gnu flag, it arrives here.
00438       while (c) {
00439         if (!gnu_type_of_flag) {
00440           // add as many flag parameters as possible.
00441           c = to_parse[i].get(index++);
00442             // c will be zero once we hit the end of the string.
00443           if (c) {
00444             command_parameter to_add(command_parameter::CHAR_FLAG, istring(c, 1));
00445             *_implementation += to_add;
00446           }
00447         } else {
00448           // the gnu flag name is added to here.
00449           c = to_parse[i].get(index++);  // zero at end of string.
00450           if (c)
00451             gnu_accumulator += c;  // one more character.
00452         }
00453       }
00454       if (gnu_accumulator.t()) {
00455         // we've accumulated a gnu flag, so store it.
00456         command_parameter to_add(command_parameter::STRING_FLAG,
00457             gnu_accumulator);
00458         *_implementation += to_add;
00459       }
00460     } else {
00461       // add a value type of command_parameter.
00462       istring found = to_parse[i];
00463       command_parameter to_add(command_parameter::VALUE, found);
00464       *_implementation += to_add;
00465     }
00466   }
00467 }
00468 
00469 istring command_line::gather(int &index) const
00470 {
00471   istring to_return;
00472   for (int i = index; i < entries(); i++) {
00473     if (get(i).type() == command_parameter::CHAR_FLAG) {
00474       index = i;
00475       return to_return;
00476     } else to_return += get(i).text();
00477   }
00478   index = entries() - 1;
00479   return to_return;
00480 }
00481 
00482 
00483 #endif //COMMAND_LINE_IMPLEMENTATION_FILE
00484 

Generated on Fri Nov 28 04:29:20 2008 for HOOPLE Libraries by  doxygen 1.5.1