Commit 0537a988 authored by Andrew Trick's avatar Andrew Trick
Browse files

Support command line option categories.

Patch by Dan Liew!

llvm-svn: 181253
parent d25db7ed
Loading
Loading
Loading
Loading
+75 −0
Original line number Diff line number Diff line
@@ -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:

+42 −8
Original line number Diff line number Diff line
@@ -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
  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

+198 −20
Original line number Diff line number Diff line
@@ -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.
+8 −0
Original line number Diff line number Diff line
@@ -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