cmArgumentParser: Model maybe-empty and non-empty lists with wrapper types

Previously bindings to `std::vector<std::string>` required at least one
value.  Some clients have been filtering `keywordsMissingValue` to
support keywords followed by empty lists.  Instead, require clients to
specify whether a keyword's list can be empty as part of the binding
type.
This commit is contained in:
Brad King
2022-07-06 11:30:22 -04:00
parent 4c50f639c7
commit e6d1e29ffa
15 changed files with 128 additions and 94 deletions

View File

@@ -11,6 +11,7 @@
#include <cm/memory>
#include <cm/optional>
#include "cmArgumentParserTypes.h" // IWYU pragma: keep
#include "cmCTestHandlerCommand.h"
#include "cmCommand.h"
@@ -44,5 +45,5 @@ protected:
void BindArguments() override;
cmCTestGenericHandler* InitializeHandler() override;
cm::optional<std::vector<std::string>> Labels;
cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Labels;
};

View File

@@ -10,6 +10,7 @@
#include <cm/optional>
#include "cmArgumentParserTypes.h"
#include "cmCTestHandlerCommand.h"
class cmCommand;
@@ -50,7 +51,7 @@ protected:
std::string RetryDelay;
std::string SubmitURL;
cm::optional<std::vector<std::string>> Files;
std::vector<std::string> HttpHeaders;
cm::optional<std::vector<std::string>> Parts;
cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Files;
ArgumentParser::MaybeEmpty<std::vector<std::string>> HttpHeaders;
cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Parts;
};

View File

@@ -10,6 +10,7 @@
#include <cm/memory>
#include "cmArgumentParserTypes.h"
#include "cmCTestHandlerCommand.h"
#include "cmCommand.h"
@@ -45,5 +46,5 @@ protected:
void CheckArguments() override;
cmCTestGenericHandler* InitializeHandler() override;
std::vector<std::string> Files;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Files;
};

View File

@@ -4,6 +4,8 @@
#include <algorithm>
#include "cmArgumentParserTypes.h"
namespace ArgumentParser {
auto ActionMap::Emplace(cm::string_view name, Action action)
@@ -44,7 +46,14 @@ void Instance::Bind(std::string& val)
this->ExpectValue = true;
}
void Instance::Bind(std::vector<std::string>& val)
void Instance::Bind(MaybeEmpty<std::vector<std::string>>& val)
{
this->CurrentString = nullptr;
this->CurrentList = &val;
this->ExpectValue = false;
}
void Instance::Bind(NonEmpty<std::vector<std::string>>& val)
{
this->CurrentString = nullptr;
this->CurrentList = &val;

View File

@@ -14,6 +14,8 @@
#include <cm/string_view>
#include <cmext/string_view>
#include "cmArgumentParserTypes.h" // IWYU pragma: keep
namespace ArgumentParser {
class Instance;
@@ -37,7 +39,8 @@ public:
void Bind(bool& val);
void Bind(std::string& val);
void Bind(std::vector<std::string>& val);
void Bind(MaybeEmpty<std::vector<std::string>>& val);
void Bind(NonEmpty<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.

View File

@@ -0,0 +1,19 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
#include "cmConfigure.h" // IWYU pragma: keep
namespace ArgumentParser {
template <typename T>
struct MaybeEmpty : public T
{
};
template <typename T>
struct NonEmpty : public T
{
};
} // namespace ArgumentParser

View File

@@ -14,6 +14,7 @@
#include <cmext/string_view>
#include "cmArgumentParser.h"
#include "cmArgumentParserTypes.h"
#include "cmDependencyProvider.h"
#include "cmExecutionStatus.h"
#include "cmGlobalGenerator.h"
@@ -237,7 +238,7 @@ bool cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(
struct SetProviderArgs
{
std::string Command;
std::vector<std::string> Methods;
ArgumentParser::NonEmpty<std::vector<std::string>> Methods;
};
auto const ArgsParser =

View File

@@ -8,6 +8,7 @@
#include <cmext/string_view>
#include "cmArgumentParser.h"
#include "cmArgumentParserTypes.h"
#include "cmExecutionStatus.h"
#include "cmMakefile.h"
#include "cmProperty.h"
@@ -51,8 +52,8 @@ bool cmDefinePropertyCommand(std::vector<std::string> const& args,
// Parse remaining arguments.
bool inherited = false;
std::string PropertyName;
std::vector<std::string> BriefDocs;
std::vector<std::string> FullDocs;
ArgumentParser::NonEmpty<std::vector<std::string>> BriefDocs;
ArgumentParser::NonEmpty<std::vector<std::string>> FullDocs;
std::string initializeFromVariable;
cmArgumentParser<void> parser;

View File

@@ -13,6 +13,7 @@
#include "cmsys/RegularExpression.hxx"
#include "cmArgumentParser.h"
#include "cmArgumentParserTypes.h"
#include "cmExecutionStatus.h"
#include "cmExperimental.h"
#include "cmExportBuildAndroidMKGenerator.h"
@@ -58,7 +59,7 @@ bool cmExportCommand(std::vector<std::string> const& args,
struct Arguments
{
std::string ExportSetName;
cm::optional<std::vector<std::string>> Targets;
cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Targets;
std::string Namespace;
std::string Filename;
std::string AndroidMKFile;

View File

@@ -30,6 +30,7 @@
#include "cmAlgorithms.h"
#include "cmArgumentParser.h"
#include "cmArgumentParserTypes.h"
#include "cmCMakePath.h"
#include "cmCryptoHash.h"
#include "cmELF.h"
@@ -2505,7 +2506,7 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
cm::optional<std::string> NewLineStyle;
bool NoSourcePermissions = false;
bool UseSourcePermissions = false;
std::vector<std::string> FilePermissions;
ArgumentParser::NonEmpty<std::vector<std::string>> FilePermissions;
};
static auto const parser =
@@ -3052,17 +3053,18 @@ bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
std::string ConflictingDependenciesPrefix;
std::string RPathPrefix;
std::string BundleExecutable;
std::vector<std::string> Executables;
std::vector<std::string> Libraries;
std::vector<std::string> Directories;
std::vector<std::string> Modules;
std::vector<std::string> PreIncludeRegexes;
std::vector<std::string> PreExcludeRegexes;
std::vector<std::string> PostIncludeRegexes;
std::vector<std::string> PostExcludeRegexes;
std::vector<std::string> PostIncludeFiles;
std::vector<std::string> PostExcludeFiles;
std::vector<std::string> PostExcludeFilesStrict;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Executables;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Libraries;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Directories;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Modules;
ArgumentParser::MaybeEmpty<std::vector<std::string>> PreIncludeRegexes;
ArgumentParser::MaybeEmpty<std::vector<std::string>> PreExcludeRegexes;
ArgumentParser::MaybeEmpty<std::vector<std::string>> PostIncludeRegexes;
ArgumentParser::MaybeEmpty<std::vector<std::string>> PostExcludeRegexes;
ArgumentParser::MaybeEmpty<std::vector<std::string>> PostIncludeFiles;
ArgumentParser::MaybeEmpty<std::vector<std::string>> PostExcludeFiles;
ArgumentParser::MaybeEmpty<std::vector<std::string>>
PostExcludeFilesStrict;
};
static auto const parser =
@@ -3098,25 +3100,10 @@ bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
return false;
}
// Arguments that are allowed to be empty lists. Keep entries sorted!
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);
if (kwend != kwbegin) {
status.SetError(cmStrCat("Keywords missing values:\n ",
cmJoin(cmMakeRange(kwbegin, kwend), "\n ")));
if (!keywordsMissingValues.empty()) {
status.SetError(
cmStrCat("Keywords missing values:\n ",
cmJoin(cmMakeRange(keywordsMissingValues), "\n ")));
cmSystemTools::SetFatalErrorOccurred();
return false;
}
@@ -3362,7 +3349,8 @@ bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
std::string CompressionLevel;
std::string MTime;
bool Verbose = false;
std::vector<std::string> Paths;
// "PATHS" requires at least one value, but use a custom check below.
ArgumentParser::MaybeEmpty<std::vector<std::string>> Paths;
};
static auto const parser =
@@ -3393,7 +3381,6 @@ bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
// 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);
@@ -3496,7 +3483,7 @@ bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
bool Verbose = false;
bool ListOnly = false;
std::string Destination;
std::vector<std::string> Patterns;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Patterns;
bool Touch = false;
};
@@ -3520,13 +3507,10 @@ bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
return false;
}
// Arguments that are allowed to be empty lists. Keep entries sorted!
static const std::vector<cm::string_view> LIST_ARGS = { "PATTERNS"_s };
auto kwbegin = keywordsMissingValues.cbegin();
auto kwend = cmRemoveMatching(keywordsMissingValues, LIST_ARGS);
if (kwend != kwbegin) {
status.SetError(cmStrCat("Keywords missing values:\n ",
cmJoin(cmMakeRange(kwbegin, kwend), "\n ")));
if (!keywordsMissingValues.empty()) {
status.SetError(
cmStrCat("Keywords missing values:\n ",
cmJoin(cmMakeRange(keywordsMissingValues), "\n ")));
cmSystemTools::SetFatalErrorOccurred();
return false;
}
@@ -3620,9 +3604,9 @@ bool HandleChmodCommandImpl(std::vector<std::string> const& args, bool recurse,
struct Arguments
{
std::vector<std::string> Permissions;
std::vector<std::string> FilePermissions;
std::vector<std::string> DirectoryPermissions;
ArgumentParser::NonEmpty<std::vector<std::string>> Permissions;
ArgumentParser::NonEmpty<std::vector<std::string>> FilePermissions;
ArgumentParser::NonEmpty<std::vector<std::string>> DirectoryPermissions;
};
static auto const parser =

View File

@@ -19,6 +19,7 @@
#include "cmsys/Glob.hxx"
#include "cmArgumentParser.h"
#include "cmArgumentParserTypes.h"
#include "cmExecutionStatus.h"
#include "cmExperimental.h"
#include "cmExportSet.h"
@@ -57,13 +58,13 @@ namespace {
struct RuntimeDependenciesArgs
{
std::vector<std::string> Directories;
std::vector<std::string> PreIncludeRegexes;
std::vector<std::string> PreExcludeRegexes;
std::vector<std::string> PostIncludeRegexes;
std::vector<std::string> PostExcludeRegexes;
std::vector<std::string> PostIncludeFiles;
std::vector<std::string> PostExcludeFiles;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Directories;
ArgumentParser::MaybeEmpty<std::vector<std::string>> PreIncludeRegexes;
ArgumentParser::MaybeEmpty<std::vector<std::string>> PreExcludeRegexes;
ArgumentParser::MaybeEmpty<std::vector<std::string>> PostIncludeRegexes;
ArgumentParser::MaybeEmpty<std::vector<std::string>> PostExcludeRegexes;
ArgumentParser::MaybeEmpty<std::vector<std::string>> PostIncludeFiles;
ArgumentParser::MaybeEmpty<std::vector<std::string>> PostExcludeFiles;
};
auto const RuntimeDependenciesArgHelper =
@@ -406,18 +407,18 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
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;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Archive;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Library;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Runtime;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Object;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Framework;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Bundle;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Includes;
ArgumentParser::MaybeEmpty<std::vector<std::string>> PrivateHeader;
ArgumentParser::MaybeEmpty<std::vector<std::string>> PublicHeader;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Resource;
ArgumentParser::MaybeEmpty<std::vector<std::string>> CxxModulesBmi;
std::vector<std::vector<std::string>> FileSets;
std::vector<std::string> CxxModulesBmi;
};
static auto const argHelper =
@@ -441,9 +442,10 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
// 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;
ArgumentParser::MaybeEmpty<std::vector<std::string>> targetList;
std::string exports;
cm::optional<std::vector<std::string>> runtimeDependenciesArgVector;
cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>>
runtimeDependenciesArgVector;
std::string runtimeDependencySetArg;
std::vector<std::string> unknownArgs;
cmInstallCommandArguments genericArgs(helper.DefaultComponentName);
@@ -1251,10 +1253,10 @@ bool HandleImportedRuntimeArtifactsMode(std::vector<std::string> const& args,
struct ArgVectors
{
std::vector<std::string> Library;
std::vector<std::string> Runtime;
std::vector<std::string> Framework;
std::vector<std::string> Bundle;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Library;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Runtime;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Framework;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Bundle;
};
static auto const argHelper = cmArgumentParser<ArgVectors>{}
@@ -1268,7 +1270,7 @@ bool HandleImportedRuntimeArtifactsMode(std::vector<std::string> const& args,
// now parse the generic args (i.e. the ones not specialized on LIBRARY,
// RUNTIME etc. (see above)
std::vector<std::string> targetList;
ArgumentParser::MaybeEmpty<std::vector<std::string>> targetList;
std::string runtimeDependencySetArg;
std::vector<std::string> unknownArgs;
cmInstallCommandArguments genericArgs(helper.DefaultComponentName);
@@ -1509,7 +1511,7 @@ bool HandleFilesMode(std::vector<std::string> const& args,
// This is the FILES mode.
bool programs = (args[0] == "PROGRAMS");
cmInstallCommandArguments ica(helper.DefaultComponentName);
std::vector<std::string> files;
ArgumentParser::MaybeEmpty<std::vector<std::string>> files;
ica.Bind(programs ? "PROGRAMS"_s : "FILES"_s, files);
std::vector<std::string> unknownArgs;
ica.Parse(args, &unknownArgs);
@@ -2140,9 +2142,9 @@ bool HandleRuntimeDependencySetMode(std::vector<std::string> const& args,
struct ArgVectors
{
std::vector<std::string> Library;
std::vector<std::string> Runtime;
std::vector<std::string> Framework;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Library;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Runtime;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Framework;
};
static auto const argHelper = cmArgumentParser<ArgVectors>{}

View File

@@ -8,6 +8,7 @@
#include <vector>
#include "cmArgumentParser.h"
#include "cmArgumentParserTypes.h"
class cmInstallCommandArguments : public cmArgumentParser<void>
{
@@ -44,8 +45,8 @@ private:
std::string NamelinkComponent;
bool ExcludeFromAll = false;
std::string Rename;
std::vector<std::string> Permissions;
std::vector<std::string> Configurations;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Permissions;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Configurations;
bool Optional = false;
bool NamelinkOnly = false;
bool NamelinkSkip = false;

View File

@@ -10,6 +10,7 @@
#include <cm/string_view>
#include "cmArgumentParser.h"
#include "cmArgumentParserTypes.h"
#include "cmExecutionStatus.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
@@ -41,7 +42,8 @@ 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 multi_map =
std::map<std::string, ArgumentParser::NonEmpty<std::vector<std::string>>>;
using options_set = std::set<cm::string_view>;
struct UserArgumentParser : public cmArgumentParser<void>

View File

@@ -9,6 +9,7 @@
#include <cmext/string_view>
#include "cmArgumentParser.h"
#include "cmArgumentParserTypes.h"
#include "cmExperimental.h"
#include "cmFileSet.h"
#include "cmGeneratorExpression.h"
@@ -28,8 +29,8 @@ struct FileSetArgs
{
std::string Type;
std::string FileSet;
std::vector<std::string> BaseDirs;
std::vector<std::string> Files;
ArgumentParser::MaybeEmpty<std::vector<std::string>> BaseDirs;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Files;
};
auto const FileSetArgsParser = cmArgumentParser<FileSetArgs>()

View File

@@ -11,6 +11,7 @@
#include <cmext/string_view>
#include "cmArgumentParser.h"
#include "cmArgumentParserTypes.h"
namespace {
@@ -23,11 +24,12 @@ struct Result
cm::optional<std::string> String2;
cm::optional<std::string> String3;
std::vector<std::string> List1;
std::vector<std::string> List2;
cm::optional<std::vector<std::string>> List3;
cm::optional<std::vector<std::string>> List4;
cm::optional<std::vector<std::string>> List5;
ArgumentParser::NonEmpty<std::vector<std::string>> List1;
ArgumentParser::NonEmpty<std::vector<std::string>> List2;
cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> List3;
cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> List4;
cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> List5;
cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> List6;
std::vector<std::vector<std::string>> Multi1;
std::vector<std::vector<std::string>> Multi2;
@@ -48,6 +50,7 @@ std::initializer_list<cm::string_view> const args = {
"LIST_3", "foo", // ... with continuation
"LIST_4", // list arg missing values, presence captured
// "LIST_5", // list arg that is not present
"LIST_6", // list arg allowed to be empty
"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
@@ -88,6 +91,8 @@ bool verifyResult(Result const& result,
ASSERT_TRUE(result.List4);
ASSERT_TRUE(result.List4->empty());
ASSERT_TRUE(!result.List5);
ASSERT_TRUE(result.List6);
ASSERT_TRUE(result.List6->empty());
ASSERT_TRUE(result.Multi1.empty());
ASSERT_TRUE(result.Multi2.size() == 1);
@@ -122,6 +127,7 @@ bool testArgumentParserDynamic()
.Bind("LIST_3"_s, result.List3)
.Bind("LIST_4"_s, result.List4)
.Bind("LIST_5"_s, result.List5)
.Bind("LIST_6"_s, result.List6)
.Bind("MULTI_1"_s, result.Multi1)
.Bind("MULTI_2"_s, result.Multi2)
.Bind("MULTI_3"_s, result.Multi3)
@@ -145,6 +151,7 @@ bool testArgumentParserStatic()
.Bind("LIST_3"_s, &Result::List3)
.Bind("LIST_4"_s, &Result::List4)
.Bind("LIST_5"_s, &Result::List5)
.Bind("LIST_6"_s, &Result::List6)
.Bind("MULTI_1"_s, &Result::Multi1)
.Bind("MULTI_2"_s, &Result::Multi2)
.Bind("MULTI_3"_s, &Result::Multi3)