From ea6144e243c106544ec068fc08ef14fb1e1644c4 Mon Sep 17 00:00:00 2001 From: Andreas Schuh Date: Fri, 25 Nov 2016 12:35:39 +0000 Subject: enh: Rename private sources, include public headers with rel path --- CMakeLists.txt | 4 +- src/completions.cc | 769 +++++++++++++++++++++++++++++++++++++++++++++ src/gflags.cc | 2 +- src/gflags.h.in | 2 +- src/gflags_completions.cc | 771 ---------------------------------------------- src/gflags_reporting.cc | 442 -------------------------- src/mutex.h | 2 +- src/reporting.cc | 442 ++++++++++++++++++++++++++ src/util.h | 1 + 9 files changed, 1217 insertions(+), 1218 deletions(-) create mode 100644 src/completions.cc delete mode 100644 src/gflags_completions.cc delete mode 100644 src/gflags_reporting.cc create mode 100644 src/reporting.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fd9d70..ca7c147 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -350,8 +350,8 @@ set (PRIVATE_HDRS set (GFLAGS_SRCS "gflags.cc" - "gflags_reporting.cc" - "gflags_completions.cc" + "reporting.cc" + "completions.cc" ) if (OS_WINDOWS) diff --git a/src/completions.cc b/src/completions.cc new file mode 100644 index 0000000..875088d --- /dev/null +++ b/src/completions.cc @@ -0,0 +1,769 @@ +// Copyright (c) 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// --- + +// Bash-style command line flag completion for C++ binaries +// +// This module implements bash-style completions. It achieves this +// goal in the following broad chunks: +// +// 1) Take a to-be-completed word, and examine it for search hints +// 2) Identify all potentially matching flags +// 2a) If there are no matching flags, do nothing. +// 2b) If all matching flags share a common prefix longer than the +// completion word, output just that matching prefix +// 3) Categorize those flags to produce a rough ordering of relevence. +// 4) Potentially trim the set of flags returned to a smaller number +// that bash is happier with +// 5) Output the matching flags in groups ordered by relevence. +// 5a) Force bash to place most-relevent groups at the top of the list +// 5b) Trim most flag's descriptions to fit on a single terminal line + +#include +#include +#include // for strlen + +#include +#include +#include +#include + +#include "config.h" +#include "util.h" + +#include "gflags/gflags.h" +#include "gflags/gflags_completions.h" + +using std::set; +using std::string; +using std::vector; + + +DEFINE_string(tab_completion_word, "", + "If non-empty, HandleCommandLineCompletions() will hijack the " + "process and attempt to do bash-style command line flag " + "completion on this value."); +DEFINE_int32(tab_completion_columns, 80, + "Number of columns to use in output for tab completion"); + + +namespace GFLAGS_NAMESPACE { + + +namespace { +// Function prototypes and Type forward declarations. Code may be +// more easily understood if it is roughly ordered according to +// control flow, rather than by C's "declare before use" ordering +struct CompletionOptions; +struct NotableFlags; + +// The entry point if flag completion is to be used. +static void PrintFlagCompletionInfo(void); + + +// 1) Examine search word +static void CanonicalizeCursorWordAndSearchOptions( + const string &cursor_word, + string *canonical_search_token, + CompletionOptions *options); + +static bool RemoveTrailingChar(string *str, char c); + + +// 2) Find all matches +static void FindMatchingFlags( + const vector &all_flags, + const CompletionOptions &options, + const string &match_token, + set *all_matches, + string *longest_common_prefix); + +static bool DoesSingleFlagMatch( + const CommandLineFlagInfo &flag, + const CompletionOptions &options, + const string &match_token); + + +// 3) Categorize matches +static void CategorizeAllMatchingFlags( + const set &all_matches, + const string &search_token, + const string &module, + const string &package_dir, + NotableFlags *notable_flags); + +static void TryFindModuleAndPackageDir( + const vector &all_flags, + string *module, + string *package_dir); + + +// 4) Decide which flags to use +static void FinalizeCompletionOutput( + const set &matching_flags, + CompletionOptions *options, + NotableFlags *notable_flags, + vector *completions); + +static void RetrieveUnusedFlags( + const set &matching_flags, + const NotableFlags ¬able_flags, + set *unused_flags); + + +// 5) Output matches +static void OutputSingleGroupWithLimit( + const set &group, + const string &line_indentation, + const string &header, + const string &footer, + bool long_output_format, + int *remaining_line_limit, + size_t *completion_elements_added, + vector *completions); + +// (helpers for #5) +static string GetShortFlagLine( + const string &line_indentation, + const CommandLineFlagInfo &info); + +static string GetLongFlagLine( + const string &line_indentation, + const CommandLineFlagInfo &info); + + +// +// Useful types + +// Try to deduce the intentions behind this completion attempt. Return the +// canonical search term in 'canonical_search_token'. Binary search options +// are returned in the various booleans, which should all have intuitive +// semantics, possibly except: +// - return_all_matching_flags: Generally, we'll trim the number of +// returned candidates to some small number, showing those that are +// most likely to be useful first. If this is set, however, the user +// really does want us to return every single flag as an option. +// - force_no_update: Any time we output lines, all of which share a +// common prefix, bash will 'helpfully' not even bother to show the +// output, instead changing the current word to be that common prefix. +// If it's clear this shouldn't happen, we'll set this boolean +struct CompletionOptions { + bool flag_name_substring_search; + bool flag_location_substring_search; + bool flag_description_substring_search; + bool return_all_matching_flags; + bool force_no_update; +}; + +// Notable flags are flags that are special or preferred for some +// reason. For example, flags that are defined in the binary's module +// are expected to be much more relevent than flags defined in some +// other random location. These sets are specified roughly in precedence +// order. Once a flag is placed in one of these 'higher' sets, it won't +// be placed in any of the 'lower' sets. +struct NotableFlags { + typedef set FlagSet; + FlagSet perfect_match_flag; + FlagSet module_flags; // Found in module file + FlagSet package_flags; // Found in same directory as module file + FlagSet most_common_flags; // One of the XXX most commonly supplied flags + FlagSet subpackage_flags; // Found in subdirectories of package +}; + + +// +// Tab completion implementation - entry point +static void PrintFlagCompletionInfo(void) { + string cursor_word = FLAGS_tab_completion_word; + string canonical_token; + CompletionOptions options = { }; + CanonicalizeCursorWordAndSearchOptions( + cursor_word, + &canonical_token, + &options); + + DVLOG(1) << "Identified canonical_token: '" << canonical_token << "'"; + + vector all_flags; + set matching_flags; + GetAllFlags(&all_flags); + DVLOG(2) << "Found " << all_flags.size() << " flags overall"; + + string longest_common_prefix; + FindMatchingFlags( + all_flags, + options, + canonical_token, + &matching_flags, + &longest_common_prefix); + DVLOG(1) << "Identified " << matching_flags.size() << " matching flags"; + DVLOG(1) << "Identified " << longest_common_prefix + << " as longest common prefix."; + if (longest_common_prefix.size() > canonical_token.size()) { + // There's actually a shared common prefix to all matching flags, + // so may as well output that and quit quickly. + DVLOG(1) << "The common prefix '" << longest_common_prefix + << "' was longer than the token '" << canonical_token + << "'. Returning just this prefix for completion."; + fprintf(stdout, "--%s", longest_common_prefix.c_str()); + return; + } + if (matching_flags.empty()) { + VLOG(1) << "There were no matching flags, returning nothing."; + return; + } + + string module; + string package_dir; + TryFindModuleAndPackageDir(all_flags, &module, &package_dir); + DVLOG(1) << "Identified module: '" << module << "'"; + DVLOG(1) << "Identified package_dir: '" << package_dir << "'"; + + NotableFlags notable_flags; + CategorizeAllMatchingFlags( + matching_flags, + canonical_token, + module, + package_dir, + ¬able_flags); + DVLOG(2) << "Categorized matching flags:"; + DVLOG(2) << " perfect_match: " << notable_flags.perfect_match_flag.size(); + DVLOG(2) << " module: " << notable_flags.module_flags.size(); + DVLOG(2) << " package: " << notable_flags.package_flags.size(); + DVLOG(2) << " most common: " << notable_flags.most_common_flags.size(); + DVLOG(2) << " subpackage: " << notable_flags.subpackage_flags.size(); + + vector completions; + FinalizeCompletionOutput( + matching_flags, + &options, + ¬able_flags, + &completions); + + if (options.force_no_update) + completions.push_back("~"); + + DVLOG(1) << "Finalized with " << completions.size() + << " chosen completions"; + + for (vector::const_iterator it = completions.begin(); + it != completions.end(); + ++it) { + DVLOG(9) << " Completion entry: '" << *it << "'"; + fprintf(stdout, "%s\n", it->c_str()); + } +} + + +// 1) Examine search word (and helper method) +static void CanonicalizeCursorWordAndSearchOptions( + const string &cursor_word, + string *canonical_search_token, + CompletionOptions *options) { + *canonical_search_token = cursor_word; + if (canonical_search_token->empty()) return; + + // Get rid of leading quotes and dashes in the search term + if ((*canonical_search_token)[0] == '"') + *canonical_search_token = canonical_search_token->substr(1); + while ((*canonical_search_token)[0] == '-') + *canonical_search_token = canonical_search_token->substr(1); + + options->flag_name_substring_search = false; + options->flag_location_substring_search = false; + options->flag_description_substring_search = false; + options->return_all_matching_flags = false; + options->force_no_update = false; + + // Look for all search options we can deduce now. Do this by walking + // backwards through the term, looking for up to three '?' and up to + // one '+' as suffixed characters. Consume them if found, and remove + // them from the canonical search token. + int found_question_marks = 0; + int found_plusses = 0; + while (true) { + if (found_question_marks < 3 && + RemoveTrailingChar(canonical_search_token, '?')) { + ++found_question_marks; + continue; + } + if (found_plusses < 1 && + RemoveTrailingChar(canonical_search_token, '+')) { + ++found_plusses; + continue; + } + break; + } + + switch (found_question_marks) { // all fallthroughs + case 3: options->flag_description_substring_search = true; + case 2: options->flag_location_substring_search = true; + case 1: options->flag_name_substring_search = true; + }; + + options->return_all_matching_flags = (found_plusses > 0); +} + +// Returns true if a char was removed +static bool RemoveTrailingChar(string *str, char c) { + if (str->empty()) return false; + if ((*str)[str->size() - 1] == c) { + *str = str->substr(0, str->size() - 1); + return true; + } + return false; +} + + +// 2) Find all matches (and helper methods) +static void FindMatchingFlags( + const vector &all_flags, + const CompletionOptions &options, + const string &match_token, + set *all_matches, + string *longest_common_prefix) { + all_matches->clear(); + bool first_match = true; + for (vector::const_iterator it = all_flags.begin(); + it != all_flags.end(); + ++it) { + if (DoesSingleFlagMatch(*it, options, match_token)) { + all_matches->insert(&*it); + if (first_match) { + first_match = false; + *longest_common_prefix = it->name; + } else { + if (longest_common_prefix->empty() || it->name.empty()) { + longest_common_prefix->clear(); + continue; + } + string::size_type pos = 0; + while (pos < longest_common_prefix->size() && + pos < it->name.size() && + (*longest_common_prefix)[pos] == it->name[pos]) + ++pos; + longest_common_prefix->erase(pos); + } + } + } +} + +// Given the set of all flags, the parsed match options, and the +// canonical search token, produce the set of all candidate matching +// flags for subsequent analysis or filtering. +static bool DoesSingleFlagMatch( + const CommandLineFlagInfo &flag, + const CompletionOptions &options, + const string &match_token) { + // Is there a prefix match? + string::size_type pos = flag.name.find(match_token); + if (pos == 0) return true; + + // Is there a substring match if we want it? + if (options.flag_name_substring_search && + pos != string::npos) + return true; + + // Is there a location match if we want it? + if (options.flag_location_substring_search && + flag.filename.find(match_token) != string::npos) + return true; + + // TODO(user): All searches should probably be case-insensitive + // (especially this one...) + if (options.flag_description_substring_search && + flag.description.find(match_token) != string::npos) + return true; + + return false; +} + +// 3) Categorize matches (and helper method) + +// Given a set of matching flags, categorize them by +// likely relevence to this specific binary +static void CategorizeAllMatchingFlags( + const set &all_matches, + const string &search_token, + const string &module, // empty if we couldn't find any + const string &package_dir, // empty if we couldn't find any + NotableFlags *notable_flags) { + notable_flags->perfect_match_flag.clear(); + notable_flags->module_flags.clear(); + notable_flags->package_flags.clear(); + notable_flags->most_common_flags.clear(); + notable_flags->subpackage_flags.clear(); + + for (set::const_iterator it = + all_matches.begin(); + it != all_matches.end(); + ++it) { + DVLOG(2) << "Examining match '" << (*it)->name << "'"; + DVLOG(7) << " filename: '" << (*it)->filename << "'"; + string::size_type pos = string::npos; + if (!package_dir.empty()) + pos = (*it)->filename.find(package_dir); + string::size_type slash = string::npos; + if (pos != string::npos) // candidate for package or subpackage match + slash = (*it)->filename.find( + PATH_SEPARATOR, + pos + package_dir.size() + 1); + + if ((*it)->name == search_token) { + // Exact match on some flag's name + notable_flags->perfect_match_flag.insert(*it); + DVLOG(3) << "Result: perfect match"; + } else if (!module.empty() && (*it)->filename == module) { + // Exact match on module filename + notable_flags->module_flags.insert(*it); + DVLOG(3) << "Result: module match"; + } else if (!package_dir.empty() && + pos != string::npos && slash == string::npos) { + // In the package, since there was no slash after the package portion + notable_flags->package_flags.insert(*it); + DVLOG(3) << "Result: package match"; + } else if (false) { + // In the list of the XXX most commonly supplied flags overall + // TODO(user): Compile this list. + DVLOG(3) << "Result: most-common match"; + } else if (!package_dir.empty() && + pos != string::npos && slash != string::npos) { + // In a subdirectory of the package + notable_flags->subpackage_flags.insert(*it); + DVLOG(3) << "Result: subpackage match"; + } + + DVLOG(3) << "Result: not special match"; + } +} + +static void PushNameWithSuffix(vector* suffixes, const char* suffix) { + suffixes->push_back( + StringPrintf("/%s%s", ProgramInvocationShortName(), suffix)); +} + +static void TryFindModuleAndPackageDir( + const vector &all_flags, + string *module, + string *package_dir) { + module->clear(); + package_dir->clear(); + + vector suffixes; + // TODO(user): There's some inherant ambiguity here - multiple directories + // could share the same trailing folder and file structure (and even worse, + // same file names), causing us to be unsure as to which of the two is the + // actual package for this binary. In this case, we'll arbitrarily choose. + PushNameWithSuffix(&suffixes, "."); + PushNameWithSuffix(&suffixes, "-main."); + PushNameWithSuffix(&suffixes, "_main."); + // These four are new but probably merited? + PushNameWithSuffix(&suffixes, "-test."); + PushNameWithSuffix(&suffixes, "_test."); + PushNameWithSuffix(&suffixes, "-unittest."); + PushNameWithSuffix(&suffixes, "_unittest."); + + for (vector::const_iterator it = all_flags.begin(); + it != all_flags.end(); + ++it) { + for (vector::const_iterator suffix = suffixes.begin(); + suffix != suffixes.end(); + ++suffix) { + // TODO(user): Make sure the match is near the end of the string + if (it->filename.find(*suffix) != string::npos) { + *module = it->filename; + string::size_type sep = it->filename.rfind(PATH_SEPARATOR); + *package_dir = it->filename.substr(0, (sep == string::npos) ? 0 : sep); + return; + } + } + } +} + +// Can't specialize template type on a locally defined type. Silly C++... +struct DisplayInfoGroup { + const char* header; + const char* footer; + set *group; + + int SizeInLines() const { + int size_in_lines = static_cast(group->size()) + 1; + if (strlen(header) > 0) { + size_in_lines++; + } + if (strlen(footer) > 0) { + size_in_lines++; + } + return size_in_lines; + } +}; + +// 4) Finalize and trim output flag set +static void FinalizeCompletionOutput( + const set &matching_flags, + CompletionOptions *options, + NotableFlags *notable_flags, + vector *completions) { + + // We want to output lines in groups. Each group needs to be indented + // the same to keep its lines together. Unless otherwise required, + // only 99 lines should be output to prevent bash from harassing the + // user. + + // First, figure out which output groups we'll actually use. For each + // nonempty group, there will be ~3 lines of header & footer, plus all + // output lines themselves. + int max_desired_lines = // "999999 flags should be enough for anyone. -dave" + (options->return_all_matching_flags ? 999999 : 98); + int lines_so_far = 0; + + vector output_groups; + bool perfect_match_found = false; + if (lines_so_far < max_desired_lines && + !notable_flags->perfect_match_flag.empty()) { + perfect_match_found = true; + DisplayInfoGroup group = + { "", + "==========", + ¬able_flags->perfect_match_flag }; + lines_so_far += group.SizeInLines(); + output_groups.push_back(group); + } + if (lines_so_far < max_desired_lines && + !notable_flags->module_flags.empty()) { + DisplayInfoGroup group = { + "-* Matching module flags *-", + "===========================", + ¬able_flags->module_flags }; + lines_so_far += group.SizeInLines(); + output_groups.push_back(group); + } + if (lines_so_far < max_desired_lines && + !notable_flags->package_flags.empty()) { + DisplayInfoGroup group = { + "-* Matching package flags *-", + "============================", + ¬able_flags->package_flags }; + lines_so_far += group.SizeInLines(); + output_groups.push_back(group); + } + if (lines_so_far < max_desired_lines && + !notable_flags->most_common_flags.empty()) { + DisplayInfoGroup group = { + "-* Commonly used flags *-", + "=========================", + ¬able_flags->most_common_flags }; + lines_so_far += group.SizeInLines(); + output_groups.push_back(group); + } + if (lines_so_far < max_desired_lines && + !notable_flags->subpackage_flags.empty()) { + DisplayInfoGroup group = { + "-* Matching sub-package flags *-", + "================================", + ¬able_flags->subpackage_flags }; + lines_so_far += group.SizeInLines(); + output_groups.push_back(group); + } + + set obscure_flags; // flags not notable + if (lines_so_far < max_desired_lines) { + RetrieveUnusedFlags(matching_flags, *notable_flags, &obscure_flags); + if (!obscure_flags.empty()) { + DisplayInfoGroup group = { + "-* Other flags *-", + "", + &obscure_flags }; + lines_so_far += group.SizeInLines(); + output_groups.push_back(group); + } + } + + // Second, go through each of the chosen output groups and output + // as many of those flags as we can, while remaining below our limit + int remaining_lines = max_desired_lines; + size_t completions_output = 0; + int indent = static_cast(output_groups.size()) - 1; + for (vector::const_iterator it = + output_groups.begin(); + it != output_groups.end(); + ++it, --indent) { + OutputSingleGroupWithLimit( + *it->group, // group + string(indent, ' '), // line indentation + string(it->header), // header + string(it->footer), // footer + perfect_match_found, // long format + &remaining_lines, // line limit - reduces this by number printed + &completions_output, // completions (not lines) added + completions); // produced completions + perfect_match_found = false; + } + + if (completions_output != matching_flags.size()) { + options->force_no_update = false; + completions->push_back("~ (Remaining flags hidden) ~"); + } else { + options->force_no_update = true; + } +} + +static void RetrieveUnusedFlags( + const set &matching_flags, + const NotableFlags ¬able_flags, + set *unused_flags) { + // Remove from 'matching_flags' set all members of the sets of + // flags we've already printed (specifically, those in notable_flags) + for (set::const_iterator it = + matching_flags.begin(); + it != matching_flags.end(); + ++it) { + if (notable_flags.perfect_match_flag.count(*it) || + notable_flags.module_flags.count(*it) || + notable_flags.package_flags.count(*it) || + notable_flags.most_common_flags.count(*it) || + notable_flags.subpackage_flags.count(*it)) + continue; + unused_flags->insert(*it); + } +} + +// 5) Output matches (and helper methods) + +static void OutputSingleGroupWithLimit( + const set &group, + const string &line_indentation, + const string &header, + const string &footer, + bool long_output_format, + int *remaining_line_limit, + size_t *completion_elements_output, + vector *completions) { + if (group.empty()) return; + if (!header.empty()) { + if (*remaining_line_limit < 2) return; + *remaining_line_limit -= 2; + completions->push_back(line_indentation + header); + completions->push_back(line_indentation + string(header.size(), '-')); + } + for (set::const_iterator it = group.begin(); + it != group.end() && *remaining_line_limit > 0; + ++it) { + --*remaining_line_limit; + ++*completion_elements_output; + completions->push_back( + (long_output_format + ? GetLongFlagLine(line_indentation, **it) + : GetShortFlagLine(line_indentation, **it))); + } + if (!footer.empty()) { + if (*remaining_line_limit < 1) return; + --*remaining_line_limit; + completions->push_back(line_indentation + footer); + } +} + +static string GetShortFlagLine( + const string &line_indentation, + const CommandLineFlagInfo &info) { + string prefix; + bool is_string = (info.type == "string"); + SStringPrintf(&prefix, "%s--%s [%s%s%s] ", + line_indentation.c_str(), + info.name.c_str(), + (is_string ? "'" : ""), + info.default_value.c_str(), + (is_string ? "'" : "")); + int remainder = + FLAGS_tab_completion_columns - static_cast(prefix.size()); + string suffix; + if (remainder > 0) + suffix = + (static_cast(info.description.size()) > remainder ? + (info.description.substr(0, remainder - 3) + "...").c_str() : + info.description.c_str()); + return prefix + suffix; +} + +static string GetLongFlagLine( + const string &line_indentation, + const CommandLineFlagInfo &info) { + + string output = DescribeOneFlag(info); + + // Replace '-' with '--', and remove trailing newline before appending + // the module definition location. + string old_flagname = "-" + info.name; + output.replace( + output.find(old_flagname), + old_flagname.size(), + "-" + old_flagname); + // Stick a newline and indentation in front of the type and default + // portions of DescribeOneFlag()s description + static const char kNewlineWithIndent[] = "\n "; + output.replace(output.find(" type:"), 1, string(kNewlineWithIndent)); + output.replace(output.find(" default:"), 1, string(kNewlineWithIndent)); + output = StringPrintf("%s Details for '--%s':\n" + "%s defined: %s", + line_indentation.c_str(), + info.name.c_str(), + output.c_str(), + info.filename.c_str()); + + // Eliminate any doubled newlines that crept in. Specifically, if + // DescribeOneFlag() decided to break the line just before "type" + // or "default", we don't want to introduce an extra blank line + static const string line_of_spaces(FLAGS_tab_completion_columns, ' '); + static const char kDoubledNewlines[] = "\n \n"; + for (string::size_type newlines = output.find(kDoubledNewlines); + newlines != string::npos; + newlines = output.find(kDoubledNewlines)) + // Replace each 'doubled newline' with a single newline + output.replace(newlines, sizeof(kDoubledNewlines) - 1, string("\n")); + + for (string::size_type newline = output.find('\n'); + newline != string::npos; + newline = output.find('\n')) { + int newline_pos = static_cast(newline) % FLAGS_tab_completion_columns; + int missing_spaces = FLAGS_tab_completion_columns - newline_pos; + output.replace(newline, 1, line_of_spaces, 1, missing_spaces); + } + return output; +} +} // anonymous + +void HandleCommandLineCompletions(void) { + if (FLAGS_tab_completion_word.empty()) return; + PrintFlagCompletionInfo(); + gflags_exitfunc(0); +} + + +} // namespace GFLAGS_NAMESPACE diff --git a/src/gflags.cc b/src/gflags.cc index 42dcd04..bc62227 100644 --- a/src/gflags.cc +++ b/src/gflags.cc @@ -88,7 +88,7 @@ // are, similarly, mostly hooks into the functionality described above. #include "config.h" -#include "gflags.h" +#include "gflags/gflags.h" #include #include diff --git a/src/gflags.h.in b/src/gflags.h.in index 3732636..43b3f7a 100644 --- a/src/gflags.h.in +++ b/src/gflags.h.in @@ -81,7 +81,7 @@ #include #include -#include "gflags_declare.h" // IWYU pragma: export +#include "gflags/gflags_declare.h" // IWYU pragma: export // We always want to export variables defined in user code diff --git a/src/gflags_completions.cc b/src/gflags_completions.cc deleted file mode 100644 index 8fefa1b..0000000 --- a/src/gflags_completions.cc +++ /dev/null @@ -1,771 +0,0 @@ -// Copyright (c) 2008, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// --- - -// Bash-style command line flag completion for C++ binaries -// -// This module implements bash-style completions. It achieves this -// goal in the following broad chunks: -// -// 1) Take a to-be-completed word, and examine it for search hints -// 2) Identify all potentially matching flags -// 2a) If there are no matching flags, do nothing. -// 2b) If all matching flags share a common prefix longer than the -// completion word, output just that matching prefix -// 3) Categorize those flags to produce a rough ordering of relevence. -// 4) Potentially trim the set of flags returned to a smaller number -// that bash is happier with -// 5) Output the matching flags in groups ordered by relevence. -// 5a) Force bash to place most-relevent groups at the top of the list -// 5b) Trim most flag's descriptions to fit on a single terminal line - - -#include "gflags_completions.h" - -#include "config.h" - -#include -#include -#include // for strlen - -#include -#include -#include -#include - -#include "gflags.h" -#include "util.h" - -using std::set; -using std::string; -using std::vector; - - -DEFINE_string(tab_completion_word, "", - "If non-empty, HandleCommandLineCompletions() will hijack the " - "process and attempt to do bash-style command line flag " - "completion on this value."); -DEFINE_int32(tab_completion_columns, 80, - "Number of columns to use in output for tab completion"); - - -namespace GFLAGS_NAMESPACE { - - -namespace { -// Function prototypes and Type forward declarations. Code may be -// more easily understood if it is roughly ordered according to -// control flow, rather than by C's "declare before use" ordering -struct CompletionOptions; -struct NotableFlags; - -// The entry point if flag completion is to be used. -static void PrintFlagCompletionInfo(void); - - -// 1) Examine search word -static void CanonicalizeCursorWordAndSearchOptions( - const string &cursor_word, - string *canonical_search_token, - CompletionOptions *options); - -static bool RemoveTrailingChar(string *str, char c); - - -// 2) Find all matches -static void FindMatchingFlags( - const vector &all_flags, - const CompletionOptions &options, - const string &match_token, - set *all_matches, - string *longest_common_prefix); - -static bool DoesSingleFlagMatch( - const CommandLineFlagInfo &flag, - const CompletionOptions &options, - const string &match_token); - - -// 3) Categorize matches -static void CategorizeAllMatchingFlags( - const set &all_matches, - const string &search_token, - const string &module, - const string &package_dir, - NotableFlags *notable_flags); - -static void TryFindModuleAndPackageDir( - const vector &all_flags, - string *module, - string *package_dir); - - -// 4) Decide which flags to use -static void FinalizeCompletionOutput( - const set &matching_flags, - CompletionOptions *options, - NotableFlags *notable_flags, - vector *completions); - -static void RetrieveUnusedFlags( - const set &matching_flags, - const NotableFlags ¬able_flags, - set *unused_flags); - - -// 5) Output matches -static void OutputSingleGroupWithLimit( - const set &group, - const string &line_indentation, - const string &header, - const string &footer, - bool long_output_format, - int *remaining_line_limit, - size_t *completion_elements_added, - vector *completions); - -// (helpers for #5) -static string GetShortFlagLine( - const string &line_indentation, - const CommandLineFlagInfo &info); - -static string GetLongFlagLine( - const string &line_indentation, - const CommandLineFlagInfo &info); - - -// -// Useful types - -// Try to deduce the intentions behind this completion attempt. Return the -// canonical search term in 'canonical_search_token'. Binary search options -// are returned in the various booleans, which should all have intuitive -// semantics, possibly except: -// - return_all_matching_flags: Generally, we'll trim the number of -// returned candidates to some small number, showing those that are -// most likely to be useful first. If this is set, however, the user -// really does want us to return every single flag as an option. -// - force_no_update: Any time we output lines, all of which share a -// common prefix, bash will 'helpfully' not even bother to show the -// output, instead changing the current word to be that common prefix. -// If it's clear this shouldn't happen, we'll set this boolean -struct CompletionOptions { - bool flag_name_substring_search; - bool flag_location_substring_search; - bool flag_description_substring_search; - bool return_all_matching_flags; - bool force_no_update; -}; - -// Notable flags are flags that are special or preferred for some -// reason. For example, flags that are defined in the binary's module -// are expected to be much more relevent than flags defined in some -// other random location. These sets are specified roughly in precedence -// order. Once a flag is placed in one of these 'higher' sets, it won't -// be placed in any of the 'lower' sets. -struct NotableFlags { - typedef set FlagSet; - FlagSet perfect_match_flag; - FlagSet module_flags; // Found in module file - FlagSet package_flags; // Found in same directory as module file - FlagSet most_common_flags; // One of the XXX most commonly supplied flags - FlagSet subpackage_flags; // Found in subdirectories of package -}; - - -// -// Tab completion implementation - entry point -static void PrintFlagCompletionInfo(void) { - string cursor_word = FLAGS_tab_completion_word; - string canonical_token; - CompletionOptions options = { }; - CanonicalizeCursorWordAndSearchOptions( - cursor_word, - &canonical_token, - &options); - - DVLOG(1) << "Identified canonical_token: '" << canonical_token << "'"; - - vector all_flags; - set matching_flags; - GetAllFlags(&all_flags); - DVLOG(2) << "Found " << all_flags.size() << " flags overall"; - - string longest_common_prefix; - FindMatchingFlags( - all_flags, - options, - canonical_token, - &matching_flags, - &longest_common_prefix); - DVLOG(1) << "Identified " << matching_flags.size() << " matching flags"; - DVLOG(1) << "Identified " << longest_common_prefix - << " as longest common prefix."; - if (longest_common_prefix.size() > canonical_token.size()) { - // There's actually a shared common prefix to all matching flags, - // so may as well output that and quit quickly. - DVLOG(1) << "The common prefix '" << longest_common_prefix - << "' was longer than the token '" << canonical_token - << "'. Returning just this prefix for completion."; - fprintf(stdout, "--%s", longest_common_prefix.c_str()); - return; - } - if (matching_flags.empty()) { - VLOG(1) << "There were no matching flags, returning nothing."; - return; - } - - string module; - string package_dir; - TryFindModuleAndPackageDir(all_flags, &module, &package_dir); - DVLOG(1) << "Identified module: '" << module << "'"; - DVLOG(1) << "Identified package_dir: '" << package_dir << "'"; - - NotableFlags notable_flags; - CategorizeAllMatchingFlags( - matching_flags, - canonical_token, - module, - package_dir, - ¬able_flags); - DVLOG(2) << "Categorized matching flags:"; - DVLOG(2) << " perfect_match: " << notable_flags.perfect_match_flag.size(); - DVLOG(2) << " module: " << notable_flags.module_flags.size(); - DVLOG(2) << " package: " << notable_flags.package_flags.size(); - DVLOG(2) << " most common: " << notable_flags.most_common_flags.size(); - DVLOG(2) << " subpackage: " << notable_flags.subpackage_flags.size(); - - vector completions; - FinalizeCompletionOutput( - matching_flags, - &options, - ¬able_flags, - &completions); - - if (options.force_no_update) - completions.push_back("~"); - - DVLOG(1) << "Finalized with " << completions.size() - << " chosen completions"; - - for (vector::const_iterator it = completions.begin(); - it != completions.end(); - ++it) { - DVLOG(9) << " Completion entry: '" << *it << "'"; - fprintf(stdout, "%s\n", it->c_str()); - } -} - - -// 1) Examine search word (and helper method) -static void CanonicalizeCursorWordAndSearchOptions( - const string &cursor_word, - string *canonical_search_token, - CompletionOptions *options) { - *canonical_search_token = cursor_word; - if (canonical_search_token->empty()) return; - - // Get rid of leading quotes and dashes in the search term - if ((*canonical_search_token)[0] == '"') - *canonical_search_token = canonical_search_token->substr(1); - while ((*canonical_search_token)[0] == '-') - *canonical_search_token = canonical_search_token->substr(1); - - options->flag_name_substring_search = false; - options->flag_location_substring_search = false; - options->flag_description_substring_search = false; - options->return_all_matching_flags = false; - options->force_no_update = false; - - // Look for all search options we can deduce now. Do this by walking - // backwards through the term, looking for up to three '?' and up to - // one '+' as suffixed characters. Consume them if found, and remove - // them from the canonical search token. - int found_question_marks = 0; - int found_plusses = 0; - while (true) { - if (found_question_marks < 3 && - RemoveTrailingChar(canonical_search_token, '?')) { - ++found_question_marks; - continue; - } - if (found_plusses < 1 && - RemoveTrailingChar(canonical_search_token, '+')) { - ++found_plusses; - continue; - } - break; - } - - switch (found_question_marks) { // all fallthroughs - case 3: options->flag_description_substring_search = true; - case 2: options->flag_location_substring_search = true; - case 1: options->flag_name_substring_search = true; - }; - - options->return_all_matching_flags = (found_plusses > 0); -} - -// Returns true if a char was removed -static bool RemoveTrailingChar(string *str, char c) { - if (str->empty()) return false; - if ((*str)[str->size() - 1] == c) { - *str = str->substr(0, str->size() - 1); - return true; - } - return false; -} - - -// 2) Find all matches (and helper methods) -static void FindMatchingFlags( - const vector &all_flags, - const CompletionOptions &options, - const string &match_token, - set *all_matches, - string *longest_common_prefix) { - all_matches->clear(); - bool first_match = true; - for (vector::const_iterator it = all_flags.begin(); - it != all_flags.end(); - ++it) { - if (DoesSingleFlagMatch(*it, options, match_token)) { - all_matches->insert(&*it); - if (first_match) { - first_match = false; - *longest_common_prefix = it->name; - } else { - if (longest_common_prefix->empty() || it->name.empty()) { - longest_common_prefix->clear(); - continue; - } - string::size_type pos = 0; - while (pos < longest_common_prefix->size() && - pos < it->name.size() && - (*longest_common_prefix)[pos] == it->name[pos]) - ++pos; - longest_common_prefix->erase(pos); - } - } - } -} - -// Given the set of all flags, the parsed match options, and the -// canonical search token, produce the set of all candidate matching -// flags for subsequent analysis or filtering. -static bool DoesSingleFlagMatch( - const CommandLineFlagInfo &flag, - const CompletionOptions &options, - const string &match_token) { - // Is there a prefix match? - string::size_type pos = flag.name.find(match_token); - if (pos == 0) return true; - - // Is there a substring match if we want it? - if (options.flag_name_substring_search && - pos != string::npos) - return true; - - // Is there a location match if we want it? - if (options.flag_location_substring_search && - flag.filename.find(match_token) != string::npos) - return true; - - // TODO(user): All searches should probably be case-insensitive - // (especially this one...) - if (options.flag_description_substring_search && - flag.description.find(match_token) != string::npos) - return true; - - return false; -} - -// 3) Categorize matches (and helper method) - -// Given a set of matching flags, categorize them by -// likely relevence to this specific binary -static void CategorizeAllMatchingFlags( - const set &all_matches, - const string &search_token, - const string &module, // empty if we couldn't find any - const string &package_dir, // empty if we couldn't find any - NotableFlags *notable_flags) { - notable_flags->perfect_match_flag.clear(); - notable_flags->module_flags.clear(); - notable_flags->package_flags.clear(); - notable_flags->most_common_flags.clear(); - notable_flags->subpackage_flags.clear(); - - for (set::const_iterator it = - all_matches.begin(); - it != all_matches.end(); - ++it) { - DVLOG(2) << "Examining match '" << (*it)->name << "'"; - DVLOG(7) << " filename: '" << (*it)->filename << "'"; - string::size_type pos = string::npos; - if (!package_dir.empty()) - pos = (*it)->filename.find(package_dir); - string::size_type slash = string::npos; - if (pos != string::npos) // candidate for package or subpackage match - slash = (*it)->filename.find( - PATH_SEPARATOR, - pos + package_dir.size() + 1); - - if ((*it)->name == search_token) { - // Exact match on some flag's name - notable_flags->perfect_match_flag.insert(*it); - DVLOG(3) << "Result: perfect match"; - } else if (!module.empty() && (*it)->filename == module) { - // Exact match on module filename - notable_flags->module_flags.insert(*it); - DVLOG(3) << "Result: module match"; - } else if (!package_dir.empty() && - pos != string::npos && slash == string::npos) { - // In the package, since there was no slash after the package portion - notable_flags->package_flags.insert(*it); - DVLOG(3) << "Result: package match"; - } else if (false) { - // In the list of the XXX most commonly supplied flags overall - // TODO(user): Compile this list. - DVLOG(3) << "Result: most-common match"; - } else if (!package_dir.empty() && - pos != string::npos && slash != string::npos) { - // In a subdirectory of the package - notable_flags->subpackage_flags.insert(*it); - DVLOG(3) << "Result: subpackage match"; - } - - DVLOG(3) << "Result: not special match"; - } -} - -static void PushNameWithSuffix(vector* suffixes, const char* suffix) { - suffixes->push_back( - StringPrintf("/%s%s", ProgramInvocationShortName(), suffix)); -} - -static void TryFindModuleAndPackageDir( - const vector &all_flags, - string *module, - string *package_dir) { - module->clear(); - package_dir->clear(); - - vector suffixes; - // TODO(user): There's some inherant ambiguity here - multiple directories - // could share the same trailing folder and file structure (and even worse, - // same file names), causing us to be unsure as to which of the two is the - // actual package for this binary. In this case, we'll arbitrarily choose. - PushNameWithSuffix(&suffixes, "."); - PushNameWithSuffix(&suffixes, "-main."); - PushNameWithSuffix(&suffixes, "_main."); - // These four are new but probably merited? - PushNameWithSuffix(&suffixes, "-test."); - PushNameWithSuffix(&suffixes, "_test."); - PushNameWithSuffix(&suffixes, "-unittest."); - PushNameWithSuffix(&suffixes, "_unittest."); - - for (vector::const_iterator it = all_flags.begin(); - it != all_flags.end(); - ++it) { - for (vector::const_iterator suffix = suffixes.begin(); - suffix != suffixes.end(); - ++suffix) { - // TODO(user): Make sure the match is near the end of the string - if (it->filename.find(*suffix) != string::npos) { - *module = it->filename; - string::size_type sep = it->filename.rfind(PATH_SEPARATOR); - *package_dir = it->filename.substr(0, (sep == string::npos) ? 0 : sep); - return; - } - } - } -} - -// Can't specialize template type on a locally defined type. Silly C++... -struct DisplayInfoGroup { - const char* header; - const char* footer; - set *group; - - int SizeInLines() const { - int size_in_lines = static_cast(group->size()) + 1; - if (strlen(header) > 0) { - size_in_lines++; - } - if (strlen(footer) > 0) { - size_in_lines++; - } - return size_in_lines; - } -}; - -// 4) Finalize and trim output flag set -static void FinalizeCompletionOutput( - const set &matching_flags, - CompletionOptions *options, - NotableFlags *notable_flags, - vector *completions) { - - // We want to output lines in groups. Each group needs to be indented - // the same to keep its lines together. Unless otherwise required, - // only 99 lines should be output to prevent bash from harassing the - // user. - - // First, figure out which output groups we'll actually use. For each - // nonempty group, there will be ~3 lines of header & footer, plus all - // output lines themselves. - int max_desired_lines = // "999999 flags should be enough for anyone. -dave" - (options->return_all_matching_flags ? 999999 : 98); - int lines_so_far = 0; - - vector output_groups; - bool perfect_match_found = false; - if (lines_so_far < max_desired_lines && - !notable_flags->perfect_match_flag.empty()) { - perfect_match_found = true; - DisplayInfoGroup group = - { "", - "==========", - ¬able_flags->perfect_match_flag }; - lines_so_far += group.SizeInLines(); - output_groups.push_back(group); - } - if (lines_so_far < max_desired_lines && - !notable_flags->module_flags.empty()) { - DisplayInfoGroup group = { - "-* Matching module flags *-", - "===========================", - ¬able_flags->module_flags }; - lines_so_far += group.SizeInLines(); - output_groups.push_back(group); - } - if (lines_so_far < max_desired_lines && - !notable_flags->package_flags.empty()) { - DisplayInfoGroup group = { - "-* Matching package flags *-", - "============================", - ¬able_flags->package_flags }; - lines_so_far += group.SizeInLines(); - output_groups.push_back(group); - } - if (lines_so_far < max_desired_lines && - !notable_flags->most_common_flags.empty()) { - DisplayInfoGroup group = { - "-* Commonly used flags *-", - "=========================", - ¬able_flags->most_common_flags }; - lines_so_far += group.SizeInLines(); - output_groups.push_back(group); - } - if (lines_so_far < max_desired_lines && - !notable_flags->subpackage_flags.empty()) { - DisplayInfoGroup group = { - "-* Matching sub-package flags *-", - "================================", - ¬able_flags->subpackage_flags }; - lines_so_far += group.SizeInLines(); - output_groups.push_back(group); - } - - set obscure_flags; // flags not notable - if (lines_so_far < max_desired_lines) { - RetrieveUnusedFlags(matching_flags, *notable_flags, &obscure_flags); - if (!obscure_flags.empty()) { - DisplayInfoGroup group = { - "-* Other flags *-", - "", - &obscure_flags }; - lines_so_far += group.SizeInLines(); - output_groups.push_back(group); - } - } - - // Second, go through each of the chosen output groups and output - // as many of those flags as we can, while remaining below our limit - int remaining_lines = max_desired_lines; - size_t completions_output = 0; - int indent = static_cast(output_groups.size()) - 1; - for (vector::const_iterator it = - output_groups.begin(); - it != output_groups.end(); - ++it, --indent) { - OutputSingleGroupWithLimit( - *it->group, // group - string(indent, ' '), // line indentation - string(it->header), // header - string(it->footer), // footer - perfect_match_found, // long format - &remaining_lines, // line limit - reduces this by number printed - &completions_output, // completions (not lines) added - completions); // produced completions - perfect_match_found = false; - } - - if (completions_output != matching_flags.size()) { - options->force_no_update = false; - completions->push_back("~ (Remaining flags hidden) ~"); - } else { - options->force_no_update = true; - } -} - -static void RetrieveUnusedFlags( - const set &matching_flags, - const NotableFlags ¬able_flags, - set *unused_flags) { - // Remove from 'matching_flags' set all members of the sets of - // flags we've already printed (specifically, those in notable_flags) - for (set::const_iterator it = - matching_flags.begin(); - it != matching_flags.end(); - ++it) { - if (notable_flags.perfect_match_flag.count(*it) || - notable_flags.module_flags.count(*it) || - notable_flags.package_flags.count(*it) || - notable_flags.most_common_flags.count(*it) || - notable_flags.subpackage_flags.count(*it)) - continue; - unused_flags->insert(*it); - } -} - -// 5) Output matches (and helper methods) - -static void OutputSingleGroupWithLimit( - const set &group, - const string &line_indentation, - const string &header, - const string &footer, - bool long_output_format, - int *remaining_line_limit, - size_t *completion_elements_output, - vector *completions) { - if (group.empty()) return; - if (!header.empty()) { - if (*remaining_line_limit < 2) return; - *remaining_line_limit -= 2; - completions->push_back(line_indentation + header); - completions->push_back(line_indentation + string(header.size(), '-')); - } - for (set::const_iterator it = group.begin(); - it != group.end() && *remaining_line_limit > 0; - ++it) { - --*remaining_line_limit; - ++*completion_elements_output; - completions->push_back( - (long_output_format - ? GetLongFlagLine(line_indentation, **it) - : GetShortFlagLine(line_indentation, **it))); - } - if (!footer.empty()) { - if (*remaining_line_limit < 1) return; - --*remaining_line_limit; - completions->push_back(line_indentation + footer); - } -} - -static string GetShortFlagLine( - const string &line_indentation, - const CommandLineFlagInfo &info) { - string prefix; - bool is_string = (info.type == "string"); - SStringPrintf(&prefix, "%s--%s [%s%s%s] ", - line_indentation.c_str(), - info.name.c_str(), - (is_string ? "'" : ""), - info.default_value.c_str(), - (is_string ? "'" : "")); - int remainder = - FLAGS_tab_completion_columns - static_cast(prefix.size()); - string suffix; - if (remainder > 0) - suffix = - (static_cast(info.description.size()) > remainder ? - (info.description.substr(0, remainder - 3) + "...").c_str() : - info.description.c_str()); - return prefix + suffix; -} - -static string GetLongFlagLine( - const string &line_indentation, - const CommandLineFlagInfo &info) { - - string output = DescribeOneFlag(info); - - // Replace '-' with '--', and remove trailing newline before appending - // the module definition location. - string old_flagname = "-" + info.name; - output.replace( - output.find(old_flagname), - old_flagname.size(), - "-" + old_flagname); - // Stick a newline and indentation in front of the type and default - // portions of DescribeOneFlag()s description - static const char kNewlineWithIndent[] = "\n "; - output.replace(output.find(" type:"), 1, string(kNewlineWithIndent)); - output.replace(output.find(" default:"), 1, string(kNewlineWithIndent)); - output = StringPrintf("%s Details for '--%s':\n" - "%s defined: %s", - line_indentation.c_str(), - info.name.c_str(), - output.c_str(), - info.filename.c_str()); - - // Eliminate any doubled newlines that crept in. Specifically, if - // DescribeOneFlag() decided to break the line just before "type" - // or "default", we don't want to introduce an extra blank line - static const string line_of_spaces(FLAGS_tab_completion_columns, ' '); - static const char kDoubledNewlines[] = "\n \n"; - for (string::size_type newlines = output.find(kDoubledNewlines); - newlines != string::npos; - newlines = output.find(kDoubledNewlines)) - // Replace each 'doubled newline' with a single newline - output.replace(newlines, sizeof(kDoubledNewlines) - 1, string("\n")); - - for (string::size_type newline = output.find('\n'); - newline != string::npos; - newline = output.find('\n')) { - int newline_pos = static_cast(newline) % FLAGS_tab_completion_columns; - int missing_spaces = FLAGS_tab_completion_columns - newline_pos; - output.replace(newline, 1, line_of_spaces, 1, missing_spaces); - } - return output; -} -} // anonymous - -void HandleCommandLineCompletions(void) { - if (FLAGS_tab_completion_word.empty()) return; - PrintFlagCompletionInfo(); - gflags_exitfunc(0); -} - - -} // namespace GFLAGS_NAMESPACE diff --git a/src/gflags_reporting.cc b/src/gflags_reporting.cc deleted file mode 100644 index fb17059..0000000 --- a/src/gflags_reporting.cc +++ /dev/null @@ -1,442 +0,0 @@ -// Copyright (c) 1999, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// --- -// -// Revamped and reorganized by Craig Silverstein -// -// This file contains code for handling the 'reporting' flags. These -// are flags that, when present, cause the program to report some -// information and then exit. --help and --version are the canonical -// reporting flags, but we also have flags like --helpxml, etc. -// -// There's only one function that's meant to be called externally: -// HandleCommandLineHelpFlags(). (Well, actually, ShowUsageWithFlags(), -// ShowUsageWithFlagsRestrict(), and DescribeOneFlag() can be called -// externally too, but there's little need for it.) These are all -// declared in the main gflags.h header file. -// -// HandleCommandLineHelpFlags() will check what 'reporting' flags have -// been defined, if any -- the "help" part of the function name is a -// bit misleading -- and do the relevant reporting. It should be -// called after all flag-values have been assigned, that is, after -// parsing the command-line. - -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "gflags.h" -#include "gflags_completions.h" -#include "util.h" - - -// The 'reporting' flags. They all call gflags_exitfunc(). -DEFINE_bool (help, false, "show help on all flags [tip: all flags can have two dashes]"); -DEFINE_bool (helpfull, false, "show help on all flags -- same as -help"); -DEFINE_bool (helpshort, false, "show help on only the main module for this program"); -DEFINE_string(helpon, "", "show help on the modules named by this flag value"); -DEFINE_string(helpmatch, "", "show help on modules whose name contains the specified substr"); -DEFINE_bool (helppackage, false, "show help on all modules in the main package"); -DEFINE_bool (helpxml, false, "produce an xml version of help"); -DEFINE_bool (version, false, "show version and build info and exit"); - - -namespace GFLAGS_NAMESPACE { - - -using std::string; -using std::vector; - - -// -------------------------------------------------------------------- -// DescribeOneFlag() -// DescribeOneFlagInXML() -// Routines that pretty-print info about a flag. These use -// a CommandLineFlagInfo, which is the way the gflags -// API exposes static info about a flag. -// -------------------------------------------------------------------- - -static const int kLineLength = 80; - -static void AddString(const string& s, - string* final_string, int* chars_in_line) { - const int slen = static_cast(s.length()); - if (*chars_in_line + 1 + slen >= kLineLength) { // < 80 chars/line - *final_string += "\n "; - *chars_in_line = 6; - } else { - *final_string += " "; - *chars_in_line += 1; - } - *final_string += s; - *chars_in_line += slen; -} - -static string PrintStringFlagsWithQuotes(const CommandLineFlagInfo& flag, - const string& text, bool current) { - const char* c_string = (current ? flag.current_value.c_str() : - flag.default_value.c_str()); - if (strcmp(flag.type.c_str(), "string") == 0) { // add quotes for strings - return StringPrintf("%s: \"%s\"", text.c_str(), c_string); - } else { - return StringPrintf("%s: %s", text.c_str(), c_string); - } -} - -// Create a descriptive string for a flag. -// Goes to some trouble to make pretty line breaks. -string DescribeOneFlag(const CommandLineFlagInfo& flag) { - string main_part; - SStringPrintf(&main_part, " -%s (%s)", - flag.name.c_str(), - flag.description.c_str()); - const char* c_string = main_part.c_str(); - int chars_left = static_cast(main_part.length()); - string final_string = ""; - int chars_in_line = 0; // how many chars in current line so far? - while (1) { - assert(static_cast(chars_left) - == strlen(c_string)); // Unless there's a \0 in there? - const char* newline = strchr(c_string, '\n'); - if (newline == NULL && chars_in_line+chars_left < kLineLength) { - // The whole remainder of the string fits on this line - final_string += c_string; - chars_in_line += chars_left; - break; - } - if (newline != NULL && newline - c_string < kLineLength - chars_in_line) { - int n = static_cast(newline - c_string); - final_string.append(c_string, n); - chars_left -= n + 1; - c_string += n + 1; - } else { - // Find the last whitespace on this 80-char line - int whitespace = kLineLength-chars_in_line-1; // < 80 chars/line - while ( whitespace > 0 && !isspace(c_string[whitespace]) ) { - --whitespace; - } - if (whitespace <= 0) { - // Couldn't find any whitespace to make a line break. Just dump the - // rest out! - final_string += c_string; - chars_in_line = kLineLength; // next part gets its own line for sure! - break; - } - final_string += string(c_string, whitespace); - chars_in_line += whitespace; - while (isspace(c_string[whitespace])) ++whitespace; - c_string += whitespace; - chars_left -= whitespace; - } - if (*c_string == '\0') - break; - StringAppendF(&final_string, "\n "); - chars_in_line = 6; - } - - // Append data type - AddString(string("type: ") + flag.type, &final_string, &chars_in_line); - // The listed default value will be the actual default from the flag - // definition in the originating source file, unless the value has - // subsequently been modified using SetCommandLineOptionWithMode() with mode - // SET_FLAGS_DEFAULT, or by setting FLAGS_foo = bar before ParseCommandLineFlags(). - AddString(PrintStringFlagsWithQuotes(flag, "default", false), &final_string, - &chars_in_line); - if (!flag.is_default) { - AddString(PrintStringFlagsWithQuotes(flag, "currently", true), - &final_string, &chars_in_line); - } - - StringAppendF(&final_string, "\n"); - return final_string; -} - -// Simple routine to xml-escape a string: escape & and < only. -static string XMLText(const string& txt) { - string ans = txt; - for (string::size_type pos = 0; (pos = ans.find("&", pos)) != string::npos; ) - ans.replace(pos++, 1, "&"); - for (string::size_type pos = 0; (pos = ans.find("<", pos)) != string::npos; ) - ans.replace(pos++, 1, "<"); - return ans; -} - -static void AddXMLTag(string* r, const char* tag, const string& txt) { - StringAppendF(r, "<%s>%s", tag, XMLText(txt).c_str(), tag); -} - - -static string DescribeOneFlagInXML(const CommandLineFlagInfo& flag) { - // The file and flagname could have been attributes, but default - // and meaning need to avoid attribute normalization. This way it - // can be parsed by simple programs, in addition to xml parsers. - string r(""); - AddXMLTag(&r, "file", flag.filename); - AddXMLTag(&r, "name", flag.name); - AddXMLTag(&r, "meaning", flag.description); - AddXMLTag(&r, "default", flag.default_value); - AddXMLTag(&r, "current", flag.current_value); - AddXMLTag(&r, "type", flag.type); - r += ""; - return r; -} - -// -------------------------------------------------------------------- -// ShowUsageWithFlags() -// ShowUsageWithFlagsRestrict() -// ShowXMLOfFlags() -// These routines variously expose the registry's list of flag -// values. ShowUsage*() prints the flag-value information -// to stdout in a user-readable format (that's what --help uses). -// The Restrict() version limits what flags are shown. -// ShowXMLOfFlags() prints the flag-value information to stdout -// in a machine-readable format. In all cases, the flags are -// sorted: first by filename they are defined in, then by flagname. -// -------------------------------------------------------------------- - -static const char* Basename(const char* filename) { - const char* sep = strrchr(filename, PATH_SEPARATOR); - return sep ? sep + 1 : filename; -} - -static string Dirname(const string& filename) { - string::size_type sep = filename.rfind(PATH_SEPARATOR); - return filename.substr(0, (sep == string::npos) ? 0 : sep); -} - -// Test whether a filename contains at least one of the substrings. -static bool FileMatchesSubstring(const string& filename, - const vector& substrings) { - for (vector::const_iterator target = substrings.begin(); - target != substrings.end(); - ++target) { - if (strstr(filename.c_str(), target->c_str()) != NULL) - return true; - // If the substring starts with a '/', that means that we want - // the string to be at the beginning of a directory component. - // That should match the first directory component as well, so - // we allow '/foo' to match a filename of 'foo'. - if (!target->empty() && (*target)[0] == PATH_SEPARATOR && - strncmp(filename.c_str(), target->c_str() + 1, - strlen(target->c_str() + 1)) == 0) - return true; - } - return false; -} - -// Show help for every filename which matches any of the target substrings. -// If substrings is empty, shows help for every file. If a flag's help message -// has been stripped (e.g. by adding '#define STRIP_FLAG_HELP 1' -// before including gflags/gflags.h), then this flag will not be displayed -// by '--help' and its variants. -static void ShowUsageWithFlagsMatching(const char *argv0, - const vector &substrings) { - fprintf(stdout, "%s: %s\n", Basename(argv0), ProgramUsage()); - - vector flags; - GetAllFlags(&flags); // flags are sorted by filename, then flagname - - string last_filename; // so we know when we're at a new file - bool first_directory = true; // controls blank lines between dirs - bool found_match = false; // stays false iff no dir matches restrict - for (vector::const_iterator flag = flags.begin(); - flag != flags.end(); - ++flag) { - if (substrings.empty() || - FileMatchesSubstring(flag->filename, substrings)) { - // If the flag has been stripped, pretend that it doesn't exist. - if (flag->description == kStrippedFlagHelp) continue; - found_match = true; // this flag passed the match! - if (flag->filename != last_filename) { // new file - if (Dirname(flag->filename) != Dirname(last_filename)) { // new dir! - if (!first_directory) - fprintf(stdout, "\n\n"); // put blank lines between directories - first_directory = false; - } - fprintf(stdout, "\n Flags from %s:\n", flag->filename.c_str()); - last_filename = flag->filename; - } - // Now print this flag - fprintf(stdout, "%s", DescribeOneFlag(*flag).c_str()); - } - } - if (!found_match && !substrings.empty()) { - fprintf(stdout, "\n No modules matched: use -help\n"); - } -} - -void ShowUsageWithFlagsRestrict(const char *argv0, const char *restrict) { - vector substrings; - if (restrict != NULL && *restrict != '\0') { - substrings.push_back(restrict); - } - ShowUsageWithFlagsMatching(argv0, substrings); -} - -void ShowUsageWithFlags(const char *argv0) { - ShowUsageWithFlagsRestrict(argv0, ""); -} - -// Convert the help, program, and usage to xml. -static void ShowXMLOfFlags(const char *prog_name) { - vector flags; - GetAllFlags(&flags); // flags are sorted: by filename, then flagname - - // XML. There is no corresponding schema yet - fprintf(stdout, "\n"); - // The document - fprintf(stdout, "\n"); - // the program name and usage - fprintf(stdout, "%s\n", - XMLText(Basename(prog_name)).c_str()); - fprintf(stdout, "%s\n", - XMLText(ProgramUsage()).c_str()); - // All the flags - for (vector::const_iterator flag = flags.begin(); - flag != flags.end(); - ++flag) { - if (flag->description != kStrippedFlagHelp) - fprintf(stdout, "%s\n", DescribeOneFlagInXML(*flag).c_str()); - } - // The end of the document - fprintf(stdout, "\n"); -} - -// -------------------------------------------------------------------- -// ShowVersion() -// Called upon --version. Prints build-related info. -// -------------------------------------------------------------------- - -static void ShowVersion() { - const char* version_string = VersionString(); - if (version_string && *version_string) { - fprintf(stdout, "%s version %s\n", - ProgramInvocationShortName(), version_string); - } else { - fprintf(stdout, "%s\n", ProgramInvocationShortName()); - } -# if !defined(NDEBUG) - fprintf(stdout, "Debug build (NDEBUG not #defined)\n"); -# endif -} - -static void AppendPrognameStrings(vector* substrings, - const char* progname) { - string r(""); - r += PATH_SEPARATOR; - r += progname; - substrings->push_back(r + "."); - substrings->push_back(r + "-main."); - substrings->push_back(r + "_main."); -} - -// -------------------------------------------------------------------- -// HandleCommandLineHelpFlags() -// Checks all the 'reporting' commandline flags to see if any -// have been set. If so, handles them appropriately. Note -// that all of them, by definition, cause the program to exit -// if they trigger. -// -------------------------------------------------------------------- - -void HandleCommandLineHelpFlags() { - const char* progname = ProgramInvocationShortName(); - - HandleCommandLineCompletions(); - - vector substrings; - AppendPrognameStrings(&substrings, progname); - - if (FLAGS_helpshort) { - // show only flags related to this binary: - // E.g. for fileutil.cc, want flags containing ... "/fileutil." cc - ShowUsageWithFlagsMatching(progname, substrings); - gflags_exitfunc(1); - - } else if (FLAGS_help || FLAGS_helpfull) { - // show all options - ShowUsageWithFlagsRestrict(progname, ""); // empty restrict - gflags_exitfunc(1); - - } else if (!FLAGS_helpon.empty()) { - string restrict = PATH_SEPARATOR + FLAGS_helpon + "."; - ShowUsageWithFlagsRestrict(progname, restrict.c_str()); - gflags_exitfunc(1); - - } else if (!FLAGS_helpmatch.empty()) { - ShowUsageWithFlagsRestrict(progname, FLAGS_helpmatch.c_str()); - gflags_exitfunc(1); - - } else if (FLAGS_helppackage) { - // Shows help for all files in the same directory as main(). We - // don't want to resort to looking at dirname(progname), because - // the user can pick progname, and it may not relate to the file - // where main() resides. So instead, we search the flags for a - // filename like "/progname.cc", and take the dirname of that. - vector flags; - GetAllFlags(&flags); - string last_package; - for (vector::const_iterator flag = flags.begin(); - flag != flags.end(); - ++flag) { - if (!FileMatchesSubstring(flag->filename, substrings)) - continue; - const string package = Dirname(flag->filename) + PATH_SEPARATOR; - if (package != last_package) { - ShowUsageWithFlagsRestrict(progname, package.c_str()); - VLOG(7) << "Found package: " << package; - if (!last_package.empty()) { // means this isn't our first pkg - LOG(WARNING) << "Multiple packages contain a file=" << progname; - } - last_package = package; - } - } - if (last_package.empty()) { // never found a package to print - LOG(WARNING) << "Unable to find a package for file=" << progname; - } - gflags_exitfunc(1); - - } else if (FLAGS_helpxml) { - ShowXMLOfFlags(progname); - gflags_exitfunc(1); - - } else if (FLAGS_version) { - ShowVersion(); - // Unlike help, we may be asking for version in a script, so return 0 - gflags_exitfunc(0); - - } -} - - -} // namespace GFLAGS_NAMESPACE diff --git a/src/mutex.h b/src/mutex.h index ff96f2b..1648c2f 100644 --- a/src/mutex.h +++ b/src/mutex.h @@ -106,7 +106,7 @@ #ifndef GFLAGS_MUTEX_H_ #define GFLAGS_MUTEX_H_ -#include "gflags_declare.h" // to figure out pthreads support +#include "gflags/gflags_declare.h" // to figure out pthreads support #if defined(NO_THREADS) typedef int MutexType; // to keep a lock-count diff --git a/src/reporting.cc b/src/reporting.cc new file mode 100644 index 0000000..7cc6691 --- /dev/null +++ b/src/reporting.cc @@ -0,0 +1,442 @@ +// Copyright (c) 1999, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// --- +// +// Revamped and reorganized by Craig Silverstein +// +// This file contains code for handling the 'reporting' flags. These +// are flags that, when present, cause the program to report some +// information and then exit. --help and --version are the canonical +// reporting flags, but we also have flags like --helpxml, etc. +// +// There's only one function that's meant to be called externally: +// HandleCommandLineHelpFlags(). (Well, actually, ShowUsageWithFlags(), +// ShowUsageWithFlagsRestrict(), and DescribeOneFlag() can be called +// externally too, but there's little need for it.) These are all +// declared in the main gflags.h header file. +// +// HandleCommandLineHelpFlags() will check what 'reporting' flags have +// been defined, if any -- the "help" part of the function name is a +// bit misleading -- and do the relevant reporting. It should be +// called after all flag-values have been assigned, that is, after +// parsing the command-line. + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "gflags/gflags.h" +#include "gflags/gflags_completions.h" +#include "util.h" + + +// The 'reporting' flags. They all call gflags_exitfunc(). +DEFINE_bool (help, false, "show help on all flags [tip: all flags can have two dashes]"); +DEFINE_bool (helpfull, false, "show help on all flags -- same as -help"); +DEFINE_bool (helpshort, false, "show help on only the main module for this program"); +DEFINE_string(helpon, "", "show help on the modules named by this flag value"); +DEFINE_string(helpmatch, "", "show help on modules whose name contains the specified substr"); +DEFINE_bool (helppackage, false, "show help on all modules in the main package"); +DEFINE_bool (helpxml, false, "produce an xml version of help"); +DEFINE_bool (version, false, "show version and build info and exit"); + + +namespace GFLAGS_NAMESPACE { + + +using std::string; +using std::vector; + + +// -------------------------------------------------------------------- +// DescribeOneFlag() +// DescribeOneFlagInXML() +// Routines that pretty-print info about a flag. These use +// a CommandLineFlagInfo, which is the way the gflags +// API exposes static info about a flag. +// -------------------------------------------------------------------- + +static const int kLineLength = 80; + +static void AddString(const string& s, + string* final_string, int* chars_in_line) { + const int slen = static_cast(s.length()); + if (*chars_in_line + 1 + slen >= kLineLength) { // < 80 chars/line + *final_string += "\n "; + *chars_in_line = 6; + } else { + *final_string += " "; + *chars_in_line += 1; + } + *final_string += s; + *chars_in_line += slen; +} + +static string PrintStringFlagsWithQuotes(const CommandLineFlagInfo& flag, + const string& text, bool current) { + const char* c_string = (current ? flag.current_value.c_str() : + flag.default_value.c_str()); + if (strcmp(flag.type.c_str(), "string") == 0) { // add quotes for strings + return StringPrintf("%s: \"%s\"", text.c_str(), c_string); + } else { + return StringPrintf("%s: %s", text.c_str(), c_string); + } +} + +// Create a descriptive string for a flag. +// Goes to some trouble to make pretty line breaks. +string DescribeOneFlag(const CommandLineFlagInfo& flag) { + string main_part; + SStringPrintf(&main_part, " -%s (%s)", + flag.name.c_str(), + flag.description.c_str()); + const char* c_string = main_part.c_str(); + int chars_left = static_cast(main_part.length()); + string final_string = ""; + int chars_in_line = 0; // how many chars in current line so far? + while (1) { + assert(static_cast(chars_left) + == strlen(c_string)); // Unless there's a \0 in there? + const char* newline = strchr(c_string, '\n'); + if (newline == NULL && chars_in_line+chars_left < kLineLength) { + // The whole remainder of the string fits on this line + final_string += c_string; + chars_in_line += chars_left; + break; + } + if (newline != NULL && newline - c_string < kLineLength - chars_in_line) { + int n = static_cast(newline - c_string); + final_string.append(c_string, n); + chars_left -= n + 1; + c_string += n + 1; + } else { + // Find the last whitespace on this 80-char line + int whitespace = kLineLength-chars_in_line-1; // < 80 chars/line + while ( whitespace > 0 && !isspace(c_string[whitespace]) ) { + --whitespace; + } + if (whitespace <= 0) { + // Couldn't find any whitespace to make a line break. Just dump the + // rest out! + final_string += c_string; + chars_in_line = kLineLength; // next part gets its own line for sure! + break; + } + final_string += string(c_string, whitespace); + chars_in_line += whitespace; + while (isspace(c_string[whitespace])) ++whitespace; + c_string += whitespace; + chars_left -= whitespace; + } + if (*c_string == '\0') + break; + StringAppendF(&final_string, "\n "); + chars_in_line = 6; + } + + // Append data type + AddString(string("type: ") + flag.type, &final_string, &chars_in_line); + // The listed default value will be the actual default from the flag + // definition in the originating source file, unless the value has + // subsequently been modified using SetCommandLineOptionWithMode() with mode + // SET_FLAGS_DEFAULT, or by setting FLAGS_foo = bar before ParseCommandLineFlags(). + AddString(PrintStringFlagsWithQuotes(flag, "default", false), &final_string, + &chars_in_line); + if (!flag.is_default) { + AddString(PrintStringFlagsWithQuotes(flag, "currently", true), + &final_string, &chars_in_line); + } + + StringAppendF(&final_string, "\n"); + return final_string; +} + +// Simple routine to xml-escape a string: escape & and < only. +static string XMLText(const string& txt) { + string ans = txt; + for (string::size_type pos = 0; (pos = ans.find("&", pos)) != string::npos; ) + ans.replace(pos++, 1, "&"); + for (string::size_type pos = 0; (pos = ans.find("<", pos)) != string::npos; ) + ans.replace(pos++, 1, "<"); + return ans; +} + +static void AddXMLTag(string* r, const char* tag, const string& txt) { + StringAppendF(r, "<%s>%s", tag, XMLText(txt).c_str(), tag); +} + + +static string DescribeOneFlagInXML(const CommandLineFlagInfo& flag) { + // The file and flagname could have been attributes, but default + // and meaning need to avoid attribute normalization. This way it + // can be parsed by simple programs, in addition to xml parsers. + string r(""); + AddXMLTag(&r, "file", flag.filename); + AddXMLTag(&r, "name", flag.name); + AddXMLTag(&r, "meaning", flag.description); + AddXMLTag(&r, "default", flag.default_value); + AddXMLTag(&r, "current", flag.current_value); + AddXMLTag(&r, "type", flag.type); + r += ""; + return r; +} + +// -------------------------------------------------------------------- +// ShowUsageWithFlags() +// ShowUsageWithFlagsRestrict() +// ShowXMLOfFlags() +// These routines variously expose the registry's list of flag +// values. ShowUsage*() prints the flag-value information +// to stdout in a user-readable format (that's what --help uses). +// The Restrict() version limits what flags are shown. +// ShowXMLOfFlags() prints the flag-value information to stdout +// in a machine-readable format. In all cases, the flags are +// sorted: first by filename they are defined in, then by flagname. +// -------------------------------------------------------------------- + +static const char* Basename(const char* filename) { + const char* sep = strrchr(filename, PATH_SEPARATOR); + return sep ? sep + 1 : filename; +} + +static string Dirname(const string& filename) { + string::size_type sep = filename.rfind(PATH_SEPARATOR); + return filename.substr(0, (sep == string::npos) ? 0 : sep); +} + +// Test whether a filename contains at least one of the substrings. +static bool FileMatchesSubstring(const string& filename, + const vector& substrings) { + for (vector::const_iterator target = substrings.begin(); + target != substrings.end(); + ++target) { + if (strstr(filename.c_str(), target->c_str()) != NULL) + return true; + // If the substring starts with a '/', that means that we want + // the string to be at the beginning of a directory component. + // That should match the first directory component as well, so + // we allow '/foo' to match a filename of 'foo'. + if (!target->empty() && (*target)[0] == PATH_SEPARATOR && + strncmp(filename.c_str(), target->c_str() + 1, + strlen(target->c_str() + 1)) == 0) + return true; + } + return false; +} + +// Show help for every filename which matches any of the target substrings. +// If substrings is empty, shows help for every file. If a flag's help message +// has been stripped (e.g. by adding '#define STRIP_FLAG_HELP 1' +// before including gflags/gflags.h), then this flag will not be displayed +// by '--help' and its variants. +static void ShowUsageWithFlagsMatching(const char *argv0, + const vector &substrings) { + fprintf(stdout, "%s: %s\n", Basename(argv0), ProgramUsage()); + + vector flags; + GetAllFlags(&flags); // flags are sorted by filename, then flagname + + string last_filename; // so we know when we're at a new file + bool first_directory = true; // controls blank lines between dirs + bool found_match = false; // stays false iff no dir matches restrict + for (vector::const_iterator flag = flags.begin(); + flag != flags.end(); + ++flag) { + if (substrings.empty() || + FileMatchesSubstring(flag->filename, substrings)) { + // If the flag has been stripped, pretend that it doesn't exist. + if (flag->description == kStrippedFlagHelp) continue; + found_match = true; // this flag passed the match! + if (flag->filename != last_filename) { // new file + if (Dirname(flag->filename) != Dirname(last_filename)) { // new dir! + if (!first_directory) + fprintf(stdout, "\n\n"); // put blank lines between directories + first_directory = false; + } + fprintf(stdout, "\n Flags from %s:\n", flag->filename.c_str()); + last_filename = flag->filename; + } + // Now print this flag + fprintf(stdout, "%s", DescribeOneFlag(*flag).c_str()); + } + } + if (!found_match && !substrings.empty()) { + fprintf(stdout, "\n No modules matched: use -help\n"); + } +} + +void ShowUsageWithFlagsRestrict(const char *argv0, const char *restrict) { + vector substrings; + if (restrict != NULL && *restrict != '\0') { + substrings.push_back(restrict); + } + ShowUsageWithFlagsMatching(argv0, substrings); +} + +void ShowUsageWithFlags(const char *argv0) { + ShowUsageWithFlagsRestrict(argv0, ""); +} + +// Convert the help, program, and usage to xml. +static void ShowXMLOfFlags(const char *prog_name) { + vector flags; + GetAllFlags(&flags); // flags are sorted: by filename, then flagname + + // XML. There is no corresponding schema yet + fprintf(stdout, "\n"); + // The document + fprintf(stdout, "\n"); + // the program name and usage + fprintf(stdout, "%s\n", + XMLText(Basename(prog_name)).c_str()); + fprintf(stdout, "%s\n", + XMLText(ProgramUsage()).c_str()); + // All the flags + for (vector::const_iterator flag = flags.begin(); + flag != flags.end(); + ++flag) { + if (flag->description != kStrippedFlagHelp) + fprintf(stdout, "%s\n", DescribeOneFlagInXML(*flag).c_str()); + } + // The end of the document + fprintf(stdout, "\n"); +} + +// -------------------------------------------------------------------- +// ShowVersion() +// Called upon --version. Prints build-related info. +// -------------------------------------------------------------------- + +static void ShowVersion() { + const char* version_string = VersionString(); + if (version_string && *version_string) { + fprintf(stdout, "%s version %s\n", + ProgramInvocationShortName(), version_string); + } else { + fprintf(stdout, "%s\n", ProgramInvocationShortName()); + } +# if !defined(NDEBUG) + fprintf(stdout, "Debug build (NDEBUG not #defined)\n"); +# endif +} + +static void AppendPrognameStrings(vector* substrings, + const char* progname) { + string r(""); + r += PATH_SEPARATOR; + r += progname; + substrings->push_back(r + "."); + substrings->push_back(r + "-main."); + substrings->push_back(r + "_main."); +} + +// -------------------------------------------------------------------- +// HandleCommandLineHelpFlags() +// Checks all the 'reporting' commandline flags to see if any +// have been set. If so, handles them appropriately. Note +// that all of them, by definition, cause the program to exit +// if they trigger. +// -------------------------------------------------------------------- + +void HandleCommandLineHelpFlags() { + const char* progname = ProgramInvocationShortName(); + + HandleCommandLineCompletions(); + + vector substrings; + AppendPrognameStrings(&substrings, progname); + + if (FLAGS_helpshort) { + // show only flags related to this binary: + // E.g. for fileutil.cc, want flags containing ... "/fileutil." cc + ShowUsageWithFlagsMatching(progname, substrings); + gflags_exitfunc(1); + + } else if (FLAGS_help || FLAGS_helpfull) { + // show all options + ShowUsageWithFlagsRestrict(progname, ""); // empty restrict + gflags_exitfunc(1); + + } else if (!FLAGS_helpon.empty()) { + string restrict = PATH_SEPARATOR + FLAGS_helpon + "."; + ShowUsageWithFlagsRestrict(progname, restrict.c_str()); + gflags_exitfunc(1); + + } else if (!FLAGS_helpmatch.empty()) { + ShowUsageWithFlagsRestrict(progname, FLAGS_helpmatch.c_str()); + gflags_exitfunc(1); + + } else if (FLAGS_helppackage) { + // Shows help for all files in the same directory as main(). We + // don't want to resort to looking at dirname(progname), because + // the user can pick progname, and it may not relate to the file + // where main() resides. So instead, we search the flags for a + // filename like "/progname.cc", and take the dirname of that. + vector flags; + GetAllFlags(&flags); + string last_package; + for (vector::const_iterator flag = flags.begin(); + flag != flags.end(); + ++flag) { + if (!FileMatchesSubstring(flag->filename, substrings)) + continue; + const string package = Dirname(flag->filename) + PATH_SEPARATOR; + if (package != last_package) { + ShowUsageWithFlagsRestrict(progname, package.c_str()); + VLOG(7) << "Found package: " << package; + if (!last_package.empty()) { // means this isn't our first pkg + LOG(WARNING) << "Multiple packages contain a file=" << progname; + } + last_package = package; + } + } + if (last_package.empty()) { // never found a package to print + LOG(WARNING) << "Unable to find a package for file=" << progname; + } + gflags_exitfunc(1); + + } else if (FLAGS_helpxml) { + ShowXMLOfFlags(progname); + gflags_exitfunc(1); + + } else if (FLAGS_version) { + ShowVersion(); + // Unlike help, we may be asking for version in a script, so return 0 + gflags_exitfunc(0); + + } +} + + +} // namespace GFLAGS_NAMESPACE diff --git a/src/util.h b/src/util.h index 164e3cf..e6b28cc 100644 --- a/src/util.h +++ b/src/util.h @@ -35,6 +35,7 @@ #define GFLAGS_UTIL_H_ #include "config.h" +#include "gflags/gflags_declare.h" // GFLAGS_NAMESPACE #include #ifdef HAVE_INTTYPES_H -- cgit v1.2.3