checking in the recent efforts at optimizing clam
[feisty_meow.git] / nucleus / library / application / command_line.cpp
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>
20 #include <configuration/application_configuration.h>
21 #include <filesystem/directory.h>
22 #include <filesystem/filename.h>
23 #include <structures/static_memory_gremlin.h>
24 #include <structures/string_array.h>
25 #include <textual/parser_bits.h>
26 #include <loggers/program_wide_logger.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
40 DEFINE_ARGC_AND_ARGV;
41
42 command_parameter::command_parameter(parameter_types type)
43 : _type(type), _text(new astring) {}
44
45 command_parameter::command_parameter(parameter_types type, const astring &text)
46 : _type(type), _text(new astring(text)) {}
47
48 command_parameter::command_parameter(const command_parameter &to_copy)
49 : _type(VALUE), _text(new astring)
50 { *this = to_copy; }
51
52 command_parameter::~command_parameter() { WHACK(_text); }
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
67 //////////////
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
81 //////////////
82
83 class internal_cmd_line_array_of_parms : public array<command_parameter> {};
84
85 //////////////
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
106 command_line::command_line(const astring &full_line)
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
156 command_line::~command_line()
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
172 void command_line::separate_command_line(const astring &cmd_line,
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
230 string_array command_line::get_command_line()
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
298 astring command_line::text_form() const
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()) {
306       case command_parameter::CHAR_FLAG:
307         to_return += astring("<char flag> ") + curr.text() + EOL;
308         break;
309       case command_parameter::STRING_FLAG:
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++) {
349     if (get(i).type() == command_parameter::STRING_FLAG) {
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