view C++/OptionParser.cpp @ 296:5806c767aec7

Zip: fix resize if read less than requested Task: #312
author David Demelier <markand@malikania.fr>
date Thu, 13 Nov 2014 21:10:13 +0100
parents ff2db0ed78f1
children 99e83685d4da
line wrap: on
line source

/*
 * OptionParser.cpp -- safe getopt(3) replacement
 *
 * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <iostream>
#include <iomanip>

#include "OptionParser.h"

/* --------------------------------------------------------
 * Option class
 * -------------------------------------------------------- */

Option::Option(const std::string &name, const std::string &help, Type type, int flags)
	: m_name(name)
	, m_help(help)
	, m_flags(flags)
	, m_type(type)
{
}

const std::string &Option::name() const
{
	return m_name;
}

const std::string &Option::token() const
{
	return m_token;
}

const std::string &Option::help() const
{
	return m_help;
}

Option::Type Option::type() const
{
	return m_type;
}

int Option::flags() const
{
	return m_flags;
}

/* --------------------------------------------------------
 * OptionValue class
 * -------------------------------------------------------- */

OptionValue::OptionValue(const std::string &name)
	: m_name(name)
	, m_enabled(true)
{
}

OptionValue::OptionValue(const std::string &name, const std::string &value)
	: m_name(name)
	, m_value(value)
{
}

const std::string &OptionValue::name() const
{
	return m_name;
}

const std::string &OptionValue::value() const
{
	return m_value;
}

OptionValue::operator bool() const
{
	return m_enabled;
}

OptionValue::operator std::string() const
{
	return m_value;
}

/* --------------------------------------------------------
 * OptionResult class
 * -------------------------------------------------------- */

auto OptionResult::begin() -> decltype(m_values.begin())
{
	return m_values.begin();
}

auto OptionResult::begin() const -> decltype(m_values.begin())
{
	return m_values.begin();
}

auto OptionResult::cbegin() const -> decltype(m_values.cbegin())
{
	return m_values.begin();
}

auto OptionResult::end() -> decltype(m_values.end())
{
	return m_values.end();
}

auto OptionResult::end() const -> decltype(m_values.end())
{
	return m_values.end();
}

auto OptionResult::cend() const -> decltype(m_values.cend())
{
	return m_values.cend();
}

int OptionResult::count() const
{
	return static_cast<int>(m_values.size());
}

int OptionResult::total() const
{
	return m_total;
}

/* --------------------------------------------------------
 * OptionParser class
 * -------------------------------------------------------- */

OptionParser::Args OptionParser::unpack(const Args &args) const
{
	if (m_style != Getopt)
		return args;

	Args ret;

	for (const auto &s : args) {
		auto length = s.length();

		if (length < 2 || s[0] != '-' || s[1] == '-') {
			ret.push_back(s);
			continue;
		}

		for (int i = 1; i < (int)length; ++i) {
			ret.push_back(std::string("-") + std::string(&s[i], 1));
		}
	}

	return ret;
}

OptionParser::OptionParser(Style style)
	: m_style(style)
{
}

void OptionParser::add(Option &&option)
{
	const auto &length = option.m_name.size();

	if (length > m_maxlength)
		m_maxlength = length;

	/*
	 * If style is Getopt, we should use a double dash as the long option
	 * like --color and a single for short options like -v
	 */
	if (m_style == Getopt) {
		if (length > 1)
			option.m_token = "--" + option.m_name;
		else
			option.m_token = "-" + option.m_name;
	} else
		option.m_token = "/" + option.m_name;

	m_options.emplace(std::make_pair(option.m_token, std::move(option)));
}

void OptionParser::add(const Option &option)
{
	add(Option(option));
}

OptionResult OptionParser::parse(int argc, const char * const *argv, int flags)
{
	Args args;

	for (int i = 0; i < argc; ++i)
		args.push_back(argv[i]);

	return parse(args, flags);
}

OptionResult OptionParser::parse(const Args &argslist, int flags)
{
	OptionResult result;
	int i;
	auto args = unpack(argslist);
	auto length = args.size();

	for (i = 0; i < (int)length; ++i) {
		/* Not a valid option at all? */
		if (m_options.count(args[i]) == 0) {
			/* When breaking is enabled we should not emit a warning */
			if (flags & Strict) {
				if (!(flags & Silent)) {
					log(args[i], args[i] + " is not a valid option");
					usage();
				}

				break;
			} else if (flags & BreakNonOption)
				break;
			else
				continue;
		}

		/*
		 * At this step, we have an option name valid, check if it requires
		 * an argument or not.
		 */
		const auto &option = m_options.at(args[i]);
		const auto &name = option.name();
		auto type = option.type();

		/*
		 * Already present and must appears only once?
		 */
		if ((option.flags() & Option::Single) && m_placed.count(name) > 0) {
			if (!(flags & Silent)) {
				log(name, "option " + name + " must appear only once");

				if (flags & Strict) {
					usage();
					break;
				}
			} else if (flags & Strict)
				break;
		}

		/*
		 * Need an argument or it is a simple switch ?
		 */
		if (type == Option::Type::Switch)
			result.m_values.push_back(OptionValue(name));
		else {
			if (i + 1 >= (int)length)
				result.m_values.push_back(OptionValue(name, ""));
			else {
				result.m_values.push_back(OptionValue(name, args[i + 1]));
				++ i;
			}
		}

		m_placed.insert(name);
	}

	result.m_total = i;

	return result;

}

void OptionParser::usage()
{
	std::cout << "options available: " << std::endl;

	for (const auto &option : m_options) {
		std::cout << " " << std::setw(m_maxlength) << option.second.name();
		std::cout << "\t" << option.second.help() << std::endl;
	}
}

void OptionParser::log(const std::string &, const std::string &error)
{
	std::cout << error << std::endl;
}