1 // Copyright (c) 2006, Google Inc. 2 // All rights reserved. 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions are 6 // met: 7 // 8 // * Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // * Redistributions in binary form must reproduce the above 11 // copyright notice, this list of conditions and the following disclaimer 12 // in the documentation and/or other materials provided with the 13 // distribution. 14 // * Neither the name of Google Inc. nor the names of its 15 // contributors may be used to endorse or promote products derived from 16 // this software without specific prior written permission. 17 // 18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30 // --- 31 // Author: Ray Sidney 32 // Revamped and reorganized by Craig Silverstein 33 // 34 // This file contains code for handling the 'reporting' flags. These 35 // are flags that, when present, cause the program to report some 36 // information and then exit. --help and --version are the canonical 37 // reporting flags, but we also have flags like --helpxml, etc. 38 // 39 // There's only one function that's meant to be called externally: 40 // HandleCommandLineHelpFlags(). (Well, actually, ShowUsageWithFlags(), 41 // ShowUsageWithFlagsRestrict(), and DescribeOneFlag() can be called 42 // externally too, but there's little need for it.) These are all 43 // declared in the main commandlineflags.h header file. 44 // 45 // HandleCommandLineHelpFlags() will check what 'reporting' flags have 46 // been defined, if any -- the "help" part of the function name is a 47 // bit misleading -- and do the relevant reporting. It should be 48 // called after all flag-values have been assigned, that is, after 49 // parsing the command-line. 50 51 #include "config.h" 52 #include <stdio.h> 53 #include <string.h> 54 #include <ctype.h> 55 #include <assert.h> 56 #include <string> 57 #include <vector> 58 #include "gflags/gflags.h" 59 60 #ifndef PATH_SEPARATOR 61 #define PATH_SEPARATOR '/' 62 #endif 63 64 using std::vector; 65 66 // The 'reporting' flags. They all call exit(). 67 DEFINE_bool(help, false, 68 "show help on all flags [tip: all flags can have two dashes]"); 69 DEFINE_bool(helpfull, false, 70 "show help on all flags -- same as -help"); 71 DEFINE_bool(helpshort, false, 72 "show help on only the main module for this program"); 73 DEFINE_string(helpon, "", 74 "show help on the modules named by this flag value"); 75 DEFINE_string(helpmatch, "", 76 "show help on modules whose name contains the specified substr"); 77 DEFINE_bool(helppackage, false, 78 "show help on all modules in the main package"); 79 DEFINE_bool(helpxml, false, 80 "produce an xml version of help"); 81 DEFINE_bool(version, false, 82 "show version and build info and exit"); 83 84 namespace google { 85 86 using std::string; 87 88 // -------------------------------------------------------------------- 89 // DescribeOneFlag() 90 // DescribeOneFlagInXML() 91 // Routines that pretty-print info about a flag. These use 92 // a CommandLineFlagInfo, which is the way the commandlineflags 93 // API exposes static info about a flag. 94 // -------------------------------------------------------------------- 95 96 static const int kLineLength = 80; 97 98 static void AddString(const string& s, 99 string* final_string, int* chars_in_line) { 100 const int slen = static_cast<int>(s.length()); 101 if (*chars_in_line + 1 + slen >= kLineLength) { // < 80 chars/line 102 *final_string += "\n "; 103 *chars_in_line = 6; 104 } else { 105 *final_string += " "; 106 *chars_in_line += 1; 107 } 108 *final_string += s; 109 *chars_in_line += slen; 110 } 111 112 // Create a descriptive string for a flag. 113 // Goes to some trouble to make pretty line breaks. 114 string DescribeOneFlag(const CommandLineFlagInfo& flag) { 115 string main_part = (string(" -") + flag.name + 116 " (" + flag.description + ')'); 117 const char* c_string = main_part.c_str(); 118 int chars_left = static_cast<int>(main_part.length()); 119 string final_string = ""; 120 int chars_in_line = 0; // how many chars in current line so far? 121 while (1) { 122 assert(chars_left == strlen(c_string)); // Unless there's a \0 in there? 123 const char* newline = strchr(c_string, '\n'); 124 if (newline == NULL && chars_in_line+chars_left < kLineLength) { 125 // The whole remainder of the string fits on this line 126 final_string += c_string; 127 chars_in_line += chars_left; 128 break; 129 } 130 if (newline != NULL && newline - c_string < kLineLength - chars_in_line) { 131 int n = static_cast<int>(newline - c_string); 132 final_string.append(c_string, n); 133 chars_left -= n + 1; 134 c_string += n + 1; 135 } else { 136 // Find the last whitespace on this 80-char line 137 int whitespace = kLineLength-chars_in_line-1; // < 80 chars/line 138 while ( whitespace > 0 && !isspace(c_string[whitespace]) ) { 139 --whitespace; 140 } 141 if (whitespace <= 0) { 142 // Couldn't find any whitespace to make a line break. Just dump the 143 // rest out! 144 final_string += c_string; 145 chars_in_line = kLineLength; // next part gets its own line for sure! 146 break; 147 } 148 final_string += string(c_string, whitespace); 149 chars_in_line += whitespace; 150 while (isspace(c_string[whitespace])) ++whitespace; 151 c_string += whitespace; 152 chars_left -= whitespace; 153 } 154 if (*c_string == '\0') 155 break; 156 final_string += "\n "; 157 chars_in_line = 6; 158 } 159 160 // Append data type 161 AddString(string("type: ") + flag.type, &final_string, &chars_in_line); 162 // Append the effective default value (i.e., the value that the flag 163 // will have after the command line is parsed if the flag is not 164 // specified on the command line), which may be different from the 165 // stored default value. This would happen if the value of the flag 166 // was modified before the command line was parsed. (Unless the 167 // value was modified using SetCommandLineOptionWithMode() with mode 168 // SET_FLAGS_DEFAULT.) 169 // Note that we are assuming this code is being executed because a help 170 // request was just parsed from the command line, in which case the 171 // printed value is indeed the effective default, as long as no value 172 // for the flag was parsed from the command line before "--help". 173 if (strcmp(flag.type.c_str(), "string") == 0) { // add quotes for strings 174 AddString(string("default: \"") + flag.current_value + string("\""), 175 &final_string, &chars_in_line); 176 } else { 177 AddString(string("default: ") + flag.current_value, 178 &final_string, &chars_in_line); 179 } 180 181 final_string += '\n'; 182 return final_string; 183 } 184 185 // Simple routine to xml-escape a string: escape & and < only. 186 static string XMLText(const string& txt) { 187 string ans = txt; 188 for (string::size_type pos = 0; (pos = ans.find("&", pos)) != string::npos; ) 189 ans.replace(pos++, 1, "&"); 190 for (string::size_type pos = 0; (pos = ans.find("<", pos)) != string::npos; ) 191 ans.replace(pos++, 1, "<"); 192 return ans; 193 } 194 195 static void AddXMLTag(string* r, const char* tag, const string& txt) { 196 *r += ('<'); 197 *r += (tag); 198 *r += ('>'); 199 *r += (XMLText(txt)); 200 *r += ("</"); 201 *r += (tag); 202 *r += ('>'); 203 } 204 205 static string DescribeOneFlagInXML(const CommandLineFlagInfo& flag) { 206 // The file and flagname could have been attributes, but default 207 // and meaning need to avoid attribute normalization. This way it 208 // can be parsed by simple programs, in addition to xml parsers. 209 string r("<flag>"); 210 AddXMLTag(&r, "file", flag.filename); 211 AddXMLTag(&r, "name", flag.name); 212 AddXMLTag(&r, "meaning", flag.description); 213 AddXMLTag(&r, "default", flag.default_value); 214 AddXMLTag(&r, "current", flag.current_value); 215 AddXMLTag(&r, "type", flag.type); 216 r += "</flag>"; 217 return r; 218 } 219 220 // -------------------------------------------------------------------- 221 // ShowUsageWithFlags() 222 // ShowUsageWithFlagsRestrict() 223 // ShowXMLOfFlags() 224 // These routines variously expose the registry's list of flag 225 // values. ShowUsage*() prints the flag-value information 226 // to stdout in a user-readable format (that's what --help uses). 227 // The Restrict() version limits what flags are shown. 228 // ShowXMLOfFlags() prints the flag-value information to stdout 229 // in a machine-readable format. In all cases, the flags are 230 // sorted: first by filename they are defined in, then by flagname. 231 // -------------------------------------------------------------------- 232 233 static const char* Basename(const char* filename) { 234 const char* sep = strrchr(filename, PATH_SEPARATOR); 235 return sep ? sep + 1 : filename; 236 } 237 238 static string Dirname(const string& filename) { 239 string::size_type sep = filename.rfind(PATH_SEPARATOR); 240 return filename.substr(0, (sep == string::npos) ? 0 : sep); 241 } 242 243 // Test whether a filename contains at least one of the substrings. 244 static bool FileMatchesSubstring(const string& filename, 245 const vector<string>& substrings) { 246 for (vector<string>::const_iterator target = substrings.begin(); 247 target != substrings.end(); 248 ++target) { 249 if (strstr(filename.c_str(), target->c_str()) != NULL) 250 return true; 251 // If the substring starts with a '/', that means that we want 252 // the string to be at the beginning of a directory component. 253 // That should match the first directory component as well, so 254 // we allow '/foo' to match a filename of 'foo'. 255 if (!target->empty() && (*target)[0] == '/' && 256 strncmp(filename.c_str(), target->c_str() + 1, 257 strlen(target->c_str() + 1)) == 0) 258 return true; 259 } 260 return false; 261 } 262 263 // Show help for every filename which matches any of the target substrings. 264 // If substrings is empty, shows help for every file. If a flag's help message 265 // has been stripped (e.g. by adding '#define STRIP_FLAG_HELP 1' before 266 // including gflags/gflags.h), then this flag will not be displayed by 267 // '--help' and its variants. 268 static void ShowUsageWithFlagsMatching(const char *argv0, 269 const vector<string> &substrings) { 270 fprintf(stdout, "%s: %s\n", Basename(argv0), ProgramUsage()); 271 272 vector<CommandLineFlagInfo> flags; 273 GetAllFlags(&flags); // flags are sorted by filename, then flagname 274 275 string last_filename; // so we know when we're at a new file 276 bool first_directory = true; // controls blank lines between dirs 277 bool found_match = false; // stays false iff no dir matches restrict 278 for (vector<CommandLineFlagInfo>::const_iterator flag = flags.begin(); 279 flag != flags.end(); 280 ++flag) { 281 if (substrings.empty() || 282 FileMatchesSubstring(flag->filename, substrings)) { 283 // If the flag has been stripped, pretend that it doesn't exist. 284 if (flag->description == kStrippedFlagHelp) continue; 285 found_match = true; // this flag passed the match! 286 if (flag->filename != last_filename) { // new file 287 if (Dirname(flag->filename) != Dirname(last_filename)) { // new dir! 288 if (!first_directory) 289 fprintf(stdout, "\n\n"); // put blank lines between directories 290 first_directory = false; 291 } 292 fprintf(stdout, "\n Flags from %s:\n", flag->filename.c_str()); 293 last_filename = flag->filename; 294 } 295 // Now print this flag 296 fprintf(stdout, "%s", DescribeOneFlag(*flag).c_str()); 297 } 298 } 299 if (!found_match && !substrings.empty()) { 300 fprintf(stdout, "\n No modules matched: use -help\n"); 301 } 302 } 303 304 void ShowUsageWithFlagsRestrict(const char *argv0, const char *restrict) { 305 vector<string> substrings; 306 if (restrict != NULL && *restrict != '\0') { 307 substrings.push_back(restrict); 308 } 309 ShowUsageWithFlagsMatching(argv0, substrings); 310 } 311 312 void ShowUsageWithFlags(const char *argv0) { 313 ShowUsageWithFlagsRestrict(argv0, ""); 314 } 315 316 // Convert the help, program, and usage to xml. 317 static void ShowXMLOfFlags(const char *prog_name) { 318 vector<CommandLineFlagInfo> flags; 319 GetAllFlags(&flags); // flags are sorted: by filename, then flagname 320 321 // XML. There is no corresponding schema yet 322 fprintf(stdout, "<?xml version=\"1.0\"?>\n"); 323 // The document 324 fprintf(stdout, "<AllFlags>\n"); 325 // the program name and usage 326 fprintf(stdout, "<program>%s</program>\n", 327 XMLText(Basename(prog_name)).c_str()); 328 fprintf(stdout, "<usage>%s</usage>\n", 329 XMLText(ProgramUsage()).c_str()); 330 // All the flags 331 for (vector<CommandLineFlagInfo>::const_iterator flag = flags.begin(); 332 flag != flags.end(); 333 ++flag) { 334 if (flag->description != kStrippedFlagHelp) 335 fprintf(stdout, "%s\n", DescribeOneFlagInXML(*flag).c_str()); 336 } 337 // The end of the document 338 fprintf(stdout, "</AllFlags>\n"); 339 } 340 341 // -------------------------------------------------------------------- 342 // ShowVersion() 343 // Called upon --version. Prints build-related info. 344 // -------------------------------------------------------------------- 345 346 static void ShowVersion() { 347 fprintf(stdout, "%s\n", ProgramInvocationShortName()); 348 // TODO: add other stuff, like a timestamp, who built it, what 349 // target they built, etc. 350 351 # if !defined(NDEBUG) 352 fprintf(stdout, "Debug build (NDEBUG not #defined)\n"); 353 # endif 354 } 355 356 static void AppendPrognameStrings(vector<string>* substrings, 357 const char* progname) { 358 string r("/"); 359 r += progname; 360 substrings->push_back(r + "."); 361 substrings->push_back(r + "-main."); 362 substrings->push_back(r + "_main."); 363 } 364 365 // -------------------------------------------------------------------- 366 // HandleCommandLineHelpFlags() 367 // Checks all the 'reporting' commandline flags to see if any 368 // have been set. If so, handles them appropriately. Note 369 // that all of them, by definition, cause the program to exit 370 // if they trigger. 371 // -------------------------------------------------------------------- 372 373 void HandleCommandLineHelpFlags() { 374 const char* progname = ProgramInvocationShortName(); 375 extern void (*commandlineflags_exitfunc)(int); // in gflags.cc 376 377 vector<string> substrings; 378 AppendPrognameStrings(&substrings, progname); 379 380 if (FLAGS_helpshort) { 381 // show only flags related to this binary: 382 // E.g. for fileutil.cc, want flags containing ... "/fileutil." cc 383 ShowUsageWithFlagsMatching(progname, substrings); 384 commandlineflags_exitfunc(1); // almost certainly exit() 385 386 } else if (FLAGS_help || FLAGS_helpfull) { 387 // show all options 388 ShowUsageWithFlagsRestrict(progname, ""); // empty restrict 389 commandlineflags_exitfunc(1); 390 391 } else if (!FLAGS_helpon.empty()) { 392 string restrict = "/" + FLAGS_helpon + "."; 393 ShowUsageWithFlagsRestrict(progname, restrict.c_str()); 394 commandlineflags_exitfunc(1); 395 396 } else if (!FLAGS_helpmatch.empty()) { 397 ShowUsageWithFlagsRestrict(progname, FLAGS_helpmatch.c_str()); 398 commandlineflags_exitfunc(1); 399 400 } else if (FLAGS_helppackage) { 401 // Shows help for all files in the same directory as main(). We 402 // don't want to resort to looking at dirname(progname), because 403 // the user can pick progname, and it may not relate to the file 404 // where main() resides. So instead, we search the flags for a 405 // filename like "/progname.cc", and take the dirname of that. 406 vector<CommandLineFlagInfo> flags; 407 GetAllFlags(&flags); 408 string last_package; 409 for (vector<CommandLineFlagInfo>::const_iterator flag = flags.begin(); 410 flag != flags.end(); 411 ++flag) { 412 if (!FileMatchesSubstring(flag->filename, substrings)) 413 continue; 414 const string package = Dirname(flag->filename) + "/"; 415 if (package != last_package) { 416 ShowUsageWithFlagsRestrict(progname, package.c_str()); 417 if (!last_package.empty()) { // means this isn't our first pkg 418 fprintf(stderr, "WARNING: Multiple packages contain a file=%s\n", 419 progname); 420 } 421 last_package = package; 422 } 423 } 424 if (last_package.empty()) { // never found a package to print 425 fprintf(stderr, "WARNING: Unable to find a package for file=%s\n", 426 progname); 427 } 428 commandlineflags_exitfunc(1); 429 430 } else if (FLAGS_helpxml) { 431 ShowXMLOfFlags(progname); 432 commandlineflags_exitfunc(1); 433 434 } else if (FLAGS_version) { 435 ShowVersion(); 436 // Unlike help, we may be asking for version in a script, so return 0 437 commandlineflags_exitfunc(0); 438 } 439 } 440 441 } // namespace google 442