File argagg.hpp of Package AppImageUpdate

/*
 * @file
 * @brief
 * Defines a very simple command line argument parser.
 *
 * @copyright
 * Copyright (c) 2018 Viet The Nguyen
 *
 * @copyright
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * @copyright
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * @copyright
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */
#pragma once
#ifndef ARGAGG_ARGAGG_ARGAGG_HPP
#define ARGAGG_ARGAGG_ARGAGG_HPP

#include <algorithm>
#include <array>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <iterator>
#include <ostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>


/**
 * @brief
 * There are only two hard things in Computer Science: cache invalidation and
 * naming things (Phil Karlton).
 *
 * The names of types have to be succinct and clear. This has turned out to be
 * a more difficult thing than I expected. Here you'll find a quick overview of
 * the type names you'll find in this namespace (and thus "library").
 *
 * When a program is invoked it is passed a number of "command line arguments".
 * Each of these "arguments" is a string (C-string to be more precise). An
 * "option" is a command line argument that has special meaning. This library
 * recognizes a command line argument as a potential option if it starts with a
 * dash ('-') or double-dash ('--').
 *
 * A "parser" is a set of "definitions" (not a literal std::set but rather a
 * std::vector). A parser is represented by the argagg::parser struct.
 *
 * A "definition" is a structure with four components that define what
 * "options" are recognized. The four components are the name of the option,
 * the strings that represent the option, the option's help text, and how many
 * arguments the option should expect. "Flags" are the individual strings that
 * represent the option ("-v" and "--verbose" are flags for the "verbose"
 * option). A definition is represented by the argagg::definition struct.
 *
 * Note at this point that the word "option" can be used interchangeably to
 * mean the notion of an option and the actual instance of an option given a
 * set of command line arguments. To be unambiguous we use a "definition" to
 * represent the notion of an option and an "option result" to represent an
 * actual option parsed from a set of command line arguments. An "option
 * result" is represented by the argagg::option_result struct.
 *
 * There's one more wrinkle to this: an option can show up multiple times in a
 * given set of command line arguments. For example, "-n 1 -n 2 -n 3". This
 * will parse into three distinct argagg::option_result instances, but all of
 * them correspond to the same argagg::definition. We aggregate these into the
 * argagg::option_results struct which represents "all parser results for a
 * given option definition". This argagg::option_results is basically a
 * std::vector of argagg::option_result.
 *
 * Options aren't the only thing parsed though. Positional arguments are also
 * parsed. Thus a parser produces a result that contains both option results
 * and positional arguments. The parser results are represented by the
 * argagg::parser_results struct. All option results are stored in a mapping
 * from option name to the argagg::option_results. All positional arguments are
 * simply stored in a vector of C-strings.
 */
namespace argagg {


/**
 * @brief
 * This exception is thrown when a long option is parsed and is given an
 * argument using the "=" syntax but the option doesn't expect an argument.
 */
struct unexpected_argument_error
: public std::runtime_error {
  using std::runtime_error::runtime_error;
};


/**
 * @brief
 * This exception is thrown when an option is parsed unexpectedly such as when
 * an argument was expected for a previous option or if an option was found
 * that has not been defined.
 */
struct unexpected_option_error
: public std::runtime_error {
  using std::runtime_error::runtime_error;
};


/**
 * @brief
 * This exception is thrown when an option requires an argument but is not
 * provided one. This can happen if another flag was found after the option or
 * if we simply reach the end of the command line arguments.
 */
struct option_lacks_argument_error
: public std::runtime_error {
  using std::runtime_error::runtime_error;
};


/**
 * @brief
 * This exception is thrown when an option's flag is invalid. This can be the
 * case if the flag is not prefixed by one or two hyphens or contains non
 * alpha-numeric characters after the hyphens. See is_valid_flag_definition()
 * for more details.
 */
struct invalid_flag
: public std::runtime_error {
  using std::runtime_error::runtime_error;
};


/**
 * @brief
 * This exception is thrown when an unknown option is requested by name from an
 * argagg::parser_results through the indexing operator ([]).
 */
struct unknown_option
: public std::runtime_error {
  using std::runtime_error::runtime_error;
};


/**
 * @brief
 * The set of template instantiations that convert C-strings to other types for
 * the option_result::as(), option_results::as(), parser_results::as(), and
 * parser_results::all_as() methods are placed in this namespace.
 */
namespace convert {

  /**
   * @brief
   * Explicit instantiations of this function are used to convert arguments to
   * types.
   */
  template <typename T>
  T arg(const char* arg);

  /**
   * @brief
   * For simple types the main extension point for adding argument conversions
   * is argagg::convert::arg<T>(). However, for complex types such as templated
   * types partial specialization of a helper struct is required. This struct
   * provides that extension point. The default, generic implementation of
   * argagg::convert::arg<T>() calls converter<T>::convert().
   *
   * @see
   * @ref argagg::csv
   */
  template <typename T>
  struct converter {
    static T convert(const char* arg);
  };

  /**
   * @brief
   * A utility function for parsing an argument as a delimited list. To use,
   * initialize a const char* pointer to the start of argument string. Then
   * call parse_next_component(), providing that pointer, a mutable reference
   * to where the parsed argument will go, and optionally the delimiting
   * character. The argument string will be read up to the next delimiting
   * character and then converted using
   * <tt>argagg::convert::arg<decltype(out_arg)>()</tt>. The pointer is then
   * incremented accordingly. If the delimiting character is no longer found
   * then false is returned meaning that parsing the list can be considered
   * finished.
   *
   * @code
     #include <argagg/argagg.hpp>

     struct position3 {
       double x;
       double y;
       double z;
     };

     namespace argagg {
     namespace convert {
       template <>
       position3 arg(const char* s)
       {
         position3 result {0.0, 0.0, 0.0};
         if (!parse_next_component(s, result.x)) {
           // could potentially throw an error if you require that at least two
           // components exist in the list
           return result;
         }
         if (!parse_next_component(s, result.y)) {
           return result;
         }
         if (!parse_next_component(s, result.z)) {
           return result;
         }
         return result;
       }
     } // namespace convert
     } // namespace argagg

     int main(int argc, char** argv)
     {
       argagg::parser argparser {{
          { "origin", {"-o", "--origin"},
            "origin as position3 specified as a comma separated list of "
            "components (e.g. '1,2,3')", 1},
        }};
       argagg::parser_results args = argparser.parse(argc, argv);
       auto my_position = args["origin"].as<position3>();
       // ...
       return 0;
     }
     @endcode
   */
  template <typename T>
  bool parse_next_component(
    const char*& s,
    T& out_arg,
    const char delim = ',');

}


/**
 * @brief
 * Represents a single option parse result.
 *
 * You can check if this has an argument by using the implicit boolean
 * conversion.
 */
struct option_result {

  /**
   * @brief
   * Argument parsed for this single option. If no argument was parsed this
   * will be set to nullptr.
   */
  const char* arg;

  /**
   * @brief
   * Converts the argument parsed for this single option instance into the
   * given type using the type matched conversion function
   * argagg::convert::arg(). If there was not an argument parsed for this
   * single option instance then a argagg::option_lacks_argument_error
   * exception is thrown. The specific conversion function may throw other
   * exceptions.
   */
  template <typename T>
  T as() const;

  /**
   * @brief
   * Converts the argument parsed for this single option instance into the
   * given type using the type matched conversion function
   * argagg::convert::arg(). If there was not an argument parsed for this
   * single option instance then the provided default value is returned
   * instead. If the conversion function throws an exception then it is ignored
   * and the default value is returned.
   */
  template <typename T>
  T as(const T& t) const;

  /**
   * @brief
   * Since we have the argagg::option_result::as() API we might as well alias
   * it as an implicit conversion operator. This performs implicit conversion
   * using the argagg::option_result::as() method.
   *
   * @note
   * An implicit boolean conversion specialization exists which returns false
   * if there is no argument for this single option instance and true
   * otherwise. This specialization DOES NOT convert the argument to a bool. If
   * you need to convert the argument to a bool then use the as() API.
   */
  template <typename T>
  operator T () const;

  /**
   * @brief
   * Explicitly define a unary not operator that wraps the implicit boolean
   * conversion specialization in case the compiler can't do it automatically.
   */
  bool operator ! () const;

};


/**
 * @brief
 * Represents multiple option parse results for a single option. If treated as
 * a single parse result it defaults to the last parse result. Note that an
 * instance of this struct is always created even if no option results are
 * parsed for a given definition. In that case it will simply be empty.
 *
 * To check if the associated option showed up at all simply use the implicit
 * boolean conversion or check if count() is greater than zero.
 */
struct option_results {

  /**
   * @brief
   * All option parse results for this option.
   */
  std::vector<option_result> all;

  /**
   * @brief
   * Gets the number of times the option shows up.
   */
  std::size_t count() const;

  /**
   * @brief
   * Gets a single option parse result by index.
   */
  option_result& operator [] (std::size_t index);

  /**
   * @brief
   * Gets a single option result by index.
   */
  const option_result& operator [] (std::size_t index) const;

  /**
   * @brief
   * Converts the argument parsed for the LAST option parse result for the
   * parent definition to the provided type. For example, if this was for "-f 1
   * -f 2 -f 3" then calling this method for an integer type will return 3. If
   * there are no option parse results then a std::out_of_range exception is
   * thrown. Any exceptions thrown by option_result::as() are not
   * handled.
   */
  template <typename T>
  T as() const;

  /**
   * @brief
   * Converts the argument parsed for the LAST option parse result for the
   * parent definition to the provided type. For example, if this was for "-f 1
   * -f 2 -f 3" then calling this method for an integer type will return 3. If
   * there are no option parse results then the provided default value is
   * returned instead.
   */
  template <typename T>
  T as(const T& t) const;

  /**
   * @brief
   * Since we have the option_results::as() API we might as well alias
   * it as an implicit conversion operator. This performs implicit conversion
   * using the option_results::as() method.
   *
   * @note
   * An implicit boolean conversion specialization exists which returns false
   * if there is no argument for this single option instance and true
   * otherwise. This specialization DOES NOT convert the argument to a bool. If
   * you need to convert the argument to a bool then use the as() API.
   */
  template <typename T>
  operator T () const;

  /**
   * @brief
   * Explicitly define a unary not operator that wraps the implicit boolean
   * conversion specialization in case the compiler can't do it automatically.
   */
  bool operator ! () const;

};


/**
 * @brief
 * Represents all results of the parser including options and positional
 * arguments.
 */
struct parser_results {

  /**
   * @brief
   * Returns the name of the program from the original arguments list. This is
   * always the first argument.
   */
  const char* program;

  /**
   * @brief
   * Maps from definition name to the structure which contains the parser
   * results for that definition.
   */
  std::unordered_map<std::string, option_results> options;

  /**
   * @brief
   * Vector of positional arguments.
   */
  std::vector<const char*> pos;

  /**
   * @brief
   * Used to check if an option was specified at all.
   */
  bool has_option(const std::string& name) const;

  /**
   * @brief
   * Get the parser results for the given definition. If the definition never
   * showed up then the exception from the unordered_map access will bubble
   * through so check if the flag exists in the first place with has_option().
   */
  option_results& operator [] (const std::string& name);

  /**
   * @brief
   * Get the parser results for the given definition. If the definition never
   * showed up then the exception from the unordered_map access will bubble
   * through so check if the flag exists in the first place with has_option().
   */
  const option_results& operator [] (const std::string& name) const;

  /**
   * @brief
   * Gets the number of positional arguments.
   */
  std::size_t count() const;

  /**
   * @brief
   * Gets a positional argument by index.
   */
  const char* operator [] (std::size_t index) const;

  /**
   * @brief
   * Gets a positional argument converted to the given type.
   */
  template <typename T>
  T as(std::size_t i = 0) const;

  /**
   * @brief
   * Gets all positional arguments converted to the given type.
   */
  template <typename T>
  std::vector<T> all_as() const;

};


/**
 * @brief
 * An option definition which essentially represents what an option is.
 */
struct definition {

  /**
   * @brief
   * Name of the option. Option parser results are keyed by this name.
   */
  const std::string name;

  /**
   * @brief
   * List of strings to match that correspond to this option. Should be fully
   * specified with hyphens (e.g. "-v" or "--verbose").
   */
  std::vector<std::string> flags;

  /**
   * @brief
   * Help string for this option.
   */
  std::string help;

  /**
   * @brief
   * Number of arguments this option requires. Must be 0 or 1. All other values
   * have undefined behavior. Okay, the code actually works with positive
   * values in general, but it's unorthodox command line behavior.
   */
  unsigned int num_args;

  /**
   * @brief
   * Returns true if this option does not want any arguments.
   */
  bool wants_no_arguments() const;

  /**
   * @brief
   * Returns true if this option requires arguments.
   */
  bool requires_arguments() const;

};


/**
 * @brief
 * Checks whether or not a command line argument should be processed as an
 * option flag. This is very similar to is_valid_flag_definition() but must
 * allow for short flag groups (e.g. "-abc") and equal-assigned long flag
 * arguments (e.g. "--output=foo.txt").
 */
bool cmd_line_arg_is_option_flag(
  const char* s);


/**
 * @brief
 * Checks whether a flag in an option definition is valid. I suggest reading
 * through the function source to understand what dictates a valid.
 */
bool is_valid_flag_definition(
  const char* s);


/**
 * @brief
 * Tests whether or not a valid flag is short. Assumes the provided cstring is
 * already a valid flag.
 */
bool flag_is_short(
  const char* s);


/**
 * @brief
 * Contains two maps which aid in option parsing. The first map, @ref
 * short_map, maps from a short flag (just a character) to a pointer to the
 * original @ref definition that the flag represents. The second map, @ref
 * long_map, maps from a long flag (an std::string) to a pointer to the
 * original @ref definition that the flag represents.
 *
 * This object is usually a temporary that only exists during the parsing
 * operation. It is typically constructed using @ref validate_definitions().
 */
struct parser_map {

  /**
   * @brief
   * Maps from a short flag (just a character) to a pointer to the original
   * @ref definition that the flag represents.
   */
  std::array<const definition*, 256> short_map;

  /**
   * @brief
   * Maps from a long flag (an std::string) to a pointer to the original @ref
   * definition that the flag represents.
   */
  std::unordered_map<std::string, const definition*> long_map;

  /**
   * @brief
   * Returns true if the provided short flag exists in the map object.
   */
  bool known_short_flag(
    const char flag) const;

  /**
   * @brief
   * If the short flag exists in the map object then it is returned by this
   * method. If it doesn't then nullptr will be returned.
   */
  const definition* get_definition_for_short_flag(
    const char flag) const;

  /**
   * @brief
   * Returns true if the provided long flag exists in the map object.
   */
  bool known_long_flag(
    const std::string& flag) const;

  /**
   * @brief
   * If the long flag exists in the map object then it is returned by this
   * method. If it doesn't then nullptr will be returned.
   */
  const definition* get_definition_for_long_flag(
    const std::string& flag) const;

};


/**
 * @brief
 * Validates a collection (specifically an std::vector) of @ref definition
 * objects by checking if the contained flags are valid. If the set of @ref
 * definition objects is not valid then an exception is thrown. Upon successful
 * validation a @ref parser_map object is returned.
 */
parser_map validate_definitions(
  const std::vector<definition>& definitions);


/**
 * @brief
 * A list of option definitions used to inform how to parse arguments.
 */
struct parser {

  /**
   * @brief
   * Vector of the option definitions which inform this parser how to parse
   * the command line arguments.
   */
  std::vector<definition> definitions;

  /**
   * @brief
   * Parses the provided command line arguments and returns the results as
   * @ref parser_results.
   *
   * @note
   * This method is not thread-safe and assumes that no modifications are made
   * to the definitions member field during the extent of this method call.
   */
  parser_results parse(int argc, const char** argv) const;

  /**
   * @brief
   * Through strict interpretation of pointer casting rules, despite this being
   * a safe operation, C++ doesn't allow implicit casts from <tt>char**</tt> to
   * <tt>const char**</tt> so here's an overload that performs a const_cast,
   * which is typically frowned upon but is safe here.
   */
  parser_results parse(int argc, char** argv) const;

};


/**
 * @brief
 * A convenience output stream that will accumulate what is streamed to it and
 * then, on destruction, format the accumulated string (via the
 * argagg::fmt_string() function) to the provided std::ostream.
 *
 * Example use:
 *
 * @code
 * {
 *   argagg::fmt_ostream f(std::cerr);
 *   f << "Usage: " << really_long_string << '\n';
 * } // on destruction here the formatted string will be streamed to std::cerr
 * @endcode
 */
struct fmt_ostream : public std::ostringstream {

  /**
   * @brief
   * Reference to the final output stream that the formatted string will be
   * streamed to.
   */
  std::ostream& output;

  /**
   * @brief
   * Construct to output to the provided output stream when this object is
   * destroyed.
   */
  fmt_ostream(std::ostream& output);

  /**
   * @brief
   * Special destructor that will format the accumulated string using fmt (via
   * the argagg::fmt_string() function) and stream it to the std::ostream
   * stored.
   */
  ~fmt_ostream();

};


/**
 * @brief
 * Processes the provided string using the fmt utility and returns the
 * resulting output as a string. Not the most efficient (in time or space) but
 * gets the job done.
 */
std::string fmt_string(const std::string& s);


} // namespace argagg


/**
 * @brief
 * Writes the option help to the given stream.
 */
std::ostream& operator << (std::ostream& os, const argagg::parser& x);


// ---- end of declarations, header-only implementations follow ----


namespace argagg {


template <typename T>
T option_result::as() const
{
  if (this->arg) {
    return convert::arg<T>(this->arg);
  } else {
    throw option_lacks_argument_error("option has no argument");
  }
}


template <typename T>
T option_result::as(const T& t) const
{
  if (this->arg) {
    try {
      return convert::arg<T>(this->arg);
    } catch (...) {
      return t;
    }
  } else {
    // I actually think this will never happen. To call this method you have
    // to access a specific option_result for an option. If there's a
    // specific option_result then the option was found. If the option
    // requires an argument then it will definitely have an argument
    // otherwise the parser would have complained.
    return t;
  }
}


template <typename T>
option_result::operator T () const
{
  return this->as<T>();
}


template <> inline
option_result::operator bool () const
{
  return this->arg != nullptr;
}


inline
bool option_result::operator ! () const
{
  return !static_cast<bool>(*this);
}


inline
std::size_t option_results::count() const
{
  return this->all.size();
}


inline
option_result& option_results::operator [] (std::size_t index)
{
  return this->all[index];
}


inline
const option_result& option_results::operator [] (std::size_t index) const
{
  return this->all[index];
}


template <typename T>
T option_results::as() const
{
  if (this->all.size() == 0) {
    throw std::out_of_range("no option arguments to convert");
  }
  return this->all.back().as<T>();
}


template <typename T>
T option_results::as(const T& t) const
{
  if (this->all.size() == 0) {
    return t;
  }
  return this->all.back().as<T>(t);
}


template <typename T>
option_results::operator T () const
{
  return this->as<T>();
}


template <> inline
option_results::operator bool () const
{
  return this->all.size() > 0;
}


inline
bool option_results::operator ! () const
{
  return !static_cast<bool>(*this);
}


inline
bool parser_results::has_option(const std::string& name) const
{
  const auto it = this->options.find(name);
  return ( it != this->options.end()) && it->second.all.size() > 0;
}


inline
option_results& parser_results::operator [] (const std::string& name)
try {
  return this->options.at(name);
} catch (const std::out_of_range& e) {
  std::ostringstream msg;
  msg << "no option named \"" << name << "\" in parser_results";
  throw unknown_option(msg.str());
}


inline
const option_results&
parser_results::operator [] (const std::string& name) const
try {
  return this->options.at(name);
} catch (const std::out_of_range& e) {
  std::ostringstream msg;
  msg << "no option named \"" << name << "\" in parser_results";
  throw unknown_option(msg.str());
}


inline
std::size_t parser_results::count() const
{
  return this->pos.size();
}


inline
const char* parser_results::operator [] (std::size_t index) const
{
  return this->pos[index];
}


template <typename T>
T parser_results::as(std::size_t i) const
{
  return convert::arg<T>(this->pos[i]);
}


template <typename T>
std::vector<T> parser_results::all_as() const
{
  std::vector<T> v(this->pos.size());
  std::transform(
    this->pos.begin(), this->pos.end(), v.begin(),
    [](const char* arg) {
      return convert::arg<T>(arg);
    });
  return v;
}


inline
bool definition::wants_no_arguments() const
{
  return this->num_args == 0;
}


inline
bool definition::requires_arguments() const
{
  return this->num_args > 0;
}


inline
bool cmd_line_arg_is_option_flag(
  const char* s)
{
  auto len = std::strlen(s);

  // The shortest possible flag has two characters: a hyphen and an
  // alpha-numeric character.
  if (len < 2) {
    return false;
  }

  // All flags must start with a hyphen.
  if (s[0] != '-') {
    return false;
  }

  // Shift the name forward by a character to account for the initial hyphen.
  // This means if s was originally "-v" then name will be "v".
  const char* name = s + 1;

  // Check if we're dealing with a long flag.
  bool is_long = false;
  if (s[1] == '-') {
    is_long = true;

    // Just -- is not a valid flag.
    if (len == 2) {
      return false;
    }

    // Shift the name forward to account for the extra hyphen. This means if s
    // was originally "--output" then name will be "output".
    name = s + 2;
  }

  // The first character of the flag name must be alpha-numeric. This is to
  // prevent things like "---a" from being valid flags.
  len = std::strlen(name);
  if (!std::isalnum(name[0])) {
    return false;
  }

  // At this point in is_valid_flag_definition() we would check if the short
  // flag has only one character. At command line specification you can group
  // short flags together or even add an argument to a short flag without a
  // space delimiter. Thus we don't check if this has only one character
  // because it might not.

  // If this is a long flag then we expect all characters *up to* an equal sign
  // to be alpha-numeric or a hyphen. After the equal sign you are specify the
  // argument to a long flag which can be basically anything.
  if (is_long) {
    bool encountered_equal = false;
    return std::all_of(name, name + len, [&](const char& c) {
        if (encountered_equal) {
          return true;
        } else {
          if (c == '=') {
            encountered_equal = true;
            return true;
          }
          return std::isalnum(c) || c == '-';
        }
      });
  }

  // At this point we are not dealing with a long flag. We already checked that
  // the first character is alpha-numeric so we've got the case of a single
  // short flag covered. This might be a short flag group though and we might
  // be tempted to check that each character of the short flag group is
  // alpha-numeric. However, you can specify the argument for a short flag
  // without a space delimiter (e.g. "-I/usr/local/include") so you can't tell
  // if the rest of a short flag group is part of the argument or not unless
  // you know what is a defined flag or not. We leave that kind of processing
  // to the parser.
  return true;
}


inline
bool is_valid_flag_definition(
  const char* s)
{
  auto len = std::strlen(s);

  // The shortest possible flag has two characters: a hyphen and an
  // alpha-numeric character.
  if (len < 2) {
    return false;
  }

  // All flags must start with a hyphen.
  if (s[0] != '-') {
    return false;
  }

  // Shift the name forward by a character to account for the initial hyphen.
  // This means if s was originally "-v" then name will be "v".
  const char* name = s + 1;

  // Check if we're dealing with a long flag.
  bool is_long = false;
  if (s[1] == '-') {
    is_long = true;

    // Just -- is not a valid flag.
    if (len == 2) {
      return false;
    }

    // Shift the name forward to account for the extra hyphen. This means if s
    // was originally "--output" then name will be "output".
    name = s + 2;
  }

  // The first character of the flag name must be alpha-numeric. This is to
  // prevent things like "---a" from being valid flags.
  len = std::strlen(name);
  if (!std::isalnum(name[0])) {
    return false;
  }

  // If this is a short flag then it must only have one character.
  if (!is_long && len > 1) {
    return false;
  }

  // The rest of the characters must be alpha-numeric, but long flags are
  // allowed to have hyphens too.
  return std::all_of(name + 1, name + len, [&](const char& c) {
      return std::isalnum(c) || (c == '-' && is_long);
    });
}


inline
bool flag_is_short(
  const char* s)
{
  return s[0] == '-' && std::isalnum(s[1]);
}


inline
bool parser_map::known_short_flag(
  const char flag) const
{
  return this->short_map[static_cast<std::size_t>(flag)] != nullptr;
}


inline
const definition* parser_map::get_definition_for_short_flag(
  const char flag) const
{
  return this->short_map[static_cast<std::size_t>(flag)];
}


inline
bool parser_map::known_long_flag(
  const std::string& flag) const
{
  const auto existing_long_flag = this->long_map.find(flag);
  return existing_long_flag != long_map.end();
}


inline
const definition* parser_map::get_definition_for_long_flag(
  const std::string& flag) const
{
  const auto existing_long_flag = this->long_map.find(flag);
  if (existing_long_flag == long_map.end()) {
    return nullptr;
  }
  return existing_long_flag->second;
}


inline
parser_map validate_definitions(
  const std::vector<definition>& definitions)
{
  std::unordered_map<std::string, const definition*> long_map;
  parser_map map {{{nullptr}}, std::move(long_map)};

  for (auto& defn : definitions) {

    if (defn.flags.size() == 0) {
      std::ostringstream msg;
      msg << "option \"" << defn.name << "\" has no flag definitions";
      throw invalid_flag(msg.str());
    }

    for (auto& flag : defn.flags) {

      if (!is_valid_flag_definition(flag.data())) {
        std::ostringstream msg;
        msg << "flag \"" << flag << "\" specified for option \"" << defn.name
            << "\" is invalid";
        throw invalid_flag(msg.str());
      }

      if (flag_is_short(flag.data())) {
        const std::size_t short_flag_letter = static_cast<std::size_t>(flag[1]);
        const auto existing_short_flag =
          map.short_map[short_flag_letter];
        bool short_flag_already_exists = (existing_short_flag != nullptr);
        if (short_flag_already_exists) {
          std::ostringstream msg;
          msg << "duplicate short flag \"" << flag
              << "\" found, specified by both option  \"" << defn.name
              << "\" and option \"" << existing_short_flag->name;
          throw invalid_flag(msg.str());
        }
        map.short_map[static_cast<std::size_t>(short_flag_letter)] = &defn;
        continue;
      }

      // If we're here then this is a valid, long-style flag.
      if (map.known_long_flag(flag)) {
        const auto existing_long_flag = map.get_definition_for_long_flag(flag);
        std::ostringstream msg;
        msg << "duplicate long flag \"" << flag
            << "\" found, specified by both option  \"" << defn.name
            << "\" and option \"" << existing_long_flag->name;
        throw invalid_flag(msg.str());
      }
      map.long_map.insert(std::make_pair(flag, &defn));
    }
  }

  return map;
}


inline
parser_results parser::parse(int argc, const char** argv) const
{
  // Inspect each definition to see if its valid. You may wonder "why don't
  // you do this validation on construction?" I had thought about it but
  // realized that since I've made the parser an aggregate type (granted it
  // just "aggregates" a single vector) I would need to track any changes to
  // the definitions vector and re-run the validity check in order to
  // maintain this expected "validity invariant" on the object. That would
  // then require hiding the definitions vector as a private entry and then
  // turning the parser into a thin interface (by re-exposing setters and
  // getters) to the vector methods just so that I can catch when the
  // definition has been modified. It seems much simpler to just enforce the
  // validity when you actually want to parse because it's at the moment of
  // parsing that you know the definitions are complete.
  parser_map map = validate_definitions(this->definitions);

  // Initialize the parser results that we'll be returning. Store the program
  // name (assumed to be the first command line argument) and initialize
  // everything else as empty.
  std::unordered_map<std::string, option_results> options {};
  std::vector<const char*> pos;
  parser_results results {argv[0], std::move(options), std::move(pos)};

  // Add an empty option result for each definition.
  for (const auto& defn : this->definitions) {
    option_results opt_results {{}};
    results.options.insert(
      std::make_pair(defn.name, opt_results));
  }

  // Don't start off ignoring flags. We only ignore flags after a -- shows up
  // in the command line arguments.
  bool ignore_flags = false;

  // Keep track of any options that are expecting arguments.
  const char* last_flag_expecting_args = nullptr;
  option_result* last_option_expecting_args = nullptr;
  unsigned int num_option_args_to_consume = 0;

  // Get pointers to pointers so we can treat the raw pointer array as an
  // iterator for standard library algorithms. This isn't used yet but can be
  // used to template this function to work on iterators over strings or
  // C-strings.
  const char** arg_i = argv + 1;
  const char** arg_end = argv + argc;

  while (arg_i != arg_end) {
    auto arg_i_cstr = *arg_i;
    auto arg_i_len = std::strlen(arg_i_cstr);

    // Some behavior to note: if the previous option is expecting an argument
    // then the next entry will be treated as a positional argument even if
    // it looks like a flag.
    bool treat_as_positional_argument = (
        ignore_flags
        || num_option_args_to_consume > 0
        || !cmd_line_arg_is_option_flag(arg_i_cstr)
      );
    if (treat_as_positional_argument) {

      // If last option is expecting some specific positive number of
      // arguments then give this argument to that option, *regardless of
      // whether or not the argument looks like a flag or is the special "--"
      // argument*.
      if (num_option_args_to_consume > 0) {
        last_option_expecting_args->arg = arg_i_cstr;
        --num_option_args_to_consume;
        ++arg_i;
        continue;
      }

      // Now we check if this is just "--" which is a special argument that
      // causes all following arguments to be treated as non-options and is
      // itselve discarded.
      if (std::strncmp(arg_i_cstr, "--", 2) == 0 && arg_i_len == 2) {
        ignore_flags = true;
        ++arg_i;
        continue;
      }

      // If there are no expectations for option arguments then simply use
      // this argument as a positional argument.
      results.pos.push_back(arg_i_cstr);
      ++arg_i;
      continue;
    }

    // Reset the "expecting argument" state.
    last_flag_expecting_args = nullptr;
    last_option_expecting_args = nullptr;
    num_option_args_to_consume = 0;

    // If we're at this point then we're definitely dealing with something
    // that is flag-like and has hyphen as the first character and has a
    // length of at least two characters. How we handle this potential flag
    // depends on whether or not it is a long-option so we check that first.
    bool is_long_flag = (arg_i_cstr[1] == '-');

    if (is_long_flag) {

      // Long flags have a complication: their arguments can be specified
      // using an '=' character right inside the argument. That means an
      // argument like "--output=foobar.txt" is actually an option with flag
      // "--output" and argument "foobar.txt". So we look for the first
      // instance of the '=' character and keep it in long_flag_arg. If
      // long_flag_arg is nullptr then we didn't find '='. We need the
      // flag_len to construct long_flag_str below.
      auto long_flag_arg = std::strchr(arg_i_cstr, '=');
      std::size_t flag_len = arg_i_len;
      if (long_flag_arg != nullptr) {
        flag_len = static_cast<std::size_t>(long_flag_arg - arg_i_cstr);
      }
      std::string long_flag_str(arg_i_cstr, flag_len);

      if (!map.known_long_flag(long_flag_str)) {
        std::ostringstream msg;
        msg << "found unexpected flag: " << long_flag_str;
        throw unexpected_option_error(msg.str());
      }

      const auto defn = map.get_definition_for_long_flag(long_flag_str);

      if (long_flag_arg != nullptr && defn->num_args == 0) {
        std::ostringstream msg;
        msg << "found argument for option not expecting an argument: "
            << arg_i_cstr;
        throw unexpected_argument_error(msg.str());
      }

      // We've got a legitimate, known long flag option so we add an option
      // result. This option result initially has an arg of nullptr, but that
      // might change in the following block.
      auto& opt_results = results.options[defn->name];
      option_result opt_result {nullptr};
      opt_results.all.push_back(std::move(opt_result));

      if (defn->requires_arguments()) {
        bool there_is_an_equal_delimited_arg = (long_flag_arg != nullptr);
        if (there_is_an_equal_delimited_arg) {
          // long_flag_arg would be "=foo" in the "--output=foo" case so we
          // increment by 1 to get rid of the equal sign.
          opt_results.all.back().arg = long_flag_arg + 1;
        } else {
          last_flag_expecting_args = arg_i_cstr;
          last_option_expecting_args = &(opt_results.all.back());
          num_option_args_to_consume = defn->num_args;
        }
      }

      ++arg_i;
      continue;
    }

    // If we've made it here then we're looking at either a short flag or a
    // group of short flags. Short flags can be grouped together so long as
    // they don't require any arguments unless the option that does is the
    // last in the group ("-o x -v" is okay, "-vo x" is okay, "-ov x" is
    // not). So starting after the dash we're going to process each character
    // as if it were a separate flag. Note "sf_idx" stands for "short flag
    // index".
    for (std::size_t sf_idx = 1; sf_idx < arg_i_len; ++sf_idx) {
      const auto short_flag = arg_i_cstr[sf_idx];

      if (!std::isalnum(short_flag)) {
        std::ostringstream msg;
        msg << "found non-alphanumeric character '" << arg_i_cstr[sf_idx]
            << "' in flag group '" << arg_i_cstr << "'";
        throw std::domain_error(msg.str());
      }

      if (!map.known_short_flag(short_flag)) {
        std::ostringstream msg;
        msg << "found unexpected flag '" << arg_i_cstr[sf_idx]
            << "' in flag group '" << arg_i_cstr << "'";
        throw unexpected_option_error(msg.str());
      }

      auto defn = map.get_definition_for_short_flag(short_flag);
      auto& opt_results = results.options[defn->name];

      // Create an option result with an empty argument (for now) and add it
      // to this option's results.
      option_result opt_result {nullptr};
      opt_results.all.push_back(std::move(opt_result));

      if (defn->requires_arguments()) {

        // If this short flag's option requires an argument and we're the
        // last flag in the short flag group then just put the parser into
        // "expecting argument for last option" state and move onto the next
        // command line argument.
        bool is_last_short_flag_in_group = (sf_idx == arg_i_len - 1);
        if (is_last_short_flag_in_group) {
          last_flag_expecting_args = arg_i_cstr;
          last_option_expecting_args = &(opt_results.all.back());
          num_option_args_to_consume = defn->num_args;
          break;
        }

        // If this short flag's option requires an argument and we're NOT the
        // last flag in the short flag group then we automatically consume
        // the rest of the short flag group as the argument for this flag.
        // This is how we get the POSIX behavior of being able to specify a
        // flag's arguments without a white space delimiter (e.g.
        // "-I/usr/local/include").
        opt_results.all.back().arg = arg_i_cstr + sf_idx + 1;
        break;
      }
    }

    ++arg_i;
    continue;
  }

  // If we're done with all of the arguments but are still expecting
  // arguments for a previous option then we haven't satisfied that option.
  // This is an error.
  if (num_option_args_to_consume > 0) {
    std::ostringstream msg;
    msg << "last option \"" << last_flag_expecting_args
        << "\" expects an argument but the parser ran out of command line "
        << "arguments to parse";
    throw option_lacks_argument_error(msg.str());
  }

  return results;
}


inline
parser_results parser::parse(int argc, char** argv) const
{
  return parse(argc, const_cast<const char**>(argv));
}


namespace convert {


  /**
   * @brief
   * Templated function for conversion to T using the @ref std::strtol()
   * function.  This is used for anything long length or shorter (long, int,
   * short, char).
   */
  template <typename T> inline
  T long_(const char* arg)
  {
    char* endptr = nullptr;
    errno = 0;
    T ret = static_cast<T>(std::strtol(arg, &endptr, 0));
    if (endptr == arg) {
      std::ostringstream msg;
      msg << "unable to convert argument to integer: \"" << arg << "\"";
      throw std::invalid_argument(msg.str());
    }
    if (errno == ERANGE) {
      throw std::out_of_range("argument numeric value out of range");
    }
    return ret;
  }


  /**
   * @brief
   * Templated function for conversion to T using the @ref std::strtoll()
   * function.  This is used for anything long long length or shorter (long
   * long).
   */
  template <typename T> inline
  T long_long_(const char* arg)
  {
    char* endptr = nullptr;
    errno = 0;
    T ret = static_cast<T>(std::strtoll(arg, &endptr, 0));
    if (endptr == arg) {
      std::ostringstream msg;
      msg << "unable to convert argument to integer: \"" << arg << "\"";
      throw std::invalid_argument(msg.str());
    }
    if (errno == ERANGE) {
      throw std::out_of_range("argument numeric value out of range");
    }
    return ret;
  }


#define DEFINE_CONVERSION_FROM_LONG_(TYPE) \
  template <> inline \
  TYPE arg(const char* arg) \
  { \
    return long_<TYPE>(arg); \
  }

  DEFINE_CONVERSION_FROM_LONG_(char)
  DEFINE_CONVERSION_FROM_LONG_(unsigned char)
  DEFINE_CONVERSION_FROM_LONG_(signed char)
  DEFINE_CONVERSION_FROM_LONG_(short)
  DEFINE_CONVERSION_FROM_LONG_(unsigned short)
  DEFINE_CONVERSION_FROM_LONG_(int)
  DEFINE_CONVERSION_FROM_LONG_(unsigned int)
  DEFINE_CONVERSION_FROM_LONG_(long)
  DEFINE_CONVERSION_FROM_LONG_(unsigned long)

#undef DEFINE_CONVERSION_FROM_LONG_


#define DEFINE_CONVERSION_FROM_LONG_LONG_(TYPE) \
  template <> inline \
  TYPE arg(const char* arg) \
  { \
    return long_long_<TYPE>(arg); \
  }

  DEFINE_CONVERSION_FROM_LONG_LONG_(long long)
  DEFINE_CONVERSION_FROM_LONG_LONG_(unsigned long long)

#undef DEFINE_CONVERSION_FROM_LONG_LONG_


  template <typename T>
  T arg(const char* arg)
  {
    return converter<T>::convert(arg);
  }


  template <> inline
  bool arg(const char* arg)
  {
    return argagg::convert::arg<int>(arg) != 0;
  }


  template <> inline
  float arg(const char* arg)
  {
    char* endptr = nullptr;
    errno = 0;
    float ret = std::strtof(arg, &endptr);
    if (endptr == arg) {
      std::ostringstream msg;
      msg << "unable to convert argument to integer: \"" << arg << "\"";
      throw std::invalid_argument(msg.str());
    }
    if (errno == ERANGE) {
      throw std::out_of_range("argument numeric value out of range");
    }
    return ret;
  }


  template <> inline
  double arg(const char* arg)
  {
    char* endptr = nullptr;
    errno = 0;
    double ret = std::strtod(arg, &endptr);
    if (endptr == arg) {
      std::ostringstream msg;
      msg << "unable to convert argument to integer: \"" << arg << "\"";
      throw std::invalid_argument(msg.str());
    }
    if (errno == ERANGE) {
      throw std::out_of_range("argument numeric value out of range");
    }
    return ret;
  }


  template <> inline
  const char* arg(const char* arg)
  {
    return arg;
  }


  template <> inline
  std::string arg(const char* arg)
  {
    return std::string(arg);
  }


  template <typename T>
  bool parse_next_component(
    const char*& s,
    T& out_arg,
    const char delim)
  {
    const char* begin = s;
    s = std::strchr(s, delim);
    if (s == nullptr) {
      std::string arg_str(begin);
      out_arg = argagg::convert::arg<T>(arg_str.c_str());
      return false;
    } else {
      std::string arg_str(begin, static_cast<std::size_t>(s - begin));
      out_arg = argagg::convert::arg<T>(arg_str.c_str());
      s += 1;
      return true;
    }
  }


} // namespace convert


inline
fmt_ostream::fmt_ostream(std::ostream& output)
: std::ostringstream(), output(output)
{
}


inline
fmt_ostream::~fmt_ostream()
{
  output << fmt_string(this->str());
}


inline
std::string lstrip(const std::string& text)
{
  auto result = text;

  result.erase(
    result.begin(),
    std::find_if(
      result.begin(),
      result.end(),
      [](int ch) { return !std::isspace(ch); }));

  return result;
}


inline
std::string rstrip(const std::string& text)
{
  auto result = text;

  result.erase(
    std::find_if(
      result.rbegin(),
      result.rend(),
      [](int ch) { return !std::isspace(ch); }).base(),
    result.end());

  return result;
}


inline
std::string construct_line(const std::string& indent,
                           const std::string& contents)
{
  return indent + rstrip(contents) + "\n";
}


/**
 * @brief
 * Return a wrapped version of a single line of text.
 */
inline
std::string wrap_line(const std::string& single_line,
                      const std::size_t wrap_width)
{
  auto indentation_spaces = single_line.find_first_not_of(" ");
  if (indentation_spaces == std::string::npos) {
    indentation_spaces = 0;
  }

  const auto line = lstrip(single_line);
  const auto indent = std::string(indentation_spaces, ' ');

  std::string result;

  std::size_t position = 0;
  std::size_t line_start = 0;
  while (true) {
    const auto new_position = line.find_first_of(" ", position);
    if (new_position == std::string::npos) {
      break;
    }

    if (new_position + indentation_spaces > line_start + wrap_width) {
      result += construct_line(
        indent, line.substr(line_start, position - line_start - 1));

      line_start = position;
    }

    position = new_position + 1;
  }

  return result + construct_line(indent, line.substr(line_start));
}


inline
std::string fmt_string(const std::string& s)
{
  std::stringstream ss(s);
  std::string line;

  std::string result;

  // Use default width of `fmt`.
  const auto column_width = 75;

  while (std::getline(ss, line, '\n')) {
    result += wrap_line(line, column_width);
  }

  return result;
}


} // namespace argagg


inline
std::ostream& operator << (std::ostream& os, const argagg::parser& x)
{
  for (auto& definition : x.definitions) {
    os << "    ";
    for (auto& flag : definition.flags) {
      os << flag;
      if (flag != definition.flags.back()) {
        os << ", ";
      }
    }
    os << "\n        " << definition.help << '\n';
  }
  return os;
}


#endif // ARGAGG_ARGAGG_ARGAGG_HPP
openSUSE Build Service is sponsored by