cxxopts icon indicating copy to clipboard operation
cxxopts copied to clipboard

`parse_positional` sets `m_positional`. We need a way to append into it too.

Open KOLANICH opened this issue 3 years ago • 4 comments

KOLANICH avatar Aug 15 '21 21:08 KOLANICH

Are you able to read the positional arguments before you call it again?

jarro2783 avatar Aug 25 '21 11:08 jarro2783

What do you mean under positional arguments (the spec for them or the actual arguments provided by a user) and what do you mean under it ?

My wrapper currently looks like this:

cxxopts.cpp
#include <HydrArgs/HydrArgs.hpp>
#include <HydrArgs/toolbox.hpp>
#include <HydrArgs/fallback/errors.hpp>

#include <cxxopts.hpp>

#include <memory>
#include <string>
#include <algorithm>
#include <streambuf>
#include <sstream>


struct CXXOptsBackend: public IBackendOwnStoredSpec{
	cxxopts::Options app;
	cxxopts::OptionAdder optAdder;
	std::vector<std::string> remaining;
	std::vector<char *> remainingPtrs;
	std::vector<cxxopts::Option> nativeArgs;

	std::vector<std::string> positionalArgs;

	bool show_help = false;

	CXXOptsBackend(const std::string& name, const std::string& descr, const std::string& usage [[maybe_unused]], std::vector<Arg*> dashedSpec, std::vector<Arg*> positionalSpec, Streams streams): IBackendOwnStoredSpec(dashedSpec, positionalSpec, streams), app(name, descr), optAdder(app.add_options()){
		std::vector<std::string> positionals;

		for(auto argPtr: dashedSpec){
			_addArg(argPtr, false);
		}

		for(auto argPtr: positionalSpec){
			_addArg(argPtr, true);
		}

		app.parse_positional(positionals);
		app.allow_unrecognised_options();
		//app.set_tab_expansion();
		optAdder(DEFAULT_HELP_ARG.long_name.undashed, DEFAULT_HELP_ARG.doc);
	}

	virtual void _addArg(Arg* argPtr, bool isPositional) override {
		switch(argPtr->type){
			case ArgType::flag:
			{
				auto specOptPtr = static_cast<FlagArg*>(argPtr);
				optAdder(specOptPtr->name, specOptPtr->description);
			}
			break;
			case ArgType::s4:
			{
				auto specOptPtr = static_cast<IntArg*>(argPtr);
				std::stringstream s;
				s << specOptPtr->value;
				optAdder(specOptPtr->name, specOptPtr->description, cxxopts::value<decltype(specOptPtr->value)>()->default_value(s.str()));
			}
			break;
			case ArgType::string:
			case ArgType::path:
			{
				auto specOptPtr = static_cast<StringArg*>(argPtr);
				optAdder(specOptPtr->name, specOptPtr->description, cxxopts::value<decltype(specOptPtr->value)>()->default_value(specOptPtr->value));
			}
			break;
		}
		if(isPositional){
			//  `m_positional` is overridden (not appended) on every `app.parse_positional` (this function adds positional arguments). There is no way to only append element there. So we have to populate an own array and then call `app.parse_positional` in `_seal`
			positionalArgs.emplace_back(argPtr->name);
			/*
			quote from the docs:
			options.parse_positional({"first", "second", "last"})
			where "last" should be the name of an option with a container type, and the others should have a single value.
			*/
		}
	}
	virtual ~CXXOptsBackend() override = default;

	virtual void _seal() override {
		app.parse_positional(positionalArgs);
	}

	virtual void _unseal() override {
	}

	virtual bool isSealed() const override {
		return false;
	}

	virtual void printHelp(std::ostream &stream, const char * const argv0 [[maybe_unused]]) override {
		stream << app.help({""});
	}

	virtual ParseResult _parseArgs(CLIRawArgs rawArgs) override {
		auto result = app.parse(rawArgs.argc, rawArgs.argv);

		PROCESS_CASE_OF_HELP_CALLED(result[DEFAULT_HELP_ARG.long_name.undashed].as<bool>());

		size_t i=0;

		std::pair<decltype(dashedSpec)&, bool> specs[]{
			{dashedSpec, false},
			{positionalSpec, true},
		};
		for(auto p: specs){
			auto spec = p.first;
			auto isPositional = p.second;
			for(auto argPtr: spec){
				if(result.count(argPtr->name)){
					switch(argPtr->type){
						case ArgType::flag:
						{
							auto specOptPtr = static_cast<FlagArg*>(argPtr);
							specOptPtr->value = result[argPtr->name].as<bool>();
						}
						break;
						case ArgType::s4:
						{
							auto specOptPtr = static_cast<IntArg*>(argPtr);
							specOptPtr->value = result[argPtr->name].as<decltype(specOptPtr->value)>();
						}
						break;
						case ArgType::string:
						case ArgType::path:
						{
							auto specOptPtr = static_cast<StringArg*>(argPtr);
							specOptPtr->value = result[argPtr->name].as<decltype(specOptPtr->value)>();
						}
						break;
					}
				} else {
					PROCESS_ARGUMENT_MISSING_CASE();
				}
				++i;
			}
		}

		//std::transform(begin(remaining), end(remaining), begin(remainingPtrs), [](std::string &el) -> char * {return el.data();});
		return {
			.parsingStatus = STATUS_OK,
			.rest={
				/*.argc = 0,
				.argv = remainingPtrs.data()*/
				.argc = 0,
				.argv = nullptr
			}
		};
	}
};

IArgsParser* argsParserFactory(const std::string& name, const std::string& descr, const std::string& usage [[maybe_unused]], std::vector<Arg*> dashedSpec, std::vector<Arg*> positionalSpec, Streams streams){
	return new CXXOptsBackend(name, descr, usage, dashedSpec, positionalSpec, streams);
}

Note the method _seal (it is called before _parseArgs by the framework). If we could append positional args, we would be able to get rid of it.

KOLANICH avatar Aug 25 '21 15:08 KOLANICH

I see the problem. You could probably just call parse_positional in your _parseArgs before you call cxxopts::Options::parse. But I can see that it makes sense to have an append positional function. I can add that.

jarro2783 avatar Oct 09 '21 06:10 jarro2783

You could probably just call parse_positional in your _parseArgs before you call cxxopts::Options::parse.

It is already done like that (seal, which calls _seal is called by parseArgs before it calls _parseArgs (methods with leading underscores are actual implementations without checks and bookkeeping, the ones without leading underscores are the methods of the framework and trigger the checks, end users (not ones creating backends, but ones using HydrArgs in own apps) should use only them, unless they have really strong reasons to use )), but I'm not happy with such a workaround.

BTW, I have created a GH repo for the project, though it is completely unfinished currently. https://github.com/HydrArgs/HydrArgs

KOLANICH avatar Oct 10 '21 20:10 KOLANICH