changeset 231:2f1d820e6e33

Merge with OptionParser
author David Demelier <markand@malikania.fr>
date Sun, 22 Jun 2014 16:19:31 +0200
parents 5e1001649d07 (current diff) 96ff112d05cf (diff)
children 420b1710355f
files
diffstat 2 files changed, 556 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/OptionParser.cpp	Sun Jun 22 16:19:31 2014 +0200
@@ -0,0 +1,228 @@
+/*
+ * 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_type(type)
+	, m_flags(flags)
+{
+}
+
+const std::string &Option::name() const
+{
+	return m_name;
+}
+
+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
+ * -------------------------------------------------------- */
+
+void OptionParser::add(Option &&option)
+{
+	const auto &name = option.name();
+
+	if (name.length() > m_maxlength)
+		m_maxlength = name.length();
+
+	m_options.emplace(std::make_pair(name, std::move(option)));
+}
+
+void OptionParser::add(const Option &option)
+{
+	add(const_cast<Option &>(option));
+}
+
+OptionResult OptionParser::parse(int argc, char **argv, int flags)
+{
+	OptionResult result;
+	int i;
+
+	for (i = 0; i < argc; ++i) {
+		/* Not a valid option at all? */
+		if (m_options.count(argv[i]) == 0) {
+			log(argv[i], std::string(argv[i]) + " is not a valid option");
+
+			if (flags & Strict) {
+				usage();
+				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(argv[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 >= argc)
+				result.m_values.push_back(OptionValue(name, ""));
+			else {
+				result.m_values.push_back(OptionValue(name, argv[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() << "\t" << option.second.help() << std::endl;
+}
+
+void OptionParser::log(const std::string &, const std::string &error)
+{
+	std::cout << error << std::endl;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/OptionParser.h	Sun Jun 22 16:19:31 2014 +0200
@@ -0,0 +1,328 @@
+/*
+ * OptionParser.h -- 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.
+ */
+
+#ifndef _OPTION_PARSER_H_
+#define _OPTION_PARSER_H_
+
+/**
+ * @file OptionParser.h
+ * @brief Command line option replacement for getopt(3)
+ *
+ * This alternative to getopt is thread safe, reentrant and stateless. It does
+ * not use any global and is customizable.
+ */
+
+#include <string>
+#include <vector>
+#include <map>
+#include <unordered_set>
+
+/**
+ * @class Option
+ * @brief Option description
+ *
+ * This class describe an option with its optional flags. Name can be arbitrary
+ * and must specify by hand - or --.
+ */
+class Option final {
+public:
+	enum class Type {
+		Switch,
+		Argument
+	};
+
+	/**
+	 * @brief Optional flags
+	 */
+	enum {
+		Multiple = 0,				//!< can appear multiple times
+		Single					//!< must appear exactly once
+	};
+
+private:
+	std::string	m_name;
+	std::string	m_help;
+	int		m_flags;
+	Type		m_type;
+
+public:
+	/**
+	 * Build an option.
+	 *
+	 * @param name the name
+	 * @param help the help message
+	 * @param flags the flags
+	 */
+	Option(const std::string &name,
+	       const std::string &help,
+	       Type type = Type::Switch,
+	       int flags = 0);
+
+	/**
+	 * Get the option name.
+	 *
+	 * @return the name
+	 */
+	const std::string &name() const;
+
+	/**
+	 * Get the help.
+	 *
+	 * @return the help
+	 */
+	const std::string &help() const;
+
+	/**
+	 * Get the option type.
+	 *
+	 * @return the type
+	 */
+	Type type() const;
+
+	/**
+	 * Get the flags.
+	 *
+	 * @return the flags
+	 */
+	int flags() const;
+};
+
+/**
+ * @class OptionValue
+ * @brief Describe the result of an option
+ *
+ * This class is instanciated when an option has been declared and successfully parsed.
+ *
+ * One should use the operator bool for boolean options and operator std::string (or value())
+ * for options with arguments.
+ */
+class OptionValue final {
+private:
+	friend class OptionParser;
+
+	std::string	m_name;
+	std::string	m_value;
+	bool		m_enabled = false;
+
+private:
+	/**
+	 * Construct an option value. This is constructor is used
+	 * with Option::Switch.
+	 *
+	 * @param name the option name
+	 */
+	OptionValue(const std::string &name);
+
+	/**
+	 * Construct a value with a parameter.
+	 *
+	 * @param name the name
+	 * @param value the value
+	 */
+	OptionValue(const std::string &name, const std::string &value);
+
+public:
+	/**
+	 * Get the option name.
+	 *
+	 * @return the name
+	 */
+	const std::string &name() const;
+
+	/**
+	 * Get the value.
+	 *
+	 * @return the value
+	 */
+	const std::string &value() const;
+
+	/**
+	 * Check if the option is enabled (only with Option::Switch)
+	 *
+	 * @return true if enabled
+	 */
+	operator bool() const;
+
+	/**
+	 * Convert the value to string.
+	 *
+	 * @return the value
+	 */
+	operator std::string() const;
+};
+
+/**
+ * @class OptionResult
+ * @brief Result of a command line parse
+ */
+class OptionResult final {
+private:
+	friend class OptionParser;
+
+	using Values	= std::vector<OptionValue>;
+
+	Values		m_values;
+	int		m_total = 0;
+
+public:
+	/**
+	 * Default constructor.
+	 */
+	OptionResult() = default;
+
+	/**
+	 * Move constructor.
+	 *
+	 * @param other the other
+	 */
+	OptionResult(OptionResult &&other) = default;
+
+	/**
+	 * Returns an iterator to the beginning of values.
+	 *
+	 * @return the iterator
+	 */
+	auto begin() -> decltype(m_values.begin());
+
+	/**
+	 * Returns a const iterator to the beginning of values.
+	 *
+	 * @return the iterator
+	 */
+	auto begin() const -> decltype(m_values.begin());
+
+	/**
+	 * Returns a const iterator to the beginning of values.
+	 *
+	 * @return the iterator
+	 */
+	auto cbegin() const -> decltype(m_values.cbegin());
+
+	/**
+	 * Returns an iterator to the end of values.
+	 *
+	 * @return the iterator
+	 */
+	auto end() -> decltype(m_values.end());
+
+	/**
+	 * Returns a const iterator to the end of values.
+	 *
+	 * @return the iterator
+	 */
+	auto end() const -> decltype(m_values.end());
+
+	/**
+	 * Returns a const iterator to the end of values.
+	 *
+	 * @return the iterator
+	 */
+	auto cend() const -> decltype(m_values.cend());
+
+	/**
+	 * Count the number of parsed options.
+	 *
+	 * @return the number
+	 */
+	int count() const;
+
+	/**
+	 * Get the total number of tokens parsed from the argv vector.
+	 *
+	 * @return the total number
+	 */
+	int total() const;
+
+	/**
+	 * Move assignment operator.
+	 *
+	 * @param other the other
+	 * @return the result
+	 */
+	OptionResult &operator=(OptionResult &&other) = default;
+};
+
+/**
+ * @class OptionParser
+ * @brief Parse command line options
+ */
+class OptionParser {
+private:
+	using Placed	= std::unordered_set<std::string>;
+
+public:
+	using Options	= std::map<std::string, Option>;
+
+	enum {
+		BreakNonOption	= (1 << 0),		//!< break on first non option
+		Strict		= (1 << 1),		//!< abort on first error
+		Silent		= (1 << 2)		//!< do not / call any log handler
+	};
+
+private:
+	Placed		m_placed;			//!< already placed options
+
+protected:
+	Options		m_options;			//!< list of options descriptions
+	int		m_maxlength = 0;		//!< max option length
+
+public:
+	/**
+	 * Default destructor.
+	 */
+	virtual ~OptionParser() = default;
+
+	/**
+	 * Move an option.
+	 *
+	 * @param option the option
+	 */
+	void add(Option &&option);
+
+	/**
+	 * Add an option.
+	 *
+	 * @param option the option
+	 */
+	void add(const Option &option);
+
+	/**
+	 * Parse options.
+	 *
+	 * @param argc the number of args
+	 * @param argv the arguments
+	 * @param flags the optional flags
+	 */
+	OptionResult parse(int argc, char **argv, int flags = 0);
+
+	/**
+	 * Show the usage. Called on error only if silent and if is strict is set, because
+	 * it will continue parsing and may show usage multiple times.
+	 */
+	virtual void usage();
+
+	/**
+	 * On error log.
+	 *
+	 * @param option the option
+	 * @param error the error
+	 */
+	virtual void log(const std::string &option, const std::string &error);
+};
+
+#endif // !_OPTION_PARSER_H_