Resource icon

[C++] Programmoptionen parsen und Parameter konvertieren

Programmiersprache(n)
C++11, nicht abwärtskompatibel
Betriebssystem(e)
unspezifisch
Es werden Optionen verarbeitet, die folgenden Syntaxregeln entsprechen:
  • Optionsnamen sollen aus Buchstaben bestehen, andere Zeichen führen zu undefiniertem Verhalten. (-a ist gültig, -1 ist ungültig)
  • Groß-, Kleinschreibung wird bei Optionsnamen beachtet. (-a ist ungleich -A)
  • Kurznamen für eine Option bestehen aus einem Bindestrich, direkt gefolgt von einem Buchstabe. (z.B. -a)
  • Wird ein Bindestrich von mehreren Buchstaben gefolgt, wird jeder Buchstabe als separater Optionsname behandelt. (-ab ist gleich -a -b)
  • Langnamen für eine Option bestehen aus zwei Bindestrichen, gefolgt von einer Zeichenfolge aus Buchstaben. (z.B. --ab)
  • Optionsnamen und/oder Parameter sind durch Zeichen voneinander getrennt, die eine Separierung im argv-Array der main-Funktion verursachen. (z.B. bei -a X trennt ein Leerzeichen den Optionsname a vom Parameter X)
  • Eine Option hat maximal einen Parameter. (z.B. bei -a X Y ist X der zugehörige Parameter zu Option a, während Y ein unbenannter Parameter ist)
  • Bei Kurznamen, bestehend aus mehreren Buchstaben, wird ein nachfolgender Parameter als zum letzten Optionsname zugehörig behandelt. (z.B. bei -ab X hat a keinen Parameter, während b den Parameter X zugeordnet bekommt)
  • Es wird (sofern spezifiziert) maximal ein unbenannter Parameter akzeptiert, der keinem Optionsname zugeordnet ist. (z.B. bei X -a Y Z ist X ein unbenannter Parameter und Y der zugehörige Parameter zu Option a; der weitere unbenannte Parameter Z führt dazu, dass alle Optionen als ungültig gewertet werden)
  • Ein auf einen Optionsname folgender Parameter wird immer dann als zum Optionsname zugehörig gewertet, wenn dieser Parameter nicht selbst als Optionsname behandelt werden kann. (z.B. bei -a -b ist b eine separate Option, während bei -a -1 dem Optionsname a der Parameter -1 zugeordnet wird)
  • Optionen dürfen nicht mehrfach übergeben werden, da sonst die Eindeutigkeit nicht mehr gewährleistet werden kann. (z.B. bei -a X -a hat Option a einmal den Parameter X und einmal keinen Parameter)

Folgende Methoden werden unterstützt:
bool valid(const name_list& option_names, const bool allow_unnamed = false) const

void named_setdefaults(const named_map& defaults)

bool named_includes(const name_list& option_names) const

bool named_excludes(const name_list& option_names) const

bool named_hasparam(const std::string& name) const

bool named_empty() const

std::size_t named_size() const

template <typename T> T named_param(const std::string& name, const bool shall_throw = true, const int base = 10) const


void unnamed_setdefault(const std::string& defaultstr)

bool unnamed_empty() const

template <typename T> T unnamed_param(const bool shall_throw = true, const int base = 10) const


Nähere Informationen bitte den Kommentaren im ProgOpt.h Header entnehmen.

ProgOpt.h
C++:
#ifndef __PROGOPT_H_INCLUDED
#define __PROGOPT_H_INCLUDED

#include <string>
#include <initializer_list>
#include <unordered_map>
#include <vector>
#include <sstream>
#include <stdexcept>

namespace progopt
{
  /** \typedef name_list  Listentyp für Optionsnamen.
   *    In geschweifte Klammern eingeschlossene, kommagetrennte Liste konstanter Strings.
   */
  typedef std::initializer_list<const std::string> name_list;

  /** \typedef named_map  Map-Typ für benannte Optionen.
   *    Map von Stringpaaren.
   */
  typedef std::unordered_map<std::string, std::string> named_map;

  struct options
  {
    /** \brief Konstruktor.
     *
     * \param  argc   Anzahl Argumente der main Funktion.
     * \param  argv   Vektor der Argumente der main Funktion.
     */
    options(const int argc, const char *const *const argv);

    /** \brief Validiert die geparsten Optionen gegen die als gültig spezifizierten Optionsnamen.
     *
     * \param  option_names   Liste der als gültig spezifizierten Optionsnamen.
     * \param  allow_unnamed  Spezifiziert, ob ein unbenannter Parameter akzeptiert wird.
     * \return bool           true, wenn Optionen übergeben wurden und diese gültig sind.
     */
    bool valid(const name_list& option_names, const bool allow_unnamed = false) const;

    /** \brief Setzt Defaultwerte für benannte Optionen, die nicht übergeben wurden
     *
     * \param  defaults   Map der Defaultpaare.
     * \return void
     */
    void named_setdefaults(const named_map& defaults);

    /** \brief Prüft, ob alle Optionsnamen der spezifizierten Liste an das Programm übergeben wurden.
     *
     * \param  option_names   Liste der Optionsnamen.
     * \return bool           true, wenn alle Optionsnamen übergeben wurden.
     */
    bool named_includes(const name_list& option_names) const;

    /** \brief Prüft, ob keine der spezifizierten Optionsnamen der Liste an das Programm übergeben wurden.
     *
     * \param  option_names   Liste der Optionsnamen.
     * \return bool           true, wenn keine der Optionsnamen übergeben wurden.
     */
    bool named_excludes(const name_list& option_names) const;

    /** \brief Prüft, ob dem spezifizierten Optionsnamen ein Parameter zugeordnet wurde.
     *
     * \param  name   Optionsname.
     * \return bool   true, wenn dem Optionsnamen ein Parameter zugeordnet wurde.
     */
    bool named_hasparam(const std::string& name) const;

    /** \brief Prüft, ob benannte Optionen übergeben wurden.
     *
     * \return bool   true, wenn keine benannten Optionen übergeben wurden.
     */
    bool named_empty() const;

    /** \brief Gibt die Anzahl der benannten Optionen zurück.
     *
     * \return std::size_t  Anzahl der benannten Optionen.
     */
    std::size_t named_size() const;

    /** \brief Konvertiert den zum spezifizierten Optionsname gehörigen Parameter in den als Template-Parameter angegebenen Typ.
     *
     * \param  name         Optionsname.
     * \param  shall_throw  true, um bei fehlerhafter Konvertierung eine Exception zu werfen; false um in diesem Fall einen Default-Wert zurück zu geben.
     * \param  base         Zahlenbasis bei ganzzahligen Konvertierungen.
     * \return T            Konvertierter Wert.
     */
    template <typename T> T named_param(const std::string& name, const bool shall_throw = true, const int base = 10) const
    {
      T ret{};
      if (shall_throw)
        convert(ret, named.at(name), shall_throw, base);
      else
      {
        const auto found(named.find(name));
        convert(ret, found == named.end() ? "" : found->second, shall_throw, base);
      }
      return ret;
    }

    /** \brief Setzt Defaultwert für die unbenannte Option, wenn diese nicht übergeben wurde
     *
     * \param  defaultstr   Default-String.
     * \return void
     */
    void unnamed_setdefault(const std::string& defaultstr);

    /** \brief Prüft, ob eine unbenannte Option übergeben wurden.
     *
     * \return bool   true, wenn keine unbenannten Optionen übergeben wurden.
     */
    bool unnamed_empty() const;

    /** \brief Konvertiert die unbenannte Option in den als Template-Parameter angegebenen Typ.
     *
     * \param  shall_throw  true, um bei fehlerhafter Konvertierung eine Exception zu werfen; false um in diesem Fall einen Default-Wert zurück zu geben.
     * \param  base         Zahlenbasis bei ganzzahligen Konvertierungen.
     * \return T            Konvertierter Wert.
     */
    template <typename T> T unnamed_param(const bool shall_throw = true, const int base = 10) const
    {
      T ret{};
      if (shall_throw)
        convert(ret, unnamed.at(0), shall_throw, base);
      else
        convert(ret, unnamed.empty() ? "" : unnamed.front(), shall_throw, base);
      return ret;
    }


  private:
    named_map named;
    std::vector<std::string> unnamed;
    std::size_t names_processed;
    static constexpr const char *const e_what{"conversion incompletable"};

    void capture(const int& argc, const char *const *const & argv);

    void convert(int& ret, const std::string& val, const bool shall_throw, const int base) const;
    void convert(long& ret, const std::string& val, const bool shall_throw, const int base) const;
    void convert(long long& ret, const std::string& val, const bool shall_throw, const int base) const;
    void convert(unsigned& ret, const std::string& val, const bool shall_throw, const int base) const;
    void convert(unsigned long& ret, const std::string& val, const bool shall_throw, const int base) const;
    void convert(unsigned long long& ret, const std::string& val, const bool shall_throw, const int base) const;
    void convert(float& ret, const std::string& val, const bool shall_throw, const int /*unused*/) const;
    void convert(double& ret, const std::string& val, const bool shall_throw, const int /*unused*/) const;
    void convert(long double& ret, const std::string& val, const bool shall_throw, const int /*unused*/) const;
    void convert(std::string& ret, const std::string& val, const bool /*unused*/, const int /*unused*/) const;
    template <typename T> void convert(T& ret, const std::string& val, const bool shall_throw, const int /*unused*/) const
    {
      try
      {
        std::istringstream stream{val};
        stream >> ret;
        stream.clear();
        if (static_cast<std::size_t>(stream.tellg()) != val.size())
          throw std::invalid_argument(e_what);
      }
      catch(const std::exception&)
      {
        if (shall_throw)
          throw;
      }
    }

  };
}

#endif // __PROGOPT_H_INCLUDED

ProgOpt.cpp
C++:
#include "ProgOpt.h"
#include <algorithm>
#include <cctype>
#include <limits>

namespace progopt
{

  options::options(const int argc, const char *const *const argv) : names_processed{}
  {
    capture(argc, argv);
  }

  bool options::valid(const name_list& option_names, const bool allow_unnamed) const
  {
    if (!(named.empty() && unnamed.empty())
        && unnamed.size() < (allow_unnamed ? 2u : 1u)
        && names_processed == named.size())
    {
      if (
        std::any_of(
          named.begin(), named.end(),
          [&option_names] (const std::pair<std::string, std::string>& element)
          {
            return (std::find(option_names.begin(), option_names.end(), element.first) == option_names.end());
          }
        )
      )
        return false;
      else
        return true;
    }

    return false;
  }

  void options::named_setdefaults(const named_map& defaults)
  {
    for (const auto& member : defaults)
      if (named.emplace(member).second)
        names_processed++;
  }

  bool options::named_includes(const name_list& option_names) const
  {
    for (const auto& name : option_names)
      if (named.find(name) == named.end())
        return false;

    return true;
  }

  bool options::named_excludes(const name_list& option_names) const
  {
    for (const auto& name : option_names)
      if (named.find(name) != named.end())
        return false;

    return true;
  }

  bool options::named_hasparam(const std::string& name) const
  {
    const auto found(named.find(name));
    return found == named.end() ? false : !(found->second.empty());
  }

  bool options::named_empty() const
  {
    return named.empty();
  }

  std::size_t options::named_size() const
  {
    return named.size();
  }

  void options::unnamed_setdefault(const std::string& defaultstr)
  {
    if (unnamed.empty())
      unnamed.push_back(defaultstr);
  }

  bool options::unnamed_empty() const
  {
    return unnamed.empty();
  }

  void options::capture(const int& argc, const char *const *const & argv)
  {
    if (argc < 1 || !argv)
      throw std::invalid_argument("Pass argc and argv (main function parameters) to 'options' constructor!");
    std::string opt;
    bool is_named{false};
    std::for_each(
      argv + 1, argv + argc,
      [this, &opt, &is_named] (const std::string arg)
      {
        if (arg.front() == '-' && arg.size() > 1 && !std::isdigit(arg.at(1)))
        {
          if (arg.at(1) == '-')
          {
            named[(opt = arg.substr(2))] = "";
            names_processed++;
            is_named = true;
          }
          else if (arg.find_first_of("1234567890") == std::string::npos)
          {
            for (const auto& ch : arg.substr(1))
            {
              opt = ch;
              named[opt] = "";
              names_processed++;
            }
            is_named = true;
          }
          else
          {
            unnamed.push_back(arg);
            is_named = false;
          }
        }
        else if (is_named)
        {
          named[opt] = arg;
          is_named = false;
        }
        else
          unnamed.push_back(arg);
      }
    );
  }

  void options::convert(int& ret, const std::string& val, const bool shall_throw, const int base) const
  {
    try
    {
      std::size_t idx{0};
      ret = std::stoi(val, &idx, base);
      if (idx < val.size())
        throw std::invalid_argument(e_what);
    }
    catch(const std::exception&)
    {
      if (shall_throw)
        throw;
    }
  }

  void options::convert(long& ret, const std::string& val, const bool shall_throw, const int base) const
  {
    try
    {
      std::size_t idx{0};
      ret = std::stol(val, &idx, base);
      if (idx < val.size())
        throw std::invalid_argument(e_what);
    }
    catch(const std::exception&)
    {
      if (shall_throw)
        throw;
    }
  }

  void options::convert(long long& ret, const std::string& val, const bool shall_throw, const int base) const
  {
    try
    {
      std::size_t idx{0};
      ret = std::stoll(val, &idx, base);
      if (idx < val.size())
        throw std::invalid_argument(e_what);
    }
    catch(const std::exception&)
    {
      if (shall_throw)
        throw;
    }
  }

  void options::convert(unsigned& ret, const std::string& val, const bool shall_throw, const int base) const
  {
    try
    {
      std::size_t idx{0};
      unsigned long ret_tmp{std::stoul(val, &idx, base)};
      if (ret_tmp > std::numeric_limits<unsigned>::max())
        throw std::out_of_range("unsigned int");
      if (idx < val.size())
        throw std::invalid_argument(e_what);
      ret = static_cast<unsigned>(ret_tmp);
    }
    catch(const std::exception&)
    {
      if (shall_throw)
        throw;
      ret = -1;
    }
  }

  void options::convert(unsigned long& ret, const std::string& val, const bool shall_throw, const int base) const
  {
    try
    {
      std::size_t idx{0};
      ret = std::stoul(val, &idx, base);
      if (idx < val.size())
        throw std::invalid_argument(e_what);
    }
    catch(const std::exception&)
    {
      if (shall_throw)
        throw;
      ret = -1l;
    }
  }

  void options::convert(unsigned long long& ret, const std::string& val, const bool shall_throw, const int base) const
  {
    try
    {
      std::size_t idx{0};
      ret = std::stoull(val, &idx, base);
      if (idx < val.size())
        throw std::invalid_argument(e_what);
    }
    catch(const std::exception&)
    {
      if (shall_throw)
        throw;
      ret = -1ll;
    }
  }

  void options::convert(float& ret, const std::string& val, const bool shall_throw, const int /*unused*/) const
  {
    try
    {
      std::size_t idx{0};
      ret = std::stof(val, &idx);
      if (idx < val.size())
        throw std::invalid_argument(e_what);
    }
    catch(const std::exception&)
    {
      if (shall_throw)
        throw;
      ret = std::numeric_limits<float>::quiet_NaN();
    }
  }

  void options::convert(double& ret, const std::string& val, const bool shall_throw, const int /*unused*/) const
  {
    try
    {
      std::size_t idx{0};
      ret = std::stod(val, &idx);
      if (idx < val.size())
        throw std::invalid_argument(e_what);
    }
    catch(const std::exception&)
    {
      if (shall_throw)
        throw;
      ret = std::numeric_limits<double>::quiet_NaN();
    }
  }

  void options::convert(long double& ret, const std::string& val, const bool shall_throw, const int /*unused*/) const
  {
    try
    {
      std::size_t idx{0};
      ret = std::stold(val, &idx);
      if (idx < val.size())
        throw std::invalid_argument(e_what);
    }
    catch(const std::exception&)
    {
      if (shall_throw)
        throw;
      ret = std::numeric_limits<long double>::quiet_NaN();
    }
  }

  void options::convert(std::string& ret, const std::string& val, const bool /*unused*/, const int /*unused*/) const
  {
    ret = val;
  }

}


~~~~~~~~~~~
Beispielprogramm (Programmname program_options):
main.cpp

C++:
#include <iostream>
#include "ProgOpt.h"

// Beispielfunktion für die Ausgabe benannter Optionen
void process_name(const progopt::options& opts, const std::string& name);


int main(int argc, char *argv[])
{
  progopt::options opts(argc, argv); // Eine Instanz von progopt::options erzeugen und dem Konstruktor argc und argv übergeben

  const progopt::named_map defaults{{"b", "111"}}; // Falls Option b nicht übergeben wurde, ist deren Defaultwert 111.
  opts.named_setdefaults(defaults);

  const progopt::name_list validnames{"a", "b", "ab"}; // Liste der gültigen Optionsnamen
  if (!opts.valid(validnames, true)) // Prüfen, ob die dem Programm übergebenen Optionen gültig sind. Es darf eine unbenannte Option übergeben werden.
  {
    std::cerr << "Ungueltige Optionen.\n";
    return 1;
  }

  if (opts.named_empty()) // Prüfen, ob benannte Optionen übergeben wurden
    std::cout << "Es wurde keine benannte Option gefunden.\n";
  else
  {
    std::cout << "Es wurden " << opts.named_size() << " benannte Optionen gefunden.\n";

    if (opts.named_excludes({"ab"})) // Prüfen, ob Option ab nicht übergeben wurde
      std::cout << "  Alle Namen haben die Kurzform.\n";
    else if (opts.named_excludes({"a", "b"})) // Prüfen, ob Optionen a und b nicht übergeben wurden
      std::cout << "  Alle Namen haben die Langform.\n";
    else
      std::cout << "  Es gibt Namen in Kurz- und Langform.\n";

    for (const auto& name : validnames) // Für jeden gültigen Optionsname die Funktion zur Ausgabe aufrufen
      process_name(opts, name);
  }
  std::cout << '\n';


  if (opts.unnamed_empty()) // Prüfen, ob keine unbenannte Option übergeben wurde
    std::cout << "Es wurde keine unbenannte Option gefunden.\n";
  else
  {
    std::cout << "Es wurde die unbenannte Optionen " << opts.unnamed_param<std::string>() << " gefunden.\n";

    try
    {
      int i = opts.unnamed_param<int>(true, 16); // Unbenannte Option als HEX Wert parsen und zum int konvertieren
      std::cout << "  Der Parameter laesst sich hexadezimal als int " << i << " konvertieren.\n";
    }
    catch(const std::exception&)
    {
      std::cout << "  Der Parameter laesst sich nicht hexadezimal als int konvertieren.\n";
    }
  }
  std::cout << '\n';

  return 0;
}


void process_name(const progopt::options& opts, const std::string& name)
{
  if (opts.named_includes({name})) // Prüfen, ob die Option übergeben wurde
  {
    std::cout << "    Option " << name << " wurde uebergeben.\n";
    if (opts.named_hasparam(name)) // Prüfen, ob der Option ein Parameter zugeordnet ist
    {
      std::cout << "      Option " << name << " hat den Parameter " << opts.named_param<std::string>(name) << ".\n";
      try
      {
        double dbl = opts.named_param<double>(name); // Parameter der Option zum double konvertieren
        std::cout << "        Der Parameter laesst sich zu double " << dbl << " konvertieren.\n";
      }
      catch(const std::exception&)
      {
        std::cout << "        Der Parameter laesst sich nicht zum double konvertieren.\n";
      }
    }
    else
      std::cout << "      Option " << name << " hat keinen Parameter.\n";
  }
  else
    std::cout << "    Option " << name << " wurde nicht uebergeben.\n";
}
Beispielaufrufe und deren Ausgabe:
program_options
Code:
Es wurden 1 benannte Optionen gefunden.
  Alle Namen haben die Kurzform.
    Option a wurde nicht uebergeben.
    Option b wurde uebergeben.
      Option b hat den Parameter 111.
        Der Parameter laesst sich zu double 111 konvertieren.
    Option ab wurde nicht uebergeben.

Es wurde keine unbenannte Option gefunden.
... Auch wenn keine Option übergeben wurde, wird Option b so behandelt, da programmatisch ein Defaultwert gesetzt wurde.

~~~~~
program_options BAE2 -ab FOO --ab -255.8
Code:
Es wurden 3 benannte Optionen gefunden.
  Es gibt Namen in Kurz- und Langform.
    Option a wurde uebergeben.
      Option a hat keinen Parameter.
    Option b wurde uebergeben.
      Option b hat den Parameter FOO.
        Der Parameter laesst sich nicht zum double konvertieren.
    Option ab wurde uebergeben.
      Option ab hat den Parameter -255.8.
        Der Parameter laesst sich zu double -255.8 konvertieren.

Es wurde die unbenannte Optionen BAE2 gefunden.
  Der Parameter laesst sich hexadezimal als int 47842 konvertieren.
~~~~~
program_options -a 123 -b BAR
Code:
Es wurden 2 benannte Optionen gefunden.
  Alle Namen haben die Kurzform.
    Option a wurde uebergeben.
      Option a hat den Parameter 123.
        Der Parameter laesst sich zu double 123 konvertieren.
    Option b wurde uebergeben.
      Option b hat den Parameter BAR.
        Der Parameter laesst sich nicht zum double konvertieren.
    Option ab wurde nicht uebergeben.

Es wurde keine unbenannte Option gefunden.
~~~~~
program_options -ba .5 --ab -3 BAE2
Code:
Es wurden 3 benannte Optionen gefunden.
  Es gibt Namen in Kurz- und Langform.
    Option a wurde uebergeben.
      Option a hat den Parameter .5.
        Der Parameter laesst sich zu double 0.5 konvertieren.
    Option b wurde uebergeben.
      Option b hat keinen Parameter.
    Option ab wurde uebergeben.
      Option ab hat den Parameter -3.
        Der Parameter laesst sich zu double -3 konvertieren.

Es wurde die unbenannte Optionen BAE2 gefunden.
  Der Parameter laesst sich hexadezimal als int 47842 konvertieren.
~~~~~
program_options -c FOO --ab BAR
Code:
Ungueltige Optionen.
... da -c im Beispielprogramm nicht in der Liste der gültigen Optionen zu finden ist.

~~~~~
program_options BAE2 -a FOO --ab BAR 1A2B
Code:
Ungueltige Optionen.
... da der Aufruf 2 unbenannte Optionen (BAE2 und 1A2B) enthält.
Autor
German
First release
Last update
Bewertung
0,00 Stern(e) 0 Bewertungen

Latest updates

  1. [C++] Programmoptionen parsen und Parameter konvertieren Ver. 1.3.

    C++14-spezifischen Code geändert und C++11 Konformität hergestellt (hoffentlich ;)).
  2. [C++] Programmoptionen parsen und Parameter konvertieren Ver. 1.2.

    Kleinere Anpassungen und Optimierungen.
  3. [C++] Programmoptionen parsen und Parameter konvertieren Ver. 1.1

    Methoden zum Setzen von Defaultwerten hinzugefügt. Überladung für Typen, die den >> Operator für...
Oben