1 /*****************************************************************************\
3 * Name : command_line *
4 * Author : Chris Koeritz *
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 \*****************************************************************************/
15 #include "command_line.h"
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>
29 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
31 using namespace basis;
32 using namespace configuration;
33 using namespace filesystem;
34 using namespace loggers;
35 using namespace structures;
36 using namespace textual;
38 namespace application {
42 command_parameter::command_parameter(parameter_types type)
43 : _type(type), _text(new astring) {}
45 command_parameter::command_parameter(parameter_types type, const astring &text)
46 : _type(type), _text(new astring(text)) {}
48 command_parameter::command_parameter(const command_parameter &to_copy)
49 : _type(VALUE), _text(new astring)
52 command_parameter::~command_parameter() { WHACK(_text); }
54 const astring &command_parameter::text() const { return *_text; }
56 void command_parameter::text(const astring &new_text) { *_text = new_text; }
58 command_parameter &command_parameter::operator =
59 (const command_parameter &to_copy)
61 if (this == &to_copy) return *this;
62 _type = to_copy._type;
63 *_text = *to_copy._text;
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' };
74 bool it_is_a_prefix_char(char to_test)
76 for (int i = 0; option_prefixes[i]; i++)
77 if (to_test == option_prefixes[i]) return true;
83 class internal_cmd_line_array_of_parms : public array<command_parameter> {};
87 SAFE_STATIC_CONST(command_parameter, command_line::cmdline_blank_parm, )
88 // our default return for erroneous indices.
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])))
94 argv++; // skip command name in argv.
96 // loop over the rest of the fields and examine them.
97 string_array string_list; // accumulated below.
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.
103 parse_string_array(string_list);
106 command_line::command_line(const astring &full_line)
107 : _implementation(new internal_cmd_line_array_of_parms),
108 _program_name(new filename)
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.
124 continue; // eat the quote character but change modes.
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.
129 to_examine = ' '; // trick parser into logging the accumulated string.
130 // intentional fall-through to space case.
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;
139 } else if (in_quote) {
140 // we're stuffing the spaces into the string since we're quoted.
141 accumulator += to_examine;
144 // not white space, so save it in the accumulator.
145 accumulator += to_examine;
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);
156 command_line::~command_line()
158 WHACK(_program_name);
159 WHACK(_implementation);
162 int command_line::entries() const { return _implementation->length(); }
164 filename command_line::program_name() const { return *_program_name; }
166 const command_parameter &command_line::get(int field) const
168 bounds_return(field, 0, entries() - 1, cmdline_blank_parm());
169 return _implementation->get(field);
172 void command_line::separate_command_line(const astring &cmd_line,
173 astring &app, astring &parms)
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.
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.
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;
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.
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());
217 bool command_line::zap(int field)
219 bounds_return(field, 0, entries() - 1, false);
220 _implementation->zap(field, field);
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"; \
230 string_array command_line::get_command_line()
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.
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();
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];
247 // we don't need a long string to be parsed; the list is ready.
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];
258 COMPLAIN_CMDS("this OS doesn't support getting the command line.");
262 // now that we have our best guess at a flat representation of the command
263 // line arguments, we'll chop it up.
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?
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.
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
286 listo_cmds += temporary.substring(last_posn + 1,
287 temporary.length() - 1);
289 break; // we're done finding spaces.
296 astring command_line::text_form() const
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;
307 case command_parameter::STRING_FLAG:
308 to_return += astring("<string flag> ") + curr.text() + EOL;
310 case command_parameter::VALUE: // pass through to default.
312 to_return += astring("<value> ") + curr.text() + EOL;
319 bool command_line::find(char option_character, int &index,
320 bool case_sense) const
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));
330 // the type is appropriate and the value is correct as well...
339 bool command_line::find(const astring &option_string, int &index,
340 bool case_sense) const
343 if (option_string.length() && (option_string[0] == '-') )
344 LOG(astring("found option string with dash! string is: ") + option_string);
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));
351 // the type is appropriate and the value is correct as well...
360 bool command_line::get_value(char option_character, astring &value,
361 bool case_sense) const
363 FUNCDEF("get_value");
365 int posn = 0; // where we find the flag.
366 if (!find(option_character, posn, case_sense)) return false;
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;
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;
376 // finally; we've found an appropriate text value.
381 bool command_line::get_value(const astring &option_string, astring &value,
382 bool case_sense) const
384 FUNCDEF("get_value");
385 if (option_string.length() && (option_string[0] == '-') )
386 LOG(astring("found option string with dash! string is: ") + option_string);
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);
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);
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;
403 // finally; we've found an appropriate text value.
405 //printf("assigning value %s\n", value.s());
409 void command_line::parse_string_array(const string_array &to_parse)
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.
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.
432 gnu_type_of_flag = true;
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.
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.
445 command_parameter to_add(command_parameter::CHAR_FLAG, astring(c, 1));
446 *_implementation += to_add;
449 // the gnu flag name is added to here.
450 c = to_parse[i].get(index++); // zero at end of string.
452 gnu_accumulator += c; // one more character.
455 if (gnu_accumulator.t()) {
456 // we've accumulated a gnu flag, so store it.
457 command_parameter to_add(command_parameter::STRING_FLAG,
459 *_implementation += to_add;
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;
470 astring command_line::gather(int &index) const
473 for (int i = index; i < entries(); i++) {
474 if (get(i).type() == command_parameter::CHAR_FLAG) {
477 } else to_return += get(i).text();
479 index = entries() - 1;