cmArgumentParser.h 3.77 KB
Newer Older
wahikihiki's avatar
wahikihiki committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#ifndef cmArgumentParser_h
#define cmArgumentParser_h

#include "cmConfigure.h" // IWYU pragma: keep

#include "cm_static_string_view.hxx"
#include "cm_string_view.hxx"

#include <cassert>
#include <functional>
#include <string>
#include <utility>
#include <vector>

namespace ArgumentParser {

using StringList = std::vector<std::string>;
using MultiStringList = std::vector<StringList>;

class Instance;
using Action = std::function<void(Instance&, void*)>;

// using ActionMap = cm::flat_map<cm::string_view, Action>;
class ActionMap : public std::vector<std::pair<cm::string_view, Action>>
{
public:
  std::pair<iterator, bool> Emplace(cm::string_view name, Action action);
  const_iterator Find(cm::string_view name) const;
};

class Instance
{
public:
  Instance(ActionMap const& bindings)
    : Bindings(bindings)
  {
  }

  void Bind(bool& val);
  void Bind(std::string& val);
  void Bind(StringList& val);
  void Bind(MultiStringList& val);

  void Consume(cm::string_view arg, void* result,
               std::vector<std::string>* unparsedArguments,
               std::vector<std::string>* keywordsMissingValue);

private:
  ActionMap const& Bindings;
  std::string* CurrentString = nullptr;
  StringList* CurrentList = nullptr;
  bool ExpectValue = false;
};

} // namespace ArgumentParser

template <typename Result>
class cmArgumentParser
{
public:
  // I *think* this function could be made `constexpr` when the code is
  // compiled as C++20.  This would allow building a parser at compile time.
  template <typename T>
  cmArgumentParser& Bind(cm::static_string_view name, T Result::*member)
  {
    bool const inserted =
      this->Bindings
        .Emplace(name,
                 [member](ArgumentParser::Instance& instance, void* result) {
                   instance.Bind(static_cast<Result*>(result)->*member);
                 })
        .second;
    assert(inserted), (void)inserted;
    return *this;
  }

  template <typename Range>
  void Parse(Result& result, Range const& args,
             std::vector<std::string>* unparsedArguments = nullptr,
             std::vector<std::string>* keywordsMissingValue = nullptr) const
  {
    ArgumentParser::Instance instance(this->Bindings);
    for (cm::string_view arg : args) {
      instance.Consume(arg, &result, unparsedArguments, keywordsMissingValue);
    }
  }

  template <typename Range>
  Result Parse(Range const& args,
               std::vector<std::string>* unparsedArguments = nullptr,
               std::vector<std::string>* keywordsMissingValue = nullptr) const
  {
    Result result;
    this->Parse(result, args, unparsedArguments, keywordsMissingValue);
    return result;
  }

private:
  ArgumentParser::ActionMap Bindings;
};

template <>
class cmArgumentParser<void>
{
public:
  template <typename T>
  cmArgumentParser& Bind(cm::static_string_view name, T& ref)
  {
    bool const inserted = this->Bind(cm::string_view(name), ref);
    assert(inserted), (void)inserted;
    return *this;
  }

  template <typename Range>
  void Parse(Range const& args,
             std::vector<std::string>* unparsedArguments = nullptr,
             std::vector<std::string>* keywordsMissingValue = nullptr) const
  {
    ArgumentParser::Instance instance(this->Bindings);
    for (cm::string_view arg : args) {
      instance.Consume(arg, nullptr, unparsedArguments, keywordsMissingValue);
    }
  }

protected:
  template <typename T>
  bool Bind(cm::string_view name, T& ref)
  {
    return this->Bindings
      .Emplace(name,
               [&ref](ArgumentParser::Instance& instance, void*) {
                 instance.Bind(ref);
               })
      .second;
  }

private:
  ArgumentParser::ActionMap Bindings;
};

#endif