// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// ---
-// Author: Dave Nicponski
-//
+
// Bash-style command line flag completion for C++ binaries
//
// This module implements bash-style completions. It achieves this
// 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 "config.h"
+#include <stdio.h>
#include <stdlib.h>
+#include <string.h> // for strlen
#include <set>
#include <string>
#include <utility>
#include <vector>
-#include "google/gflags.h"
+#include "config.h"
+#include "gflags/gflags.h"
+#include "gflags/gflags_completions.h"
+#include "util.h"
using std::set;
using std::string;
using std::vector;
-#ifndef PATH_SEPARATOR
-#define PATH_SEPARATOR '/'
-#endif
DEFINE_string(tab_completion_word, "",
"If non-empty, HandleCommandLineCompletions() will hijack the "
DEFINE_int32(tab_completion_columns, 80,
"Number of columns to use in output for tab completion");
-_START_GOOGLE_NAMESPACE_
-namespace {
+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
NotableFlags *notable_flags);
static void TryFindModuleAndPackageDir(
- const vector<CommandLineFlagInfo> all_flags,
+ const vector<CommandLineFlagInfo> &all_flags,
string *module,
string *package_dir);
const string &footer,
bool long_output_format,
int *remaining_line_limit,
- int *completion_elements_added,
+ size_t *completion_elements_added,
vector<string> *completions);
// (helpers for #5)
static void PrintFlagCompletionInfo(void) {
string cursor_word = FLAGS_tab_completion_word;
string canonical_token;
- CompletionOptions options;
+ CompletionOptions options = { };
CanonicalizeCursorWordAndSearchOptions(
cursor_word,
&canonical_token,
&options);
- //VLOG(1) << "Identified canonical_token: '" << canonical_token << "'";
+ DVLOG(1) << "Identified canonical_token: '" << canonical_token << "'";
vector<CommandLineFlagInfo> all_flags;
set<const CommandLineFlagInfo *> matching_flags;
GetAllFlags(&all_flags);
- //VLOG(2) << "Found " << all_flags.size() << " flags overall";
+ DVLOG(2) << "Found " << all_flags.size() << " flags overall";
string longest_common_prefix;
FindMatchingFlags(
canonical_token,
&matching_flags,
&longest_common_prefix);
- //VLOG(1) << "Identified " << matching_flags.size() << " matching flags";
- //VLOG(1) << "Identified " << longest_common_prefix
- // << " as 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.
- //VLOG(1) << "The common prefix '" << longest_common_prefix
- // << "' was longer than the token '" << canonical_token
- // << "'. Returning just this prefix for completion.";
+ 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.";
+ VLOG(1) << "There were no matching flags, returning nothing.";
return;
}
string module;
string package_dir;
TryFindModuleAndPackageDir(all_flags, &module, &package_dir);
- //VLOG(1) << "Identified module: '" << module << "'";
- //VLOG(1) << "Identified package_dir: '" << package_dir << "'";
+ DVLOG(1) << "Identified module: '" << module << "'";
+ DVLOG(1) << "Identified package_dir: '" << package_dir << "'";
NotableFlags notable_flags;
CategorizeAllMatchingFlags(
module,
package_dir,
¬able_flags);
- //VLOG(2) << "Categorized matching flags:";
- //VLOG(2) << " perfect_match: " << notable_flags.perfect_match_flag.size();
- //VLOG(2) << " module: " << notable_flags.module_flags.size();
- //VLOG(2) << " package: " << notable_flags.package_flags.size();
- //VLOG(2) << " most common: " << notable_flags.most_common_flags.size();
- //VLOG(2) << " subpackage: " << notable_flags.subpackage_flags.size();
+ 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<string> completions;
FinalizeCompletionOutput(
if (options.force_no_update)
completions.push_back("~");
- //VLOG(1) << "Finalized with " << completions.size()
- // << " chosen completions";
+ DVLOG(1) << "Finalized with " << completions.size()
+ << " chosen completions";
for (vector<string>::const_iterator it = completions.begin();
it != completions.end();
++it) {
- //VLOG(9) << " Completion entry: '" << *it << "'";
+ DVLOG(9) << " Completion entry: '" << *it << "'";
fprintf(stdout, "%s\n", it->c_str());
}
}
flag.filename.find(match_token) != string::npos)
return true;
- // TODO(daven): All searches should probably be case-insensitive
+ // 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)
all_matches.begin();
it != all_matches.end();
++it) {
- //VLOG(2) << "Examinging match '" << (*it)->name << "'";
- //VLOG(7) << " filename: '" << (*it)->filename << "'";
+ 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);
if ((*it)->name == search_token) {
// Exact match on some flag's name
notable_flags->perfect_match_flag.insert(*it);
- //VLOG(3) << "Result: perfect match";
+ DVLOG(3) << "Result: perfect match";
} else if (!module.empty() && (*it)->filename == module) {
// Exact match on module filename
notable_flags->module_flags.insert(*it);
- //VLOG(3) << "Result: module match";
+ 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);
- //VLOG(3) << "Result: package match";
+ DVLOG(3) << "Result: package match";
} else if (false) {
// In the list of the XXX most commonly supplied flags overall
- // TODO(daven): Compile this list.
- //VLOG(3) << "Result: most-common match";
+ // 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);
- //VLOG(3) << "Result: subpackage match";
+ DVLOG(3) << "Result: subpackage match";
}
- //VLOG(3) << "Result: not special match";
+ DVLOG(3) << "Result: not special match";
}
}
+static void PushNameWithSuffix(vector<string>* suffixes, const char* suffix) {
+ suffixes->push_back(
+ StringPrintf("/%s%s", ProgramInvocationShortName(), suffix));
+}
+
static void TryFindModuleAndPackageDir(
- const vector<CommandLineFlagInfo> all_flags,
+ const vector<CommandLineFlagInfo> &all_flags,
string *module,
string *package_dir) {
module->clear();
package_dir->clear();
vector<string> suffixes;
- // TODO(daven): There's some inherant ambiguity here - multiple directories
+ // 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.
- string progname(ProgramInvocationShortName());
- suffixes.push_back("/" + progname + ".");
- suffixes.push_back("/" + progname + "-main.");
- suffixes.push_back("/" + progname + "_main.");
+ PushNameWithSuffix(&suffixes, ".");
+ PushNameWithSuffix(&suffixes, "-main.");
+ PushNameWithSuffix(&suffixes, "_main.");
// These four are new but probably merited?
- suffixes.push_back("/" + progname + "_test.");
- suffixes.push_back("/" + progname + "-test.");
- suffixes.push_back("/" + progname + "_unittest.");
- suffixes.push_back("/" + progname + "-unittest.");
+ PushNameWithSuffix(&suffixes, "-test.");
+ PushNameWithSuffix(&suffixes, "_test.");
+ PushNameWithSuffix(&suffixes, "-unittest.");
+ PushNameWithSuffix(&suffixes, "_unittest.");
for (vector<CommandLineFlagInfo>::const_iterator it = all_flags.begin();
it != all_flags.end();
for (vector<string>::const_iterator suffix = suffixes.begin();
suffix != suffixes.end();
++suffix) {
- // TODO(daven): Make sure the match is near the end of the string
+ // 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);
// Can't specialize template type on a locally defined type. Silly C++...
struct DisplayInfoGroup {
- string header;
- string footer;
+ const char* header;
+ const char* footer;
set<const CommandLineFlagInfo *> *group;
+
+ int SizeInLines() const {
+ int size_in_lines = static_cast<int>(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
if (lines_so_far < max_desired_lines &&
!notable_flags->perfect_match_flag.empty()) {
perfect_match_found = true;
- lines_so_far += notable_flags->perfect_match_flag.size() + 2; // no header
DisplayInfoGroup group =
- { "", "==========", ¬able_flags->perfect_match_flag };
+ { "",
+ "==========",
+ ¬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()) {
- lines_so_far += notable_flags->module_flags.size() + 3;
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()) {
- lines_so_far += notable_flags->package_flags.size() + 3;
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()) {
- lines_so_far += notable_flags->most_common_flags.size() + 3;
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()) {
- lines_so_far += notable_flags->subpackage_flags.size() + 3;
DisplayInfoGroup group = {
"-* Matching sub-package flags *-",
"================================",
¬able_flags->subpackage_flags };
+ lines_so_far += group.SizeInLines();
output_groups.push_back(group);
}
if (lines_so_far < max_desired_lines) {
RetrieveUnusedFlags(matching_flags, *notable_flags, &obscure_flags);
if (!obscure_flags.empty()) {
- lines_so_far += obscure_flags.size() + 2; // no footer
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;
- int completions_output = 0;
- int indent = output_groups.size() - 1;
+ size_t completions_output = 0;
+ int indent = static_cast<int>(output_groups.size()) - 1;
for (vector<DisplayInfoGroup>::const_iterator it =
output_groups.begin();
it != output_groups.end();
OutputSingleGroupWithLimit(
*it->group, // group
string(indent, ' '), // line indentation
- it->header, // header
- it->footer, // footer
+ 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
}
}
-// 5) Output matches (and helpfer methods)
+// 5) Output matches (and helper methods)
static void OutputSingleGroupWithLimit(
const set<const CommandLineFlagInfo *> &group,
const string &footer,
bool long_output_format,
int *remaining_line_limit,
- int *completion_elements_output,
+ size_t *completion_elements_output,
vector<string> *completions) {
if (group.empty()) return;
if (!header.empty()) {
static string GetShortFlagLine(
const string &line_indentation,
const CommandLineFlagInfo &info) {
- string prefix =
- line_indentation + "--" + info.name + " [" +
- (info.type == "string" ?
- ("'" + info.default_value + "'") :
- info.default_value)
- + "] ";
- int remainder = FLAGS_tab_completion_columns - prefix.size();
- string suffix = "";
+ 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<int>(prefix.size());
+ string suffix;
if (remainder > 0)
suffix =
- (info.description.size() > remainder ?
+ (static_cast<int>(info.description.size()) > remainder ?
(info.description.substr(0, remainder - 3) + "...").c_str() :
info.description.c_str());
return prefix + suffix;
static const char kNewlineWithIndent[] = "\n ";
output.replace(output.find(" type:"), 1, string(kNewlineWithIndent));
output.replace(output.find(" default:"), 1, string(kNewlineWithIndent));
- output = line_indentation + " Details for '--" + info.name + "':\n" +
- output + " defined: " + info.filename;
+ 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"
for (string::size_type newline = output.find('\n');
newline != string::npos;
newline = output.find('\n')) {
- int newline_pos = newline % FLAGS_tab_completion_columns;
+ int newline_pos = static_cast<int>(newline) % FLAGS_tab_completion_columns;
int missing_spaces = FLAGS_tab_completion_columns - newline_pos;
output.replace(newline, 1, line_of_spaces, 1, missing_spaces);
}
void HandleCommandLineCompletions(void) {
if (FLAGS_tab_completion_word.empty()) return;
PrintFlagCompletionInfo();
- exit(0);
+ gflags_exitfunc(0);
}
-_END_GOOGLE_NAMESPACE_
+
+} // namespace GFLAGS_NAMESPACE