command_line.cpp

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