feisty meow concerns codebase  2.140
command_line.cpp
Go to the documentation of this file.
1 /*****************************************************************************\
2 * *
3 * Name : command_line *
4 * Author : Chris Koeritz *
5 * *
6 *******************************************************************************
7 * Copyright (c) 1992-$now By Author. This program is free software; you can *
8 * redistribute it and/or modify it under the terms of the GNU General Public *
9 * License as published by the Free Software Foundation; either version 2 of *
10 * the License or (at your option) any later version. This is online at: *
11 * http://www.fsf.org/copyleft/gpl.html *
12 * Please send any updates to: fred@gruntose.com *
13 \*****************************************************************************/
14 
15 #include "command_line.h"
16 
17 #include <basis/functions.h>
18 #include <basis/astring.h>
19 #include <basis/mutex.h>
21 #include <filesystem/directory.h>
22 #include <filesystem/filename.h>
25 #include <textual/parser_bits.h>
27 
28 #undef LOG
29 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
30 
31 using namespace basis;
32 using namespace configuration;
33 using namespace filesystem;
34 using namespace loggers;
35 using namespace structures;
36 using namespace textual;
37 
38 namespace application {
39 
41 
42 command_parameter::command_parameter(parameter_types type)
43 : _type(type), _text(new astring) {}
44 
46 : _type(type), _text(new astring(text)) {}
47 
49 : _type(VALUE), _text(new astring)
50 { *this = to_copy; }
51 
53 
54 const astring &command_parameter::text() const { return *_text; }
55 
56 void command_parameter::text(const astring &new_text) { *_text = new_text; }
57 
58 command_parameter &command_parameter::operator =
59  (const command_parameter &to_copy)
60 {
61  if (this == &to_copy) return *this;
62  _type = to_copy._type;
63  *_text = *to_copy._text;
64  return *this;
65 }
66 
68 
69 // option_prefixes: the list of valid prefixes for options on a command line.
70 // these are the characters that precede command line arguments. we have
71 // dropped any but the linux default of dash ('-').
72 static char option_prefixes[] = { '-', '\0' };
73 
74 bool it_is_a_prefix_char(char to_test)
75 {
76  for (int i = 0; option_prefixes[i]; i++)
77  if (to_test == option_prefixes[i]) return true;
78  return false;
79 }
80 
82 
83 class internal_cmd_line_array_of_parms : public array<command_parameter> {};
84 
86 
87 SAFE_STATIC_CONST(command_parameter, command_line::cmdline_blank_parm, )
88  // our default return for erroneous indices.
89 
90 command_line::command_line(int argc, char *argv[])
91 : _implementation(new internal_cmd_line_array_of_parms),
92  _program_name(new filename(directory::absolute_path(argv[0])))
93 {
94  argv++; // skip command name in argv.
95 
96  // loop over the rest of the fields and examine them.
97  string_array string_list; // accumulated below.
98  while (--argc > 0) {
99  astring to_store = argv[0]; // retrieve the current string.
100  string_list += to_store; // put the string in our list.
101  argv++; // next string.
102  }
103  parse_string_array(string_list);
104 }
105 
107 : _implementation(new internal_cmd_line_array_of_parms),
108  _program_name(new filename)
109 {
110  astring accumulator;
111  string_array string_list;
112  bool in_quote = false;
113 //hmmm: this is not quite right yet.
114 // use the separate command line method, but get it to run iteratively
115 // so we can keep pulling them apart? maybe it already does!
116 // separate is better because it handles escaped quotes.
117 //hmmm: does above complaint parse? what's not right yet?
118  for (int i = 0; i < full_line.length(); i++) {
119  char to_examine = full_line.get(i);
120  if (to_examine == '"') {
121  // it's a quote character, so maybe we can start eating spaces.
122  if (!in_quote) {
123  in_quote = true;
124  continue; // eat the quote character but change modes.
125  }
126  // nope, we're closing a quote. we assume that the quotes are
127  // around the whole argument. that's the best win32 can do at least.
128  in_quote = false;
129  to_examine = ' '; // trick parser into logging the accumulated string.
130  // intentional fall-through to space case.
131  }
132 
133  if (parser_bits::white_space(to_examine)) {
134  // if this is a white space, then we start a new string.
135  if (!in_quote && accumulator.t()) {
136  // only grab the accumulator if there are some contents.
137  string_list += accumulator;
138  accumulator = "";
139  } else if (in_quote) {
140  // we're stuffing the spaces into the string since we're quoted.
141  accumulator += to_examine;
142  }
143  } else {
144  // not white space, so save it in the accumulator.
145  accumulator += to_examine;
146  }
147  }
148  if (accumulator.t()) string_list += accumulator;
149  // that partial string wasn't snarfed during the loop.
150  // grab the program name off the list so the parsing occurs as expected.
151  *_program_name = directory::absolute_path(string_list[0]);
152  string_list.zap(0, 0);
153  parse_string_array(string_list);
154 }
155 
157 {
158  WHACK(_program_name);
159  WHACK(_implementation);
160 }
161 
162 int command_line::entries() const { return _implementation->length(); }
163 
164 filename command_line::program_name() const { return *_program_name; }
165 
166 const command_parameter &command_line::get(int field) const
167 {
168  bounds_return(field, 0, entries() - 1, cmdline_blank_parm());
169  return _implementation->get(field);
170 }
171 
173  astring &app, astring &parms)
174 {
175  char to_find = ' '; // the command separator.
176  if (cmd_line[0] == '\"') to_find = '\"';
177  // if the first character is a quote, then we are seeing a quoted phrase
178  // and need to look for its completing quote. otherwise, we'll just look
179  // for the next space.
180 
181  int seek_posn = 1; // skip the first character. we have accounted for it.
182  // skim down the string, looking for the ending of the first phrase.
183  while (seek_posn < cmd_line.length()) {
184  // look for our parameter separator. this will signify the end of the
185  // first phrase / chunk. if we don't find it, then it should just mean
186  // there was only one item on the command line.
187  int indy = cmd_line.find(to_find, seek_posn);
188  if (negative(indy)) {
189  // yep, there wasn't a matching separator, so we think this is just
190  // one chunk--the app name.
191  app = cmd_line;
192  break;
193  } else {
194  // now that we know where our separator is, we need to find the right
195  // two parts (app and parms) based on the separator character in use.
196  if (to_find == '\"') {
197  // we are looking for a quote character to complete the app name.
198  if (cmd_line[indy - 1] == '\\') {
199  // we have a backslash escaping this quote! keep seeking.
200  seek_posn = indy + 1;
201  continue;
202  }
203  app = cmd_line.substring(0, indy);
204  parms = cmd_line.substring(indy + 2, cmd_line.end());
205  // skip the quote and the obligatory space character after it.
206  break;
207  } else {
208  // simple space handling here; no escapes to worry about.
209  app = cmd_line.substring(0, indy - 1);
210  parms = cmd_line.substring(indy + 1, cmd_line.end());
211  break;
212  }
213  }
214  }
215 }
216 
217 bool command_line::zap(int field)
218 {
219  bounds_return(field, 0, entries() - 1, false);
220  _implementation->zap(field, field);
221  return true;
222 }
223 
224 // makes a complaint about a failure and sets the hidden commands to have a
225 // bogus entry so they aren't queried again.
226 #define COMPLAIN_CMDS(s) \
227  listo_cmds += "unknown"; \
228  COMPLAIN(s)
229 
231 {
232  FUNCDEF("get_command_line");
233  string_array listo_cmds;
234  // the temporary string below can be given a flat formatting of the commands
235  // and it will be popped out into a list of arguments.
236  astring temporary;
237 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
238  if (!_global_argc || !_global_argv) {
239  // our global parameters have not been set, so we must calculate them.
240  temporary = application_configuration::get_cmdline_from_proc();
241  } else {
242  // we have easy access to command line arguments supposedly, so use them.
243  for (int i = 0; i < _global_argc; i++) {
244  // add a string entry for each argument.
245  listo_cmds += _global_argv[i];
246  }
247  // we don't need a long string to be parsed; the list is ready.
248  return listo_cmds;
249  }
250 /*
251 #elif defined(_MSC_VER)
252  // we have easy access to the original list of commands.
253  for (int i = 0; i < _global_argc; i++) {
254  // add a string entry for each argument.
255  listo_cmds += _global_argv[i];
256  }
257  return listo_cmds;
258 */
259 #else
260  COMPLAIN_CMDS("this OS doesn't support getting the command line.");
261  return listo_cmds;
262 #endif
263 
264  // now that we have our best guess at a flat representation of the command
265  // line arguments, we'll chop it up.
266 
267 //hmmm: this algorithm doesn't support spaces in filenames currently.
268 //hmmm: for windows, we can parse the quotes that should be around cmd name.
269 //hmmm: but for unix, the ps command doesn't support spaces either. how to
270 // get around that to support programs with spaces in the name?
271  int posn = 0;
272  int last_posn = -1;
273  while (posn < temporary.length()) {
274  posn = temporary.find(' ', posn);
275  if (non_negative(posn)) {
276  // found another space to turn into a portion of the command line.
277  listo_cmds += temporary.substring(last_posn + 1, posn - 1);
278  // grab the piece of string between the point just beyond where we
279  // last saw a space and the position just before the space.
280  last_posn = posn; // save the last space position.
281  posn++; // push the pointer past the space.
282  } else {
283  // no more spaces in the string. grab what we can from the last bit
284  // of the string that we see.
285  if (last_posn < temporary.length() - 1) {
286  // there's something worthwhile grabbing after the last place we
287  // saw a space.
288  listo_cmds += temporary.substring(last_posn + 1,
289  temporary.length() - 1);
290  }
291  break; // we're done finding spaces.
292  }
293  }
294 
295  return listo_cmds;
296 }
297 
299 {
300  astring to_return;
301  const astring EOL = parser_bits::platform_eol_to_chars();
302  for (int i = 0; i < entries(); i++) {
303  const command_parameter &curr = get(i);
304  to_return += a_sprintf("%d: ", i + 1);
305  switch (curr.type()) {
307  to_return += astring("<char flag> ") + curr.text() + EOL;
308  break;
310  to_return += astring("<string flag> ") + curr.text() + EOL;
311  break;
312  case command_parameter::VALUE: // pass through to default.
313  default:
314  to_return += astring("<value> ") + curr.text() + EOL;
315  break;
316  }
317  }
318  return to_return;
319 }
320 
321 bool command_line::find(char option_character, int &index,
322  bool case_sense) const
323 {
324  astring opt(option_character, 1); // convert to a string once here.
325  if (!case_sense) opt.to_lower(); // no case-sensitivity.
326  for (int i = index; i < entries(); i++) {
327 //hmmm: optimize this too.
328  if (get(i).type() == command_parameter::CHAR_FLAG) {
329  bool success = (!case_sense && get(i).text().iequals(opt))
330  || (case_sense && (get(i).text() == opt));
331  if (success) {
332  // the type is appropriate and the value is correct as well...
333  index = i;
334  return true;
335  }
336  }
337  }
338  return false;
339 }
340 
341 bool command_line::find(const astring &option_string, int &index,
342  bool case_sense) const
343 {
344  FUNCDEF("find");
345 if (option_string.length() && (option_string[0] == '-') )
346 LOG(astring("found option string with dash! string is: ") + option_string);
347 
348  for (int i = index; i < entries(); i++) {
350  bool success = (!case_sense && get(i).text().iequals(option_string))
351  || (case_sense && (get(i).text() == option_string));
352  if (success) {
353  // the type is appropriate and the value is correct as well...
354  index = i;
355  return true;
356  }
357  }
358  }
359  return false;
360 }
361 
362 bool command_line::get_value(char option_character, astring &value,
363  bool case_sense) const
364 {
365  FUNCDEF("get_value");
366  value = "";
367  int posn = 0; // where we find the flag.
368  if (!find(option_character, posn, case_sense)) return false;
369 
370  // get the value after the flag, if there is such.
371  posn++; // this is where we think our flag's value lives.
372  if (posn >= entries()) return false;
373 
374  // there's still an entry after where we found our flag; grab it.
375  command_parameter cp = get(posn);
376  if (cp.type() != command_parameter::VALUE) return false;
377 
378  // finally; we've found an appropriate text value.
379  value = cp.text();
380  return true;
381 }
382 
383 bool command_line::get_value(const astring &option_string, astring &value,
384  bool case_sense) const
385 {
386  FUNCDEF("get_value");
387 if (option_string.length() && (option_string[0] == '-') )
388 LOG(astring("found option string with dash! string is: ") + option_string);
389 
390  value = "";
391  int posn = 0; // where we find the flag.
392  if (!find(option_string, posn, case_sense)) return false;
393 //printf("found the flag! at %d\n", posn);
394 
395  // get the value after the flag, if there is such.
396  posn++; // this is where we think our flag's value lives.
397  if (posn >= entries()) return false;
398 //printf("next posn is still okay at %d\n", posn);
399 
400  // there's still an entry after where we found our flag; grab it.
401  command_parameter cp = get(posn);
402 //printf("comm parm has text %s\n", cp.text().s());
403  if (cp.type() != command_parameter::VALUE) return false;
404 
405  // finally; we've found an appropriate text value.
406  value = cp.text();
407 //printf("assigning value %s\n", value.s());
408  return true;
409 }
410 
411 void command_line::parse_string_array(const string_array &to_parse)
412 {
413  bool still_looking_for_flags = true; // goes to false when only values left.
414  // loop over the fields and examine them.
415  for (int i = 0; i < to_parse.length(); i++) {
416  // retrieve a character from the current string.
417  int index = 0;
418  char c = to_parse[i].get(index++);
419  // we check whether it's a prefix character, and if so, what kind.
420  if (still_looking_for_flags && it_is_a_prefix_char(c)) {
421  // at least one prefix is there, so treat this as a flag.
422  bool gnu_type_of_flag = false;
423  if (it_is_a_prefix_char(to_parse[i].get(index))) {
424  // there's a special GNU double flag beginner.
425  index++; // skip that extra one.
426  if ( (index >= to_parse[i].length())
427  || parser_bits::white_space(to_parse[i].get(index))) {
428  // special case of '--' (or '//' i suppose) with white space or
429  // nothing else afterwards; indicates that the rest of the items
430  // should just be values, not flags.
431  still_looking_for_flags = false;
432  continue; // we ate that item.
433  }
434  gnu_type_of_flag = true;
435  }
436  // everything after the prefixes is considered part of the flag; they're
437  // either individual flag characters (on a single prefix) or they're the
438  // full name for the flag (gnu style).
439  c = 1; // reset to a true bool value.
440  astring gnu_accumulator; // if processing a gnu flag, it arrives here.
441  while (c) {
442  if (!gnu_type_of_flag) {
443  // add as many flag parameters as possible.
444  c = to_parse[i].get(index++);
445  // c will be zero once we hit the end of the string.
446  if (c) {
447  command_parameter to_add(command_parameter::CHAR_FLAG, astring(c, 1));
448  *_implementation += to_add;
449  }
450  } else {
451  // the gnu flag name is added to here.
452  c = to_parse[i].get(index++); // zero at end of string.
453  if (c)
454  gnu_accumulator += c; // one more character.
455  }
456  }
457  if (gnu_accumulator.t()) {
458  // we've accumulated a gnu flag, so store it.
459  command_parameter to_add(command_parameter::STRING_FLAG,
460  gnu_accumulator);
461  *_implementation += to_add;
462  }
463  } else {
464  // add a value type of command_parameter.
465  astring found = to_parse[i];
466  command_parameter to_add(command_parameter::VALUE, found);
467  *_implementation += to_add;
468  }
469  }
470 }
471 
472 astring command_line::gather(int &index) const
473 {
474  astring to_return;
475  for (int i = index; i < entries(); i++) {
476  if (get(i).type() == command_parameter::CHAR_FLAG) {
477  index = i;
478  return to_return;
479  } else to_return += get(i).text();
480  }
481  index = entries() - 1;
482  return to_return;
483 }
484 
485 } //namespace.
486 
int entries() const
Returns the number of fields found on the command line.
static structures::string_array get_command_line()
returns the command line passed to the program as a list of strings.
filesystem::filename program_name() const
Returns the program name found in the command line.
static void separate_command_line(const basis::astring &cmd_line, basis::astring &app, basis::astring &parms)
breaks apart a command line in "cmd_line" into "app" and "parms".
bool find(char option_character, int &index, bool case_sense=true) const
Returns true if the "option_character" is found in the parameters.
bool get_value(char option_character, basis::astring &value, bool case_sense=true) const
retrieves the "value" found for the option flag specified.
const command_parameter & get(int field) const
Returns the parameter at the "field" specified.
basis::astring text_form() const
returns a string with all the information we have for the command line.
basis::astring gather(int &index) const
coalesces parameters together until the next option flag.
bool zap(int field)
eats the entry at position "field".
command_line(int argc, char *argv[])
takes command line parameters in the form of "argc" and "argv".
const basis::astring & text() const
observes the string contents.
command_parameter(parameter_types type=BOGUS_ITEM)
default constructor initializes to mostly blank state.
parameter_types type() const
observes the type of the parameter.
Definition: command_line.h:69
a_sprintf is a specialization of astring that provides printf style support.
Definition: astring.h:440
Represents a sequential, ordered, contiguous collection of objects.
Definition: array.h:54
const contents & get(int index) const
Accesses individual objects stored in "this" at the "index" position.
Definition: array.h:372
int length() const
Returns the current reported length of the allocated C array.
Definition: array.h:115
outcome zap(int start, int end)
Deletes from "this" the objects inclusively between "start" and "end".
Definition: array.h:769
Provides a dynamically resizable ASCII character string.
Definition: astring.h:35
bool t() const
t() is a shortcut for the string being "true", as in non-empty.
Definition: astring.h:97
virtual char get(int index) const
a constant peek at the string's internals at the specified index.
Definition: astring.cpp:138
bool substring(astring &target, int start, int end) const
a version that stores the substring in an existing "target" string.
Definition: astring.cpp:865
bool iequals(const astring &that) const
returns true if this is case-insensitively equal to "that".
Definition: astring.cpp:565
int end() const
returns the index of the last (non-null) character in the string.
Definition: astring.h:86
int length() const
Returns the current length of the string.
Definition: astring.cpp:132
int find(char to_find, int position=0, bool reverse=false) const
Locates "to_find" in "this".
Definition: astring.cpp:574
void to_lower()
to_lower modifies "this" by replacing capitals with lower-case.
Definition: astring.cpp:528
Implements a scanner that finds all filenames in the directory specified.
Definition: directory.h:27
Provides operations commonly needed on file names.
Definition: filename.h:64
An array of strings with some additional helpful methods.
Definition: string_array.h:32
#define LOG(s)
#define COMPLAIN_CMDS(s)
#define FUNCDEF(func_in)
FUNCDEF sets the name of a function (and plugs it into the callstack).
Definition: enhance_cpp.h:57
#define bounds_return(value, low, high, to_return)
Verifies that "value" is between "low" and "high", inclusive.
Definition: guards.h:48
Implements an application lock to ensure only one is running at once.
bool it_is_a_prefix_char(char to_test)
char ** _global_argv
The guards collection helps in testing preconditions and reporting errors.
Definition: array.h:30
void WHACK(contents *&ptr)
deletion with clearing of the pointer.
Definition: functions.h:121
bool non_negative(const type &a)
non_negative returns true if "a" is greater than or equal to zero.
Definition: functions.h:45
bool negative(const type &a)
negative returns true if "a" is less than zero.
Definition: functions.h:43
A platform independent way to obtain the timestamp of a file.
Definition: byte_filer.cpp:37
A logger that sends to the console screen using the standard output device.
A dynamic container class that holds any kind of object via pointers.
Definition: amorph.h:55
DEFINE_ARGC_AND_ARGV
Definition: sleep_ms.cpp:31
#define SAFE_STATIC_CONST(type, func_name, parms)
this version returns a constant object instead.