updates to fix bundler on win32
[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 #elif defined(_MSC_VER)
251   // we have easy access to the original list of commands.
252   for (int i = 0; i < _global_argc; i++) {
253     // add a string entry for each argument.
254     listo_cmds += _global_argv[i];
255   }
256   return listo_cmds;
257 #else
258   COMPLAIN_CMDS("this OS doesn't support getting the command line.");
259   return listo_cmds;
260 #endif
261
262   // now that we have our best guess at a flat representation of the command
263   // line arguments, we'll chop it up.
264
265 //hmmm: this algorithm doesn't support spaces in filenames currently.
266 //hmmm: for windows, we can parse the quotes that should be around cmd name.
267 //hmmm: but for unix, the ps command doesn't support spaces either.  how to
268 //      get around that to support programs with spaces in the name?
269   int posn = 0;
270   int last_posn = -1;
271   while (posn < temporary.length()) {
272     posn = temporary.find(' ', posn);
273     if (non_negative(posn)) {
274       // found another space to turn into a portion of the command line.
275       listo_cmds += temporary.substring(last_posn + 1, posn - 1);
276         // grab the piece of string between the point just beyond where we
277         // last saw a space and the position just before the space.
278       last_posn = posn;  // save the last space position.
279       posn++;  // push the pointer past the space.
280     } else {
281       // no more spaces in the string.  grab what we can from the last bit
282       // of the string that we see.
283       if (last_posn < temporary.length() - 1) {
284         // there's something worthwhile grabbing after the last place we
285         // saw a space.
286         listo_cmds += temporary.substring(last_posn + 1,
287             temporary.length() - 1);
288       }
289       break;  // we're done finding spaces.
290     }
291   }
292
293   return listo_cmds;
294 }
295
296 astring command_line::text_form() const
297 {
298   astring to_return;
299   const astring EOL = parser_bits::platform_eol_to_chars();
300   for (int i = 0; i < entries(); i++) {
301     const command_parameter &curr = get(i);
302     to_return += a_sprintf("%d: ", i + 1);
303     switch (curr.type()) {
304       case command_parameter::CHAR_FLAG:
305         to_return += astring("<char flag> ") + curr.text() + EOL;
306         break;
307       case command_parameter::STRING_FLAG:
308         to_return += astring("<string flag> ") + curr.text() + EOL;
309         break;
310       case command_parameter::VALUE:  // pass through to default.
311       default:
312         to_return += astring("<value> ") + curr.text() + EOL;
313         break;
314     }
315   }
316   return to_return;
317 }
318
319 bool command_line::find(char option_character, int &index,
320     bool case_sense) const
321 {
322   astring opt(option_character, 1);  // convert to a string once here.
323   if (!case_sense) opt.to_lower();  // no case-sensitivity.
324   for (int i = index; i < entries(); i++) {
325 //hmmm: optimize this too.
326     if (get(i).type() == command_parameter::CHAR_FLAG) {
327       bool success = (!case_sense && get(i).text().iequals(opt))
328           || (case_sense && (get(i).text() == opt));
329       if (success) {
330         // the type is appropriate and the value is correct as well...
331         index = i;
332         return true;
333       }
334     }
335   }
336   return false;
337 }
338
339 bool command_line::find(const astring &option_string, int &index,
340     bool case_sense) const
341 {
342   FUNCDEF("find");
343 if (option_string.length() && (option_string[0] == '-') )
344 LOG(astring("found option string with dash!  string is: ") + option_string);
345
346   for (int i = index; i < entries(); i++) {
347     if (get(i).type() == command_parameter::STRING_FLAG) {
348       bool success = (!case_sense && get(i).text().iequals(option_string))
349           || (case_sense && (get(i).text() == option_string));
350       if (success) {
351         // the type is appropriate and the value is correct as well...
352         index = i;
353         return true;
354       }
355     }
356   }
357   return false;
358 }
359
360 bool command_line::get_value(char option_character, astring &value,
361     bool case_sense) const
362 {
363   FUNCDEF("get_value");
364   value = "";
365   int posn = 0;  // where we find the flag.
366   if (!find(option_character, posn, case_sense)) return false;
367
368   // get the value after the flag, if there is such.
369   posn++;  // this is where we think our flag's value lives.
370   if (posn >= entries()) return false;
371
372   // there's still an entry after where we found our flag; grab it.
373   command_parameter cp = get(posn);
374   if (cp.type() != command_parameter::VALUE) return false;
375
376   // finally; we've found an appropriate text value.
377   value = cp.text();
378   return true;
379 }
380
381 bool command_line::get_value(const astring &option_string, astring &value,
382     bool case_sense) const
383 {
384   FUNCDEF("get_value");
385 if (option_string.length() && (option_string[0] == '-') )
386 LOG(astring("found option string with dash!  string is: ") + option_string);
387
388   value = "";
389   int posn = 0;  // where we find the flag.
390   if (!find(option_string, posn, case_sense)) return false;
391 //printf("found the flag! at %d\n", posn);
392
393   // get the value after the flag, if there is such.
394   posn++;  // this is where we think our flag's value lives.
395   if (posn >= entries()) return false;
396 //printf("next posn is still okay at %d\n", posn);
397
398   // there's still an entry after where we found our flag; grab it.
399   command_parameter cp = get(posn);
400 //printf("comm parm has text %s\n", cp.text().s());
401   if (cp.type() != command_parameter::VALUE) return false;
402
403   // finally; we've found an appropriate text value.
404   value = cp.text();
405 //printf("assigning value %s\n", value.s());
406   return true;
407 }
408
409 void command_line::parse_string_array(const string_array &to_parse)
410 {
411   bool still_looking_for_flags = true;  // goes to false when only values left.
412   // loop over the fields and examine them.
413   for (int i = 0; i < to_parse.length(); i++) {
414     // retrieve a character from the current string.
415     int index = 0;
416     char c = to_parse[i].get(index++);
417     // we check whether it's a prefix character, and if so, what kind.
418     if (still_looking_for_flags && it_is_a_prefix_char(c)) {
419       // at least one prefix is there, so treat this as a flag.
420       bool gnu_type_of_flag = false;
421       if (it_is_a_prefix_char(to_parse[i].get(index))) {
422         // there's a special GNU double flag beginner.
423         index++;  // skip that extra one.
424         if ( (index >= to_parse[i].length())
425             || parser_bits::white_space(to_parse[i].get(index))) {
426           // special case of '--' (or '//' i suppose) with white space or
427           // nothing else afterwards; indicates that the rest of the items
428           // should just be values, not flags.
429           still_looking_for_flags = false;
430           continue;  // we ate that item.
431         }
432         gnu_type_of_flag = true;
433       }
434       // everything after the prefixes is considered part of the flag; they're
435       // either individual flag characters (on a single prefix) or they're the
436       // full name for the flag (gnu style).
437       c = 1;  // reset to a true bool value.
438       astring gnu_accumulator;  // if processing a gnu flag, it arrives here.
439       while (c) {
440         if (!gnu_type_of_flag) {
441           // add as many flag parameters as possible.
442           c = to_parse[i].get(index++);
443             // c will be zero once we hit the end of the string.
444           if (c) {
445             command_parameter to_add(command_parameter::CHAR_FLAG, astring(c, 1));
446             *_implementation += to_add;
447           }
448         } else {
449           // the gnu flag name is added to here.
450           c = to_parse[i].get(index++);  // zero at end of string.
451           if (c)
452             gnu_accumulator += c;  // one more character.
453         }
454       }
455       if (gnu_accumulator.t()) {
456         // we've accumulated a gnu flag, so store it.
457         command_parameter to_add(command_parameter::STRING_FLAG,
458             gnu_accumulator);
459         *_implementation += to_add;
460       }
461     } else {
462       // add a value type of command_parameter.
463       astring found = to_parse[i];
464       command_parameter to_add(command_parameter::VALUE, found);
465       *_implementation += to_add;
466     }
467   }
468 }
469
470 astring command_line::gather(int &index) const
471 {
472   astring to_return;
473   for (int i = index; i < entries(); i++) {
474     if (get(i).type() == command_parameter::CHAR_FLAG) {
475       index = i;
476       return to_return;
477     } else to_return += get(i).text();
478   }
479   index = entries() - 1;
480   return to_return;
481 }
482
483 } //namespace.
484