How to hide a particular enum value from llvm Command Line help string?

Viewed 205

I am using llvm CommandLine library for argument parsing in one of my C++ projects. It has an option which accepts multiple logging levels, which is of enum type as below:

enum LogLevels {
    error,
    critical_warnings,
    all_warnings,
    info,
    trace
};

cl::OptionCategory Logging("Verbose Options:");

cl::opt<LogLevels> verbose("logging", cl::desc("Control logging levels:"),
        cl::values(
            clEnumValN(error,"error-only", "Level:1 - Report fatal errors"),
            clEnumValN(critical_warnings,"critical-warnings", "Level:2 - Report critical warnings and errors (default)"),
            clEnumValN(all_warnings, "all-warnings", "Level:3 - Report all warnings and errors"),
            clEnumValN(info, "info", "Level:4 - Report info messages along with error/warnings"),
            clEnumValN(trace, "trace", "Level:5 - Report all internal/developer details in logs")),
            cl::init(LogLevels::critical_warnings), cl::cat(Logging)
        );

How can I hide a particular enum value from user help string (output of -help) but still continue to accept it? In this case, I want to hide "trace" from the user help string and still continue to accept --logging=trace for internal use only.

I tried looking at https://llvm.org/docs/CommandLine.html# but couldn't figure it out. I need something similar to the cl::Hidden or cl::ReallyHidden modifiers, but for enum values.

Current output from -help is:

Verbose Options::

  --logging=<value>                                   - Control logging levels:
    =error-only                                       -   Level:1 - Report fatal errors
    =critical-warnings                                -   Level:2 - Report critical warnings and errors (default)
    =all-warnings                                     -   Level:3 - Report all warnings and errors
    =info                                             -   Level:4 - Report info messages along with error/warnings
    =trace                                            -   Level:5 - Report all internal/developer details in logs
2 Answers

The OptionEnumValue applies to entire options, and as we can see from the constructor of the cl::opt class template

 template <class DataType, bool ExternalStorage = false,
           class ParserClass = parser<DataType>>
 class opt : public Option,
             public opt_storage<DataType, ExternalStorage,
                                std::is_class<DataType>::value> {
     // ...

     template <class... Mods>
     explicit opt(const Mods &... Ms)
         : Option(Optional, NotHidden), Parser(*this) {
       apply(this, Ms...);
       done();
     }

     // ...
 };

the mods (values, in the context of the OP) for a given option cannot, by themselves, hold a help visibility option; i.e., does not have its own cl::OptionHidden state.

I looks as if the only workaround to hide the trace option variant would be to remove the trace value from the verbose option and create it as a stand-alone Hidden option with no values/mods, say --logging_trace.

As stated above, there's no cl::OptionHidden for enum values (just the overall option). Fortunately, the llvm::cl::parser<> class itself is a template , so we can specialize to do whatever we want.

If you look at the source for generic_parser_base::printOptionInfo, you'll see what we really want to be able to specialize shouldPrintOption(), but that method is static...weak sauce. What isn't static is getNumOptions(). So we can do something like this:

#include <llvm/Support/CommandLine.h>


enum MyEnum{
  VISIBLE,
  HIDDEN
};

llvm::cl::opt<MyEnum> myOpt("myopt",
                            llvm::cl::values(
                                clEnumValN(MyEnum::VISIBLE,"visible", "Visible"),
                                clEnumValN(MyEnum::HIDDEN,"hidden", "Hidden")));

namespace llvm::cl{
template <> unsigned parser<MyEnum>::getNumOptions() const {
  return 1; // Lies!
}

}

This will get you almost all of the way there. The problem is the generic_parser_base::findOption(), which uses getNumOptions() but is neither virtual nor templated. Fortunately, findOption() is only used two places parser<>::addLiteralOption(...) and parser<parser>::removeLiteralOption(...), and these are templated, so we can specialize them (If you never remove options, then you can also skip the latter). For example:

#include <llvm/Support/CommandLine.h>

enum MyEnum{
  VISIBLE,
  HIDDEN
};

llvm::cl::opt<MyEnum> myOpt("myopt",
                            llvm::cl::values(
                                clEnumValN(MyEnum::VISIBLE,"visible", "Visible"),
                                clEnumValN(MyEnum::HIDDEN,"hidden", "Hidden")));

namespace llvm::cl{
template <> unsigned parser<MyEnum>::getNumOptions() const {
  return 1; // Lies!
}

template<>
template <class DT>
void addLiteralOption(StringRef Name, const DT &V, StringRef HelpStr) {
  //Commending out the findOption() check below:
  //assert(findOption(Name) == Values.size() && "Option already exists!");
  OptionInfo X(Name, static_cast<DataType>(V), HelpStr);
  Values.push_back(X);
  AddLiteralOption(Owner, Name);
}

}

This will allow you to use --myopt=hidden from the command line, but the --help output will only show something like:

 --myopt=<value>                   -
    =visible                       -   Visible
Related