Skip to content

Commit 140f75f

Browse files
committed
[CommandLine] Improve help text for cl::values style options
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
1 parent ac1b75b commit 140f75f

File tree

3 files changed

+162
-6
lines changed

3 files changed

+162
-6
lines changed

llvm/lib/Support/CommandLine.cpp

+27-5
Original file line numberDiff line numberDiff line change
@@ -1663,13 +1663,35 @@ size_t generic_parser_base::getOptionWidth(const Option &O) const {
16631663
void generic_parser_base::printOptionInfo(const Option &O,
16641664
size_t GlobalWidth) const {
16651665
if (O.hasArgStr()) {
1666-
outs() << " -" << O.ArgStr;
1667-
Option::printHelpStr(O.HelpStr, GlobalWidth, O.ArgStr.size() + 6);
1666+
// When the value is optional, first print a line just describing the option
1667+
// without values.
1668+
if (O.getValueExpectedFlag() == ValueOptional) {
1669+
for (unsigned i = 0, e = getNumOptions(); i != e; ++i) {
1670+
if (getOption(i).empty()) {
1671+
outs() << " -" << O.ArgStr;
1672+
Option::printHelpStr(O.HelpStr, GlobalWidth, O.ArgStr.size() + 6);
1673+
break;
1674+
}
1675+
}
1676+
}
16681677

1678+
outs() << " -" << O.ArgStr << "=<value>";
1679+
Option::printHelpStr(O.HelpStr, GlobalWidth, O.ArgStr.size() + 14);
16691680
for (unsigned i = 0, e = getNumOptions(); i != e; ++i) {
1670-
size_t NumSpaces = GlobalWidth - getOption(i).size() - 8;
1671-
outs() << " =" << getOption(i);
1672-
outs().indent(NumSpaces) << " - " << getDescription(i) << '\n';
1681+
StringRef ValueName = getOption(i);
1682+
StringRef Description = getDescription(i);
1683+
if (O.getValueExpectedFlag() == ValueOptional && ValueName.empty() &&
1684+
Description.empty())
1685+
continue;
1686+
size_t NumSpaces = GlobalWidth - ValueName.size() - 8;
1687+
outs() << " =" << ValueName;
1688+
if (ValueName.empty()) {
1689+
outs() << "<empty>";
1690+
NumSpaces -= 7;
1691+
}
1692+
if (!Description.empty())
1693+
outs().indent(NumSpaces) << " - " << Description;
1694+
outs() << '\n';
16731695
}
16741696
} else {
16751697
if (!O.HelpStr.empty())

llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ ClUseSymbolTable("use-symbol-table", cl::init(true),
3838

3939
static cl::opt<FunctionNameKind> ClPrintFunctions(
4040
"functions", cl::init(FunctionNameKind::LinkageName),
41-
cl::desc("Print function name for a given address:"), cl::ValueOptional,
41+
cl::desc("Print function name for a given address"), cl::ValueOptional,
4242
cl::values(clEnumValN(FunctionNameKind::None, "none", "omit function name"),
4343
clEnumValN(FunctionNameKind::ShortName, "short",
4444
"print short function name"),

llvm/unittests/Support/CommandLineTest.cpp

+134
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "llvm/Config/config.h"
1414
#include "llvm/Support/FileSystem.h"
1515
#include "llvm/Support/InitLLVM.h"
16+
#include "llvm/Support/MemoryBuffer.h"
1617
#include "llvm/Support/Path.h"
1718
#include "llvm/Support/Program.h"
1819
#include "llvm/Support/StringSaver.h"
@@ -839,4 +840,137 @@ TEST(CommandLineTest, GetCommandLineArguments) {
839840
}
840841
#endif
841842

843+
class OutputRedirector {
844+
public:
845+
OutputRedirector(int RedirectFD)
846+
: RedirectFD(RedirectFD), OldFD(dup(RedirectFD)) {
847+
if (OldFD == -1 ||
848+
sys::fs::createTemporaryFile("unittest-redirect", "", NewFD,
849+
FilePath) ||
850+
dup2(NewFD, RedirectFD) == -1)
851+
Valid = false;
852+
}
853+
854+
~OutputRedirector() {
855+
dup2(OldFD, RedirectFD);
856+
close(OldFD);
857+
close(NewFD);
858+
}
859+
860+
SmallVector<char, 128> FilePath;
861+
bool Valid = true;
862+
863+
private:
864+
int RedirectFD;
865+
int OldFD;
866+
int NewFD;
867+
};
868+
869+
struct AutoDeleteFile {
870+
SmallVector<char, 128> FilePath;
871+
~AutoDeleteFile() {
872+
if (!FilePath.empty())
873+
sys::fs::remove(std::string(FilePath.data(), FilePath.size()));
874+
}
875+
};
876+
877+
class PrintOptionInfoTest : public ::testing::Test {
878+
public:
879+
// Return std::string because the output of a failing EXPECT check is
880+
// unreadable for StringRef. It also avoids any lifetime issues.
881+
template <typename... Ts> std::string runTest(Ts... OptionAttributes) {
882+
AutoDeleteFile File;
883+
{
884+
OutputRedirector Stdout(fileno(stdout));
885+
if (!Stdout.Valid)
886+
return "";
887+
File.FilePath = Stdout.FilePath;
888+
889+
StackOption<OptionValue> TestOption(Opt, cl::desc(HelpText),
890+
OptionAttributes...);
891+
printOptionInfo(TestOption, 25);
892+
outs().flush();
893+
}
894+
auto Buffer = MemoryBuffer::getFile(File.FilePath);
895+
if (!Buffer)
896+
return "";
897+
return Buffer->get()->getBuffer().str();
898+
}
899+
900+
enum class OptionValue { Val };
901+
const StringRef Opt = "some-option";
902+
const StringRef HelpText = "some help";
903+
904+
private:
905+
// This is a workaround for cl::Option sub-classes having their
906+
// printOptionInfo functions private.
907+
void printOptionInfo(const cl::Option &O, size_t Width) {
908+
O.printOptionInfo(Width);
909+
}
910+
};
911+
912+
TEST_F(PrintOptionInfoTest, PrintOptionInfoValueOptionalWithoutSentinel) {
913+
std::string Output =
914+
runTest(cl::ValueOptional,
915+
cl::values(clEnumValN(OptionValue::Val, "v1", "desc1")));
916+
917+
// clang-format off
918+
EXPECT_EQ(Output, (" -" + Opt + "=<value> - " + HelpText + "\n"
919+
" =v1 - desc1\n")
920+
.str());
921+
// clang-format on
922+
}
923+
924+
TEST_F(PrintOptionInfoTest, PrintOptionInfoValueOptionalWithSentinel) {
925+
std::string Output = runTest(
926+
cl::ValueOptional, cl::values(clEnumValN(OptionValue::Val, "v1", "desc1"),
927+
clEnumValN(OptionValue::Val, "", "")));
928+
929+
// clang-format off
930+
EXPECT_EQ(Output,
931+
(" -" + Opt + " - " + HelpText + "\n"
932+
" -" + Opt + "=<value> - " + HelpText + "\n"
933+
" =v1 - desc1\n")
934+
.str());
935+
// clang-format on
936+
}
937+
938+
TEST_F(PrintOptionInfoTest, PrintOptionInfoValueOptionalWithSentinelWithHelp) {
939+
std::string Output = runTest(
940+
cl::ValueOptional, cl::values(clEnumValN(OptionValue::Val, "v1", "desc1"),
941+
clEnumValN(OptionValue::Val, "", "desc2")));
942+
943+
// clang-format off
944+
EXPECT_EQ(Output, (" -" + Opt + " - " + HelpText + "\n"
945+
" -" + Opt + "=<value> - " + HelpText + "\n"
946+
" =v1 - desc1\n"
947+
" =<empty> - desc2\n")
948+
.str());
949+
// clang-format on
950+
}
951+
952+
TEST_F(PrintOptionInfoTest, PrintOptionInfoValueRequiredWithEmptyValueName) {
953+
std::string Output = runTest(
954+
cl::ValueRequired, cl::values(clEnumValN(OptionValue::Val, "v1", "desc1"),
955+
clEnumValN(OptionValue::Val, "", "")));
956+
957+
// clang-format off
958+
EXPECT_EQ(Output, (" -" + Opt + "=<value> - " + HelpText + "\n"
959+
" =v1 - desc1\n"
960+
" =<empty>\n")
961+
.str());
962+
// clang-format on
963+
}
964+
965+
TEST_F(PrintOptionInfoTest, PrintOptionInfoEmptyValueDescription) {
966+
std::string Output = runTest(
967+
cl::ValueRequired, cl::values(clEnumValN(OptionValue::Val, "v1", "")));
968+
969+
// clang-format off
970+
EXPECT_EQ(Output,
971+
(" -" + Opt + "=<value> - " + HelpText + "\n"
972+
" =v1\n").str());
973+
// clang-format on
974+
}
975+
842976
} // anonymous namespace

0 commit comments

Comments
 (0)