f132867f8e500d9005b539fe564c5f575fd706e0
[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 #include <stdio.h>
29 //temp
30
31 #undef LOG
32 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
33
34 using namespace basis;
35 using namespace configuration;
36 using namespace filesystem;
37 using namespace loggers;
38 using namespace structures;
39 using namespace textual;
40
41 namespace application {
42
43 DEFINE_ARGC_AND_ARGV;
44
45 command_parameter::command_parameter(parameter_types type)
46 : _type(type), _text(new astring) {}
47
48 command_parameter::command_parameter(parameter_types type, const astring &text)
49 : _type(type), _text(new astring(text)) {}
50
51 command_parameter::command_parameter(const command_parameter &to_copy)
52 : _type(VALUE), _text(new astring)
53 { *this = to_copy; }
54
55 command_parameter::~command_parameter() { WHACK(_text); }
56
57 const astring &command_parameter::text() const { return *_text; }
58
59 void command_parameter::text(const astring &new_text) { *_text = new_text; }
60
61 command_parameter &command_parameter::operator =
62     (const command_parameter &to_copy)
63 {
64   if (this == &to_copy) return *this;
65   _type = to_copy._type;
66   *_text = *to_copy._text;
67   return *this;
68 }
69
70 //////////////
71
72 // option_prefixes: the list of valid prefixes for options on a command line.
73 // these are the characters that precede command line arguments.  For Unix,
74 // the default is a dash (-), while for DOS most programs use forward-slash
75 // (/).  Adding more characters is trivial; just add a character to the list
76 // before the sentinel of '\0'.
77 #if defined(_MSC_VER) || defined(__MINGW32__) || defined(__WIN32__)
78   static char option_prefixes[] = { '-', '/', '\0' };
79 #elif defined(__UNIX__)
80   static char option_prefixes[] = { '-', '\0' };
81 #else
82   #error "I don't know what kind of operating system this is."
83 #endif
84
85 bool it_is_a_prefix_char(char to_test)
86 {
87   for (int i = 0; option_prefixes[i]; i++)
88     if (to_test == option_prefixes[i]) return true;
89   return false;
90 }
91
92 //////////////
93
94 class internal_cmd_line_array_of_parms : public array<command_parameter> {};
95
96 //////////////
97
98 SAFE_STATIC_CONST(command_parameter, command_line::cmdline_blank_parm, )
99   // our default return for erroneous indices.
100
101 command_line::command_line(int argc, char *argv[])
102 : _implementation(new internal_cmd_line_array_of_parms),
103   _program_name(new filename(directory::absolute_path(argv[0])))
104 {
105   argv++;  // skip command name in argv.
106
107   // loop over the rest of the fields and examine them.
108   string_array string_list;  // accumulated below.
109   while (--argc > 0) {
110     astring to_store = argv[0];  // retrieve the current string.
111     string_list += to_store;  // put the string in our list.
112     argv++;  // next string.
113   }
114   parse_string_array(string_list);
115 }
116
117 command_line::command_line(const astring &full_line)
118 : _implementation(new internal_cmd_line_array_of_parms),
119   _program_name(new filename)
120 {
121   astring accumulator;
122   string_array string_list;
123   bool in_quote = false;
124 //hmmm: this is not quote right yet.
125 //      use the separate command line method, but get it to run iteratively
126 //      so we can keep pulling them apart?  maybe it already does!
127 //      separate is better because it handles escaped quotes.
128   for (int i = 0; i < full_line.length(); i++) {
129     char to_examine = full_line.get(i);
130     if (to_examine == '"') {
131       // it's a quote character, so maybe we can start eating spaces.
132       if (!in_quote) {
133         in_quote = true;
134         continue;  // eat the quote character but change modes.
135       }
136       // nope, we're closing a quote.  we assume that the quotes are
137       // around the whole argument.  that's the best win32 can do at least.
138       in_quote = false;
139       to_examine = ' ';  // trick parser into logging the accumulated string.
140       // intentional fall-through to space case.
141     }
142
143     if (parser_bits::white_space(to_examine)) {
144       // if this is a white space, then we start a new string.
145       if (!in_quote && accumulator.t()) {
146         // only grab the accumulator if there are some contents.
147         string_list += accumulator;
148         accumulator = "";
149       } else if (in_quote) {
150         // we're stuffing the spaces into the string since we're quoted.
151         accumulator += to_examine;
152       }
153     } else {
154       // not white space, so save it in the accumulator.
155       accumulator += to_examine;
156     }
157   }
158   if (accumulator.t()) string_list += accumulator;
159     // that partial string wasn't snarfed during the loop.
160   // grab the program name off the list so the parsing occurs as expected.
161   *_program_name = directory::absolute_path(string_list[0]);
162   string_list.zap(0, 0);
163   parse_string_array(string_list);
164 }
165
166 command_line::~command_line()
167 {
168   WHACK(_program_name);
169   WHACK(_implementation);
170 }
171
172 int command_line::entries() const { return _implementation->length(); }
173
174 filename command_line::program_name() const { return *_program_name; }
175
176 const command_parameter &command_line::get(int field) const
177 {
178   bounds_return(field, 0, entries() - 1, cmdline_blank_parm());
179   return _implementation->get(field);
180 }
181
182 void command_line::separate_command_line(const astring &cmd_line,
183     astring &app, astring &parms)
184 {
185   char to_find = ' ';  // the command separator.
186   if (cmd_line[0] == '\"') to_find = '\"';
187     // if the first character is a quote, then we are seeing a quoted phrase
188     // and need to look for its completing quote.  otherwise, we'll just look
189     // for the next space.
190
191   int seek_posn = 1;  // skip the first character.  we have accounted for it.
192   // skim down the string, looking for the ending of the first phrase.
193   while (seek_posn < cmd_line.length()) {
194     // look for our parameter separator.  this will signify the end of the
195     // first phrase / chunk.  if we don't find it, then it should just mean
196     // there was only one item on the command line.
197     int indy = cmd_line.find(to_find, seek_posn);
198     if (negative(indy)) {
199       // yep, there wasn't a matching separator, so we think this is just
200       // one chunk--the app name.
201       app = cmd_line;
202       break;
203     } else {
204       // now that we know where our separator is, we need to find the right
205       // two parts (app and parms) based on the separator character in use.
206       if (to_find == '\"') {
207         // we are looking for a quote character to complete the app name.
208         if (cmd_line[indy - 1] == '\\') {
209           // we have a backslash escaping this quote!  keep seeking.
210           seek_posn = indy + 1;
211           continue;
212         }
213         app = cmd_line.substring(0, indy);
214         parms = cmd_line.substring(indy + 2, cmd_line.end());
215           // skip the quote and the obligatory space character after it.
216         break;
217       } else {
218         // simple space handling here; no escapes to worry about.
219         app = cmd_line.substring(0, indy - 1);
220         parms = cmd_line.substring(indy + 1, cmd_line.end());
221         break;
222       }
223     }
224   }
225 }
226
227 bool command_line::zap(int field)
228 {
229   bounds_return(field, 0, entries() - 1, false);
230   _implementation->zap(field, field);
231   return true;
232 }
233
234 // makes a complaint about a failure and sets the hidden commands to have a
235 // bogus entry so they aren't queried again.
236 #define COMPLAIN_CMDS(s) \
237   listo_cmds += "unknown"; \
238   COMPLAIN(s)
239
240 string_array command_line::get_command_line()
241 {
242   FUNCDEF("get_command_line");
243   string_array listo_cmds;
244   // the temporary string below can be given a flat formatting of the commands
245   // and it will be popped out into a list of arguments.
246   astring temporary;
247 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
248   if (!_global_argc || !_global_argv) {
249     // our global parameters have not been set, so we must calculate them.
250     temporary = application_configuration::get_cmdline_from_proc();
251   } else {
252     // we have easy access to command line arguments supposedly, so use them.
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     // we don't need a long string to be parsed; the list is ready.
258     return listo_cmds;
259   }
260 #elif defined(_MSC_VER)
261   // we have easy access to the original list of commands.
262   for (int i = 0; i < _global_argc; i++) {
263     // add a string entry for each argument.
264     listo_cmds += _global_argv[i];
265   }
266   return listo_cmds;
267 #else
268   COMPLAIN_CMDS("this OS doesn't support getting the command line.");
269   return listo_cmds;
270 #endif
271
272   // now that we have our best guess at a flat representation of the command
273   // line arguments, we'll chop it up.
274
275 //hmmm: this algorithm doesn't support spaces in filenames currently.
276 //hmmm: for windows, we can parse the quotes that should be around cmd name.
277 //hmmm: but for unix, the ps command doesn't support spaces either.  how to
278 //      get around that to support programs with spaces in the name?
279   int posn = 0;
280   int last_posn = -1;
281   while (posn < temporary.length()) {
282     posn = temporary.find(' ', posn);
283     if (non_negative(posn)) {
284       // found another space to turn into a portion of the command line.
285       listo_cmds += temporary.substring(last_posn + 1, posn - 1);
286         // grab the piece of string between the point just beyond where we
287         // last saw a space and the position just before the space.
288       last_posn = posn;  // save the last space position.
289       posn++;  // push the pointer past the space.
290     } else {
291       // no more spaces in the string.  grab what we can from the last bit
292       // of the string that we see.
293       if (last_posn < temporary.length() - 1) {
294         // there's something worthwhile grabbing after the last place we
295         // saw a space.
296         listo_cmds += temporary.substring(last_posn + 1,
297             temporary.length() - 1);
298       }
299       break;  // we're done finding spaces.
300     }
301   }
302
303   return listo_cmds;
304 }
305
306 astring command_line::text_form() const
307 {
308   astring to_return;
309   const astring EOL = parser_bits::platform_eol_to_chars();
310   for (int i = 0; i < entries(); i++) {
311     const command_parameter &curr = get(i);
312     to_return += a_sprintf("%d: ", i + 1);
313     switch (curr.type()) {
314       case command_parameter::CHAR_FLAG:
315         to_return += astring("<char flag> ") + curr.text() + EOL;
316         break;
317       case command_parameter::STRING_FLAG:
318         to_return += astring("<string flag> ") + curr.text() + EOL;
319         break;
320       case command_parameter::VALUE:  // pass through to default.
321       default:
322         to_return += astring("<value> ") + curr.text() + EOL;
323         break;
324     }
325   }
326   return to_return;
327 }
328
329 bool command_line::find(char option_character, int &index,
330     bool case_sense) const
331 {
332   astring opt(option_character, 1);  // convert to a string once here.
333   if (!case_sense) opt.to_lower();  // no case-sensitivity.
334   for (int i = index; i < entries(); i++) {
335 //hmmm: optimize this too.
336     if (get(i).type() == command_parameter::CHAR_FLAG) {
337       bool success = (!case_sense && get(i).text().iequals(opt))
338           || (case_sense && (get(i).text() == opt));
339       if (success) {
340         // the type is appropriate and the value is correct as well...
341         index = i;
342         return true;
343       }
344     }
345   }
346   return false;
347 }
348
349 bool command_line::find(const astring &option_string, int &index,
350     bool case_sense) const
351 {
352   FUNCDEF("find");
353 if (option_string.length() && (option_string[0] == '-') )
354 LOG(astring("found option string with dash!  string is: ") + option_string);
355
356   for (int i = index; i < entries(); i++) {
357     if (get(i).type() == command_parameter::STRING_FLAG) {
358       bool success = (!case_sense && get(i).text().iequals(option_string))
359           || (case_sense && (get(i).text() == option_string));
360       if (success) {
361         // the type is appropriate and the value is correct as well...
362         index = i;
363         return true;
364       }
365     }
366   }
367   return false;
368 }
369
370 bool command_line::get_value(char option_character, astring &value,
371     bool case_sense) const
372 {
373   FUNCDEF("get_value");
374   value = "";
375   int posn = 0;  // where we find the flag.
376   if (!find(option_character, posn, case_sense)) return false;
377
378   // get the value after the flag, if there is such.
379   posn++;  // this is where we think our flag's value lives.
380   if (posn >= entries()) return false;
381
382   // there's still an entry after where we found our flag; grab it.
383   command_parameter cp = get(posn);
384   if (cp.type() != command_parameter::VALUE) return false;
385
386   // finally; we've found an appropriate text value.
387   value = cp.text();
388   return true;
389 }
390
391 bool command_line::get_value(const astring &option_string, astring &value,
392     bool case_sense) const
393 {
394   FUNCDEF("get_value");
395 if (option_string.length() && (option_string[0] == '-') )
396 LOG(astring("found option string with dash!  string is: ") + option_string);
397
398   value = "";
399   int posn = 0;  // where we find the flag.
400   if (!find(option_string, posn, case_sense)) return false;
401 //printf("found the flag! at %d\n", posn);
402
403   // get the value after the flag, if there is such.
404   posn++;  // this is where we think our flag's value lives.
405   if (posn >= entries()) return false;
406 //printf("next posn is still okay at %d\n", posn);
407
408   // there's still an entry after where we found our flag; grab it.
409   command_parameter cp = get(posn);
410 //printf("comm parm has text %s\n", cp.text().s());
411   if (cp.type() != command_parameter::VALUE) return false;
412
413   // finally; we've found an appropriate text value.
414   value = cp.text();
415 //printf("assigning value %s\n", value.s());
416   return true;
417 }
418
419 void command_line::parse_string_array(const string_array &to_parse)
420 {
421   bool still_looking_for_flags = true;  // goes to false when only values left.
422   // loop over the fields and examine them.
423   for (int i = 0; i < to_parse.length(); i++) {
424     // retrieve a character from the current string.
425     int index = 0;
426     char c = to_parse[i].get(index++);
427     // we check whether it's a prefix character, and if so, what kind.
428     if (still_looking_for_flags && it_is_a_prefix_char(c)) {
429       // at least one prefix is there, so treat this as a flag.
430       bool gnu_type_of_flag = false;
431       if (it_is_a_prefix_char(to_parse[i].get(index))) {
432         // there's a special GNU double flag beginner.
433         index++;  // skip that extra one.
434         if ( (index >= to_parse[i].length())
435             || parser_bits::white_space(to_parse[i].get(index))) {
436           // special case of '--' (or '//' i suppose) with white space or
437           // nothing else afterwards; indicates that the rest of the items
438           // should just be values, not flags.
439           still_looking_for_flags = false;
440           continue;  // we ate that item.
441         }
442         gnu_type_of_flag = true;
443       }
444       // everything after the prefixes is considered part of the flag; they're
445       // either individual flag characters (on a single prefix) or they're the
446       // full name for the flag (gnu style).
447       c = 1;  // reset to a true bool value.
448       astring gnu_accumulator;  // if processing a gnu flag, it arrives here.
449       while (c) {
450         if (!gnu_type_of_flag) {
451           // add as many flag parameters as possible.
452           c = to_parse[i].get(index++);
453             // c will be zero once we hit the end of the string.
454           if (c) {
455             command_parameter to_add(command_parameter::CHAR_FLAG, astring(c, 1));
456             *_implementation += to_add;
457           }
458         } else {
459           // the gnu flag name is added to here.
460           c = to_parse[i].get(index++);  // zero at end of string.
461           if (c)
462             gnu_accumulator += c;  // one more character.
463         }
464       }
465       if (gnu_accumulator.t()) {
466         // we've accumulated a gnu flag, so store it.
467         command_parameter to_add(command_parameter::STRING_FLAG,
468             gnu_accumulator);
469         *_implementation += to_add;
470       }
471     } else {
472       // add a value type of command_parameter.
473       astring found = to_parse[i];
474       command_parameter to_add(command_parameter::VALUE, found);
475       *_implementation += to_add;
476     }
477   }
478 }
479
480 astring command_line::gather(int &index) const
481 {
482   astring to_return;
483   for (int i = index; i < entries(); i++) {
484     if (get(i).type() == command_parameter::CHAR_FLAG) {
485       index = i;
486       return to_return;
487     } else to_return += get(i).text();
488   }
489   index = entries() - 1;
490   return to_return;
491 }
492
493 } //namespace.
494