Sfoglia il codice sorgente

Merge topic 'command-arg-parser-optional'

bff468c988 cmFileCommand: Use cm::optional for keyword argument presence
2586afa31b cmCTest*Command:: Use cm::optional for keyword argument presence
5446b15c5c cmInstallCommand: Use cm::optional for keyword argument presence
298f226cb4 cmExportCommand: Use cm::optional for keyword argument presence
0a4c5164c9 cmArgumentParser: Offer cm::optional bindings to capture keyword presence
f3dbf4b89d cmArgumentParser: Remove unnecessary local names for common types
2873f41bd9 cmArgumentParser: Require callers to consider unparsed arguments
1ee5a4a548 cmArgumentParser: Avoid allocating copies of keyword strings
...

Acked-by: Kitware Robot <[email protected]>
Acked-by: buildbot <[email protected]>
Merge-request: !7450
Brad King 3 anni fa
parent
commit
ad2e7f3c53
34 ha cambiato i file con 294 aggiunte e 254 eliminazioni
  1. 2 10
      Source/CTest/cmCTestCoverageCommand.cxx
  2. 2 3
      Source/CTest/cmCTestCoverageCommand.h
  3. 5 5
      Source/CTest/cmCTestHandlerCommand.cxx
  4. 1 1
      Source/CTest/cmCTestHandlerCommand.h
  5. 31 34
      Source/CTest/cmCTestSubmitCommand.cxx
  6. 5 5
      Source/CTest/cmCTestSubmitCommand.h
  7. 1 1
      Source/CTest/cmCTestUploadCommand.cxx
  8. 1 1
      Source/CTest/cmCTestUploadCommand.h
  9. 6 6
      Source/cmArgumentParser.cxx
  10. 25 19
      Source/cmArgumentParser.h
  11. 1 1
      Source/cmCMakeHostSystemInformationCommand.cxx
  12. 6 6
      Source/cmCMakePathCommand.cxx
  13. 3 3
      Source/cmExecuteProcessCommand.cxx
  14. 5 8
      Source/cmExportCommand.cxx
  15. 99 119
      Source/cmFileCommand.cxx
  16. 10 11
      Source/cmInstallCommand.cxx
  17. 2 2
      Source/cmParseArgumentsCommand.cxx
  18. 1 1
      Source/cmTargetSourcesCommand.cxx
  19. 42 14
      Tests/CMakeLib/testArgumentParser.cxx
  20. 3 1
      Tests/RunCMake/File_Configure/BadArgContent-stderr.txt
  21. 3 1
      Tests/RunCMake/File_Configure/BadArgOutput-stderr.txt
  22. 1 0
      Tests/RunCMake/File_Configure/NoArgContent-result.txt
  23. 4 0
      Tests/RunCMake/File_Configure/NoArgContent-stderr.txt
  24. 1 0
      Tests/RunCMake/File_Configure/NoArgContent.cmake
  25. 1 0
      Tests/RunCMake/File_Configure/NoArgOutput-result.txt
  26. 4 0
      Tests/RunCMake/File_Configure/NoArgOutput-stderr.txt
  27. 1 0
      Tests/RunCMake/File_Configure/NoArgOutput.cmake
  28. 2 0
      Tests/RunCMake/File_Configure/RunCMakeTest.cmake
  29. 3 1
      Tests/RunCMake/File_Generate/EmptyCondition1-stderr.txt
  30. 8 0
      Tests/RunCMake/File_Generate/InputAndContent-check.cmake
  31. 1 0
      Tests/RunCMake/File_Generate/InputAndContent-input.txt
  32. 10 0
      Tests/RunCMake/File_Generate/InputAndContent.cmake
  33. 3 1
      Tests/RunCMake/File_Generate/NewLineStyle-NoArg-stderr.txt
  34. 1 0
      Tests/RunCMake/File_Generate/RunCMakeTest.cmake

+ 2 - 10
Source/CTest/cmCTestCoverageCommand.cxx

@@ -4,7 +4,6 @@
 
 #include <set>
 
-#include <cmext/algorithm>
 #include <cmext/string_view>
 
 #include "cmCTest.h"
@@ -18,13 +17,6 @@ void cmCTestCoverageCommand::BindArguments()
   this->Bind("LABELS"_s, this->Labels);
 }
 
-void cmCTestCoverageCommand::CheckArguments(
-  std::vector<std::string> const& keywords)
-{
-  this->LabelsMentioned =
-    !this->Labels.empty() || cm::contains(keywords, "LABELS");
-}
-
 cmCTestGenericHandler* cmCTestCoverageCommand::InitializeHandler()
 {
   this->CTest->SetCTestConfigurationFromCMakeVariable(
@@ -36,9 +28,9 @@ cmCTestGenericHandler* cmCTestCoverageCommand::InitializeHandler()
   handler->Initialize();
 
   // If a LABELS option was given, select only files with the labels.
-  if (this->LabelsMentioned) {
+  if (this->Labels) {
     handler->SetLabelFilter(
-      std::set<std::string>(this->Labels.begin(), this->Labels.end()));
+      std::set<std::string>(this->Labels->begin(), this->Labels->end()));
   }
 
   handler->SetQuiet(this->Quiet);

+ 2 - 3
Source/CTest/cmCTestCoverageCommand.h

@@ -9,6 +9,7 @@
 #include <vector>
 
 #include <cm/memory>
+#include <cm/optional>
 
 #include "cmCTestHandlerCommand.h"
 #include "cmCommand.h"
@@ -41,9 +42,7 @@ public:
 
 protected:
   void BindArguments() override;
-  void CheckArguments(std::vector<std::string> const& keywords) override;
   cmCTestGenericHandler* InitializeHandler() override;
 
-  bool LabelsMentioned;
-  std::vector<std::string> Labels;
+  cm::optional<std::vector<std::string>> Labels;
 };

+ 5 - 5
Source/CTest/cmCTestHandlerCommand.cxx

@@ -7,6 +7,7 @@
 #include <cstring>
 #include <sstream>
 
+#include <cm/string_view>
 #include <cmext/string_view>
 
 #include "cmCTest.h"
@@ -81,11 +82,10 @@ bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
 
   // Process input arguments.
   std::vector<std::string> unparsedArguments;
-  std::vector<std::string> keywordsMissingValue;
-  std::vector<std::string> parsedKeywords;
-  this->Parse(args, &unparsedArguments, &keywordsMissingValue,
+  std::vector<cm::string_view> parsedKeywords;
+  this->Parse(args, &unparsedArguments, /*keywordsMissingValue=*/nullptr,
               &parsedKeywords);
-  this->CheckArguments(keywordsMissingValue);
+  this->CheckArguments();
 
   std::sort(parsedKeywords.begin(), parsedKeywords.end());
   auto it = std::adjacent_find(parsedKeywords.begin(), parsedKeywords.end());
@@ -242,6 +242,6 @@ void cmCTestHandlerCommand::BindArguments()
   this->Bind("SUBMIT_INDEX"_s, this->SubmitIndex);
 }
 
-void cmCTestHandlerCommand::CheckArguments(std::vector<std::string> const&)
+void cmCTestHandlerCommand::CheckArguments()
 {
 }

+ 1 - 1
Source/CTest/cmCTestHandlerCommand.h

@@ -42,7 +42,7 @@ protected:
 
   // Command argument handling.
   virtual void BindArguments();
-  virtual void CheckArguments(std::vector<std::string> const& keywords);
+  virtual void CheckArguments();
 
   bool Append = false;
   bool Quiet = false;

+ 31 - 34
Source/CTest/cmCTestSubmitCommand.cxx

@@ -8,7 +8,6 @@
 
 #include <cm/memory>
 #include <cm/vector>
-#include <cmext/algorithm>
 #include <cmext/string_view>
 
 #include "cmCTest.h"
@@ -87,7 +86,7 @@ cmCTestGenericHandler* cmCTestSubmitCommand::InitializeHandler()
   // If FILES are given, but not PARTS, only the FILES are submitted
   // and *no* PARTS are submitted.
   //  (This is why we select the empty "noParts" set in the
-  //   FilesMentioned block below...)
+  //   if(this->Files) block below...)
   //
   // If PARTS are given, only the selected PARTS are submitted.
   //
@@ -96,7 +95,7 @@ cmCTestGenericHandler* cmCTestSubmitCommand::InitializeHandler()
 
   // If given explicit FILES to submit, pass them to the handler.
   //
-  if (this->FilesMentioned) {
+  if (this->Files) {
     // Intentionally select *no* PARTS. (Pass an empty set.) If PARTS
     // were also explicitly mentioned, they will be selected below...
     // But FILES with no PARTS mentioned should just submit the FILES
@@ -104,14 +103,14 @@ cmCTestGenericHandler* cmCTestSubmitCommand::InitializeHandler()
     //
     handler->SelectParts(std::set<cmCTest::Part>());
     handler->SelectFiles(
-      std::set<std::string>(this->Files.begin(), this->Files.end()));
+      std::set<std::string>(this->Files->begin(), this->Files->end()));
   }
 
   // If a PARTS option was given, select only the named parts for submission.
   //
-  if (this->PartsMentioned) {
+  if (this->Parts) {
     auto parts =
-      cmMakeRange(this->Parts).transform([this](std::string const& arg) {
+      cmMakeRange(*(this->Parts)).transform([this](std::string const& arg) {
         return this->CTest->GetPartFromName(arg);
       });
     handler->SelectParts(std::set<cmCTest::Part>(parts.begin(), parts.end()));
@@ -172,33 +171,31 @@ void cmCTestSubmitCommand::BindArguments()
   this->cmCTestHandlerCommand::BindArguments();
 }
 
-void cmCTestSubmitCommand::CheckArguments(
-  std::vector<std::string> const& keywords)
+void cmCTestSubmitCommand::CheckArguments()
 {
-  this->PartsMentioned =
-    !this->Parts.empty() || cm::contains(keywords, "PARTS");
-  this->FilesMentioned =
-    !this->Files.empty() || cm::contains(keywords, "FILES");
-
-  cm::erase_if(this->Parts, [this](std::string const& arg) -> bool {
-    cmCTest::Part p = this->CTest->GetPartFromName(arg);
-    if (p == cmCTest::PartCount) {
-      std::ostringstream e;
-      e << "Part name \"" << arg << "\" is invalid.";
-      this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
-      return true;
-    }
-    return false;
-  });
-
-  cm::erase_if(this->Files, [this](std::string const& arg) -> bool {
-    if (!cmSystemTools::FileExists(arg)) {
-      std::ostringstream e;
-      e << "File \"" << arg << "\" does not exist. Cannot submit "
-        << "a non-existent file.";
-      this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
-      return true;
-    }
-    return false;
-  });
+  if (this->Parts) {
+    cm::erase_if(*(this->Parts), [this](std::string const& arg) -> bool {
+      cmCTest::Part p = this->CTest->GetPartFromName(arg);
+      if (p == cmCTest::PartCount) {
+        std::ostringstream e;
+        e << "Part name \"" << arg << "\" is invalid.";
+        this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+        return true;
+      }
+      return false;
+    });
+  }
+
+  if (this->Files) {
+    cm::erase_if(*(this->Files), [this](std::string const& arg) -> bool {
+      if (!cmSystemTools::FileExists(arg)) {
+        std::ostringstream e;
+        e << "File \"" << arg << "\" does not exist. Cannot submit "
+          << "a non-existent file.";
+        this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+        return true;
+      }
+      return false;
+    });
+  }
 }

+ 5 - 5
Source/CTest/cmCTestSubmitCommand.h

@@ -8,6 +8,8 @@
 #include <string>
 #include <vector>
 
+#include <cm/optional>
+
 #include "cmCTestHandlerCommand.h"
 
 class cmCommand;
@@ -35,13 +37,11 @@ public:
 
 protected:
   void BindArguments() override;
-  void CheckArguments(std::vector<std::string> const& keywords) override;
+  void CheckArguments() override;
   cmCTestGenericHandler* InitializeHandler() override;
 
   bool CDashUpload = false;
-  bool FilesMentioned = false;
   bool InternalTest = false;
-  bool PartsMentioned = false;
 
   std::string BuildID;
   std::string CDashUploadFile;
@@ -50,7 +50,7 @@ protected:
   std::string RetryDelay;
   std::string SubmitURL;
 
-  std::vector<std::string> Files;
+  cm::optional<std::vector<std::string>> Files;
   std::vector<std::string> HttpHeaders;
-  std::vector<std::string> Parts;
+  cm::optional<std::vector<std::string>> Parts;
 };

+ 1 - 1
Source/CTest/cmCTestUploadCommand.cxx

@@ -21,7 +21,7 @@ void cmCTestUploadCommand::BindArguments()
   this->Bind("CAPTURE_CMAKE_ERROR"_s, this->CaptureCMakeError);
 }
 
-void cmCTestUploadCommand::CheckArguments(std::vector<std::string> const&)
+void cmCTestUploadCommand::CheckArguments()
 {
   cm::erase_if(this->Files, [this](std::string const& arg) -> bool {
     if (!cmSystemTools::FileExists(arg)) {

+ 1 - 1
Source/CTest/cmCTestUploadCommand.h

@@ -42,7 +42,7 @@ public:
 
 protected:
   void BindArguments() override;
-  void CheckArguments(std::vector<std::string> const&) override;
+  void CheckArguments() override;
   cmCTestGenericHandler* InitializeHandler() override;
 
   std::vector<std::string> Files;

+ 6 - 6
Source/cmArgumentParser.cxx

@@ -44,14 +44,14 @@ void Instance::Bind(std::string& val)
   this->ExpectValue = true;
 }
 
-void Instance::Bind(StringList& val)
+void Instance::Bind(std::vector<std::string>& val)
 {
   this->CurrentString = nullptr;
   this->CurrentList = &val;
   this->ExpectValue = true;
 }
 
-void Instance::Bind(MultiStringList& val)
+void Instance::Bind(std::vector<std::vector<std::string>>& val)
 {
   this->CurrentString = nullptr;
   this->CurrentList = (static_cast<void>(val.emplace_back()), &val.back());
@@ -60,17 +60,17 @@ void Instance::Bind(MultiStringList& val)
 
 void Instance::Consume(cm::string_view arg, void* result,
                        std::vector<std::string>* unparsedArguments,
-                       std::vector<std::string>* keywordsMissingValue,
-                       std::vector<std::string>* parsedKeywords)
+                       std::vector<cm::string_view>* keywordsMissingValue,
+                       std::vector<cm::string_view>* parsedKeywords)
 {
   auto const it = this->Bindings.Find(arg);
   if (it != this->Bindings.end()) {
     if (parsedKeywords != nullptr) {
-      parsedKeywords->emplace_back(arg);
+      parsedKeywords->emplace_back(it->first);
     }
     it->second(*this, result);
     if (this->ExpectValue && keywordsMissingValue != nullptr) {
-      keywordsMissingValue->emplace_back(arg);
+      keywordsMissingValue->emplace_back(it->first);
     }
     return;
   }

+ 25 - 19
Source/cmArgumentParser.h

@@ -10,14 +10,12 @@
 #include <utility>
 #include <vector>
 
+#include <cm/optional>
 #include <cm/string_view>
 #include <cmext/string_view>
 
 namespace ArgumentParser {
 
-using StringList = std::vector<std::string>;
-using MultiStringList = std::vector<StringList>;
-
 class Instance;
 using Action = std::function<void(Instance&, void*)>;
 
@@ -39,18 +37,28 @@ public:
 
   void Bind(bool& val);
   void Bind(std::string& val);
-  void Bind(StringList& val);
-  void Bind(MultiStringList& val);
+  void Bind(std::vector<std::string>& val);
+  void Bind(std::vector<std::vector<std::string>>& val);
+
+  // cm::optional<> records the presence the keyword to which it binds.
+  template <typename T>
+  void Bind(cm::optional<T>& optVal)
+  {
+    if (!optVal) {
+      optVal.emplace();
+    }
+    this->Bind(*optVal);
+  }
 
   void Consume(cm::string_view arg, void* result,
                std::vector<std::string>* unparsedArguments,
-               std::vector<std::string>* keywordsMissingValue,
-               std::vector<std::string>* parsedKeywords);
+               std::vector<cm::string_view>* keywordsMissingValue,
+               std::vector<cm::string_view>* parsedKeywords);
 
 private:
   ActionMap const& Bindings;
   std::string* CurrentString = nullptr;
-  StringList* CurrentList = nullptr;
+  std::vector<std::string>* CurrentList = nullptr;
   bool ExpectValue = false;
 };
 
@@ -78,9 +86,9 @@ public:
 
   template <typename Range>
   void Parse(Result& result, Range const& args,
-             std::vector<std::string>* unparsedArguments = nullptr,
-             std::vector<std::string>* keywordsMissingValue = nullptr,
-             std::vector<std::string>* parsedKeywords = nullptr) const
+             std::vector<std::string>* unparsedArguments,
+             std::vector<cm::string_view>* keywordsMissingValue = nullptr,
+             std::vector<cm::string_view>* parsedKeywords = nullptr) const
   {
     ArgumentParser::Instance instance(this->Bindings);
     for (cm::string_view arg : args) {
@@ -90,10 +98,9 @@ public:
   }
 
   template <typename Range>
-  Result Parse(Range const& args,
-               std::vector<std::string>* unparsedArguments = nullptr,
-               std::vector<std::string>* keywordsMissingValue = nullptr,
-               std::vector<std::string>* parsedKeywords = nullptr) const
+  Result Parse(Range const& args, std::vector<std::string>* unparsedArguments,
+               std::vector<cm::string_view>* keywordsMissingValue = nullptr,
+               std::vector<cm::string_view>* parsedKeywords = nullptr) const
   {
     Result result;
     this->Parse(result, args, unparsedArguments, keywordsMissingValue,
@@ -118,10 +125,9 @@ public:
   }
 
   template <typename Range>
-  void Parse(Range const& args,
-             std::vector<std::string>* unparsedArguments = nullptr,
-             std::vector<std::string>* keywordsMissingValue = nullptr,
-             std::vector<std::string>* parsedKeywords = nullptr) const
+  void Parse(Range const& args, std::vector<std::string>* unparsedArguments,
+             std::vector<cm::string_view>* keywordsMissingValue = nullptr,
+             std::vector<cm::string_view>* parsedKeywords = nullptr) const
   {
     ArgumentParser::Instance instance(this->Bindings);
     for (cm::string_view arg : args) {

+ 1 - 1
Source/cmCMakeHostSystemInformationCommand.cxx

@@ -491,7 +491,7 @@ bool QueryWindowsRegistry(Range args, cmExecutionStatus& status,
     .Bind("SEPARATOR"_s, &Arguments::Separator)
     .Bind("ERROR_VARIABLE"_s, &Arguments::ErrorVariable);
   std::vector<std::string> invalidArgs;
-  std::vector<std::string> keywordsMissingValue;
+  std::vector<cm::string_view> keywordsMissingValue;
 
   Arguments const arguments =
     parser.Parse(args.advance(1), &invalidArgs, &keywordsMissingValue);

+ 6 - 6
Source/cmCMakePathCommand.cxx

@@ -44,8 +44,8 @@ public:
 
   template <int Advance = 2>
   Result Parse(std::vector<std::string> const& args,
-               std::vector<std::string>* keywordsMissingValue = nullptr,
-               std::vector<std::string>* parsedKeywords = nullptr) const
+               std::vector<cm::string_view>* keywordsMissingValue = nullptr,
+               std::vector<cm::string_view>* parsedKeywords = nullptr) const
   {
     this->Inputs.clear();
 
@@ -89,11 +89,11 @@ public:
       args, &this->KeywordsMissingValue, &this->ParsedKeywords);
   }
 
-  const std::vector<std::string>& GetKeywordsMissingValue() const
+  const std::vector<cm::string_view>& GetKeywordsMissingValue() const
   {
     return this->KeywordsMissingValue;
   }
-  const std::vector<std::string>& GetParsedKeywords() const
+  const std::vector<cm::string_view>& GetParsedKeywords() const
   {
     return this->ParsedKeywords;
   }
@@ -121,8 +121,8 @@ public:
   }
 
 private:
-  mutable std::vector<std::string> KeywordsMissingValue;
-  mutable std::vector<std::string> ParsedKeywords;
+  mutable std::vector<cm::string_view> KeywordsMissingValue;
+  mutable std::vector<cm::string_view> ParsedKeywords;
 };
 
 struct OutputVariable

+ 3 - 3
Source/cmExecuteProcessCommand.cxx

@@ -95,13 +95,13 @@ bool cmExecuteProcessCommand(std::vector<std::string> const& args,
       .Bind("COMMAND_ERROR_IS_FATAL"_s, &Arguments::CommandErrorIsFatal);
 
   std::vector<std::string> unparsedArguments;
-  std::vector<std::string> keywordsMissingValue;
+  std::vector<cm::string_view> keywordsMissingValue;
   Arguments const arguments =
     parser.Parse(args, &unparsedArguments, &keywordsMissingValue);
 
   if (!keywordsMissingValue.empty()) {
-    status.SetError(" called with no value for " +
-                    keywordsMissingValue.front() + ".");
+    status.SetError(cmStrCat(" called with no value for ",
+                             keywordsMissingValue.front(), "."));
     return false;
   }
   if (!unparsedArguments.empty()) {

+ 5 - 8
Source/cmExportCommand.cxx

@@ -7,7 +7,7 @@
 #include <utility>
 
 #include <cm/memory>
-#include <cmext/algorithm>
+#include <cm/optional>
 #include <cmext/string_view>
 
 #include "cmsys/RegularExpression.hxx"
@@ -57,7 +57,7 @@ bool cmExportCommand(std::vector<std::string> const& args,
   struct Arguments
   {
     std::string ExportSetName;
-    std::vector<std::string> Targets;
+    cm::optional<std::vector<std::string>> Targets;
     std::string Namespace;
     std::string Filename;
     std::string AndroidMKFile;
@@ -79,9 +79,7 @@ bool cmExportCommand(std::vector<std::string> const& args,
   }
 
   std::vector<std::string> unknownArgs;
-  std::vector<std::string> keywordsMissingValue;
-  Arguments const arguments =
-    parser.Parse(args, &unknownArgs, &keywordsMissingValue);
+  Arguments const arguments = parser.Parse(args, &unknownArgs);
 
   if (!unknownArgs.empty()) {
     status.SetError("Unknown argument: \"" + unknownArgs.front() + "\".");
@@ -145,9 +143,8 @@ bool cmExportCommand(std::vector<std::string> const& args,
       return false;
     }
     exportSet = &it->second;
-  } else if (!arguments.Targets.empty() ||
-             cm::contains(keywordsMissingValue, "TARGETS")) {
-    for (std::string const& currentTarget : arguments.Targets) {
+  } else if (arguments.Targets) {
+    for (std::string const& currentTarget : *arguments.Targets) {
       if (mf.IsAlias(currentTarget)) {
         std::ostringstream e;
         e << "given ALIAS target \"" << currentTarget

+ 99 - 119
Source/cmFileCommand.cxx

@@ -172,7 +172,8 @@ bool HandleReadCommand(std::vector<std::string> const& args,
                                .Bind("LIMIT"_s, &Arguments::Limit)
                                .Bind("HEX"_s, &Arguments::Hex);
 
-  Arguments const arguments = parser.Parse(cmMakeRange(args).advance(3));
+  Arguments const arguments = parser.Parse(cmMakeRange(args).advance(3),
+                                           /*unparsedArguments=*/nullptr);
 
   std::string fileName = fileNameArg;
   if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
@@ -958,8 +959,8 @@ bool HandleRPathChangeCommand(std::vector<std::string> const& args,
   bool removeEnvironmentRPath = false;
   cmArgumentParser<void> parser;
   std::vector<std::string> unknownArgs;
-  std::vector<std::string> missingArgs;
-  std::vector<std::string> parsedArgs;
+  std::vector<cm::string_view> missingArgs;
+  std::vector<cm::string_view> parsedArgs;
   parser.Bind("FILE"_s, file)
     .Bind("OLD_RPATH"_s, oldRPath)
     .Bind("NEW_RPATH"_s, newRPath)
@@ -1028,8 +1029,8 @@ bool HandleRPathSetCommand(std::vector<std::string> const& args,
   std::string newRPath;
   cmArgumentParser<void> parser;
   std::vector<std::string> unknownArgs;
-  std::vector<std::string> missingArgs;
-  std::vector<std::string> parsedArgs;
+  std::vector<cm::string_view> missingArgs;
+  std::vector<cm::string_view> parsedArgs;
   parser.Bind("FILE"_s, file).Bind("NEW_RPATH"_s, newRPath);
   parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs,
                &parsedArgs);
@@ -1087,7 +1088,7 @@ bool HandleRPathRemoveCommand(std::vector<std::string> const& args,
   std::string file;
   cmArgumentParser<void> parser;
   std::vector<std::string> unknownArgs;
-  std::vector<std::string> missingArgs;
+  std::vector<cm::string_view> missingArgs;
   parser.Bind("FILE"_s, file);
   parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs);
   if (!unknownArgs.empty()) {
@@ -1138,8 +1139,8 @@ bool HandleRPathCheckCommand(std::vector<std::string> const& args,
   std::string rpath;
   cmArgumentParser<void> parser;
   std::vector<std::string> unknownArgs;
-  std::vector<std::string> missingArgs;
-  std::vector<std::string> parsedArgs;
+  std::vector<cm::string_view> missingArgs;
+  std::vector<cm::string_view> parsedArgs;
   parser.Bind("FILE"_s, file).Bind("RPATH"_s, rpath);
   parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs,
                &parsedArgs);
@@ -1197,7 +1198,8 @@ bool HandleReadElfCommand(std::vector<std::string> const& args,
                                .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));
+  Arguments const arguments = parser.Parse(cmMakeRange(args).advance(2),
+                                           /*unparsedArguments=*/nullptr);
 
   if (!cmSystemTools::FileExists(fileNameArg, true)) {
     status.SetError(cmStrCat("READ_ELF given FILE \"", fileNameArg,
@@ -1252,7 +1254,7 @@ bool HandleRealPathCommand(std::vector<std::string> const& args,
 
   struct Arguments
   {
-    std::string BaseDirectory;
+    cm::optional<std::string> BaseDirectory;
     bool ExpandTilde = false;
   };
   static auto const parser =
@@ -1261,11 +1263,9 @@ bool HandleRealPathCommand(std::vector<std::string> const& args,
       .Bind("EXPAND_TILDE"_s, &Arguments::ExpandTilde);
 
   std::vector<std::string> unparsedArguments;
-  std::vector<std::string> keywordsMissingValue;
-  std::vector<std::string> parsedKeywords;
-  auto arguments =
-    parser.Parse(cmMakeRange(args).advance(3), &unparsedArguments,
-                 &keywordsMissingValue, &parsedKeywords);
+  std::vector<cm::string_view> keywordsMissingValue;
+  auto arguments = parser.Parse(cmMakeRange(args).advance(3),
+                                &unparsedArguments, &keywordsMissingValue);
 
   if (!unparsedArguments.empty()) {
     status.SetError("REAL_PATH called with unexpected arguments");
@@ -1276,7 +1276,7 @@ bool HandleRealPathCommand(std::vector<std::string> const& args,
     return false;
   }
 
-  if (parsedKeywords.empty()) {
+  if (!arguments.BaseDirectory) {
     arguments.BaseDirectory = status.GetMakefile().GetCurrentSourceDirectory();
   }
 
@@ -1295,7 +1295,7 @@ bool HandleRealPathCommand(std::vector<std::string> const& args,
   }
 
   cmCMakePath path(input, cmCMakePath::auto_format);
-  path = path.Absolute(arguments.BaseDirectory).Normal();
+  path = path.Absolute(*arguments.BaseDirectory).Normal();
   auto realPath = cmSystemTools::GetRealPath(path.GenericString());
 
   status.GetMakefile().AddDefinition(args[2], realPath);
@@ -2497,12 +2497,12 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
 
   struct Arguments
   {
-    std::string Output;
-    std::string Input;
-    std::string Content;
-    std::string Condition;
-    std::string Target;
-    std::string NewLineStyle;
+    cm::optional<std::string> Output;
+    cm::optional<std::string> Input;
+    cm::optional<std::string> Content;
+    cm::optional<std::string> Condition;
+    cm::optional<std::string> Target;
+    cm::optional<std::string> NewLineStyle;
     bool NoSourcePermissions = false;
     bool UseSourcePermissions = false;
     std::vector<std::string> FilePermissions;
@@ -2521,14 +2521,16 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
       .Bind("NEWLINE_STYLE"_s, &Arguments::NewLineStyle);
 
   std::vector<std::string> unparsedArguments;
-  std::vector<std::string> keywordsMissingValues;
-  std::vector<std::string> parsedKeywords;
+  std::vector<cm::string_view> keywordsMissingValues;
+  std::vector<cm::string_view> parsedKeywords;
   Arguments const arguments =
     parser.Parse(cmMakeRange(args).advance(1), &unparsedArguments,
                  &keywordsMissingValues, &parsedKeywords);
 
   if (!keywordsMissingValues.empty()) {
-    status.SetError("Incorrect arguments to GENERATE subcommand.");
+    status.SetError(
+      cmStrCat("GENERATE keywords missing values:\n  ",
+               cmJoin(cmMakeRange(keywordsMissingValues), "\n  ")));
     return false;
   }
 
@@ -2537,56 +2539,41 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
     return false;
   }
 
-  bool mandatoryOptionsSpecified = false;
-  if (parsedKeywords.size() > 1) {
-    const bool outputOprionSpecified = parsedKeywords[0] == "OUTPUT"_s;
-    const bool inputOrContentSpecified =
-      parsedKeywords[1] == "INPUT"_s || parsedKeywords[1] == "CONTENT"_s;
-    if (outputOprionSpecified && inputOrContentSpecified) {
-      mandatoryOptionsSpecified = true;
-    }
+  if (!arguments.Output || parsedKeywords[0] != "OUTPUT"_s) {
+    status.SetError("GENERATE requires OUTPUT as first option.");
+    return false;
   }
-  if (!mandatoryOptionsSpecified) {
-    status.SetError("Incorrect arguments to GENERATE subcommand.");
+  std::string const& output = *arguments.Output;
+
+  if (!arguments.Input && !arguments.Content) {
+    status.SetError("GENERATE requires INPUT or CONTENT option.");
     return false;
   }
+  const bool inputIsContent = parsedKeywords[1] == "CONTENT"_s;
+  if (!inputIsContent && parsedKeywords[1] == "INPUT") {
+    status.SetError("Unknown argument to GENERATE subcommand.");
+  }
+  std::string const& input =
+    inputIsContent ? *arguments.Content : *arguments.Input;
 
-  const bool conditionOptionSpecified =
-    std::find(parsedKeywords.begin(), parsedKeywords.end(), "CONDITION"_s) !=
-    parsedKeywords.end();
-  if (conditionOptionSpecified && arguments.Condition.empty()) {
+  if (arguments.Condition && arguments.Condition->empty()) {
     status.SetError("CONDITION of sub-command GENERATE must not be empty "
                     "if specified.");
     return false;
   }
+  std::string const& condition =
+    arguments.Condition ? *arguments.Condition : std::string();
 
-  const bool targetOptionSpecified =
-    std::find(parsedKeywords.begin(), parsedKeywords.end(), "TARGET"_s) !=
-    parsedKeywords.end();
-  if (targetOptionSpecified && arguments.Target.empty()) {
+  if (arguments.Target && arguments.Target->empty()) {
     status.SetError("TARGET of sub-command GENERATE must not be empty "
                     "if specified.");
     return false;
   }
+  std::string const& target =
+    arguments.Target ? *arguments.Target : std::string();
 
-  const bool outputOptionSpecified =
-    std::find(parsedKeywords.begin(), parsedKeywords.end(), "OUTPUT"_s) !=
-    parsedKeywords.end();
-  if (outputOptionSpecified && parsedKeywords[0] != "OUTPUT"_s) {
-    status.SetError("Incorrect arguments to GENERATE subcommand.");
-    return false;
-  }
-
-  const bool inputIsContent = parsedKeywords[1] != "INPUT"_s;
-  if (inputIsContent && parsedKeywords[1] != "CONTENT") {
-    status.SetError("Unknown argument to GENERATE subcommand.");
-  }
-
-  const bool newLineStyleSpecified =
-    std::find(parsedKeywords.begin(), parsedKeywords.end(),
-              "NEWLINE_STYLE"_s) != parsedKeywords.end();
   cmNewLineStyle newLineStyle;
-  if (newLineStyleSpecified) {
+  if (arguments.NewLineStyle) {
     std::string errorMessage;
     if (!newLineStyle.ReadFromArguments(args, errorMessage)) {
       status.SetError(cmStrCat("GENERATE ", errorMessage));
@@ -2594,11 +2581,6 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
     }
   }
 
-  std::string input = arguments.Input;
-  if (inputIsContent) {
-    input = arguments.Content;
-  }
-
   if (arguments.NoSourcePermissions && arguments.UseSourcePermissions) {
     status.SetError("given both NO_SOURCE_PERMISSIONS and "
                     "USE_SOURCE_PERMISSIONS. Only one option allowed.");
@@ -2656,8 +2638,7 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
     }
   }
 
-  AddEvaluationFile(input, arguments.Target, arguments.Output,
-                    arguments.Condition, inputIsContent,
+  AddEvaluationFile(input, target, output, condition, inputIsContent,
                     newLineStyle.GetCharacters(), permissions, status);
   return true;
 }
@@ -3102,7 +3083,7 @@ bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
       .Bind("POST_EXCLUDE_FILES_STRICT"_s, &Arguments::PostExcludeFilesStrict);
 
   std::vector<std::string> unrecognizedArguments;
-  std::vector<std::string> keywordsMissingValues;
+  std::vector<cm::string_view> keywordsMissingValues;
   auto parsedArgs =
     parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
                  &keywordsMissingValues);
@@ -3114,18 +3095,18 @@ bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
   }
 
   // Arguments that are allowed to be empty lists.  Keep entries sorted!
-  const std::vector<std::string> LIST_ARGS = {
-    "DIRECTORIES",
-    "EXECUTABLES",
-    "LIBRARIES",
-    "MODULES",
-    "POST_EXCLUDE_FILES",
-    "POST_EXCLUDE_FILES_STRICT",
-    "POST_EXCLUDE_REGEXES",
-    "POST_INCLUDE_FILES",
-    "POST_INCLUDE_REGEXES",
-    "PRE_EXCLUDE_REGEXES",
-    "PRE_INCLUDE_REGEXES",
+  static const std::vector<cm::string_view> LIST_ARGS = {
+    "DIRECTORIES"_s,
+    "EXECUTABLES"_s,
+    "LIBRARIES"_s,
+    "MODULES"_s,
+    "POST_EXCLUDE_FILES"_s,
+    "POST_EXCLUDE_FILES_STRICT"_s,
+    "POST_EXCLUDE_REGEXES"_s,
+    "POST_INCLUDE_FILES"_s,
+    "POST_INCLUDE_REGEXES"_s,
+    "PRE_EXCLUDE_REGEXES"_s,
+    "PRE_INCLUDE_REGEXES"_s,
   };
   auto kwbegin = keywordsMissingValues.cbegin();
   auto kwend = cmRemoveMatching(keywordsMissingValues, LIST_ARGS);
@@ -3235,8 +3216,8 @@ bool HandleConfigureCommand(std::vector<std::string> const& args,
 {
   struct Arguments
   {
-    std::string Output;
-    std::string Content;
+    cm::optional<std::string> Output;
+    cm::optional<std::string> Content;
     bool EscapeQuotes = false;
     bool AtOnly = false;
     std::string NewlineStyle;
@@ -3251,11 +3232,10 @@ bool HandleConfigureCommand(std::vector<std::string> const& args,
       .Bind("NEWLINE_STYLE"_s, &Arguments::NewlineStyle);
 
   std::vector<std::string> unrecognizedArguments;
-  std::vector<std::string> keywordsMissingArguments;
-  std::vector<std::string> parsedKeywords;
+  std::vector<cm::string_view> keywordsMissingValues;
   auto parsedArgs =
     parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
-                 &keywordsMissingArguments, &parsedKeywords);
+                 &keywordsMissingValues);
 
   auto argIt = unrecognizedArguments.begin();
   if (argIt != unrecognizedArguments.end()) {
@@ -3265,28 +3245,28 @@ bool HandleConfigureCommand(std::vector<std::string> const& args,
     return false;
   }
 
-  std::vector<std::string> mandatoryOptions{ "OUTPUT", "CONTENT" };
-  for (auto const& e : mandatoryOptions) {
-    const bool optionHasNoValue =
-      std::find(keywordsMissingArguments.begin(),
-                keywordsMissingArguments.end(),
-                e) != keywordsMissingArguments.end();
-    if (optionHasNoValue) {
-      status.SetError(cmStrCat("CONFIGURE ", e, " option needs a value."));
-      cmSystemTools::SetFatalErrorOccurred();
-      return false;
-    }
+  // Arguments that are allowed to be empty lists.  Keep entries sorted!
+  static const std::vector<cm::string_view> LIST_ARGS = {
+    "NEWLINE_STYLE"_s, // Filter here so we can issue a custom error below.
+  };
+  auto kwbegin = keywordsMissingValues.cbegin();
+  auto kwend = cmRemoveMatching(keywordsMissingValues, LIST_ARGS);
+  if (kwend != kwbegin) {
+    status.SetError(cmStrCat("CONFIGURE keywords missing values:\n  ",
+                             cmJoin(cmMakeRange(kwbegin, kwend), "\n  ")));
+    cmSystemTools::SetFatalErrorOccurred();
+    return false;
   }
 
-  for (auto const& e : mandatoryOptions) {
-    const bool optionGiven =
-      std::find(parsedKeywords.begin(), parsedKeywords.end(), e) !=
-      parsedKeywords.end();
-    if (!optionGiven) {
-      status.SetError(cmStrCat("CONFIGURE ", e, " option is mandatory."));
-      cmSystemTools::SetFatalErrorOccurred();
-      return false;
-    }
+  if (!parsedArgs.Output) {
+    status.SetError("CONFIGURE OUTPUT option is mandatory.");
+    cmSystemTools::SetFatalErrorOccurred();
+    return false;
+  }
+  if (!parsedArgs.Content) {
+    status.SetError("CONFIGURE CONTENT option is mandatory.");
+    cmSystemTools::SetFatalErrorOccurred();
+    return false;
   }
 
   std::string errorMessage;
@@ -3298,7 +3278,7 @@ bool HandleConfigureCommand(std::vector<std::string> const& args,
 
   // Check for generator expressions
   std::string outputFile = cmSystemTools::CollapseFullPath(
-    parsedArgs.Output, status.GetMakefile().GetCurrentBinaryDirectory());
+    *parsedArgs.Output, status.GetMakefile().GetCurrentBinaryDirectory());
 
   std::string::size_type pos = outputFile.find_first_of("<>");
   if (pos != std::string::npos) {
@@ -3347,7 +3327,7 @@ bool HandleConfigureCommand(std::vector<std::string> const& args,
   fout.SetCopyIfDifferent(true);
 
   // copy input to output and expand variables from input at the same time
-  std::stringstream sin(parsedArgs.Content, std::ios::in);
+  std::stringstream sin(*parsedArgs.Content, std::ios::in);
   std::string inLine;
   std::string outLine;
   bool hasNewLine = false;
@@ -3392,7 +3372,7 @@ bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
       .Bind("PATHS"_s, &Arguments::Paths);
 
   std::vector<std::string> unrecognizedArguments;
-  std::vector<std::string> keywordsMissingValues;
+  std::vector<cm::string_view> keywordsMissingValues;
   auto parsedArgs =
     parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
                  &keywordsMissingValues);
@@ -3404,12 +3384,12 @@ bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
   }
 
   // Arguments that are allowed to be empty lists.  Keep entries sorted!
-  const std::vector<std::string> LIST_ARGS = {
-    "MTIME", // "MTIME" should not be in this list because it requires one
-             // value, but it has long been accidentally accepted without
-             // one and treated as if an empty value were given.
-             // Fixing this would require a policy.
-    "PATHS", // "PATHS" is here only so we can issue a custom error below.
+  static const std::vector<cm::string_view> LIST_ARGS = {
+    "MTIME"_s, // "MTIME" should not be in this list because it requires one
+               // value, but it has long been accidentally accepted without
+               // one and treated as if an empty value were given.
+               // Fixing this would require a policy.
+    "PATHS"_s, // "PATHS" is here only so we can issue a custom error below.
   };
   auto kwbegin = keywordsMissingValues.cbegin();
   auto kwend = cmRemoveMatching(keywordsMissingValues, LIST_ARGS);
@@ -3525,7 +3505,7 @@ bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
                                .Bind("TOUCH"_s, &Arguments::Touch);
 
   std::vector<std::string> unrecognizedArguments;
-  std::vector<std::string> keywordsMissingValues;
+  std::vector<cm::string_view> keywordsMissingValues;
   auto parsedArgs =
     parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
                  &keywordsMissingValues);
@@ -3537,7 +3517,7 @@ bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
   }
 
   // Arguments that are allowed to be empty lists.  Keep entries sorted!
-  const std::vector<std::string> LIST_ARGS = { "PATTERNS" };
+  static const std::vector<cm::string_view> LIST_ARGS = { "PATTERNS"_s };
   auto kwbegin = keywordsMissingValues.cbegin();
   auto kwend = cmRemoveMatching(keywordsMissingValues, LIST_ARGS);
   if (kwend != kwbegin) {
@@ -3648,7 +3628,7 @@ bool HandleChmodCommandImpl(std::vector<std::string> const& args, bool recurse,
       .Bind("DIRECTORY_PERMISSIONS"_s, &Arguments::DirectoryPermissions);
 
   std::vector<std::string> pathEntries;
-  std::vector<std::string> keywordsMissingValues;
+  std::vector<cm::string_view> keywordsMissingValues;
   Arguments parsedArgs = parser.Parse(cmMakeRange(args).advance(1),
                                       &pathEntries, &keywordsMissingValues);
 
@@ -3672,7 +3652,7 @@ bool HandleChmodCommandImpl(std::vector<std::string> const& args, bool recurse,
 
   if (!keywordsMissingValues.empty()) {
     for (const auto& i : keywordsMissingValues) {
-      status.SetError(i + " is not given any arguments");
+      status.SetError(cmStrCat(i, " is not given any arguments"));
       cmSystemTools::SetFatalErrorOccurred();
     }
     return false;

+ 10 - 11
Source/cmInstallCommand.cxx

@@ -12,6 +12,7 @@
 #include <utility>
 
 #include <cm/memory>
+#include <cm/optional>
 #include <cm/string_view>
 #include <cmext/string_view>
 
@@ -436,24 +437,22 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
   // These generic args also contain the targets and the export stuff
   std::vector<std::string> targetList;
   std::string exports;
-  std::vector<std::string> runtimeDependenciesArgVector;
+  cm::optional<std::vector<std::string>> runtimeDependenciesArgVector;
   std::string runtimeDependencySetArg;
   std::vector<std::string> unknownArgs;
-  std::vector<std::string> parsedArgs;
   cmInstallCommandArguments genericArgs(helper.DefaultComponentName);
   genericArgs.Bind("TARGETS"_s, targetList);
   genericArgs.Bind("EXPORT"_s, exports);
   genericArgs.Bind("RUNTIME_DEPENDENCIES"_s, runtimeDependenciesArgVector);
   genericArgs.Bind("RUNTIME_DEPENDENCY_SET"_s, runtimeDependencySetArg);
-  genericArgs.Parse(genericArgVector, &unknownArgs, nullptr, &parsedArgs);
+  genericArgs.Parse(genericArgVector, &unknownArgs);
   bool success = genericArgs.Finalize();
 
-  bool withRuntimeDependencies =
-    std::find(parsedArgs.begin(), parsedArgs.end(), "RUNTIME_DEPENDENCIES") !=
-    parsedArgs.end();
   RuntimeDependenciesArgs runtimeDependenciesArgs =
-    RuntimeDependenciesArgHelper.Parse(runtimeDependenciesArgVector,
-                                       &unknownArgs);
+    runtimeDependenciesArgVector
+    ? RuntimeDependenciesArgHelper.Parse(*runtimeDependenciesArgVector,
+                                         &unknownArgs)
+    : RuntimeDependenciesArgs();
 
   cmInstallCommandArguments archiveArgs(helper.DefaultComponentName);
   cmInstallCommandArguments libraryArgs(helper.DefaultComponentName);
@@ -597,7 +596,7 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
   }
 
   cmInstallRuntimeDependencySet* runtimeDependencySet = nullptr;
-  if (withRuntimeDependencies) {
+  if (runtimeDependenciesArgVector) {
     if (!runtimeDependencySetArg.empty()) {
       status.SetError("TARGETS cannot have both RUNTIME_DEPENDENCIES and "
                       "RUNTIME_DEPENDENCY_SET.");
@@ -1137,7 +1136,7 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
     }
   }
 
-  if (withRuntimeDependencies && !runtimeDependencySet->Empty()) {
+  if (runtimeDependenciesArgVector && !runtimeDependencySet->Empty()) {
     AddInstallRuntimeDependenciesGenerator(
       helper, runtimeDependencySet, runtimeArgs, libraryArgs, frameworkArgs,
       std::move(runtimeDependenciesArgs), installsRuntime, installsLibrary,
@@ -2106,7 +2105,7 @@ bool HandleRuntimeDependencySetMode(std::vector<std::string> const& args,
   // These generic args also contain the runtime dependency set
   std::string runtimeDependencySetArg;
   std::vector<std::string> runtimeDependencyArgVector;
-  std::vector<std::string> parsedArgs;
+  std::vector<cm::string_view> parsedArgs;
   cmInstallCommandArguments genericArgs(helper.DefaultComponentName);
   genericArgs.Bind("RUNTIME_DEPENDENCY_SET"_s, runtimeDependencySetArg);
   genericArgs.Parse(genericArgVector, &runtimeDependencyArgVector, nullptr,

+ 2 - 2
Source/cmParseArgumentsCommand.cxx

@@ -42,7 +42,7 @@ namespace {
 using options_map = std::map<std::string, bool>;
 using single_map = std::map<std::string, std::string>;
 using multi_map = std::map<std::string, std::vector<std::string>>;
-using options_set = std::set<std::string>;
+using options_set = std::set<cm::string_view>;
 
 struct UserArgumentParser : public cmArgumentParser<void>
 {
@@ -208,7 +208,7 @@ bool cmParseArgumentsCommand(std::vector<std::string> const& args,
     }
   }
 
-  std::vector<std::string> keywordsMissingValues;
+  std::vector<cm::string_view> keywordsMissingValues;
 
   parser.Parse(list, &unparsed, &keywordsMissingValues);
 

+ 1 - 1
Source/cmTargetSourcesCommand.cxx

@@ -197,7 +197,7 @@ std::vector<std::string> TargetSourcesImpl::ConvertToAbsoluteContent(
 bool TargetSourcesImpl::HandleFileSetMode(
   const std::string& scope, const std::vector<std::string>& content)
 {
-  auto args = FileSetsArgsParser.Parse(content);
+  auto args = FileSetsArgsParser.Parse(content, /*unparsedArguments=*/nullptr);
 
   for (auto& argList : args.FileSets) {
     argList.emplace(argList.begin(), "FILE_SET"_s);

+ 42 - 14
Tests/CMakeLib/testArgumentParser.cxx

@@ -6,6 +6,7 @@
 #include <string>
 #include <vector>
 
+#include <cm/optional>
 #include <cm/string_view>
 #include <cmext/string_view>
 
@@ -19,39 +20,50 @@ struct Result
   bool Option2 = false;
 
   std::string String1;
-  std::string String2;
+  cm::optional<std::string> String2;
+  cm::optional<std::string> String3;
 
   std::vector<std::string> List1;
   std::vector<std::string> List2;
-  std::vector<std::string> List3;
+  cm::optional<std::vector<std::string>> List3;
+  cm::optional<std::vector<std::string>> List4;
+  cm::optional<std::vector<std::string>> List5;
 
   std::vector<std::vector<std::string>> Multi1;
   std::vector<std::vector<std::string>> Multi2;
-  std::vector<std::vector<std::string>> Multi3;
+  cm::optional<std::vector<std::vector<std::string>>> Multi3;
+  cm::optional<std::vector<std::vector<std::string>>> Multi4;
 };
 
 std::initializer_list<cm::string_view> const args = {
   /* clang-format off */
   "OPTION_1",                // option
+  // "OPTION_2",             // option that is not present
   "STRING_1",                // string arg missing value
-  "STRING_2", "foo", "bar",  // string arg + unparsed value
+  "STRING_2", "foo", "bar",  // string arg + unparsed value, presence captured
+  // "STRING_3",             // string arg that is not present
   "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
+  "LIST_4",                  // list arg missing values, presence captured
+  // "LIST_5",               // list arg that is not present
   "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
+  // "MULTI_4",              // multi list arg that is not present
   /* clang-format on */
 };
 
 bool verifyResult(Result const& result,
                   std::vector<std::string> const& unparsedArguments,
-                  std::vector<std::string> const& keywordsMissingValue)
+                  std::vector<cm::string_view> 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" };
+  static std::vector<cm::string_view> const missing = { "STRING_1"_s,
+                                                        "LIST_1"_s,
+                                                        "LIST_4"_s };
 
 #define ASSERT_TRUE(x)                                                        \
   do {                                                                        \
@@ -65,18 +77,26 @@ bool verifyResult(Result const& result,
   ASSERT_TRUE(!result.Option2);
 
   ASSERT_TRUE(result.String1.empty());
-  ASSERT_TRUE(result.String2 == "foo");
+  ASSERT_TRUE(result.String2);
+  ASSERT_TRUE(*result.String2 == "foo");
+  ASSERT_TRUE(!result.String3);
 
   ASSERT_TRUE(result.List1.empty());
   ASSERT_TRUE(result.List2 == foobar);
-  ASSERT_TRUE(result.List3 == barfoo);
+  ASSERT_TRUE(result.List3);
+  ASSERT_TRUE(*result.List3 == barfoo);
+  ASSERT_TRUE(result.List4);
+  ASSERT_TRUE(result.List4->empty());
+  ASSERT_TRUE(!result.List5);
 
   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(result.Multi3);
+  ASSERT_TRUE((*result.Multi3).size() == 2);
+  ASSERT_TRUE((*result.Multi3)[0] == foobar);
+  ASSERT_TRUE((*result.Multi3)[1] == barfoo);
+  ASSERT_TRUE(!result.Multi4);
 
   ASSERT_TRUE(unparsedArguments.size() == 1);
   ASSERT_TRUE(unparsedArguments[0] == "bar");
@@ -89,19 +109,23 @@ bool testArgumentParserDynamic()
 {
   Result result;
   std::vector<std::string> unparsedArguments;
-  std::vector<std::string> keywordsMissingValue;
+  std::vector<cm::string_view> 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("STRING_3"_s, result.String3)
     .Bind("LIST_1"_s, result.List1)
     .Bind("LIST_2"_s, result.List2)
     .Bind("LIST_3"_s, result.List3)
+    .Bind("LIST_4"_s, result.List4)
+    .Bind("LIST_5"_s, result.List5)
     .Bind("MULTI_1"_s, result.Multi1)
     .Bind("MULTI_2"_s, result.Multi2)
     .Bind("MULTI_3"_s, result.Multi3)
+    .Bind("MULTI_4"_s, result.Multi4)
     .Parse(args, &unparsedArguments, &keywordsMissingValue);
 
   return verifyResult(result, unparsedArguments, keywordsMissingValue);
@@ -115,15 +139,19 @@ bool testArgumentParserStatic()
       .Bind("OPTION_2"_s, &Result::Option2)
       .Bind("STRING_1"_s, &Result::String1)
       .Bind("STRING_2"_s, &Result::String2)
+      .Bind("STRING_3"_s, &Result::String3)
       .Bind("LIST_1"_s, &Result::List1)
       .Bind("LIST_2"_s, &Result::List2)
       .Bind("LIST_3"_s, &Result::List3)
+      .Bind("LIST_4"_s, &Result::List4)
+      .Bind("LIST_5"_s, &Result::List5)
       .Bind("MULTI_1"_s, &Result::Multi1)
       .Bind("MULTI_2"_s, &Result::Multi2)
-      .Bind("MULTI_3"_s, &Result::Multi3);
+      .Bind("MULTI_3"_s, &Result::Multi3)
+      .Bind("MULTI_4"_s, &Result::Multi4);
 
   std::vector<std::string> unparsedArguments;
-  std::vector<std::string> keywordsMissingValue;
+  std::vector<cm::string_view> keywordsMissingValue;
   Result const result =
     parser.Parse(args, &unparsedArguments, &keywordsMissingValue);
 

+ 3 - 1
Tests/RunCMake/File_Configure/BadArgContent-stderr.txt

@@ -1,4 +1,6 @@
 CMake Error at BadArgContent.cmake:[0-9]+ \(file\):
-  file CONFIGURE CONTENT option needs a value.
+  file CONFIGURE keywords missing values:
+
+    CONTENT
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)

+ 3 - 1
Tests/RunCMake/File_Configure/BadArgOutput-stderr.txt

@@ -1,4 +1,6 @@
 CMake Error at BadArgOutput.cmake:[0-9]+ \(file\):
-  file CONFIGURE OUTPUT option needs a value.
+  file CONFIGURE keywords missing values:
+
+    OUTPUT
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)

+ 1 - 0
Tests/RunCMake/File_Configure/NoArgContent-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/File_Configure/NoArgContent-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at NoArgContent.cmake:[0-9]+ \(file\):
+  file CONFIGURE CONTENT option is mandatory.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$

+ 1 - 0
Tests/RunCMake/File_Configure/NoArgContent.cmake

@@ -0,0 +1 @@
+file(CONFIGURE OUTPUT "")

+ 1 - 0
Tests/RunCMake/File_Configure/NoArgOutput-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/File_Configure/NoArgOutput-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at NoArgOutput.cmake:[0-9]+ \(file\):
+  file CONFIGURE OUTPUT option is mandatory.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$

+ 1 - 0
Tests/RunCMake/File_Configure/NoArgOutput.cmake

@@ -0,0 +1 @@
+file(CONFIGURE CONTENT "")

+ 2 - 0
Tests/RunCMake/File_Configure/RunCMakeTest.cmake

@@ -9,6 +9,8 @@ run_cmake(DirOutput)
 run_cmake(NewLineStyle-NoArg)
 run_cmake(NewLineStyle-ValidArg)
 run_cmake(NewLineStyle-WrongArg)
+run_cmake(NoArgOutput)
+run_cmake(NoArgContent)
 run_cmake(SubDir)
 run_cmake(AtOnly)
 run_cmake(EscapeQuotes)

+ 3 - 1
Tests/RunCMake/File_Generate/EmptyCondition1-stderr.txt

@@ -1,4 +1,6 @@
 CMake Error at EmptyCondition1.cmake:2 \(file\):
-  file Incorrect arguments to GENERATE subcommand.
+  file GENERATE keywords missing values:
+
+    CONDITION
 Call Stack \(most recent call first\):
   CMakeLists.txt:[0-9]+ \(include\)

+ 8 - 0
Tests/RunCMake/File_Generate/InputAndContent-check.cmake

@@ -0,0 +1,8 @@
+file(READ "${RunCMake_TEST_BINARY_DIR}/output-INPUT.txt" input)
+if(NOT input MATCHES "INPUT file")
+  string(APPEND RunCMake_TEST_FAILED "INPUT incorrectly overridden by CONTENT")
+endif()
+file(READ "${RunCMake_TEST_BINARY_DIR}/output-CONTENT.txt" content)
+if(NOT content MATCHES "CONTENT argument")
+  string(APPEND RunCMake_TEST_FAILED "CONTENT incorrectly overridden by INPUT")
+endif()

+ 1 - 0
Tests/RunCMake/File_Generate/InputAndContent-input.txt

@@ -0,0 +1 @@
+INPUT file

+ 10 - 0
Tests/RunCMake/File_Generate/InputAndContent.cmake

@@ -0,0 +1,10 @@
+file(GENERATE
+  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/output-INPUT.txt"
+  INPUT "${CMAKE_CURRENT_SOURCE_DIR}/InputAndContent-input.txt"
+  CONTENT "CONTENT argument"
+)
+file(GENERATE
+  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/output-CONTENT.txt"
+  CONTENT "CONTENT argument"
+  INPUT "${CMAKE_CURRENT_SOURCE_DIR}/InputAndContent-input.txt"
+)

+ 3 - 1
Tests/RunCMake/File_Generate/NewLineStyle-NoArg-stderr.txt

@@ -1,4 +1,6 @@
 CMake Error at NewLineStyle-NoArg.cmake:[0-9]+ \(file\):
-  file Incorrect arguments to GENERATE subcommand.
+  file GENERATE keywords missing values:
+
+    NEWLINE_STYLE
 Call Stack \(most recent call first\):
   CMakeLists.txt:[0-9]+ \(include\)

+ 1 - 0
Tests/RunCMake/File_Generate/RunCMakeTest.cmake

@@ -17,6 +17,7 @@ run_cmake(EmptyCondition2)
 run_cmake(BadCondition)
 run_cmake(DebugEvaluate)
 run_cmake(GenerateSource)
+run_cmake(InputAndContent)
 run_cmake(OutputNameMatchesSources)
 run_cmake(OutputNameMatchesObjects)
 run_cmake(OutputNameMatchesOtherSources)