Browse Source

Merge topic 'argument-parser'

b783e62533 cmExecuteProcessCommand: Port to cmArgumentParser
9bddb03f31 cmParseArgumentsCommand: Port to cmArgumentParser
45edf1ad66 Retire cmCommandArgumentsHelper
f5acecaa6f cmExportCommand: Port to cmArgumentParser
e6b6bb0618 cmInstallCommand: Port to cmArgumentParser
4336a29edd cmFileCommand: Port to cmArgumentParser
4359fe133b Introduce cmArgumentParser

Acked-by: Kitware Robot <[email protected]>
Acked-by: Leonid Pospelov <[email protected]>
Merge-request: !3137
Brad King 6 years ago
parent
commit
aa0692de67

+ 2 - 2
Source/CMakeLists.txt

@@ -143,6 +143,8 @@ set(SRCS
   cmAffinity.cxx
   cmAffinity.h
   cmArchiveWrite.cxx
+  cmArgumentParser.cxx
+  cmArgumentParser.h
   cmBase32.cxx
   cmCacheManager.cxx
   cmCacheManager.h
@@ -443,8 +445,6 @@ set(SRCS
   cmCMakeMinimumRequired.h
   cmCMakePolicyCommand.cxx
   cmCMakePolicyCommand.h
-  cmCommandArgumentsHelper.cxx
-  cmCommandArgumentsHelper.h
   cmConditionEvaluator.cxx
   cmConditionEvaluator.h
   cmConfigureFileCommand.cxx

+ 93 - 0
Source/cmArgumentParser.cxx

@@ -0,0 +1,93 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmArgumentParser.h"
+
+#include <algorithm>
+#include <type_traits>
+
+namespace ArgumentParser {
+
+auto ActionMap::Emplace(cm::string_view name, Action action)
+  -> std::pair<iterator, bool>
+{
+  auto const it =
+    std::lower_bound(this->begin(), this->end(), name,
+                     [](value_type const& elem, cm::string_view const& k) {
+                       return elem.first < k;
+                     });
+  return (it != this->end() && it->first == name)
+    ? std::make_pair(it, false)
+    : std::make_pair(this->emplace(it, name, std::move(action)), true);
+}
+
+auto ActionMap::Find(cm::string_view name) const -> const_iterator
+{
+  auto const it =
+    std::lower_bound(this->begin(), this->end(), name,
+                     [](value_type const& elem, cm::string_view const& k) {
+                       return elem.first < k;
+                     });
+  return (it != this->end() && it->first == name) ? it : this->end();
+}
+
+void Instance::Bind(bool& val)
+{
+  val = true;
+  this->CurrentString = nullptr;
+  this->CurrentList = nullptr;
+  this->ExpectValue = false;
+}
+
+void Instance::Bind(std::string& val)
+{
+  this->CurrentString = &val;
+  this->CurrentList = nullptr;
+  this->ExpectValue = true;
+}
+
+void Instance::Bind(StringList& val)
+{
+  this->CurrentString = nullptr;
+  this->CurrentList = &val;
+  this->ExpectValue = true;
+}
+
+void Instance::Bind(MultiStringList& val)
+{
+  this->CurrentString = nullptr;
+  this->CurrentList = (val.emplace_back(), &val.back());
+  this->ExpectValue = false;
+}
+
+void Instance::Consume(cm::string_view arg, void* result,
+                       std::vector<std::string>* unparsedArguments,
+                       std::vector<std::string>* keywordsMissingValue)
+{
+  auto const it = this->Bindings.Find(arg);
+  if (it != this->Bindings.end()) {
+    it->second(*this, result);
+    if (this->ExpectValue && keywordsMissingValue != nullptr) {
+      keywordsMissingValue->emplace_back(arg);
+    }
+    return;
+  }
+
+  if (this->CurrentString != nullptr) {
+    this->CurrentString->assign(std::string(arg));
+    this->CurrentString = nullptr;
+    this->CurrentList = nullptr;
+  } else if (this->CurrentList != nullptr) {
+    this->CurrentList->emplace_back(arg);
+  } else if (unparsedArguments != nullptr) {
+    unparsedArguments->emplace_back(arg);
+  }
+
+  if (this->ExpectValue) {
+    if (keywordsMissingValue != nullptr) {
+      keywordsMissingValue->pop_back();
+    }
+    this->ExpectValue = false;
+  }
+}
+
+} // namespace ArgumentParser

+ 143 - 0
Source/cmArgumentParser.h

@@ -0,0 +1,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

+ 0 - 233
Source/cmCommandArgumentsHelper.cxx

@@ -1,233 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing for details.  */
-#include "cmCommandArgumentsHelper.h"
-
-cmCommandArgument::cmCommandArgument(cmCommandArgumentsHelper* args,
-                                     const char* key,
-                                     cmCommandArgumentGroup* group)
-  : Key(key)
-  , Group(group)
-  , WasActive(false)
-  , ArgumentsBeforeEmpty(true)
-  , CurrentIndex(0)
-{
-  if (args != nullptr) {
-    args->AddArgument(this);
-  }
-
-  if (this->Group != nullptr) {
-    this->Group->ContainedArguments.push_back(this);
-  }
-}
-
-void cmCommandArgument::Reset()
-{
-  this->WasActive = false;
-  this->CurrentIndex = 0;
-  this->DoReset();
-}
-
-void cmCommandArgument::Follows(const cmCommandArgument* arg)
-{
-  this->ArgumentsBeforeEmpty = false;
-  this->ArgumentsBefore.insert(arg);
-}
-
-void cmCommandArgument::FollowsGroup(const cmCommandArgumentGroup* group)
-{
-  if (group != nullptr) {
-    this->ArgumentsBeforeEmpty = false;
-    this->ArgumentsBefore.insert(group->ContainedArguments.begin(),
-                                 group->ContainedArguments.end());
-  }
-}
-
-bool cmCommandArgument::MayFollow(const cmCommandArgument* current) const
-{
-  if (this->ArgumentsBeforeEmpty) {
-    return true;
-  }
-  return this->ArgumentsBefore.find(current) != this->ArgumentsBefore.end();
-}
-
-bool cmCommandArgument::KeyMatches(const std::string& key) const
-{
-  if ((this->Key == nullptr) || (this->Key[0] == '\0')) {
-    return true;
-  }
-  return (key == this->Key);
-}
-
-void cmCommandArgument::ApplyOwnGroup()
-{
-  if (this->Group != nullptr) {
-    for (cmCommandArgument* cargs : this->Group->ContainedArguments) {
-      if (cargs != this) {
-        this->ArgumentsBefore.insert(cargs);
-      }
-    }
-  }
-}
-
-void cmCommandArgument::Activate()
-{
-  this->WasActive = true;
-  this->CurrentIndex = 0;
-}
-
-bool cmCommandArgument::Consume(const std::string& arg)
-{
-  bool res = this->DoConsume(arg, this->CurrentIndex);
-  this->CurrentIndex++;
-  return res;
-}
-
-cmCAStringVector::cmCAStringVector(cmCommandArgumentsHelper* args,
-                                   const char* key,
-                                   cmCommandArgumentGroup* group)
-  : cmCommandArgument(args, key, group)
-  , Ignore(nullptr)
-{
-  if ((key == nullptr) || (*key == 0)) {
-    this->DataStart = 0;
-  } else {
-    this->DataStart = 1;
-  }
-}
-
-bool cmCAStringVector::DoConsume(const std::string& arg, unsigned int index)
-{
-  if (index >= this->DataStart) {
-    if ((this->Ignore == nullptr) || (arg != this->Ignore)) {
-      this->Vector.push_back(arg);
-    }
-  }
-
-  return false;
-}
-
-void cmCAStringVector::DoReset()
-{
-  this->Vector.clear();
-}
-
-cmCAString::cmCAString(cmCommandArgumentsHelper* args, const char* key,
-                       cmCommandArgumentGroup* group)
-  : cmCommandArgument(args, key, group)
-{
-  if ((key == nullptr) || (*key == 0)) {
-    this->DataStart = 0;
-  } else {
-    this->DataStart = 1;
-  }
-}
-
-bool cmCAString::DoConsume(const std::string& arg, unsigned int index)
-{
-  if (index == this->DataStart) {
-    this->String = arg;
-  }
-
-  return index >= this->DataStart;
-}
-
-void cmCAString::DoReset()
-{
-  this->String.clear();
-}
-
-cmCAEnabler::cmCAEnabler(cmCommandArgumentsHelper* args, const char* key,
-                         cmCommandArgumentGroup* group)
-  : cmCommandArgument(args, key, group)
-  , Enabled(false)
-{
-}
-
-bool cmCAEnabler::DoConsume(const std::string&, unsigned int index)
-{
-  if (index == 0) {
-    this->Enabled = true;
-  }
-  return true;
-}
-
-void cmCAEnabler::DoReset()
-{
-  this->Enabled = false;
-}
-
-cmCADisabler::cmCADisabler(cmCommandArgumentsHelper* args, const char* key,
-                           cmCommandArgumentGroup* group)
-  : cmCommandArgument(args, key, group)
-  , Enabled(true)
-{
-}
-
-bool cmCADisabler::DoConsume(const std::string&, unsigned int index)
-{
-  if (index == 0) {
-    this->Enabled = false;
-  }
-  return true;
-}
-
-void cmCADisabler::DoReset()
-{
-  this->Enabled = true;
-}
-
-void cmCommandArgumentGroup::Follows(const cmCommandArgument* arg)
-{
-  for (cmCommandArgument* ca : this->ContainedArguments) {
-    ca->Follows(arg);
-  }
-}
-
-void cmCommandArgumentGroup::FollowsGroup(const cmCommandArgumentGroup* group)
-{
-  for (cmCommandArgument* ca : this->ContainedArguments) {
-    ca->FollowsGroup(group);
-  }
-}
-
-void cmCommandArgumentsHelper::Parse(const std::vector<std::string>* args,
-                                     std::vector<std::string>* unconsumedArgs)
-{
-  if (args == nullptr) {
-    return;
-  }
-
-  for (cmCommandArgument* ca : this->Arguments) {
-    ca->ApplyOwnGroup();
-    ca->Reset();
-  }
-
-  cmCommandArgument* activeArgument = nullptr;
-  const cmCommandArgument* previousArgument = nullptr;
-  for (std::string const& it : *args) {
-    for (cmCommandArgument* ca : this->Arguments) {
-      if (ca->KeyMatches(it) && (ca->MayFollow(previousArgument))) {
-        activeArgument = ca;
-        activeArgument->Activate();
-        break;
-      }
-    }
-
-    if (activeArgument) {
-      bool argDone = activeArgument->Consume(it);
-      previousArgument = activeArgument;
-      if (argDone) {
-        activeArgument = nullptr;
-      }
-    } else {
-      if (unconsumedArgs != nullptr) {
-        unconsumedArgs->push_back(it);
-      }
-    }
-  }
-}
-
-void cmCommandArgumentsHelper::AddArgument(cmCommandArgument* arg)
-{
-  this->Arguments.push_back(arg);
-}

+ 0 - 194
Source/cmCommandArgumentsHelper.h

@@ -1,194 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing for details.  */
-#ifndef cmCommandArgumentsHelper_h
-#define cmCommandArgumentsHelper_h
-
-#include "cmConfigure.h" // IWYU pragma: keep
-
-#include <set>
-#include <string>
-#include <vector>
-
-class cmCommandArgumentGroup;
-class cmCommandArgumentsHelper;
-
-/* cmCommandArgumentsHelper, cmCommandArgumentGroup and cmCommandArgument (i.e.
-its derived classes cmCAXXX can be used to simplify the processing of
-arguments to cmake commands. Maybe they can also be used to generate
-documentation.
-
-For every argument supported by a command one cmCommandArgument is created
-and added to cmCommandArgumentsHelper. cmCommand has a cmCommandArgumentsHelper
-as member variable so this should be used.
-
-The order of the arguments is defined using the Follows(arg) method. It says
-that this argument follows immediateley the given argument. It can be used
-with multiple arguments if the argument can follow after different arguments.
-
-Arguments can be arranged in groups using cmCommandArgumentGroup. Every
-member of a group can follow any other member of the group. These groups
-can also be used to define the order.
-
-Once all arguments and groups are set up, cmCommandArgumentsHelper::Parse()
-is called and afterwards the values of the arguments can be evaluated.
-
-For an example see cmExportCommand.cxx.
-*/
-class cmCommandArgument
-{
-public:
-  cmCommandArgument(cmCommandArgumentsHelper* args, const char* key,
-                    cmCommandArgumentGroup* group = nullptr);
-  virtual ~cmCommandArgument() = default;
-
-  /// this argument may follow after arg. 0 means it comes first.
-  void Follows(const cmCommandArgument* arg);
-
-  /// this argument may follow after any of the arguments in the given group
-  void FollowsGroup(const cmCommandArgumentGroup* group);
-
-  /// Returns true if the argument was found in the argument list
-  bool WasFound() const { return this->WasActive; }
-
-  // The following methods are only called from
-  // cmCommandArgumentsHelper::Parse(), but making this a friend would
-  // give it access to everything
-
-  /// Make the current argument the currently active argument
-  void Activate();
-  /// Consume the current string
-  bool Consume(const std::string& arg);
-
-  /// Return true if this argument may follow after the given argument.
-  bool MayFollow(const cmCommandArgument* current) const;
-
-  /** Returns true if the given key matches the key for this argument.
-  If this argument has an empty key everything matches. */
-  bool KeyMatches(const std::string& key) const;
-
-  /// Make this argument follow all members of the own group
-  void ApplyOwnGroup();
-
-  /// Reset argument, so it's back to its initial state
-  void Reset();
-
-private:
-  const char* Key;
-  std::set<const cmCommandArgument*> ArgumentsBefore;
-  cmCommandArgumentGroup* Group;
-  bool WasActive;
-  bool ArgumentsBeforeEmpty;
-  unsigned int CurrentIndex;
-
-  virtual bool DoConsume(const std::string& arg, unsigned int index) = 0;
-  virtual void DoReset() = 0;
-};
-
-/** cmCAStringVector is to be used for arguments which can consist of more
-than one string, e.g. the FILES argument in INSTALL(FILES f1 f2 f3 ...). */
-class cmCAStringVector : public cmCommandArgument
-{
-public:
-  cmCAStringVector(cmCommandArgumentsHelper* args, const char* key,
-                   cmCommandArgumentGroup* group = nullptr);
-
-  /// Return the vector of strings
-  const std::vector<std::string>& GetVector() const { return this->Vector; }
-
-  /** Is there a keyword which should be skipped in
-  the arguments (e.g. ARGS for ADD_CUSTOM_COMMAND) ? */
-  void SetIgnore(const char* ignore) { this->Ignore = ignore; }
-
-private:
-  std::vector<std::string> Vector;
-  unsigned int DataStart;
-  const char* Ignore;
-  bool DoConsume(const std::string& arg, unsigned int index) override;
-  void DoReset() override;
-};
-
-/** cmCAString is to be used for arguments which consist of one value,
-e.g. the executable name in ADD_EXECUTABLE(). */
-class cmCAString : public cmCommandArgument
-{
-public:
-  cmCAString(cmCommandArgumentsHelper* args, const char* key,
-             cmCommandArgumentGroup* group = nullptr);
-
-  /// Return the string
-  const std::string& GetString() const { return this->String; }
-  const char* GetCString() const { return this->String.c_str(); }
-
-private:
-  std::string String;
-  unsigned int DataStart;
-  bool DoConsume(const std::string& arg, unsigned int index) override;
-  void DoReset() override;
-};
-
-/** cmCAEnabler is to be used for options which are off by default and can be
-enabled using a special argument, e.g. EXCLUDE_FROM_ALL in ADD_EXECUTABLE(). */
-class cmCAEnabler : public cmCommandArgument
-{
-public:
-  cmCAEnabler(cmCommandArgumentsHelper* args, const char* key,
-              cmCommandArgumentGroup* group = nullptr);
-
-  /// Has it been enabled ?
-  bool IsEnabled() const { return this->Enabled; }
-
-private:
-  bool Enabled;
-  bool DoConsume(const std::string& arg, unsigned int index) override;
-  void DoReset() override;
-};
-
-/** cmCADisable is to be used for options which are on by default and can be
-disabled using a special argument.*/
-class cmCADisabler : public cmCommandArgument
-{
-public:
-  cmCADisabler(cmCommandArgumentsHelper* args, const char* key,
-               cmCommandArgumentGroup* group = nullptr);
-
-  /// Is it still enabled ?
-  bool IsEnabled() const { return this->Enabled; }
-
-private:
-  bool Enabled;
-  bool DoConsume(const std::string& arg, unsigned int index) override;
-  void DoReset() override;
-};
-
-/** Group of arguments, needed for ordering. E.g. WIN32, EXCLUDE_FROM_ALL and
-MACSOX_BUNDLE from ADD_EXECUTABLE() are a group.
-*/
-class cmCommandArgumentGroup
-{
-  friend class cmCommandArgument;
-
-public:
-  /// All members of this group may follow the given argument
-  void Follows(const cmCommandArgument* arg);
-
-  /// All members of this group may follow all members of the given group
-  void FollowsGroup(const cmCommandArgumentGroup* group);
-
-private:
-  std::vector<cmCommandArgument*> ContainedArguments;
-};
-
-class cmCommandArgumentsHelper
-{
-public:
-  /// Parse the argument list
-  void Parse(const std::vector<std::string>* args,
-             std::vector<std::string>* unconsumedArgs);
-  /// Add an argument.
-  void AddArgument(cmCommandArgument* arg);
-
-private:
-  std::vector<cmCommandArgument*> Arguments;
-};
-
-#endif

+ 110 - 170
Source/cmExecuteProcessCommand.cxx

@@ -2,12 +2,14 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmExecuteProcessCommand.h"
 
+#include "cm_static_string_view.hxx"
 #include "cmsys/Process.h"
+#include <algorithm>
 #include <ctype.h> /* isspace */
-#include <sstream>
 #include <stdio.h>
 
 #include "cmAlgorithms.h"
+#include "cmArgumentParser.h"
 #include "cmMakefile.h"
 #include "cmProcessOutput.h"
 #include "cmSystemTools.h"
@@ -32,157 +34,85 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
     this->SetError("called with incorrect number of arguments");
     return false;
   }
-  std::vector<std::vector<const char*>> cmds;
-  std::string arguments;
-  bool doing_command = false;
-  size_t command_index = 0;
-  bool output_quiet = false;
-  bool error_quiet = false;
-  bool output_strip_trailing_whitespace = false;
-  bool error_strip_trailing_whitespace = false;
-  std::string timeout_string;
-  std::string input_file;
-  std::string output_file;
-  std::string error_file;
-  std::string output_variable;
-  std::string error_variable;
-  std::string result_variable;
-  std::string results_variable;
-  std::string working_directory;
-  cmProcessOutput::Encoding encoding = cmProcessOutput::None;
-  for (size_t i = 0; i < args.size(); ++i) {
-    if (args[i] == "COMMAND") {
-      doing_command = true;
-      command_index = cmds.size();
-      cmds.emplace_back();
-    } else if (args[i] == "OUTPUT_VARIABLE") {
-      doing_command = false;
-      if (++i < args.size()) {
-        output_variable = args[i];
-      } else {
-        this->SetError(" called with no value for OUTPUT_VARIABLE.");
-        return false;
-      }
-    } else if (args[i] == "ERROR_VARIABLE") {
-      doing_command = false;
-      if (++i < args.size()) {
-        error_variable = args[i];
-      } else {
-        this->SetError(" called with no value for ERROR_VARIABLE.");
-        return false;
-      }
-    } else if (args[i] == "RESULT_VARIABLE") {
-      doing_command = false;
-      if (++i < args.size()) {
-        result_variable = args[i];
-      } else {
-        this->SetError(" called with no value for RESULT_VARIABLE.");
-        return false;
-      }
-    } else if (args[i] == "RESULTS_VARIABLE") {
-      doing_command = false;
-      if (++i < args.size()) {
-        results_variable = args[i];
-      } else {
-        this->SetError(" called with no value for RESULTS_VARIABLE.");
-        return false;
-      }
-    } else if (args[i] == "WORKING_DIRECTORY") {
-      doing_command = false;
-      if (++i < args.size()) {
-        working_directory = args[i];
-      } else {
-        this->SetError(" called with no value for WORKING_DIRECTORY.");
-        return false;
-      }
-    } else if (args[i] == "INPUT_FILE") {
-      doing_command = false;
-      if (++i < args.size()) {
-        input_file = args[i];
-      } else {
-        this->SetError(" called with no value for INPUT_FILE.");
-        return false;
-      }
-    } else if (args[i] == "OUTPUT_FILE") {
-      doing_command = false;
-      if (++i < args.size()) {
-        output_file = args[i];
-      } else {
-        this->SetError(" called with no value for OUTPUT_FILE.");
-        return false;
-      }
-    } else if (args[i] == "ERROR_FILE") {
-      doing_command = false;
-      if (++i < args.size()) {
-        error_file = args[i];
-      } else {
-        this->SetError(" called with no value for ERROR_FILE.");
-        return false;
-      }
-    } else if (args[i] == "TIMEOUT") {
-      doing_command = false;
-      if (++i < args.size()) {
-        timeout_string = args[i];
-      } else {
-        this->SetError(" called with no value for TIMEOUT.");
-        return false;
-      }
-    } else if (args[i] == "OUTPUT_QUIET") {
-      doing_command = false;
-      output_quiet = true;
-    } else if (args[i] == "ERROR_QUIET") {
-      doing_command = false;
-      error_quiet = true;
-    } else if (args[i] == "OUTPUT_STRIP_TRAILING_WHITESPACE") {
-      doing_command = false;
-      output_strip_trailing_whitespace = true;
-    } else if (args[i] == "ERROR_STRIP_TRAILING_WHITESPACE") {
-      doing_command = false;
-      error_strip_trailing_whitespace = true;
-    } else if (args[i] == "ENCODING") {
-      doing_command = false;
-      if (++i < args.size()) {
-        encoding = cmProcessOutput::FindEncoding(args[i]);
-      } else {
-        this->SetError(" called with no value for ENCODING.");
-        return false;
-      }
-    } else if (doing_command) {
-      cmds[command_index].push_back(args[i].c_str());
-    } else {
-      std::ostringstream e;
-      e << " given unknown argument \"" << args[i] << "\".";
-      this->SetError(e.str());
-      return false;
-    }
+
+  struct Arguments
+  {
+    std::vector<std::vector<std::string>> Commands;
+    std::string OutputVariable;
+    std::string ErrorVariable;
+    std::string ResultVariable;
+    std::string ResultsVariable;
+    std::string WorkingDirectory;
+    std::string InputFile;
+    std::string OutputFile;
+    std::string ErrorFile;
+    std::string Timeout;
+    bool OutputQuiet = false;
+    bool ErrorQuiet = false;
+    bool OutputStripTrailingWhitespace = false;
+    bool ErrorStripTrailingWhitespace = false;
+    std::string Encoding;
+  };
+
+  static auto const parser =
+    cmArgumentParser<Arguments>{}
+      .Bind("COMMAND"_s, &Arguments::Commands)
+      .Bind("OUTPUT_VARIABLE"_s, &Arguments::OutputVariable)
+      .Bind("ERROR_VARIABLE"_s, &Arguments::ErrorVariable)
+      .Bind("RESULT_VARIABLE"_s, &Arguments::ResultVariable)
+      .Bind("RESULTS_VARIABLE"_s, &Arguments::ResultsVariable)
+      .Bind("WORKING_DIRECTORY"_s, &Arguments::WorkingDirectory)
+      .Bind("INPUT_FILE"_s, &Arguments::InputFile)
+      .Bind("OUTPUT_FILE"_s, &Arguments::OutputFile)
+      .Bind("ERROR_FILE"_s, &Arguments::ErrorFile)
+      .Bind("TIMEOUT"_s, &Arguments::Timeout)
+      .Bind("OUTPUT_QUIET"_s, &Arguments::OutputQuiet)
+      .Bind("ERROR_QUIET"_s, &Arguments::ErrorQuiet)
+      .Bind("OUTPUT_STRIP_TRAILING_WHITESPACE"_s,
+            &Arguments::OutputStripTrailingWhitespace)
+      .Bind("ERROR_STRIP_TRAILING_WHITESPACE"_s,
+            &Arguments::ErrorStripTrailingWhitespace)
+      .Bind("ENCODING"_s, &Arguments::Encoding);
+
+  std::vector<std::string> unparsedArguments;
+  std::vector<std::string> keywordsMissingValue;
+  Arguments const arguments =
+    parser.Parse(args, &unparsedArguments, &keywordsMissingValue);
+
+  if (!keywordsMissingValue.empty()) {
+    this->SetError(" called with no value for " +
+                   keywordsMissingValue.front() + ".");
+    return false;
+  }
+  if (!unparsedArguments.empty()) {
+    this->SetError(" given unknown argument \"" + unparsedArguments.front() +
+                   "\".");
+    return false;
   }
 
-  if (!this->Makefile->CanIWriteThisFile(output_file)) {
-    std::string e = "attempted to output into a file: " + output_file +
-      " into a source directory.";
-    this->SetError(e);
+  if (!this->Makefile->CanIWriteThisFile(arguments.OutputFile)) {
+    this->SetError("attempted to output into a file: " + arguments.OutputFile +
+                   " into a source directory.");
     cmSystemTools::SetFatalErrorOccured();
     return false;
   }
 
   // Check for commands given.
-  if (cmds.empty()) {
+  if (arguments.Commands.empty()) {
     this->SetError(" called with no COMMAND argument.");
     return false;
   }
-  for (auto& cmd : cmds) {
+  for (std::vector<std::string> const& cmd : arguments.Commands) {
     if (cmd.empty()) {
       this->SetError(" given COMMAND argument with no value.");
       return false;
     }
-    // Add the null terminating pointer to the command argument list.
-    cmd.push_back(nullptr);
   }
 
   // Parse the timeout string.
   double timeout = -1;
-  if (!timeout_string.empty()) {
-    if (sscanf(timeout_string.c_str(), "%lg", &timeout) != 1) {
+  if (!arguments.Timeout.empty()) {
+    if (sscanf(arguments.Timeout.c_str(), "%lg", &timeout) != 1) {
       this->SetError(" called with TIMEOUT value that could not be parsed.");
       return false;
     }
@@ -192,13 +122,17 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
   cmsysProcess* cp = cmsysProcess_New();
 
   // Set the command sequence.
-  for (auto const& cmd : cmds) {
-    cmsysProcess_AddCommand(cp, cmd.data());
+  for (std::vector<std::string> const& cmd : arguments.Commands) {
+    std::vector<const char*> argv(cmd.size() + 1);
+    std::transform(cmd.begin(), cmd.end(), argv.begin(),
+                   [](std::string const& s) { return s.c_str(); });
+    argv.back() = nullptr;
+    cmsysProcess_AddCommand(cp, argv.data());
   }
 
   // Set the process working directory.
-  if (!working_directory.empty()) {
-    cmsysProcess_SetWorkingDirectory(cp, working_directory.c_str());
+  if (!arguments.WorkingDirectory.empty()) {
+    cmsysProcess_SetWorkingDirectory(cp, arguments.WorkingDirectory.c_str());
   }
 
   // Always hide the process window.
@@ -206,22 +140,24 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
 
   // Check the output variables.
   bool merge_output = false;
-  if (!input_file.empty()) {
-    cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDIN, input_file.c_str());
+  if (!arguments.InputFile.empty()) {
+    cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDIN,
+                             arguments.InputFile.c_str());
   }
-  if (!output_file.empty()) {
+  if (!arguments.OutputFile.empty()) {
     cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDOUT,
-                             output_file.c_str());
+                             arguments.OutputFile.c_str());
   }
-  if (!error_file.empty()) {
-    if (error_file == output_file) {
+  if (!arguments.ErrorFile.empty()) {
+    if (arguments.ErrorFile == arguments.OutputFile) {
       merge_output = true;
     } else {
       cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDERR,
-                               error_file.c_str());
+                               arguments.ErrorFile.c_str());
     }
   }
-  if (!output_variable.empty() && output_variable == error_variable) {
+  if (!arguments.OutputVariable.empty() &&
+      arguments.OutputVariable == arguments.ErrorVariable) {
     merge_output = true;
   }
   if (merge_output) {
@@ -242,19 +178,20 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
   int length;
   char* data;
   int p;
-  cmProcessOutput processOutput(encoding);
+  cmProcessOutput processOutput(
+    cmProcessOutput::FindEncoding(arguments.Encoding));
   std::string strdata;
   while ((p = cmsysProcess_WaitForData(cp, &data, &length, nullptr))) {
     // Put the output in the right place.
-    if (p == cmsysProcess_Pipe_STDOUT && !output_quiet) {
-      if (output_variable.empty()) {
+    if (p == cmsysProcess_Pipe_STDOUT && !arguments.OutputQuiet) {
+      if (arguments.OutputVariable.empty()) {
         processOutput.DecodeText(data, length, strdata, 1);
         cmSystemTools::Stdout(strdata);
       } else {
         cmExecuteProcessCommandAppend(tempOutput, data, length);
       }
-    } else if (p == cmsysProcess_Pipe_STDERR && !error_quiet) {
-      if (error_variable.empty()) {
+    } else if (p == cmsysProcess_Pipe_STDERR && !arguments.ErrorQuiet) {
+      if (arguments.ErrorVariable.empty()) {
         processOutput.DecodeText(data, length, strdata, 2);
         cmSystemTools::Stderr(strdata);
       } else {
@@ -262,13 +199,13 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
       }
     }
   }
-  if (!output_quiet && output_variable.empty()) {
+  if (!arguments.OutputQuiet && arguments.OutputVariable.empty()) {
     processOutput.DecodeText(std::string(), strdata, 1);
     if (!strdata.empty()) {
       cmSystemTools::Stdout(strdata);
     }
   }
-  if (!error_quiet && error_variable.empty()) {
+  if (!arguments.ErrorQuiet && arguments.ErrorVariable.empty()) {
     processOutput.DecodeText(std::string(), strdata, 2);
     if (!strdata.empty()) {
       cmSystemTools::Stderr(strdata);
@@ -281,46 +218,49 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
   processOutput.DecodeText(tempError, tempError);
 
   // Fix the text in the output strings.
-  cmExecuteProcessCommandFixText(tempOutput, output_strip_trailing_whitespace);
-  cmExecuteProcessCommandFixText(tempError, error_strip_trailing_whitespace);
+  cmExecuteProcessCommandFixText(tempOutput,
+                                 arguments.OutputStripTrailingWhitespace);
+  cmExecuteProcessCommandFixText(tempError,
+                                 arguments.ErrorStripTrailingWhitespace);
 
   // Store the output obtained.
-  if (!output_variable.empty() && !tempOutput.empty()) {
-    this->Makefile->AddDefinition(output_variable, tempOutput.data());
+  if (!arguments.OutputVariable.empty() && !tempOutput.empty()) {
+    this->Makefile->AddDefinition(arguments.OutputVariable, tempOutput.data());
   }
-  if (!merge_output && !error_variable.empty() && !tempError.empty()) {
-    this->Makefile->AddDefinition(error_variable, tempError.data());
+  if (!merge_output && !arguments.ErrorVariable.empty() &&
+      !tempError.empty()) {
+    this->Makefile->AddDefinition(arguments.ErrorVariable, tempError.data());
   }
 
   // Store the result of running the process.
-  if (!result_variable.empty()) {
+  if (!arguments.ResultVariable.empty()) {
     switch (cmsysProcess_GetState(cp)) {
       case cmsysProcess_State_Exited: {
         int v = cmsysProcess_GetExitValue(cp);
         char buf[16];
         sprintf(buf, "%d", v);
-        this->Makefile->AddDefinition(result_variable, buf);
+        this->Makefile->AddDefinition(arguments.ResultVariable, buf);
       } break;
       case cmsysProcess_State_Exception:
-        this->Makefile->AddDefinition(result_variable,
+        this->Makefile->AddDefinition(arguments.ResultVariable,
                                       cmsysProcess_GetExceptionString(cp));
         break;
       case cmsysProcess_State_Error:
-        this->Makefile->AddDefinition(result_variable,
+        this->Makefile->AddDefinition(arguments.ResultVariable,
                                       cmsysProcess_GetErrorString(cp));
         break;
       case cmsysProcess_State_Expired:
-        this->Makefile->AddDefinition(result_variable,
+        this->Makefile->AddDefinition(arguments.ResultVariable,
                                       "Process terminated due to timeout");
         break;
     }
   }
   // Store the result of running the processes.
-  if (!results_variable.empty()) {
+  if (!arguments.ResultsVariable.empty()) {
     switch (cmsysProcess_GetState(cp)) {
       case cmsysProcess_State_Exited: {
         std::vector<std::string> res;
-        for (size_t i = 0; i < cmds.size(); ++i) {
+        for (size_t i = 0; i < arguments.Commands.size(); ++i) {
           switch (cmsysProcess_GetStateByIndex(cp, static_cast<int>(i))) {
             case kwsysProcess_StateByIndex_Exited: {
               int exitCode =
@@ -339,19 +279,19 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
               break;
           }
         }
-        this->Makefile->AddDefinition(results_variable,
+        this->Makefile->AddDefinition(arguments.ResultsVariable,
                                       cmJoin(res, ";").c_str());
       } break;
       case cmsysProcess_State_Exception:
-        this->Makefile->AddDefinition(results_variable,
+        this->Makefile->AddDefinition(arguments.ResultsVariable,
                                       cmsysProcess_GetExceptionString(cp));
         break;
       case cmsysProcess_State_Error:
-        this->Makefile->AddDefinition(results_variable,
+        this->Makefile->AddDefinition(arguments.ResultsVariable,
                                       cmsysProcess_GetErrorString(cp));
         break;
       case cmsysProcess_State_Expired:
-        this->Makefile->AddDefinition(results_variable,
+        this->Makefile->AddDefinition(arguments.ResultsVariable,
                                       "Process terminated due to timeout");
         break;
     }

+ 48 - 54
Source/cmExportCommand.cxx

@@ -2,10 +2,13 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmExportCommand.h"
 
+#include "cm_static_string_view.hxx"
 #include "cmsys/RegularExpression.hxx"
 #include <map>
 #include <sstream>
+#include <utility>
 
+#include "cmArgumentParser.h"
 #include "cmExportBuildAndroidMKGenerator.h"
 #include "cmExportBuildFileGenerator.h"
 #include "cmExportSetMap.h"
@@ -18,6 +21,7 @@
 #include "cmSystemTools.h"
 #include "cmTarget.h"
 
+class cmExportSet;
 class cmExecutionStatus;
 
 #if defined(__HAIKU__)
@@ -25,19 +29,6 @@ class cmExecutionStatus;
 #  include <StorageDefs.h>
 #endif
 
-cmExportCommand::cmExportCommand()
-  : Targets(&Helper, "TARGETS")
-  , Append(&Helper, "APPEND", &ArgumentGroup)
-  , ExportSetName(&Helper, "EXPORT", &ArgumentGroup)
-  , Namespace(&Helper, "NAMESPACE", &ArgumentGroup)
-  , Filename(&Helper, "FILE", &ArgumentGroup)
-  , ExportOld(&Helper, "EXPORT_LINK_INTERFACE_LIBRARIES", &ArgumentGroup)
-  , AndroidMKFile(&Helper, "ANDROID_MK")
-{
-  this->ExportSet = nullptr;
-}
-
-// cmExportCommand
 bool cmExportCommand::InitialPass(std::vector<std::string> const& args,
                                   cmExecutionStatus&)
 {
@@ -49,45 +40,62 @@ bool cmExportCommand::InitialPass(std::vector<std::string> const& args,
   if (args[0] == "PACKAGE") {
     return this->HandlePackage(args);
   }
+
+  struct Arguments
+  {
+    std::string ExportSetName;
+    std::vector<std::string> Targets;
+    std::string Namespace;
+    std::string Filename;
+    std::string AndroidMKFile;
+    bool Append = false;
+    bool ExportOld = false;
+  };
+
+  auto parser = cmArgumentParser<Arguments>{}
+                  .Bind("NAMESPACE"_s, &Arguments::Namespace)
+                  .Bind("FILE"_s, &Arguments::Filename);
+
   if (args[0] == "EXPORT") {
-    this->ExportSetName.Follows(nullptr);
-    this->ArgumentGroup.Follows(&this->ExportSetName);
+    parser.Bind("EXPORT"_s, &Arguments::ExportSetName);
   } else {
-    this->Targets.Follows(nullptr);
-    this->ArgumentGroup.Follows(&this->Targets);
+    parser.Bind("TARGETS"_s, &Arguments::Targets);
+    parser.Bind("ANDROID_MK"_s, &Arguments::AndroidMKFile);
+    parser.Bind("APPEND"_s, &Arguments::Append);
+    parser.Bind("EXPORT_LINK_INTERFACE_LIBRARIES"_s, &Arguments::ExportOld);
   }
 
   std::vector<std::string> unknownArgs;
-  this->Helper.Parse(&args, &unknownArgs);
+  Arguments const arguments = parser.Parse(args, &unknownArgs);
 
   if (!unknownArgs.empty()) {
-    this->SetError("Unknown arguments.");
+    this->SetError("Unknown argument: \"" + unknownArgs.front() + "\".");
     return false;
   }
 
   std::string fname;
   bool android = false;
-  if (this->AndroidMKFile.WasFound()) {
-    fname = this->AndroidMKFile.GetString();
+  if (!arguments.AndroidMKFile.empty()) {
+    fname = arguments.AndroidMKFile;
     android = true;
   }
-  if (!this->Filename.WasFound() && fname.empty()) {
+  if (arguments.Filename.empty() && fname.empty()) {
     if (args[0] != "EXPORT") {
       this->SetError("FILE <filename> option missing.");
       return false;
     }
-    fname = this->ExportSetName.GetString() + ".cmake";
+    fname = arguments.ExportSetName + ".cmake";
   } else if (fname.empty()) {
     // Make sure the file has a .cmake extension.
-    if (cmSystemTools::GetFilenameLastExtension(this->Filename.GetCString()) !=
+    if (cmSystemTools::GetFilenameLastExtension(arguments.Filename) !=
         ".cmake") {
       std::ostringstream e;
-      e << "FILE option given filename \"" << this->Filename.GetString()
+      e << "FILE option given filename \"" << arguments.Filename
         << "\" which does not have an extension of \".cmake\".\n";
       this->SetError(e.str());
       return false;
     }
-    fname = this->Filename.GetString();
+    fname = arguments.Filename;
   }
 
   // Get the file to write.
@@ -109,33 +117,19 @@ bool cmExportCommand::InitialPass(std::vector<std::string> const& args,
 
   cmGlobalGenerator* gg = this->Makefile->GetGlobalGenerator();
 
+  cmExportSet* ExportSet = nullptr;
   if (args[0] == "EXPORT") {
-    if (this->Append.IsEnabled()) {
-      std::ostringstream e;
-      e << "EXPORT signature does not recognise the APPEND option.";
-      this->SetError(e.str());
-      return false;
-    }
-
-    if (this->ExportOld.IsEnabled()) {
-      std::ostringstream e;
-      e << "EXPORT signature does not recognise the "
-           "EXPORT_LINK_INTERFACE_LIBRARIES option.";
-      this->SetError(e.str());
-      return false;
-    }
-
     cmExportSetMap& setMap = gg->GetExportSets();
-    std::string setName = this->ExportSetName.GetString();
-    if (setMap.find(setName) == setMap.end()) {
+    auto const it = setMap.find(arguments.ExportSetName);
+    if (it == setMap.end()) {
       std::ostringstream e;
-      e << "Export set \"" << setName << "\" not found.";
+      e << "Export set \"" << arguments.ExportSetName << "\" not found.";
       this->SetError(e.str());
       return false;
     }
-    this->ExportSet = setMap[setName];
-  } else if (this->Targets.WasFound()) {
-    for (std::string const& currentTarget : this->Targets.GetVector()) {
+    ExportSet = it->second;
+  } else if (!arguments.Targets.empty()) {
+    for (std::string const& currentTarget : arguments.Targets) {
       if (this->Makefile->IsAlias(currentTarget)) {
         std::ostringstream e;
         e << "given ALIAS target \"" << currentTarget
@@ -159,7 +153,7 @@ bool cmExportCommand::InitialPass(std::vector<std::string> const& args,
       }
       targets.push_back(currentTarget);
     }
-    if (this->Append.IsEnabled()) {
+    if (arguments.Append) {
       if (cmExportBuildFileGenerator* ebfg =
             gg->GetExportedTargetsFile(fname)) {
         ebfg->AppendTargets(targets);
@@ -179,15 +173,15 @@ bool cmExportCommand::InitialPass(std::vector<std::string> const& args,
     ebfg = new cmExportBuildFileGenerator;
   }
   ebfg->SetExportFile(fname.c_str());
-  ebfg->SetNamespace(this->Namespace.GetCString());
-  ebfg->SetAppendMode(this->Append.IsEnabled());
-  if (this->ExportSet) {
-    ebfg->SetExportSet(this->ExportSet);
+  ebfg->SetNamespace(arguments.Namespace);
+  ebfg->SetAppendMode(arguments.Append);
+  if (ExportSet != nullptr) {
+    ebfg->SetExportSet(ExportSet);
   } else {
     ebfg->SetTargets(targets);
   }
   this->Makefile->AddExportBuildFileGenerator(ebfg);
-  ebfg->SetExportOld(this->ExportOld.IsEnabled());
+  ebfg->SetExportOld(arguments.ExportOld);
 
   // Compute the set of configurations exported.
   std::vector<std::string> configurationTypes;
@@ -198,7 +192,7 @@ bool cmExportCommand::InitialPass(std::vector<std::string> const& args,
   for (std::string const& ct : configurationTypes) {
     ebfg->AddConfiguration(ct);
   }
-  if (this->ExportSet) {
+  if (ExportSet != nullptr) {
     gg->AddBuildExportExportSet(ebfg);
   } else {
     gg->AddBuildExportSet(ebfg);

+ 0 - 24
Source/cmExportCommand.h

@@ -9,21 +9,12 @@
 #include <vector>
 
 #include "cmCommand.h"
-#include "cmCommandArgumentsHelper.h"
 
 class cmExecutionStatus;
-class cmExportSet;
 
-/** \class cmExportLibraryDependenciesCommand
- * \brief Add a test to the lists of tests to run.
- *
- * cmExportLibraryDependenciesCommand adds a test to the list of tests to run
- *
- */
 class cmExportCommand : public cmCommand
 {
 public:
-  cmExportCommand();
   /**
    * This is a virtual constructor for the command.
    */
@@ -37,21 +28,6 @@ public:
                    cmExecutionStatus& status) override;
 
 private:
-  cmCommandArgumentsHelper Helper;
-  cmCommandArgumentGroup ArgumentGroup;
-  cmCAStringVector Targets;
-  cmCAEnabler Append;
-  cmCAString ExportSetName;
-  cmCAString Namespace;
-  cmCAString Filename;
-  cmCAEnabler ExportOld;
-  cmCAString AndroidMKFile;
-
-  cmExportSet* ExportSet;
-
-  friend class cmExportBuildFileGenerator;
-  std::string ErrorMessage;
-
   bool HandlePackage(std::vector<std::string> const& args);
   void StorePackageRegistryWin(std::string const& package, const char* content,
                                const char* hash);

+ 74 - 81
Source/cmFileCommand.cxx

@@ -3,6 +3,7 @@
 #include "cmFileCommand.h"
 
 #include "cm_kwiml.h"
+#include "cm_static_string_view.hxx"
 #include "cmsys/FStream.hxx"
 #include "cmsys/Glob.hxx"
 #include "cmsys/RegularExpression.hxx"
@@ -19,7 +20,7 @@
 #include <vector>
 
 #include "cmAlgorithms.h"
-#include "cmCommandArgumentsHelper.h"
+#include "cmArgumentParser.h"
 #include "cmCryptoHash.h"
 #include "cmFileCopier.h"
 #include "cmFileInstaller.h"
@@ -268,36 +269,34 @@ bool cmFileCommand::HandleReadCommand(std::vector<std::string> const& args)
     return false;
   }
 
-  cmCommandArgumentsHelper argHelper;
-  cmCommandArgumentGroup group;
+  std::string const& fileNameArg = args[1];
+  std::string const& variable = args[2];
 
-  cmCAString readArg(&argHelper, "READ");
-  cmCAString fileNameArg(&argHelper, nullptr);
-  cmCAString resultArg(&argHelper, nullptr);
+  struct Arguments
+  {
+    std::string Offset;
+    std::string Limit;
+    bool Hex = false;
+  };
+
+  static auto const parser = cmArgumentParser<Arguments>{}
+                               .Bind("OFFSET"_s, &Arguments::Offset)
+                               .Bind("LIMIT"_s, &Arguments::Limit)
+                               .Bind("HEX"_s, &Arguments::Hex);
 
-  cmCAString offsetArg(&argHelper, "OFFSET", &group);
-  cmCAString limitArg(&argHelper, "LIMIT", &group);
-  cmCAEnabler hexOutputArg(&argHelper, "HEX", &group);
-  readArg.Follows(nullptr);
-  fileNameArg.Follows(&readArg);
-  resultArg.Follows(&fileNameArg);
-  group.Follows(&resultArg);
-  argHelper.Parse(&args, nullptr);
+  Arguments const arguments = parser.Parse(cmMakeRange(args).advance(3));
 
-  std::string fileName = fileNameArg.GetString();
+  std::string fileName = fileNameArg;
   if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
     fileName = this->Makefile->GetCurrentSourceDirectory();
-    fileName += "/" + fileNameArg.GetString();
+    fileName += "/" + fileNameArg;
   }
 
-  std::string variable = resultArg.GetString();
-
 // Open the specified file.
 #if defined(_WIN32) || defined(__CYGWIN__)
-  cmsys::ifstream file(
-    fileName.c_str(),
-    std::ios::in |
-      (hexOutputArg.IsEnabled() ? std::ios::binary : std::ios::in));
+  cmsys::ifstream file(fileName.c_str(),
+                       arguments.Hex ? (std::ios::binary | std::ios::in)
+                                     : std::ios::in);
 #else
   cmsys::ifstream file(fileName.c_str());
 #endif
@@ -313,21 +312,21 @@ bool cmFileCommand::HandleReadCommand(std::vector<std::string> const& args)
 
   // is there a limit?
   long sizeLimit = -1;
-  if (!limitArg.GetString().empty()) {
-    sizeLimit = atoi(limitArg.GetCString());
+  if (!arguments.Limit.empty()) {
+    sizeLimit = atoi(arguments.Limit.c_str());
   }
 
   // is there an offset?
   long offset = 0;
-  if (!offsetArg.GetString().empty()) {
-    offset = atoi(offsetArg.GetCString());
+  if (!arguments.Offset.empty()) {
+    offset = atoi(arguments.Offset.c_str());
   }
 
   file.seekg(offset, std::ios::beg); // explicit ios::beg for IBM VisualAge 6
 
   std::string output;
 
-  if (hexOutputArg.IsEnabled()) {
+  if (arguments.Hex) {
     // Convert part of the file into hex code
     char c;
     while ((sizeLimit != 0) && (file.get(c))) {
@@ -1272,55 +1271,54 @@ bool cmFileCommand::HandleReadElfCommand(std::vector<std::string> const& args)
     return false;
   }
 
-  cmCommandArgumentsHelper argHelper;
-  cmCommandArgumentGroup group;
-
-  cmCAString readArg(&argHelper, "READ_ELF");
-  cmCAString fileNameArg(&argHelper, nullptr);
+  std::string const& fileNameArg = args[1];
 
-  cmCAString rpathArg(&argHelper, "RPATH", &group);
-  cmCAString runpathArg(&argHelper, "RUNPATH", &group);
-  cmCAString errorArg(&argHelper, "CAPTURE_ERROR", &group);
+  struct Arguments
+  {
+    std::string RPath;
+    std::string RunPath;
+    std::string Error;
+  };
 
-  readArg.Follows(nullptr);
-  fileNameArg.Follows(&readArg);
-  group.Follows(&fileNameArg);
-  argHelper.Parse(&args, nullptr);
+  static auto const parser = cmArgumentParser<Arguments>{}
+                               .Bind("RPATH"_s, &Arguments::RPath)
+                               .Bind("RUNPATH"_s, &Arguments::RunPath)
+                               .Bind("CAPTURE_ERROR"_s, &Arguments::Error);
+  Arguments const arguments = parser.Parse(cmMakeRange(args).advance(2));
 
-  if (!cmSystemTools::FileExists(fileNameArg.GetString(), true)) {
+  if (!cmSystemTools::FileExists(fileNameArg, true)) {
     std::ostringstream e;
-    e << "READ_ELF given FILE \"" << fileNameArg.GetString()
-      << "\" that does not exist.";
+    e << "READ_ELF given FILE \"" << fileNameArg << "\" that does not exist.";
     this->SetError(e.str());
     return false;
   }
 
 #if defined(CMAKE_USE_ELF_PARSER)
-  cmELF elf(fileNameArg.GetCString());
+  cmELF elf(fileNameArg.c_str());
 
-  if (!rpathArg.GetString().empty()) {
+  if (!arguments.RPath.empty()) {
     if (cmELF::StringEntry const* se_rpath = elf.GetRPath()) {
       std::string rpath(se_rpath->Value);
       std::replace(rpath.begin(), rpath.end(), ':', ';');
-      this->Makefile->AddDefinition(rpathArg.GetString(), rpath.c_str());
+      this->Makefile->AddDefinition(arguments.RPath, rpath.c_str());
     }
   }
-  if (!runpathArg.GetString().empty()) {
+  if (!arguments.RunPath.empty()) {
     if (cmELF::StringEntry const* se_runpath = elf.GetRunPath()) {
       std::string runpath(se_runpath->Value);
       std::replace(runpath.begin(), runpath.end(), ':', ';');
-      this->Makefile->AddDefinition(runpathArg.GetString(), runpath.c_str());
+      this->Makefile->AddDefinition(arguments.RunPath, runpath.c_str());
     }
   }
 
   return true;
 #else
   std::string error = "ELF parser not available on this platform.";
-  if (errorArg.GetString().empty()) {
+  if (arguments.Error.empty()) {
     this->SetError(error);
     return false;
   }
-  this->Makefile->AddDefinition(errorArg.GetString(), error.c_str());
+  this->Makefile->AddDefinition(arguments.Error, error.c_str());
   return true;
 #endif
 }
@@ -2597,44 +2595,39 @@ bool cmFileCommand::HandleCreateLinkCommand(
     return false;
   }
 
-  cmCommandArgumentsHelper argHelper;
-  cmCommandArgumentGroup group;
-
-  cmCAString linkArg(&argHelper, "CREATE_LINK");
-  cmCAString fileArg(&argHelper, nullptr);
-  cmCAString newFileArg(&argHelper, nullptr);
+  std::string const& fileName = args[1];
+  std::string const& newFileName = args[2];
 
-  cmCAString resultArg(&argHelper, "RESULT", &group);
-  cmCAEnabler copyOnErrorArg(&argHelper, "COPY_ON_ERROR", &group);
-  cmCAEnabler symbolicArg(&argHelper, "SYMBOLIC", &group);
+  struct Arguments
+  {
+    std::string Result;
+    bool CopyOnError = false;
+    bool Symbolic = false;
+  };
 
-  linkArg.Follows(nullptr);
-  fileArg.Follows(&linkArg);
-  newFileArg.Follows(&fileArg);
-  group.Follows(&newFileArg);
+  static auto const parser =
+    cmArgumentParser<Arguments>{}
+      .Bind("RESULT"_s, &Arguments::Result)
+      .Bind("COPY_ON_ERROR"_s, &Arguments::CopyOnError)
+      .Bind("SYMBOLIC"_s, &Arguments::Symbolic);
 
   std::vector<std::string> unconsumedArgs;
-  argHelper.Parse(&args, &unconsumedArgs);
+  Arguments const arguments =
+    parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
 
   if (!unconsumedArgs.empty()) {
     this->SetError("unknown argument: \"" + unconsumedArgs.front() + '\"');
     return false;
   }
 
-  std::string fileName = fileArg.GetString();
-  std::string newFileName = newFileArg.GetString();
-
-  // Output variable for storing the result.
-  const std::string& resultVar = resultArg.GetString();
-
   // The system error message generated in the operation.
   std::string result;
 
   // Check if the paths are distinct.
   if (fileName == newFileName) {
     result = "CREATE_LINK cannot use same file and newfile";
-    if (!resultVar.empty()) {
-      this->Makefile->AddDefinition(resultVar, result.c_str());
+    if (!arguments.Result.empty()) {
+      this->Makefile->AddDefinition(arguments.Result, result.c_str());
       return true;
     }
     this->SetError(result);
@@ -2642,10 +2635,10 @@ bool cmFileCommand::HandleCreateLinkCommand(
   }
 
   // Hard link requires original file to exist.
-  if (!symbolicArg.IsEnabled() && !cmSystemTools::FileExists(fileName)) {
+  if (!arguments.Symbolic && !cmSystemTools::FileExists(fileName)) {
     result = "Cannot hard link \'" + fileName + "\' as it does not exist.";
-    if (!resultVar.empty()) {
-      this->Makefile->AddDefinition(resultVar, result.c_str());
+    if (!arguments.Result.empty()) {
+      this->Makefile->AddDefinition(arguments.Result, result.c_str());
       return true;
     }
     this->SetError(result);
@@ -2661,8 +2654,8 @@ bool cmFileCommand::HandleCreateLinkCommand(
       << "' because existing path cannot be removed: "
       << cmSystemTools::GetLastSystemError() << "\n";
 
-    if (!resultVar.empty()) {
-      this->Makefile->AddDefinition(resultVar, e.str().c_str());
+    if (!arguments.Result.empty()) {
+      this->Makefile->AddDefinition(arguments.Result, e.str().c_str());
       return true;
     }
     this->SetError(e.str());
@@ -2673,14 +2666,14 @@ bool cmFileCommand::HandleCreateLinkCommand(
   bool completed = false;
 
   // Check if the command requires a symbolic link.
-  if (symbolicArg.IsEnabled()) {
+  if (arguments.Symbolic) {
     completed = cmSystemTools::CreateSymlink(fileName, newFileName, &result);
   } else {
     completed = cmSystemTools::CreateLink(fileName, newFileName, &result);
   }
 
   // Check if copy-on-error is enabled in the arguments.
-  if (!completed && copyOnErrorArg.IsEnabled()) {
+  if (!completed && arguments.CopyOnError) {
     completed = cmsys::SystemTools::CopyFileAlways(fileName, newFileName);
     if (!completed) {
       result = "Copy failed: " + cmSystemTools::GetLastSystemError();
@@ -2690,14 +2683,14 @@ bool cmFileCommand::HandleCreateLinkCommand(
   // Check if the operation was successful.
   if (completed) {
     result = "0";
-  } else if (resultVar.empty()) {
+  } else if (arguments.Result.empty()) {
     // The operation failed and the result is not reported in a variable.
     this->SetError(result);
     return false;
   }
 
-  if (!resultVar.empty()) {
-    this->Makefile->AddDefinition(resultVar, result.c_str());
+  if (!arguments.Result.empty()) {
+    this->Makefile->AddDefinition(arguments.Result, result.c_str());
   }
 
   return true;

+ 90 - 82
Source/cmInstallCommand.cxx

@@ -2,6 +2,7 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmInstallCommand.h"
 
+#include "cm_static_string_view.hxx"
 #include "cmsys/Glob.hxx"
 #include <set>
 #include <sstream>
@@ -9,7 +10,7 @@
 #include <utility>
 
 #include "cmAlgorithms.h"
-#include "cmCommandArgumentsHelper.h"
+#include "cmArgumentParser.h"
 #include "cmExportSet.h"
 #include "cmExportSetMap.h"
 #include "cmGeneratorExpression.h"
@@ -219,49 +220,51 @@ bool cmInstallCommand::HandleScriptMode(std::vector<std::string> const& args)
   return true;
 }
 
-/*struct InstallPart
-{
-  InstallPart(cmCommandArgumentsHelper* helper, const char* key,
-         cmCommandArgumentGroup* group);
-  cmCAStringVector argVector;
-  cmInstallCommandArguments args;
-};*/
-
 bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
 {
   // This is the TARGETS mode.
   std::vector<cmTarget*> targets;
 
-  cmCommandArgumentsHelper argHelper;
-  cmCommandArgumentGroup group;
-  cmCAStringVector genericArgVector(&argHelper, nullptr);
-  cmCAStringVector archiveArgVector(&argHelper, "ARCHIVE", &group);
-  cmCAStringVector libraryArgVector(&argHelper, "LIBRARY", &group);
-  cmCAStringVector runtimeArgVector(&argHelper, "RUNTIME", &group);
-  cmCAStringVector objectArgVector(&argHelper, "OBJECTS", &group);
-  cmCAStringVector frameworkArgVector(&argHelper, "FRAMEWORK", &group);
-  cmCAStringVector bundleArgVector(&argHelper, "BUNDLE", &group);
-  cmCAStringVector includesArgVector(&argHelper, "INCLUDES", &group);
-  cmCAStringVector privateHeaderArgVector(&argHelper, "PRIVATE_HEADER",
-                                          &group);
-  cmCAStringVector publicHeaderArgVector(&argHelper, "PUBLIC_HEADER", &group);
-  cmCAStringVector resourceArgVector(&argHelper, "RESOURCE", &group);
-  genericArgVector.Follows(nullptr);
-  group.Follows(&genericArgVector);
-
-  argHelper.Parse(&args, nullptr);
+  struct ArgVectors
+  {
+    std::vector<std::string> Archive;
+    std::vector<std::string> Library;
+    std::vector<std::string> Runtime;
+    std::vector<std::string> Object;
+    std::vector<std::string> Framework;
+    std::vector<std::string> Bundle;
+    std::vector<std::string> Includes;
+    std::vector<std::string> PrivateHeader;
+    std::vector<std::string> PublicHeader;
+    std::vector<std::string> Resource;
+  };
+
+  static auto const argHelper =
+    cmArgumentParser<ArgVectors>{}
+      .Bind("ARCHIVE"_s, &ArgVectors::Archive)
+      .Bind("LIBRARY"_s, &ArgVectors::Library)
+      .Bind("RUNTIME"_s, &ArgVectors::Runtime)
+      .Bind("OBJECTS"_s, &ArgVectors::Object)
+      .Bind("FRAMEWORK"_s, &ArgVectors::Framework)
+      .Bind("BUNDLE"_s, &ArgVectors::Bundle)
+      .Bind("INCLUDES"_s, &ArgVectors::Includes)
+      .Bind("PRIVATE_HEADER"_s, &ArgVectors::PrivateHeader)
+      .Bind("PUBLIC_HEADER"_s, &ArgVectors::PublicHeader)
+      .Bind("RESOURCE"_s, &ArgVectors::Resource);
+
+  std::vector<std::string> genericArgVector;
+  ArgVectors const argVectors = argHelper.Parse(args, &genericArgVector);
 
   // now parse the generic args (i.e. the ones not specialized on LIBRARY/
   // ARCHIVE, RUNTIME etc. (see above)
   // These generic args also contain the targets and the export stuff
+  std::vector<std::string> targetList;
+  std::string exports;
   std::vector<std::string> unknownArgs;
   cmInstallCommandArguments genericArgs(this->DefaultComponentName);
-  cmCAStringVector targetList(&genericArgs.Parser, "TARGETS");
-  cmCAString exports(&genericArgs.Parser, "EXPORT",
-                     &genericArgs.ArgumentGroup);
-  targetList.Follows(nullptr);
-  genericArgs.ArgumentGroup.Follows(&targetList);
-  genericArgs.Parse(&genericArgVector.GetVector(), &unknownArgs);
+  genericArgs.Bind("TARGETS"_s, targetList);
+  genericArgs.Bind("EXPORT"_s, exports);
+  genericArgs.Parse(genericArgVector, &unknownArgs);
   bool success = genericArgs.Finalize();
 
   cmInstallCommandArguments archiveArgs(this->DefaultComponentName);
@@ -277,16 +280,16 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
 
   // now parse the args for specific parts of the target (e.g. LIBRARY,
   // RUNTIME, ARCHIVE etc.
-  archiveArgs.Parse(&archiveArgVector.GetVector(), &unknownArgs);
-  libraryArgs.Parse(&libraryArgVector.GetVector(), &unknownArgs);
-  runtimeArgs.Parse(&runtimeArgVector.GetVector(), &unknownArgs);
-  objectArgs.Parse(&objectArgVector.GetVector(), &unknownArgs);
-  frameworkArgs.Parse(&frameworkArgVector.GetVector(), &unknownArgs);
-  bundleArgs.Parse(&bundleArgVector.GetVector(), &unknownArgs);
-  privateHeaderArgs.Parse(&privateHeaderArgVector.GetVector(), &unknownArgs);
-  publicHeaderArgs.Parse(&publicHeaderArgVector.GetVector(), &unknownArgs);
-  resourceArgs.Parse(&resourceArgVector.GetVector(), &unknownArgs);
-  includesArgs.Parse(&includesArgVector.GetVector(), &unknownArgs);
+  archiveArgs.Parse(argVectors.Archive, &unknownArgs);
+  libraryArgs.Parse(argVectors.Library, &unknownArgs);
+  runtimeArgs.Parse(argVectors.Runtime, &unknownArgs);
+  objectArgs.Parse(argVectors.Object, &unknownArgs);
+  frameworkArgs.Parse(argVectors.Framework, &unknownArgs);
+  bundleArgs.Parse(argVectors.Bundle, &unknownArgs);
+  privateHeaderArgs.Parse(argVectors.PrivateHeader, &unknownArgs);
+  publicHeaderArgs.Parse(argVectors.PublicHeader, &unknownArgs);
+  resourceArgs.Parse(argVectors.Resource, &unknownArgs);
+  includesArgs.Parse(&argVectors.Includes, &unknownArgs);
 
   if (!unknownArgs.empty()) {
     // Unknown argument.
@@ -382,7 +385,7 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
   }
 
   // Check if there is something to do.
-  if (targetList.GetVector().empty()) {
+  if (targetList.empty()) {
     return true;
   }
 
@@ -390,7 +393,7 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
   bool dll_platform =
     !this->Makefile->GetSafeDefinition("CMAKE_IMPORT_LIBRARY_SUFFIX").empty();
 
-  for (std::string const& tgt : targetList.GetVector()) {
+  for (std::string const& tgt : targetList) {
 
     if (this->Makefile->IsAlias(tgt)) {
       std::ostringstream e;
@@ -748,7 +751,7 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
 
     // Add this install rule to an export if one was specified and
     // this is not a namelink-only rule.
-    if (!exports.GetString().empty() && !namelinkOnly) {
+    if (!exports.empty() && !namelinkOnly) {
       cmTargetExport* te = new cmTargetExport;
       te->TargetName = target.GetName();
       te->ArchiveGenerator = archiveGenerator;
@@ -759,7 +762,7 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
       te->RuntimeGenerator = runtimeGenerator;
       te->ObjectsGenerator = objectGenerator;
       this->Makefile->GetGlobalGenerator()
-        ->GetExportSets()[exports.GetString()]
+        ->GetExportSets()[exports]
         ->AddTargetExport(te);
 
       te->InterfaceIncludeDirectories =
@@ -818,11 +821,10 @@ bool cmInstallCommand::HandleFilesMode(std::vector<std::string> const& args)
   // This is the FILES mode.
   bool programs = (args[0] == "PROGRAMS");
   cmInstallCommandArguments ica(this->DefaultComponentName);
-  cmCAStringVector files(&ica.Parser, programs ? "PROGRAMS" : "FILES");
-  files.Follows(nullptr);
-  ica.ArgumentGroup.Follows(&files);
+  std::vector<std::string> files;
+  ica.Bind(programs ? "PROGRAMS"_s : "FILES"_s, files);
   std::vector<std::string> unknownArgs;
-  ica.Parse(&args, &unknownArgs);
+  ica.Parse(args, &unknownArgs);
 
   if (!unknownArgs.empty()) {
     // Unknown argument.
@@ -840,7 +842,7 @@ bool cmInstallCommand::HandleFilesMode(std::vector<std::string> const& args)
     return false;
   }
 
-  const std::vector<std::string>& filesVector = files.GetVector();
+  const std::vector<std::string>& filesVector = files;
 
   // Check if there is something to do.
   if (filesVector.empty()) {
@@ -1271,16 +1273,19 @@ bool cmInstallCommand::HandleExportAndroidMKMode(
 #ifdef CMAKE_BUILD_WITH_CMAKE
   // This is the EXPORT mode.
   cmInstallCommandArguments ica(this->DefaultComponentName);
-  cmCAString exp(&ica.Parser, "EXPORT_ANDROID_MK");
-  cmCAString name_space(&ica.Parser, "NAMESPACE", &ica.ArgumentGroup);
-  cmCAEnabler exportOld(&ica.Parser, "EXPORT_LINK_INTERFACE_LIBRARIES",
-                        &ica.ArgumentGroup);
-  cmCAString filename(&ica.Parser, "FILE", &ica.ArgumentGroup);
-  exp.Follows(nullptr);
-
-  ica.ArgumentGroup.Follows(&exp);
+
+  std::string exp;
+  std::string name_space;
+  bool exportOld = false;
+  std::string filename;
+
+  ica.Bind("EXPORT_ANDROID_MK"_s, exp);
+  ica.Bind("NAMESPACE"_s, name_space);
+  ica.Bind("EXPORT_LINK_INTERFACE_LIBRARIES"_s, exportOld);
+  ica.Bind("FILE"_s, filename);
+
   std::vector<std::string> unknownArgs;
-  ica.Parse(&args, &unknownArgs);
+  ica.Parse(args, &unknownArgs);
 
   if (!unknownArgs.empty()) {
     // Unknown argument.
@@ -1304,7 +1309,7 @@ bool cmInstallCommand::HandleExportAndroidMKMode(
   }
 
   // Check the file name.
-  std::string fname = filename.GetString();
+  std::string fname = filename;
   if (fname.find_first_of(":/\\") != std::string::npos) {
     std::ostringstream e;
     e << args[0] << " given invalid export file name \"" << fname << "\".  "
@@ -1325,7 +1330,7 @@ bool cmInstallCommand::HandleExportAndroidMKMode(
   }
   if (fname.find_first_of(":/\\") != std::string::npos) {
     std::ostringstream e;
-    e << args[0] << " given export name \"" << exp.GetString() << "\".  "
+    e << args[0] << " given export name \"" << exp << "\".  "
       << "This name cannot be safely converted to a file name.  "
       << "Specify a different export name or use the FILE option to set "
       << "a file name explicitly.";
@@ -1338,7 +1343,7 @@ bool cmInstallCommand::HandleExportAndroidMKMode(
   }
 
   cmExportSet* exportSet =
-    this->Makefile->GetGlobalGenerator()->GetExportSets()[exp.GetString()];
+    this->Makefile->GetGlobalGenerator()->GetExportSets()[exp];
 
   cmInstallGenerator::MessageLevel message =
     cmInstallGenerator::SelectMessageLevel(this->Makefile);
@@ -1347,8 +1352,8 @@ bool cmInstallCommand::HandleExportAndroidMKMode(
   cmInstallExportGenerator* exportGenerator = new cmInstallExportGenerator(
     exportSet, ica.GetDestination().c_str(), ica.GetPermissions().c_str(),
     ica.GetConfigurations(), ica.GetComponent().c_str(), message,
-    ica.GetExcludeFromAll(), fname.c_str(), name_space.GetCString(),
-    exportOld.IsEnabled(), true);
+    ica.GetExcludeFromAll(), fname.c_str(), name_space.c_str(), exportOld,
+    true);
   this->Makefile->AddInstallGenerator(exportGenerator);
 
   return true;
@@ -1363,16 +1368,19 @@ bool cmInstallCommand::HandleExportMode(std::vector<std::string> const& args)
 {
   // This is the EXPORT mode.
   cmInstallCommandArguments ica(this->DefaultComponentName);
-  cmCAString exp(&ica.Parser, "EXPORT");
-  cmCAString name_space(&ica.Parser, "NAMESPACE", &ica.ArgumentGroup);
-  cmCAEnabler exportOld(&ica.Parser, "EXPORT_LINK_INTERFACE_LIBRARIES",
-                        &ica.ArgumentGroup);
-  cmCAString filename(&ica.Parser, "FILE", &ica.ArgumentGroup);
-  exp.Follows(nullptr);
-
-  ica.ArgumentGroup.Follows(&exp);
+
+  std::string exp;
+  std::string name_space;
+  bool exportOld = false;
+  std::string filename;
+
+  ica.Bind("EXPORT"_s, exp);
+  ica.Bind("NAMESPACE"_s, name_space);
+  ica.Bind("EXPORT_LINK_INTERFACE_LIBRARIES"_s, exportOld);
+  ica.Bind("FILE"_s, filename);
+
   std::vector<std::string> unknownArgs;
-  ica.Parse(&args, &unknownArgs);
+  ica.Parse(args, &unknownArgs);
 
   if (!unknownArgs.empty()) {
     // Unknown argument.
@@ -1396,7 +1404,7 @@ bool cmInstallCommand::HandleExportMode(std::vector<std::string> const& args)
   }
 
   // Check the file name.
-  std::string fname = filename.GetString();
+  std::string fname = filename;
   if (fname.find_first_of(":/\\") != std::string::npos) {
     std::ostringstream e;
     e << args[0] << " given invalid export file name \"" << fname << "\".  "
@@ -1418,12 +1426,12 @@ bool cmInstallCommand::HandleExportMode(std::vector<std::string> const& args)
 
   // Construct the file name.
   if (fname.empty()) {
-    fname = exp.GetString();
+    fname = exp;
     fname += ".cmake";
 
     if (fname.find_first_of(":/\\") != std::string::npos) {
       std::ostringstream e;
-      e << args[0] << " given export name \"" << exp.GetString() << "\".  "
+      e << args[0] << " given export name \"" << exp << "\".  "
         << "This name cannot be safely converted to a file name.  "
         << "Specify a different export name or use the FILE option to set "
         << "a file name explicitly.";
@@ -1433,8 +1441,8 @@ bool cmInstallCommand::HandleExportMode(std::vector<std::string> const& args)
   }
 
   cmExportSet* exportSet =
-    this->Makefile->GetGlobalGenerator()->GetExportSets()[exp.GetString()];
-  if (exportOld.IsEnabled()) {
+    this->Makefile->GetGlobalGenerator()->GetExportSets()[exp];
+  if (exportOld) {
     for (cmTargetExport* te : *exportSet->GetTargetExports()) {
       cmTarget* tgt =
         this->Makefile->GetGlobalGenerator()->FindTarget(te->TargetName);
@@ -1461,8 +1469,8 @@ bool cmInstallCommand::HandleExportMode(std::vector<std::string> const& args)
   cmInstallExportGenerator* exportGenerator = new cmInstallExportGenerator(
     exportSet, ica.GetDestination().c_str(), ica.GetPermissions().c_str(),
     ica.GetConfigurations(), ica.GetComponent().c_str(), message,
-    ica.GetExcludeFromAll(), fname.c_str(), name_space.GetCString(),
-    exportOld.IsEnabled(), false);
+    ica.GetExcludeFromAll(), fname.c_str(), name_space.c_str(), exportOld,
+    false);
   this->Makefile->AddInstallGenerator(exportGenerator);
 
   return true;

+ 30 - 36
Source/cmInstallCommandArguments.cxx

@@ -4,6 +4,7 @@
 
 #include "cmRange.h"
 #include "cmSystemTools.h"
+#include "cm_static_string_view.hxx"
 
 #include <utility>
 
@@ -18,20 +19,19 @@ const std::string cmInstallCommandArguments::EmptyString;
 
 cmInstallCommandArguments::cmInstallCommandArguments(
   std::string defaultComponent)
-  : Destination(&Parser, "DESTINATION", &ArgumentGroup)
-  , Component(&Parser, "COMPONENT", &ArgumentGroup)
-  , NamelinkComponent(&Parser, "NAMELINK_COMPONENT", &ArgumentGroup)
-  , ExcludeFromAll(&Parser, "EXCLUDE_FROM_ALL", &ArgumentGroup)
-  , Rename(&Parser, "RENAME", &ArgumentGroup)
-  , Permissions(&Parser, "PERMISSIONS", &ArgumentGroup)
-  , Configurations(&Parser, "CONFIGURATIONS", &ArgumentGroup)
-  , Optional(&Parser, "OPTIONAL", &ArgumentGroup)
-  , NamelinkOnly(&Parser, "NAMELINK_ONLY", &ArgumentGroup)
-  , NamelinkSkip(&Parser, "NAMELINK_SKIP", &ArgumentGroup)
-  , Type(&Parser, "TYPE", &ArgumentGroup)
-  , GenericArguments(nullptr)
-  , DefaultComponentName(std::move(defaultComponent))
+  : DefaultComponentName(std::move(defaultComponent))
 {
+  this->Bind("DESTINATION"_s, this->Destination);
+  this->Bind("COMPONENT"_s, this->Component);
+  this->Bind("NAMELINK_COMPONENT"_s, this->NamelinkComponent);
+  this->Bind("EXCLUDE_FROM_ALL"_s, this->ExcludeFromAll);
+  this->Bind("RENAME"_s, this->Rename);
+  this->Bind("PERMISSIONS"_s, this->Permissions);
+  this->Bind("CONFIGURATIONS"_s, this->Configurations);
+  this->Bind("OPTIONAL"_s, this->Optional);
+  this->Bind("NAMELINK_ONLY"_s, this->NamelinkOnly);
+  this->Bind("NAMELINK_SKIP"_s, this->NamelinkSkip);
+  this->Bind("TYPE"_s, this->Type);
 }
 
 const std::string& cmInstallCommandArguments::GetDestination() const
@@ -47,8 +47,8 @@ const std::string& cmInstallCommandArguments::GetDestination() const
 
 const std::string& cmInstallCommandArguments::GetComponent() const
 {
-  if (!this->Component.GetString().empty()) {
-    return this->Component.GetString();
+  if (!this->Component.empty()) {
+    return this->Component;
   }
   if (this->GenericArguments != nullptr) {
     return this->GenericArguments->GetComponent();
@@ -62,16 +62,16 @@ const std::string& cmInstallCommandArguments::GetComponent() const
 
 const std::string& cmInstallCommandArguments::GetNamelinkComponent() const
 {
-  if (!this->NamelinkComponent.GetString().empty()) {
-    return this->NamelinkComponent.GetString();
+  if (!this->NamelinkComponent.empty()) {
+    return this->NamelinkComponent;
   }
   return this->GetComponent();
 }
 
 const std::string& cmInstallCommandArguments::GetRename() const
 {
-  if (!this->Rename.GetString().empty()) {
-    return this->Rename.GetString();
+  if (!this->Rename.empty()) {
+    return this->Rename;
   }
   if (this->GenericArguments != nullptr) {
     return this->GenericArguments->GetRename();
@@ -92,7 +92,7 @@ const std::string& cmInstallCommandArguments::GetPermissions() const
 
 bool cmInstallCommandArguments::GetOptional() const
 {
-  if (this->Optional.IsEnabled()) {
+  if (this->Optional) {
     return true;
   }
   if (this->GenericArguments != nullptr) {
@@ -103,7 +103,7 @@ bool cmInstallCommandArguments::GetOptional() const
 
 bool cmInstallCommandArguments::GetExcludeFromAll() const
 {
-  if (this->ExcludeFromAll.IsEnabled()) {
+  if (this->ExcludeFromAll) {
     return true;
   }
   if (this->GenericArguments != nullptr) {
@@ -114,7 +114,7 @@ bool cmInstallCommandArguments::GetExcludeFromAll() const
 
 bool cmInstallCommandArguments::GetNamelinkOnly() const
 {
-  if (this->NamelinkOnly.IsEnabled()) {
+  if (this->NamelinkOnly) {
     return true;
   }
   if (this->GenericArguments != nullptr) {
@@ -125,7 +125,7 @@ bool cmInstallCommandArguments::GetNamelinkOnly() const
 
 bool cmInstallCommandArguments::GetNamelinkSkip() const
 {
-  if (this->NamelinkSkip.IsEnabled()) {
+  if (this->NamelinkSkip) {
     return true;
   }
   if (this->GenericArguments != nullptr) {
@@ -136,7 +136,7 @@ bool cmInstallCommandArguments::GetNamelinkSkip() const
 
 bool cmInstallCommandArguments::HasNamelinkComponent() const
 {
-  if (!this->NamelinkComponent.GetString().empty()) {
+  if (!this->NamelinkComponent.empty()) {
     return true;
   }
   if (this->GenericArguments != nullptr) {
@@ -147,19 +147,19 @@ bool cmInstallCommandArguments::HasNamelinkComponent() const
 
 const std::string& cmInstallCommandArguments::GetType() const
 {
-  return this->Type.GetString();
+  return this->Type;
 }
 
 const std::vector<std::string>& cmInstallCommandArguments::GetConfigurations()
   const
 {
-  if (!this->Configurations.GetVector().empty()) {
-    return this->Configurations.GetVector();
+  if (!this->Configurations.empty()) {
+    return this->Configurations;
   }
   if (this->GenericArguments != nullptr) {
     return this->GenericArguments->GetConfigurations();
   }
-  return this->Configurations.GetVector();
+  return this->Configurations;
 }
 
 bool cmInstallCommandArguments::Finalize()
@@ -167,21 +167,15 @@ bool cmInstallCommandArguments::Finalize()
   if (!this->CheckPermissions()) {
     return false;
   }
-  this->DestinationString = this->Destination.GetString();
+  this->DestinationString = this->Destination;
   cmSystemTools::ConvertToUnixSlashes(this->DestinationString);
   return true;
 }
 
-void cmInstallCommandArguments::Parse(const std::vector<std::string>* args,
-                                      std::vector<std::string>* unconsumedArgs)
-{
-  this->Parser.Parse(args, unconsumedArgs);
-}
-
 bool cmInstallCommandArguments::CheckPermissions()
 {
   this->PermissionsString.clear();
-  for (std::string const& perm : this->Permissions.GetVector()) {
+  for (std::string const& perm : this->Permissions) {
     if (!cmInstallCommandArguments::CheckPermissions(
           perm, this->PermissionsString)) {
       return false;

+ 14 - 21
Source/cmInstallCommandArguments.h

@@ -8,9 +8,9 @@
 #include <string>
 #include <vector>
 
-#include "cmCommandArgumentsHelper.h"
+#include "cmArgumentParser.h"
 
-class cmInstallCommandArguments
+class cmInstallCommandArguments : public cmArgumentParser<void>
 {
 public:
   cmInstallCommandArguments(std::string defaultComponent);
@@ -18,8 +18,6 @@ public:
   {
     this->GenericArguments = args;
   }
-  void Parse(const std::vector<std::string>* args,
-             std::vector<std::string>* unconsumedArgs);
 
   // Compute destination path.and check permissions
   bool Finalize();
@@ -37,30 +35,25 @@ public:
   bool HasNamelinkComponent() const;
   const std::string& GetType() const;
 
-  // once HandleDirectoryMode() is also switched to using
-  // cmInstallCommandArguments then these two functions can become non-static
-  // private member functions without arguments
   static bool CheckPermissions(const std::string& onePerm, std::string& perm);
-  cmCommandArgumentsHelper Parser;
-  cmCommandArgumentGroup ArgumentGroup;
 
 private:
-  cmCAString Destination;
-  cmCAString Component;
-  cmCAString NamelinkComponent;
-  cmCAEnabler ExcludeFromAll;
-  cmCAString Rename;
-  cmCAStringVector Permissions;
-  cmCAStringVector Configurations;
-  cmCAEnabler Optional;
-  cmCAEnabler NamelinkOnly;
-  cmCAEnabler NamelinkSkip;
-  cmCAString Type;
+  std::string Destination;
+  std::string Component;
+  std::string NamelinkComponent;
+  bool ExcludeFromAll = false;
+  std::string Rename;
+  std::vector<std::string> Permissions;
+  std::vector<std::string> Configurations;
+  bool Optional = false;
+  bool NamelinkOnly = false;
+  bool NamelinkSkip = false;
+  std::string Type;
 
   std::string DestinationString;
   std::string PermissionsString;
 
-  cmInstallCommandArguments* GenericArguments;
+  cmInstallCommandArguments* GenericArguments = nullptr;
   static const char* PermissionsTable[];
   static const std::string EmptyString;
   std::string DefaultComponentName;

+ 45 - 114
Source/cmParseArgumentsCommand.cxx

@@ -8,10 +8,12 @@
 #include <utility>
 
 #include "cmAlgorithms.h"
+#include "cmArgumentParser.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmRange.h"
 #include "cmSystemTools.h"
+#include "cm_string_view.hxx"
 
 class cmExecutionStatus;
 
@@ -28,42 +30,43 @@ static std::string EscapeArg(const std::string& arg)
   return escapedArg;
 }
 
-namespace {
-enum insideValues
+static std::string JoinList(std::vector<std::string> const& arg, bool escape)
 {
-  NONE,
-  SINGLE,
-  MULTI
-};
+  return escape ? cmJoin(cmMakeRange(arg).transform(EscapeArg), ";")
+                : cmJoin(cmMakeRange(arg), ";");
+}
+
+namespace {
 
 typedef std::map<std::string, bool> options_map;
 typedef std::map<std::string, std::string> single_map;
 typedef std::map<std::string, std::vector<std::string>> multi_map;
 typedef std::set<std::string> options_set;
-}
 
-// function to be called every time, a new key word was parsed or all
-// parameters where parsed.
-static void DetectKeywordsMissingValues(insideValues currentState,
-                                        const std::string& currentArgName,
-                                        int& argumentsFound,
-                                        options_set& keywordsMissingValues)
+struct UserArgumentParser : public cmArgumentParser<void>
 {
-  if (currentState == SINGLE ||
-      (currentState == MULTI && argumentsFound == 0)) {
-    keywordsMissingValues.insert(currentArgName);
+  template <typename T, typename H>
+  void Bind(std::vector<std::string> const& names,
+            std::map<std::string, T>& ref, H duplicateKey)
+  {
+    for (std::string const& key : names) {
+      auto const it = ref.emplace(key, T{}).first;
+      bool const inserted = this->cmArgumentParser<void>::Bind(
+        cm::string_view(it->first), it->second);
+      if (!inserted) {
+        duplicateKey(key);
+      }
+    }
   }
+};
 
-  argumentsFound = 0;
-}
+} // namespace
 
-static void PassParsedArguments(const std::string& prefix,
-                                cmMakefile& makefile,
-                                const options_map& options,
-                                const single_map& singleValArgs,
-                                const multi_map& multiValArgs,
-                                const std::vector<std::string>& unparsed,
-                                const options_set& keywordsMissingValues)
+static void PassParsedArguments(
+  const std::string& prefix, cmMakefile& makefile, const options_map& options,
+  const single_map& singleValArgs, const multi_map& multiValArgs,
+  const std::vector<std::string>& unparsed,
+  const options_set& keywordsMissingValues, bool parseFromArgV)
 {
   for (auto const& iter : options) {
     makefile.AddDefinition(prefix + iter.first,
@@ -81,7 +84,7 @@ static void PassParsedArguments(const std::string& prefix,
   for (auto const& iter : multiValArgs) {
     if (!iter.second.empty()) {
       makefile.AddDefinition(prefix + iter.first,
-                             cmJoin(cmMakeRange(iter.second), ";").c_str());
+                             JoinList(iter.second, parseFromArgV).c_str());
     } else {
       makefile.RemoveDefinition(prefix + iter.first);
     }
@@ -89,7 +92,7 @@ static void PassParsedArguments(const std::string& prefix,
 
   if (!unparsed.empty()) {
     makefile.AddDefinition(prefix + "UNPARSED_ARGUMENTS",
-                           cmJoin(cmMakeRange(unparsed), ";").c_str());
+                           JoinList(unparsed, parseFromArgV).c_str());
   } else {
     makefile.RemoveDefinition(prefix + "UNPARSED_ARGUMENTS");
   }
@@ -141,6 +144,8 @@ bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
   // the first argument is the prefix
   const std::string prefix = (*argIter++) + "_";
 
+  UserArgumentParser parser;
+
   // define the result maps holding key/value pairs for
   // options, single values and multi values
   options_map options;
@@ -150,45 +155,25 @@ bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
   // anything else is put into a vector of unparsed strings
   std::vector<std::string> unparsed;
 
-  // remember already defined keywords
-  std::set<std::string> used_keywords;
-  const std::string dup_warning = "keyword defined more than once: ";
+  auto const duplicateKey = [this](std::string const& key) {
+    this->GetMakefile()->IssueMessage(
+      MessageType::WARNING, "keyword defined more than once: " + key);
+  };
 
   // the second argument is a (cmake) list of options without argument
   std::vector<std::string> list;
   cmSystemTools::ExpandListArgument(*argIter++, list);
-  for (std::string const& iter : list) {
-    if (!used_keywords.insert(iter).second) {
-      this->GetMakefile()->IssueMessage(MessageType::WARNING,
-                                        dup_warning + iter);
-    }
-    options[iter]; // default initialize
-  }
+  parser.Bind(list, options, duplicateKey);
 
   // the third argument is a (cmake) list of single argument options
   list.clear();
   cmSystemTools::ExpandListArgument(*argIter++, list);
-  for (std::string const& iter : list) {
-    if (!used_keywords.insert(iter).second) {
-      this->GetMakefile()->IssueMessage(MessageType::WARNING,
-                                        dup_warning + iter);
-    }
-    singleValArgs[iter]; // default initialize
-  }
+  parser.Bind(list, singleValArgs, duplicateKey);
 
   // the fourth argument is a (cmake) list of multi argument options
   list.clear();
   cmSystemTools::ExpandListArgument(*argIter++, list);
-  for (std::string const& iter : list) {
-    if (!used_keywords.insert(iter).second) {
-      this->GetMakefile()->IssueMessage(MessageType::WARNING,
-                                        dup_warning + iter);
-    }
-    multiValArgs[iter]; // default initialize
-  }
-
-  insideValues insideValues = NONE;
-  std::string currentArgName;
+  parser.Bind(list, multiValArgs, duplicateKey);
 
   list.clear();
   if (!parseFromArgV) {
@@ -223,68 +208,14 @@ bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
     }
   }
 
-  options_set keywordsMissingValues;
-  int multiArgumentsFound = 0;
-
-  // iterate over the arguments list and fill in the values where applicable
-  for (std::string const& arg : list) {
-    const options_map::iterator optIter = options.find(arg);
-    if (optIter != options.end()) {
-      DetectKeywordsMissingValues(insideValues, currentArgName,
-                                  multiArgumentsFound, keywordsMissingValues);
-      insideValues = NONE;
-      optIter->second = true;
-      continue;
-    }
-
-    const single_map::iterator singleIter = singleValArgs.find(arg);
-    if (singleIter != singleValArgs.end()) {
-      DetectKeywordsMissingValues(insideValues, currentArgName,
-                                  multiArgumentsFound, keywordsMissingValues);
-      insideValues = SINGLE;
-      currentArgName = arg;
-      continue;
-    }
-
-    const multi_map::iterator multiIter = multiValArgs.find(arg);
-    if (multiIter != multiValArgs.end()) {
-      DetectKeywordsMissingValues(insideValues, currentArgName,
-                                  multiArgumentsFound, keywordsMissingValues);
-      insideValues = MULTI;
-      currentArgName = arg;
-      continue;
-    }
-
-    switch (insideValues) {
-      case SINGLE:
-        singleValArgs[currentArgName] = arg;
-        insideValues = NONE;
-        break;
-      case MULTI:
-        ++multiArgumentsFound;
-        if (parseFromArgV) {
-          multiValArgs[currentArgName].push_back(EscapeArg(arg));
-        } else {
-          multiValArgs[currentArgName].push_back(arg);
-        }
-        break;
-      default:
-        multiArgumentsFound = 0;
-
-        if (parseFromArgV) {
-          unparsed.push_back(EscapeArg(arg));
-        } else {
-          unparsed.push_back(arg);
-        }
-        break;
-    }
-  }
+  std::vector<std::string> keywordsMissingValues;
 
-  DetectKeywordsMissingValues(insideValues, currentArgName,
-                              multiArgumentsFound, keywordsMissingValues);
+  parser.Parse(list, &unparsed, &keywordsMissingValues);
 
-  PassParsedArguments(prefix, *this->Makefile, options, singleValArgs,
-                      multiValArgs, unparsed, keywordsMissingValues);
+  PassParsedArguments(
+    prefix, *this->Makefile, options, singleValArgs, multiValArgs, unparsed,
+    options_set(keywordsMissingValues.begin(), keywordsMissingValues.end()),
+    parseFromArgV);
 
   return true;
 }

+ 1 - 0
Tests/CMakeLib/CMakeLists.txt

@@ -5,6 +5,7 @@ include_directories(
   )
 
 set(CMakeLib_TESTS
+  testArgumentParser.cxx
   testGeneratedFileStream.cxx
   testRST.cxx
   testRange.cxx

+ 148 - 0
Tests/CMakeLib/testArgumentParser.cxx

@@ -0,0 +1,148 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmArgumentParser.h"
+
+#include "cm_static_string_view.hxx"
+#include "cm_string_view.hxx"
+
+#include <initializer_list>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace {
+
+struct Result
+{
+  bool Option1 = false;
+  bool Option2 = false;
+
+  std::string String1;
+  std::string String2;
+
+  std::vector<std::string> List1;
+  std::vector<std::string> List2;
+  std::vector<std::string> List3;
+
+  std::vector<std::vector<std::string>> Multi1;
+  std::vector<std::vector<std::string>> Multi2;
+  std::vector<std::vector<std::string>> Multi3;
+};
+
+std::initializer_list<cm::string_view> const args = {
+  /* clang-format off */
+  "OPTION_1",                // option
+  "STRING_1",                // string arg missing value
+  "STRING_2", "foo", "bar",  // string arg + unparsed value
+  "LIST_1",                  // list arg missing values
+  "LIST_2", "foo", "bar",    // list arg with 2 elems
+  "LIST_3", "bar",           // list arg ...
+  "LIST_3", "foo",           // ... with continuation
+  "MULTI_2",                 // multi list with 0 lists
+  "MULTI_3", "foo", "bar",   // multi list with first list with two elems
+  "MULTI_3", "bar", "foo",   // multi list with second list with two elems
+  /* clang-format on */
+};
+
+bool verifyResult(Result const& result,
+                  std::vector<std::string> const& unparsedArguments,
+                  std::vector<std::string> const& keywordsMissingValue)
+{
+  static std::vector<std::string> const foobar = { "foo", "bar" };
+  static std::vector<std::string> const barfoo = { "bar", "foo" };
+  static std::vector<std::string> const missing = { "STRING_1", "LIST_1" };
+
+#define ASSERT_TRUE(x)                                                        \
+  do {                                                                        \
+    if (!(x)) {                                                               \
+      std::cout << "ASSERT_TRUE(" #x ") failed on line " << __LINE__ << "\n"; \
+      return false;                                                           \
+    }                                                                         \
+  } while (false)
+
+  ASSERT_TRUE(result.Option1);
+  ASSERT_TRUE(!result.Option2);
+
+  ASSERT_TRUE(result.String1.empty());
+  ASSERT_TRUE(result.String2 == "foo");
+
+  ASSERT_TRUE(result.List1.empty());
+  ASSERT_TRUE(result.List2 == foobar);
+  ASSERT_TRUE(result.List3 == barfoo);
+
+  ASSERT_TRUE(result.Multi1.empty());
+  ASSERT_TRUE(result.Multi2.size() == 1);
+  ASSERT_TRUE(result.Multi2[0].empty());
+  ASSERT_TRUE(result.Multi3.size() == 2);
+  ASSERT_TRUE(result.Multi3[0] == foobar);
+  ASSERT_TRUE(result.Multi3[1] == barfoo);
+
+  ASSERT_TRUE(unparsedArguments.size() == 1);
+  ASSERT_TRUE(unparsedArguments[0] == "bar");
+  ASSERT_TRUE(keywordsMissingValue == missing);
+
+  return true;
+}
+
+bool testArgumentParserDynamic()
+{
+  Result result;
+  std::vector<std::string> unparsedArguments;
+  std::vector<std::string> keywordsMissingValue;
+
+  cmArgumentParser<void>{}
+    .Bind("OPTION_1"_s, result.Option1)
+    .Bind("OPTION_2"_s, result.Option2)
+    .Bind("STRING_1"_s, result.String1)
+    .Bind("STRING_2"_s, result.String2)
+    .Bind("LIST_1"_s, result.List1)
+    .Bind("LIST_2"_s, result.List2)
+    .Bind("LIST_3"_s, result.List3)
+    .Bind("MULTI_1"_s, result.Multi1)
+    .Bind("MULTI_2"_s, result.Multi2)
+    .Bind("MULTI_3"_s, result.Multi3)
+    .Parse(args, &unparsedArguments, &keywordsMissingValue);
+
+  return verifyResult(result, unparsedArguments, keywordsMissingValue);
+}
+
+bool testArgumentParserStatic()
+{
+  static auto const parser = //
+    cmArgumentParser<Result>{}
+      .Bind("OPTION_1"_s, &Result::Option1)
+      .Bind("OPTION_2"_s, &Result::Option2)
+      .Bind("STRING_1"_s, &Result::String1)
+      .Bind("STRING_2"_s, &Result::String2)
+      .Bind("LIST_1"_s, &Result::List1)
+      .Bind("LIST_2"_s, &Result::List2)
+      .Bind("LIST_3"_s, &Result::List3)
+      .Bind("MULTI_1"_s, &Result::Multi1)
+      .Bind("MULTI_2"_s, &Result::Multi2)
+      .Bind("MULTI_3"_s, &Result::Multi3);
+
+  std::vector<std::string> unparsedArguments;
+  std::vector<std::string> keywordsMissingValue;
+  Result const result =
+    parser.Parse(args, &unparsedArguments, &keywordsMissingValue);
+
+  return verifyResult(result, unparsedArguments, keywordsMissingValue);
+}
+
+} // namespace
+
+int testArgumentParser(int /*unused*/, char* /*unused*/ [])
+{
+  if (!testArgumentParserDynamic()) {
+    std::cout << "While executing testArgumentParserDynamic().\n";
+    return -1;
+  }
+
+  if (!testArgumentParserStatic()) {
+    std::cout << "While executing testArgumentParserStatic().\n";
+    return -1;
+  }
+
+  return 0;
+}

+ 1 - 1
Tests/RunCMake/export/AppendExport-stderr.txt

@@ -1,4 +1,4 @@
 CMake Error at AppendExport.cmake:[0-9]+ \(export\):
-  export EXPORT signature does not recognise the APPEND option.
+  export Unknown argument: "APPEND".
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)

+ 1 - 2
Tests/RunCMake/export/OldIface-stderr.txt

@@ -1,5 +1,4 @@
 CMake Error at OldIface.cmake:[0-9]+ \(export\):
-  export EXPORT signature does not recognise the
-  EXPORT_LINK_INTERFACE_LIBRARIES option.
+  export Unknown argument: "EXPORT_LINK_INTERFACE_LIBRARIES".
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)

+ 1 - 0
Utilities/IWYU/mapping.imp

@@ -68,6 +68,7 @@
   { symbol: [ "std::__decay_and_strip<cmGeneratorTarget *&>::__type", private, "\"cmConfigure.h\"", public ] },
   { symbol: [ "std::__decay_and_strip<cmFindCommon::PathLabel &>::__type", private, "\"cmConfigure.h\"", public ] },
   { symbol: [ "std::__decay_and_strip<cmSearchPath>::__type", private, "\"cmConfigure.h\"", public ] },
+  { symbol: [ "std::__decay_and_strip<cm::string_view>::__type", private, "\"cmConfigure.h\"", public ] },
   { symbol: [ "std::__decay_and_strip<std::basic_string<char> &>::__type", private, "\"cmConfigure.h\"", public ] },
   { symbol: [ "std::__decay_and_strip<const std::basic_string<char> &>::__type", private, "\"cmConfigure.h\"", public ] },
   { symbol: [ "std::__decay_and_strip<cmFindPackageCommand::PathLabel &>::__type", private, "\"cmConfigure.h\"", public ] },

+ 1 - 1
bootstrap

@@ -260,6 +260,7 @@ CMAKE_CXX_SOURCES="\
   cmAddLibraryCommand \
   cmAddSubDirectoryCommand \
   cmAddTestCommand \
+  cmArgumentParser \
   cmBreakCommand \
   cmBuildCommand \
   cmCMakeMinimumRequired \
@@ -268,7 +269,6 @@ CMAKE_CXX_SOURCES="\
   cmCacheManager \
   cmCommand \
   cmCommandArgumentParserHelper \
-  cmCommandArgumentsHelper \
   cmCommands \
   cmCommonTargetGenerator \
   cmComputeComponentGraph \