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 64using std::vector; 65 66// The 'reporting' flags. They all call exit(). 67DEFINE_bool(help, false, 68 "show help on all flags [tip: all flags can have two dashes]"); 69DEFINE_bool(helpfull, false, 70 "show help on all flags -- same as -help"); 71DEFINE_bool(helpshort, false, 72 "show help on only the main module for this program"); 73DEFINE_string(helpon, "", 74 "show help on the modules named by this flag value"); 75DEFINE_string(helpmatch, "", 76 "show help on modules whose name contains the specified substr"); 77DEFINE_bool(helppackage, false, 78 "show help on all modules in the main package"); 79DEFINE_bool(helpxml, false, 80 "produce an xml version of help"); 81DEFINE_bool(version, false, 82 "show version and build info and exit"); 83 84namespace google { 85 86using 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 96static const int kLineLength = 80; 97 98static 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. 114string 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. 186static 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 195static 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 205static 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 233static const char* Basename(const char* filename) { 234 const char* sep = strrchr(filename, PATH_SEPARATOR); 235 return sep ? sep + 1 : filename; 236} 237 238static 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. 244static 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. 268static 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 304void 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 312void ShowUsageWithFlags(const char *argv0) { 313 ShowUsageWithFlagsRestrict(argv0, ""); 314} 315 316// Convert the help, program, and usage to xml. 317static 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 346static 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 356static 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 373void 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