catchorg / Clara

A simple to use, composable, command line parser for C++ 11 and beyond
Boost Software License 1.0
648 stars 67 forks source link

Optional arguments to Opt using std::optional #89

Open parnmatt opened 5 years ago

parnmatt commented 5 years ago

There is code to handle std::optional via CLARA_CONFIG_OPTIONAL_TYPE; however Opt still is expecting an argument if none is given.

For example for logging: ./program, run with no logging (default), ./program -l enable logging, ./program -l file.log enable logging to named file.

This would make sense to use std::optional here.

For example, the default will not do anything; giving the "flag" -l will act like a pass the log through to std::clog; giving the optional with a filename -l /path/to/file.log will pass through to the /path/to/file.log.

std::ostream m_log{nullptr};
std::ofstream m_log_{};

clara::Opt{[&](std::optional<fs::path> filename) {
               if (!filename) {
                   m_log.rdbuf(std::clog.rdbuf());
                   return clara::ParserResult::ok(clara::ParseResultType::Matched);
               }
               m_log_.open(*filename);
               m_log.rdbuf(m_log_.rdbuf());
               return clara::ParserResult::ok(clara::ParseResultType::Matched);
           },
           "filename"s}["-l"s]["--log"s]("enable logging [set log file {stderr}]"s);
parnmatt commented 5 years ago

My current work around is to use a class with a set of stream operators.

edited slightly to only show the logging

namespace utils {

    // in utils.hxx
    class log {
    public:
        log() = default;

        explicit log(std::ostream& os)
            : m_log{os.rdbuf()} {}

        explicit log(fs::path const& path)
            : m_log_{path}
            , m_log{m_log_.rdbuf()} {}

        template <typename T>
        friend auto inline operator<<(utils::log& log, T const& value) -> utils::log& {
            log.m_log << value;
            return log;
        }

        friend auto operator>>(std::istream& is, log& log) -> std::istream&;

    private:
        std::ofstream m_log_{};
        std::ostream m_log{nullptr};
    };

    // in utils.cxx
    auto operator>>(std::istream& is, log& log) -> std::istream& {
        auto buf = std::string{};
        is >> buf;

        // check if boolean value
        auto const static negative = std::array{"0"s, "false"s, "off"s, "n"s, "no"s};
        auto const static positive = std::array{"1"s, "true"s, "on"s, "y"s, "yes"s};
        auto boolean = buf;
        std::transform(std::begin(boolean), std::end(boolean), std::begin(boolean),
                       [](unsigned char c) { return std::tolower(c); });

        if (std::find(std::begin(negative), std::end(negative), boolean) != std::end(negative)) {
            // disable logging

        } else if (std::find(std::begin(positive), std::end(positive), boolean) != std::end(positive)) {
            // enable logging to stderr
            log.m_log.rdbuf(std::clog.rdbuf());

        } else {
            // enable logging to file
            auto const filename = fs::path{buf};
            log.m_log_.open(filename);
            log.m_log.rdbuf(log.m_log_.rdbuf());
        }

        return is;
    }

}  // namespace utils

// relevant bit of parser
bool m_help{false};
utils::log m_log{};

auto args = clara::Help(m_help)
            | clara::Opt{m_log, "false|true|filename"s}["-l"s]["--log"s](
                "<false> disable logging\n"
                "<true> enable logging to stderr\n"
                "<filename> enable logging to filename\t{false}"s);

As you can see it's not quite ideal; I also have to call it ./program -l true rather than ./program -l. Considering Clara also has bool parsing, and std::optional, I think it would be a nice addition to the library.