Clara icon indicating copy to clipboard operation
Clara copied to clipboard

Optional arguments to Opt using std::optional

Open parnmatt opened this issue 6 years ago • 1 comments

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 avatar Apr 10 '19 14:04 parnmatt

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.

parnmatt avatar Apr 10 '19 18:04 parnmatt