Skip to content

Commit

Permalink
[CommandLine] Improve help text for cl::values style options
Browse files Browse the repository at this point in the history
In order to make an option value truly optional, both the ValueOptional
and an empty-named value are required. This empty-named value appears in
the command-line help text, which is not ideal.

This change improves the help text for these sort of options in a number
of ways:
1) ValueOptional options with an empty-named value now print their help
text twice: both without and then with '=<value>' after the name. The
latter version then lists the allowed values after it.
2) Empty-named values with no help text in ValueOptional options are not
listed in the permitted values.
3) Otherwise empty-named options are printed as =<empty> rather than
simply '='.
4) Option values without help text do not have the '-' separator
printed.

It also tweaks the llvm-symbolizer -functions help text to not print a
trailing ':' as that looks bad combined with 1) above.

Reviewed by: thopre, ruiu

Differential Revision: https://reviews.llvm.org/D57030

llvm-svn: 352750
  • Loading branch information
jh7370 committed Jan 31, 2019
1 parent ac1b75b commit 140f75f
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 6 deletions.
32 changes: 27 additions & 5 deletions llvm/lib/Support/CommandLine.cpp
Expand Up @@ -1663,13 +1663,35 @@ size_t generic_parser_base::getOptionWidth(const Option &O) const {
void generic_parser_base::printOptionInfo(const Option &O,
size_t GlobalWidth) const {
if (O.hasArgStr()) {
outs() << " -" << O.ArgStr;
Option::printHelpStr(O.HelpStr, GlobalWidth, O.ArgStr.size() + 6);
// When the value is optional, first print a line just describing the option
// without values.
if (O.getValueExpectedFlag() == ValueOptional) {
for (unsigned i = 0, e = getNumOptions(); i != e; ++i) {
if (getOption(i).empty()) {
outs() << " -" << O.ArgStr;
Option::printHelpStr(O.HelpStr, GlobalWidth, O.ArgStr.size() + 6);
break;
}
}
}

outs() << " -" << O.ArgStr << "=<value>";
Option::printHelpStr(O.HelpStr, GlobalWidth, O.ArgStr.size() + 14);
for (unsigned i = 0, e = getNumOptions(); i != e; ++i) {
size_t NumSpaces = GlobalWidth - getOption(i).size() - 8;
outs() << " =" << getOption(i);
outs().indent(NumSpaces) << " - " << getDescription(i) << '\n';
StringRef ValueName = getOption(i);
StringRef Description = getDescription(i);
if (O.getValueExpectedFlag() == ValueOptional && ValueName.empty() &&
Description.empty())
continue;
size_t NumSpaces = GlobalWidth - ValueName.size() - 8;
outs() << " =" << ValueName;
if (ValueName.empty()) {
outs() << "<empty>";
NumSpaces -= 7;
}
if (!Description.empty())
outs().indent(NumSpaces) << " - " << Description;
outs() << '\n';
}
} else {
if (!O.HelpStr.empty())
Expand Down
2 changes: 1 addition & 1 deletion llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp
Expand Up @@ -38,7 +38,7 @@ ClUseSymbolTable("use-symbol-table", cl::init(true),

static cl::opt<FunctionNameKind> ClPrintFunctions(
"functions", cl::init(FunctionNameKind::LinkageName),
cl::desc("Print function name for a given address:"), cl::ValueOptional,
cl::desc("Print function name for a given address"), cl::ValueOptional,
cl::values(clEnumValN(FunctionNameKind::None, "none", "omit function name"),
clEnumValN(FunctionNameKind::ShortName, "short",
"print short function name"),
Expand Down
134 changes: 134 additions & 0 deletions llvm/unittests/Support/CommandLineTest.cpp
Expand Up @@ -13,6 +13,7 @@
#include "llvm/Config/config.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/StringSaver.h"
Expand Down Expand Up @@ -839,4 +840,137 @@ TEST(CommandLineTest, GetCommandLineArguments) {
}
#endif

class OutputRedirector {
public:
OutputRedirector(int RedirectFD)
: RedirectFD(RedirectFD), OldFD(dup(RedirectFD)) {
if (OldFD == -1 ||
sys::fs::createTemporaryFile("unittest-redirect", "", NewFD,
FilePath) ||
dup2(NewFD, RedirectFD) == -1)
Valid = false;
}

~OutputRedirector() {
dup2(OldFD, RedirectFD);
close(OldFD);
close(NewFD);
}

SmallVector<char, 128> FilePath;
bool Valid = true;

private:
int RedirectFD;
int OldFD;
int NewFD;
};

struct AutoDeleteFile {
SmallVector<char, 128> FilePath;
~AutoDeleteFile() {
if (!FilePath.empty())
sys::fs::remove(std::string(FilePath.data(), FilePath.size()));
}
};

class PrintOptionInfoTest : public ::testing::Test {
public:
// Return std::string because the output of a failing EXPECT check is
// unreadable for StringRef. It also avoids any lifetime issues.
template <typename... Ts> std::string runTest(Ts... OptionAttributes) {
AutoDeleteFile File;
{
OutputRedirector Stdout(fileno(stdout));
if (!Stdout.Valid)
return "";
File.FilePath = Stdout.FilePath;

StackOption<OptionValue> TestOption(Opt, cl::desc(HelpText),
OptionAttributes...);
printOptionInfo(TestOption, 25);
outs().flush();
}
auto Buffer = MemoryBuffer::getFile(File.FilePath);
if (!Buffer)
return "";
return Buffer->get()->getBuffer().str();
}

enum class OptionValue { Val };
const StringRef Opt = "some-option";
const StringRef HelpText = "some help";

private:
// This is a workaround for cl::Option sub-classes having their
// printOptionInfo functions private.
void printOptionInfo(const cl::Option &O, size_t Width) {
O.printOptionInfo(Width);
}
};

TEST_F(PrintOptionInfoTest, PrintOptionInfoValueOptionalWithoutSentinel) {
std::string Output =
runTest(cl::ValueOptional,
cl::values(clEnumValN(OptionValue::Val, "v1", "desc1")));

// clang-format off
EXPECT_EQ(Output, (" -" + Opt + "=<value> - " + HelpText + "\n"
" =v1 - desc1\n")
.str());
// clang-format on
}

TEST_F(PrintOptionInfoTest, PrintOptionInfoValueOptionalWithSentinel) {
std::string Output = runTest(
cl::ValueOptional, cl::values(clEnumValN(OptionValue::Val, "v1", "desc1"),
clEnumValN(OptionValue::Val, "", "")));

// clang-format off
EXPECT_EQ(Output,
(" -" + Opt + " - " + HelpText + "\n"
" -" + Opt + "=<value> - " + HelpText + "\n"
" =v1 - desc1\n")
.str());
// clang-format on
}

TEST_F(PrintOptionInfoTest, PrintOptionInfoValueOptionalWithSentinelWithHelp) {
std::string Output = runTest(
cl::ValueOptional, cl::values(clEnumValN(OptionValue::Val, "v1", "desc1"),
clEnumValN(OptionValue::Val, "", "desc2")));

// clang-format off
EXPECT_EQ(Output, (" -" + Opt + " - " + HelpText + "\n"
" -" + Opt + "=<value> - " + HelpText + "\n"
" =v1 - desc1\n"
" =<empty> - desc2\n")
.str());
// clang-format on
}

TEST_F(PrintOptionInfoTest, PrintOptionInfoValueRequiredWithEmptyValueName) {
std::string Output = runTest(
cl::ValueRequired, cl::values(clEnumValN(OptionValue::Val, "v1", "desc1"),
clEnumValN(OptionValue::Val, "", "")));

// clang-format off
EXPECT_EQ(Output, (" -" + Opt + "=<value> - " + HelpText + "\n"
" =v1 - desc1\n"
" =<empty>\n")
.str());
// clang-format on
}

TEST_F(PrintOptionInfoTest, PrintOptionInfoEmptyValueDescription) {
std::string Output = runTest(
cl::ValueRequired, cl::values(clEnumValN(OptionValue::Val, "v1", "")));

// clang-format off
EXPECT_EQ(Output,
(" -" + Opt + "=<value> - " + HelpText + "\n"
" =v1\n").str());
// clang-format on
}

} // anonymous namespace

0 comments on commit 140f75f

Please sign in to comment.