diff options
-rw-r--r-- | docs/CommandLine.rst | 75 | ||||
-rw-r--r-- | include/llvm/Support/CommandLine.h | 50 | ||||
-rw-r--r-- | lib/Support/CommandLine.cpp | 218 | ||||
-rw-r--r-- | unittests/Support/CommandLineTest.cpp | 8 |
4 files changed, 323 insertions, 28 deletions
diff --git a/docs/CommandLine.rst b/docs/CommandLine.rst index ea0c369670..8a266e23b2 100644 --- a/docs/CommandLine.rst +++ b/docs/CommandLine.rst @@ -618,6 +618,53 @@ would yield the help output: -help - display available options (-help-hidden for more) -o <filename> - Specify output filename +Grouping options into categories +-------------------------------- + +If our program has a large number of options it may become difficult for users +of our tool to navigate the output of ``-help``. To alleviate this problem we +can put our options into categories. This can be done by declaring option +categories (`cl::OptionCategory`_ objects) and then placing our options into +these categories using the `cl::cat`_ option attribute. For example: + +.. code-block:: c++ + + cl::OptionCategory StageSelectionCat("Stage Selection Options", + "These control which stages are run."); + + cl::opt<bool> Preprocessor("E",cl::desc("Run preprocessor stage."), + cl::cat(StageSelectionCat)); + + cl::opt<bool> NoLink("c",cl::desc("Run all stages except linking."), + cl::cat(StageSelectionCat)); + +The output of ``-help`` will become categorized if an option category is +declared. The output looks something like :: + + OVERVIEW: This is a small program to demo the LLVM CommandLine API + USAGE: Sample [options] + + OPTIONS: + + General options: + + -help - Display available options (-help-hidden for more) + -help-list - Display list of available options (-help-list-hidden for more) + + + Stage Selection Options: + These control which stages are run. + + -E - Run preprocessor stage. + -c - Run all stages except linking. + +In addition to the behaviour of ``-help`` changing when an option category is +declared, the command line option ``-help-list`` becomes visible which will +print the command line options as uncategorized list. + +Note that Options that are not explicitly categorized will be placed in the +``cl::GeneralCategory`` category. + .. _Reference Guide: Reference Guide @@ -946,6 +993,11 @@ This section describes the basic attributes that you can specify on options. of the usual modifiers on multi-valued options (besides ``cl::ValueDisallowed``, obviously). +.. _cl::cat: + +* The **cl::cat** attribute specifies the option category that the option + belongs to. The category should be a `cl::OptionCategory`_ object. + Option Modifiers ---------------- @@ -1385,6 +1437,29 @@ For example: cl::extrahelp("\nADDITIONAL HELP:\n\n This is the extra help\n"); +.. _cl::OptionCategory: + +The ``cl::OptionCategory`` class +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``cl::OptionCategory`` class is a simple class for declaring +option categories. + +.. code-block:: c++ + + namespace cl { + class OptionCategory; + } + +An option category must have a name and optionally a description which are +passed to the constructor as ``const char*``. + +Note that declaring an option category and associating it with an option before +parsing options (e.g. statically) will change the output of ``-help`` from +uncategorized to categorized. If an option category is declared but not +associated with an option then it will be hidden from the output of ``-help`` +but will be shown in the output of ``-help-hidden``. + .. _different parser: .. _discussed previously: diff --git a/include/llvm/Support/CommandLine.h b/include/llvm/Support/CommandLine.h index 2e84d7b349..19b716427f 100644 --- a/include/llvm/Support/CommandLine.h +++ b/include/llvm/Support/CommandLine.h @@ -137,7 +137,23 @@ enum MiscFlags { // Miscellaneous flags to adjust argument Sink = 0x04 // Should this cl::list eat all unknown options? }; +//===----------------------------------------------------------------------===// +// Option Category class +// +class OptionCategory { +private: + const char *const Name; + const char *const Description; + void registerCategory(); +public: + OptionCategory(const char *const Name, const char *const Description = 0) + : Name(Name), Description(Description) { registerCategory(); } + const char *getName() { return Name; } + const char *getDescription() { return Description; } +}; +// The general Option Category (used as default category). +extern OptionCategory GeneralCategory; //===----------------------------------------------------------------------===// // Option Base class @@ -173,10 +189,12 @@ class Option { unsigned Position; // Position of last occurrence of the option unsigned AdditionalVals;// Greater than 0 for multi-valued option. Option *NextRegistered; // Singly linked list of registered options. + public: - const char *ArgStr; // The argument string itself (ex: "help", "o") - const char *HelpStr; // The descriptive text message for -help - const char *ValueStr; // String describing what the value of this option is + const char *ArgStr; // The argument string itself (ex: "help", "o") + const char *HelpStr; // The descriptive text message for -help + const char *ValueStr; // String describing what the value of this option is + OptionCategory *Category; // The Category this option belongs to inline enum NumOccurrencesFlag getNumOccurrencesFlag() const { return (enum NumOccurrencesFlag)Occurrences; @@ -214,13 +232,14 @@ public: void setFormattingFlag(enum FormattingFlags V) { Formatting = V; } void setMiscFlag(enum MiscFlags M) { Misc |= M; } void setPosition(unsigned pos) { Position = pos; } + void setCategory(OptionCategory &C) { Category = &C; } protected: explicit Option(enum NumOccurrencesFlag OccurrencesFlag, enum OptionHidden Hidden) : NumOccurrences(0), Occurrences(OccurrencesFlag), Value(0), HiddenFlag(Hidden), Formatting(NormalFormatting), Misc(0), Position(0), AdditionalVals(0), NextRegistered(0), - ArgStr(""), HelpStr(""), ValueStr("") { + ArgStr(""), HelpStr(""), ValueStr(""), Category(&GeneralCategory) { } inline void setNumAdditionalVals(unsigned n) { AdditionalVals = n; } @@ -312,6 +331,16 @@ struct LocationClass { template<class Ty> LocationClass<Ty> location(Ty &L) { return LocationClass<Ty>(L); } +// cat - Specifiy the Option category for the command line argument to belong +// to. +struct cat { + OptionCategory &Category; + cat(OptionCategory &c) : Category(c) {} + + template<class Opt> + void apply(Opt &O) const { O.setCategory(Category); } +}; + //===----------------------------------------------------------------------===// // OptionValue class @@ -1674,10 +1703,15 @@ struct extrahelp { }; void PrintVersionMessage(); -// This function just prints the help message, exactly the same way as if the -// -help option had been given on the command line. -// NOTE: THIS FUNCTION TERMINATES THE PROGRAM! -void PrintHelpMessage(); + +/// This function just prints the help message, exactly the same way as if the +/// -help or -help-hidden option had been given on the command line. +/// +/// NOTE: THIS FUNCTION TERMINATES THE PROGRAM! +/// +/// \param hidden if true will print hidden options +/// \param categorized if true print options in categories +void PrintHelpMessage(bool Hidden=false, bool Categorized=false); } // End namespace cl diff --git a/lib/Support/CommandLine.cpp b/lib/Support/CommandLine.cpp index 560d7eb289..8e937794be 100644 --- a/lib/Support/CommandLine.cpp +++ b/lib/Support/CommandLine.cpp @@ -33,6 +33,7 @@ #include "llvm/Support/system_error.h" #include <cerrno> #include <cstdlib> +#include <map> using namespace llvm; using namespace cl; @@ -106,6 +107,17 @@ void Option::addArgument() { MarkOptionsChanged(); } +// This collects the different option categories that have been registered. +typedef SmallPtrSet<OptionCategory*,16> OptionCatSet; +static ManagedStatic<OptionCatSet> RegisteredOptionCategories; + +// Initialise the general option category. +OptionCategory llvm::cl::GeneralCategory("General options"); + +void OptionCategory::registerCategory() +{ + RegisteredOptionCategories->insert(this); +} //===----------------------------------------------------------------------===// // Basic, shared command line option processing machinery. @@ -1222,11 +1234,20 @@ sortOpts(StringMap<Option*> &OptMap, namespace { class HelpPrinter { +protected: const bool ShowHidden; + typedef SmallVector<std::pair<const char *, Option*>,128> StrOptionPairVector; + // Print the options. Opts is assumed to be alphabetically sorted. + virtual void printOptions(StrOptionPairVector &Opts, size_t MaxArgLen) { + for (size_t i = 0, e = Opts.size(); i != e; ++i) + Opts[i].second->printOptionInfo(MaxArgLen); + } public: explicit HelpPrinter(bool showHidden) : ShowHidden(showHidden) {} + virtual ~HelpPrinter() {} + // Invoke the printer. void operator=(bool Value) { if (Value == false) return; @@ -1236,7 +1257,7 @@ public: StringMap<Option*> OptMap; GetOptionInfo(PositionalOpts, SinkOpts, OptMap); - SmallVector<std::pair<const char *, Option*>, 128> Opts; + StrOptionPairVector Opts; sortOpts(OptMap, Opts, ShowHidden); if (ProgramOverview) @@ -1267,12 +1288,12 @@ public: MaxArgLen = std::max(MaxArgLen, Opts[i].second->getOptionWidth()); outs() << "OPTIONS:\n"; - for (size_t i = 0, e = Opts.size(); i != e; ++i) - Opts[i].second->printOptionInfo(MaxArgLen); + printOptions(Opts, MaxArgLen); // Print any extra help the user has declared. for (std::vector<const char *>::iterator I = MoreHelp->begin(), - E = MoreHelp->end(); I != E; ++I) + E = MoreHelp->end(); + I != E; ++I) outs() << *I; MoreHelp->clear(); @@ -1280,21 +1301,152 @@ public: exit(1); } }; + +class CategorizedHelpPrinter : public HelpPrinter { +public: + explicit CategorizedHelpPrinter(bool showHidden) : HelpPrinter(showHidden) {} + + // Helper function for printOptions(). + // It shall return true if A's name should be lexographically + // ordered before B's name. It returns false otherwise. + static bool OptionCategoryCompare(OptionCategory *A, OptionCategory *B) { + int Length = strcmp(A->getName(), B->getName()); + assert(Length != 0 && "Duplicate option categories"); + return Length < 0; + } + + // Make sure we inherit our base class's operator=() + using HelpPrinter::operator= ; + +protected: + virtual void printOptions(StrOptionPairVector &Opts, size_t MaxArgLen) { + std::vector<OptionCategory *> SortedCategories; + std::map<OptionCategory *, std::vector<Option *> > CategorizedOptions; + + // Collect registered option categories into vector in preperation for + // sorting. + for (OptionCatSet::const_iterator I = RegisteredOptionCategories->begin(), + E = RegisteredOptionCategories->end(); + I != E; ++I) + SortedCategories.push_back(*I); + + // Sort the different option categories alphabetically. + assert(SortedCategories.size() > 0 && "No option categories registered!"); + std::sort(SortedCategories.begin(), SortedCategories.end(), + OptionCategoryCompare); + + // Create map to empty vectors. + for (std::vector<OptionCategory *>::const_iterator + I = SortedCategories.begin(), + E = SortedCategories.end(); + I != E; ++I) + CategorizedOptions[*I] = std::vector<Option *>(); + + // Walk through pre-sorted options and assign into categories. + // Because the options are already alphabetically sorted the + // options within categories will also be alphabetically sorted. + for (size_t I = 0, E = Opts.size(); I != E; ++I) { + Option *Opt = Opts[I].second; + assert(CategorizedOptions.count(Opt->Category) > 0 && + "Option has an unregistered category"); + CategorizedOptions[Opt->Category].push_back(Opt); + } + + // Now do printing. + for (std::vector<OptionCategory *>::const_iterator + Category = SortedCategories.begin(), + E = SortedCategories.end(); + Category != E; ++Category) { + // Hide empty categories for -help, but show for -help-hidden. + bool IsEmptyCategory = CategorizedOptions[*Category].size() == 0; + if (!ShowHidden && IsEmptyCategory) + continue; + + // Print category information. + outs() << "\n"; + outs() << (*Category)->getName() << ":\n"; + + // Check if description is set. + if ((*Category)->getDescription() != 0) + outs() << (*Category)->getDescription() << "\n\n"; + else + outs() << "\n"; + + // When using -help-hidden explicitly state if the category has no + // options associated with it. + if (IsEmptyCategory) { + outs() << " This option category has no options.\n"; + continue; + } + // Loop over the options in the category and print. + for (std::vector<Option *>::const_iterator + Opt = CategorizedOptions[*Category].begin(), + E = CategorizedOptions[*Category].end(); + Opt != E; ++Opt) + (*Opt)->printOptionInfo(MaxArgLen); + } + } +}; + +// This wraps the Uncategorizing and Categorizing printers and decides +// at run time which should be invoked. +class HelpPrinterWrapper { +private: + HelpPrinter &UncategorizedPrinter; + CategorizedHelpPrinter &CategorizedPrinter; + +public: + explicit HelpPrinterWrapper(HelpPrinter &UncategorizedPrinter, + CategorizedHelpPrinter &CategorizedPrinter) : + UncategorizedPrinter(UncategorizedPrinter), + CategorizedPrinter(CategorizedPrinter) { } + + // Invoke the printer. + void operator=(bool Value); +}; + } // End anonymous namespace -// Define the two HelpPrinter instances that are used to print out help, or -// help-hidden... -// -static HelpPrinter NormalPrinter(false); -static HelpPrinter HiddenPrinter(true); +// Declare the four HelpPrinter instances that are used to print out help, or +// help-hidden as an uncategorized list or in categories. +static HelpPrinter UncategorizedNormalPrinter(false); +static HelpPrinter UncategorizedHiddenPrinter(true); +static CategorizedHelpPrinter CategorizedNormalPrinter(false); +static CategorizedHelpPrinter CategorizedHiddenPrinter(true); + +// Declare HelpPrinter wrappers that will decide whether or not to invoke +// a categorizing help printer +static HelpPrinterWrapper WrappedNormalPrinter(UncategorizedNormalPrinter, + CategorizedNormalPrinter); +static HelpPrinterWrapper WrappedHiddenPrinter(UncategorizedHiddenPrinter, + CategorizedHiddenPrinter); + +// Define uncategorized help printers. +// -help-list is hidden by default because if Option categories are being used +// then -help behaves the same as -help-list. static cl::opt<HelpPrinter, true, parser<bool> > -HOp("help", cl::desc("Display available options (-help-hidden for more)"), - cl::location(NormalPrinter), cl::ValueDisallowed); +HLOp("help-list", + cl::desc("Display list of available options (-help-list-hidden for more)"), + cl::location(UncategorizedNormalPrinter), cl::Hidden, cl::ValueDisallowed); static cl::opt<HelpPrinter, true, parser<bool> > +HLHOp("help-list-hidden", + cl::desc("Display list of all available options"), + cl::location(UncategorizedHiddenPrinter), cl::Hidden, cl::ValueDisallowed); + +// Define uncategorized/categorized help printers. These printers change their +// behaviour at runtime depending on whether one or more Option categories have +// been declared. +static cl::opt<HelpPrinterWrapper, true, parser<bool> > +HOp("help", cl::desc("Display available options (-help-hidden for more)"), + cl::location(WrappedNormalPrinter), cl::ValueDisallowed); + +static cl::opt<HelpPrinterWrapper, true, parser<bool> > HHOp("help-hidden", cl::desc("Display all available options"), - cl::location(HiddenPrinter), cl::Hidden, cl::ValueDisallowed); + cl::location(WrappedHiddenPrinter), cl::Hidden, cl::ValueDisallowed); + + static cl::opt<bool> PrintOptions("print-options", @@ -1306,6 +1458,24 @@ PrintAllOptions("print-all-options", cl::desc("Print all option values after command line parsing"), cl::Hidden, cl::init(false)); +void HelpPrinterWrapper::operator=(bool Value) { + if (Value == false) + return; + + // Decide which printer to invoke. If more than one option category is + // registered then it is useful to show the categorized help instead of + // uncategorized help. + if (RegisteredOptionCategories->size() > 1) { + // unhide -help-list option so user can have uncategorized output if they + // want it. + HLOp.setHiddenFlag(NotHidden); + + CategorizedPrinter = true; // Invoke categorized printer + } + else + UncategorizedPrinter = true; // Invoke uncategorized printer +} + // Print the value of each option. void cl::PrintOptionValues() { if (!PrintOptions && !PrintAllOptions) return; @@ -1393,14 +1563,22 @@ VersOp("version", cl::desc("Display the version of this program"), cl::location(VersionPrinterInstance), cl::ValueDisallowed); // Utility function for printing the help message. -void cl::PrintHelpMessage() { - // This looks weird, but it actually prints the help message. The - // NormalPrinter variable is a HelpPrinter and the help gets printed when - // its operator= is invoked. That's because the "normal" usages of the - // help printer is to be assigned true/false depending on whether the - // -help option was given or not. Since we're circumventing that we have - // to make it look like -help was given, so we assign true. - NormalPrinter = true; +void cl::PrintHelpMessage(bool Hidden, bool Categorized) { + // This looks weird, but it actually prints the help message. The Printers are + // types of HelpPrinter and the help gets printed when its operator= is + // invoked. That's because the "normal" usages of the help printer is to be + // assigned true/false depending on whether -help or -help-hidden was given or + // not. Since we're circumventing that we have to make it look like -help or + // -help-hidden were given, so we assign true. + + if (!Hidden && !Categorized) + UncategorizedNormalPrinter = true; + else if (!Hidden && Categorized) + CategorizedNormalPrinter = true; + else if (Hidden && !Categorized) + UncategorizedHiddenPrinter = true; + else + CategorizedHiddenPrinter = true; } /// Utility function for printing version number. diff --git a/unittests/Support/CommandLineTest.cpp b/unittests/Support/CommandLineTest.cpp index 43c8cbd123..815212ff39 100644 --- a/unittests/Support/CommandLineTest.cpp +++ b/unittests/Support/CommandLineTest.cpp @@ -66,4 +66,12 @@ TEST(CommandLineTest, ParseEnvironmentToLocalVar) { #endif // SKIP_ENVIRONMENT_TESTS +TEST(CommandLineTest, UseOptionCategory) { + cl::OptionCategory TestCategory("Test Options", "Description"); + cl::opt<int> TestOption("test-option", cl::cat(TestCategory)); + + ASSERT_EQ(&TestCategory,TestOption.Category) << "Failed to assign Option " + "Category."; +} + } // anonymous namespace |