|
|
|
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
|
|
#include "cmListCommand.h"
|
|
|
|
|
|
|
|
#include <cassert>
|
|
|
|
#include <cstdio>
|
|
|
|
#include <functional>
|
|
|
|
#include <set>
|
|
|
|
#include <sstream>
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include <cm/memory>
|
|
|
|
#include <cm/optional>
|
|
|
|
#include <cmext/algorithm>
|
|
|
|
#include <cmext/string_view>
|
|
|
|
|
|
|
|
#include "cmExecutionStatus.h"
|
|
|
|
#include "cmList.h"
|
|
|
|
#include "cmMakefile.h"
|
|
|
|
#include "cmMessageType.h"
|
|
|
|
#include "cmPolicies.h"
|
|
|
|
#include "cmRange.h"
|
|
|
|
#include "cmStringAlgorithms.h"
|
|
|
|
#include "cmSubcommandTable.h"
|
|
|
|
#include "cmValue.h"
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
bool GetIndexArg(const std::string& arg, int* idx, cmMakefile& mf)
|
|
|
|
{
|
|
|
|
long value;
|
|
|
|
if (!cmStrToLong(arg, &value)) {
|
|
|
|
switch (mf.GetPolicyStatus(cmPolicies::CMP0121)) {
|
|
|
|
case cmPolicies::WARN: {
|
|
|
|
// Default is to warn and use old behavior OLD behavior is to allow
|
|
|
|
// compatibility, so issue a warning and use the previous behavior.
|
|
|
|
std::string warn =
|
|
|
|
cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0121),
|
|
|
|
" Invalid list index \"", arg, "\".");
|
|
|
|
mf.IssueMessage(MessageType::AUTHOR_WARNING, warn);
|
|
|
|
CM_FALLTHROUGH;
|
|
|
|
}
|
|
|
|
case cmPolicies::OLD:
|
|
|
|
// OLD behavior is to allow compatibility, so just ignore the
|
|
|
|
// situation.
|
|
|
|
break;
|
|
|
|
case cmPolicies::NEW:
|
|
|
|
return false;
|
|
|
|
case cmPolicies::REQUIRED_IF_USED:
|
|
|
|
case cmPolicies::REQUIRED_ALWAYS:
|
|
|
|
std::string msg =
|
|
|
|
cmStrCat(cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0121),
|
|
|
|
" Invalid list index \"", arg, "\".");
|
|
|
|
mf.IssueMessage(MessageType::FATAL_ERROR, msg);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Truncation is happening here, but it had always been happening here.
|
|
|
|
*idx = static_cast<int>(value);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GetListString(std::string& listString, const std::string& var,
|
|
|
|
const cmMakefile& makefile)
|
|
|
|
{
|
|
|
|
// get the old value
|
|
|
|
cmValue cacheValue = makefile.GetDefinition(var);
|
|
|
|
if (!cacheValue) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
listString = *cacheValue;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
cm::optional<cmList> GetList(const std::string& var,
|
|
|
|
const cmMakefile& makefile)
|
|
|
|
{
|
|
|
|
cm::optional<cmList> list;
|
|
|
|
|
|
|
|
std::string listString;
|
|
|
|
if (!GetListString(listString, var, makefile)) {
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
// if the size of the list
|
|
|
|
if (listString.empty()) {
|
|
|
|
list.emplace();
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
// expand the variable into a list
|
|
|
|
list.emplace(listString, cmList::EmptyElements::Yes);
|
|
|
|
// if no empty elements then just return
|
|
|
|
if (!cm::contains(*list, std::string())) {
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
// if we have empty elements we need to check policy CMP0007
|
|
|
|
switch (makefile.GetPolicyStatus(cmPolicies::CMP0007)) {
|
|
|
|
case cmPolicies::WARN: {
|
|
|
|
// Default is to warn and use old behavior
|
|
|
|
// OLD behavior is to allow compatibility, so recall
|
|
|
|
// ExpandListArgument without the true which will remove
|
|
|
|
// empty values
|
|
|
|
list->assign(listString);
|
|
|
|
std::string warn =
|
|
|
|
cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0007),
|
|
|
|
" List has value = [", listString, "].");
|
|
|
|
makefile.IssueMessage(MessageType::AUTHOR_WARNING, warn);
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
case cmPolicies::OLD:
|
|
|
|
// OLD behavior is to allow compatibility, so recall
|
|
|
|
// ExpandListArgument without the true which will remove
|
|
|
|
// empty values
|
|
|
|
list->assign(listString);
|
|
|
|
return list;
|
|
|
|
case cmPolicies::NEW:
|
|
|
|
return list;
|
|
|
|
case cmPolicies::REQUIRED_IF_USED:
|
|
|
|
case cmPolicies::REQUIRED_ALWAYS:
|
|
|
|
makefile.IssueMessage(
|
|
|
|
MessageType::FATAL_ERROR,
|
|
|
|
cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0007));
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleLengthCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
if (args.size() != 3) {
|
|
|
|
status.SetError("sub-command LENGTH requires two arguments.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& listName = args[1];
|
|
|
|
const std::string& variableName = args.back();
|
|
|
|
|
|
|
|
auto list = GetList(listName, status.GetMakefile());
|
|
|
|
status.GetMakefile().AddDefinition(variableName,
|
|
|
|
std::to_string(list ? list->size() : 0));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleGetCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
if (args.size() < 4) {
|
|
|
|
status.SetError("sub-command GET requires at least three arguments.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& listName = args[1];
|
|
|
|
const std::string& variableName = args.back();
|
|
|
|
// expand the variable
|
|
|
|
auto list = GetList(listName, status.GetMakefile());
|
|
|
|
if (!list) {
|
|
|
|
status.GetMakefile().AddDefinition(variableName, "NOTFOUND");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// FIXME: Add policy to make non-existing lists an error like empty lists.
|
|
|
|
if (list->empty()) {
|
|
|
|
status.SetError("GET given empty list");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<int> indexes;
|
|
|
|
for (std::size_t cc = 2; cc < args.size() - 1; cc++) {
|
|
|
|
int index;
|
|
|
|
if (!GetIndexArg(args[cc], &index, status.GetMakefile())) {
|
|
|
|
status.SetError(cmStrCat("index: ", args[cc], " is not a valid index"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
indexes.push_back(index);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
auto values = list->get_items(indexes.begin(), indexes.end());
|
|
|
|
status.GetMakefile().AddDefinition(variableName, values.to_string());
|
|
|
|
return true;
|
|
|
|
} catch (std::out_of_range& e) {
|
|
|
|
status.SetError(e.what());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleAppendCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
assert(args.size() >= 2);
|
|
|
|
|
|
|
|
// Skip if nothing to append.
|
|
|
|
if (args.size() < 3) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmMakefile& makefile = status.GetMakefile();
|
|
|
|
std::string const& listName = args[1];
|
|
|
|
// expand the variable
|
|
|
|
std::string listString;
|
|
|
|
GetListString(listString, listName, makefile);
|
|
|
|
|
|
|
|
makefile.AddDefinition(
|
|
|
|
listName, cmList::append(listString, args.begin() + 2, args.end()));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandlePrependCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
assert(args.size() >= 2);
|
|
|
|
|
|
|
|
// Skip if nothing to prepend.
|
|
|
|
if (args.size() < 3) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmMakefile& makefile = status.GetMakefile();
|
|
|
|
std::string const& listName = args[1];
|
|
|
|
// expand the variable
|
|
|
|
std::string listString;
|
|
|
|
GetListString(listString, listName, makefile);
|
|
|
|
|
|
|
|
makefile.AddDefinition(
|
|
|
|
listName, cmList::prepend(listString, args.begin() + 2, args.end()));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandlePopBackCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
assert(args.size() >= 2);
|
|
|
|
|
|
|
|
cmMakefile& makefile = status.GetMakefile();
|
|
|
|
auto ai = args.cbegin();
|
|
|
|
++ai; // Skip subcommand name
|
|
|
|
std::string const& listName = *ai++;
|
|
|
|
auto list = GetList(listName, makefile);
|
|
|
|
|
|
|
|
if (!list) {
|
|
|
|
// Can't get the list definition... undefine any vars given after.
|
|
|
|
for (; ai != args.cend(); ++ai) {
|
|
|
|
makefile.RemoveDefinition(*ai);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!list->empty()) {
|
|
|
|
if (ai == args.cend()) {
|
|
|
|
// No variables are given... Just remove one element.
|
|
|
|
list->pop_back();
|
|
|
|
} else {
|
|
|
|
// Ok, assign elements to be removed to the given variables
|
|
|
|
for (; !list->empty() && ai != args.cend(); ++ai) {
|
|
|
|
assert(!ai->empty());
|
|
|
|
makefile.AddDefinition(*ai, list->back());
|
|
|
|
list->pop_back();
|
|
|
|
}
|
|
|
|
// Undefine the rest variables if the list gets empty earlier...
|
|
|
|
for (; ai != args.cend(); ++ai) {
|
|
|
|
makefile.RemoveDefinition(*ai);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
makefile.AddDefinition(listName, list->to_string());
|
|
|
|
|
|
|
|
} else if (ai !=
|
|
|
|
args.cend()) { // The list is empty, but some args were given
|
|
|
|
// Need to *undefine* 'em all, cuz there are no items to assign...
|
|
|
|
for (; ai != args.cend(); ++ai) {
|
|
|
|
makefile.RemoveDefinition(*ai);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandlePopFrontCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
assert(args.size() >= 2);
|
|
|
|
|
|
|
|
cmMakefile& makefile = status.GetMakefile();
|
|
|
|
auto ai = args.cbegin();
|
|
|
|
++ai; // Skip subcommand name
|
|
|
|
std::string const& listName = *ai++;
|
|
|
|
auto list = GetList(listName, makefile);
|
|
|
|
|
|
|
|
if (!list) {
|
|
|
|
// Can't get the list definition... undefine any vars given after.
|
|
|
|
for (; ai != args.cend(); ++ai) {
|
|
|
|
makefile.RemoveDefinition(*ai);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!list->empty()) {
|
|
|
|
if (ai == args.cend()) {
|
|
|
|
// No variables are given... Just remove one element.
|
|
|
|
list->pop_front();
|
|
|
|
} else {
|
|
|
|
// Ok, assign elements to be removed to the given variables
|
|
|
|
auto vi = list->begin();
|
|
|
|
for (; vi != list->end() && ai != args.cend(); ++ai, ++vi) {
|
|
|
|
assert(!ai->empty());
|
|
|
|
makefile.AddDefinition(*ai, *vi);
|
|
|
|
}
|
|
|
|
list->erase(list->begin(), vi);
|
|
|
|
// Undefine the rest variables if the list gets empty earlier...
|
|
|
|
for (; ai != args.cend(); ++ai) {
|
|
|
|
makefile.RemoveDefinition(*ai);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
makefile.AddDefinition(listName, list->to_string());
|
|
|
|
|
|
|
|
} else if (ai !=
|
|
|
|
args.cend()) { // The list is empty, but some args were given
|
|
|
|
// Need to *undefine* 'em all, cuz there are no items to assign...
|
|
|
|
for (; ai != args.cend(); ++ai) {
|
|
|
|
makefile.RemoveDefinition(*ai);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleFindCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
if (args.size() != 4) {
|
|
|
|
status.SetError("sub-command FIND requires three arguments.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& listName = args[1];
|
|
|
|
const std::string& variableName = args.back();
|
|
|
|
// expand the variable
|
|
|
|
auto list = GetList(listName, status.GetMakefile());
|
|
|
|
|
|
|
|
if (!list) {
|
|
|
|
status.GetMakefile().AddDefinition(variableName, "-1");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto index = list->find(args[2]);
|
|
|
|
status.GetMakefile().AddDefinition(
|
|
|
|
variableName, index == cmList::npos ? "-1" : std::to_string(index));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleInsertCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
if (args.size() < 4) {
|
|
|
|
status.SetError("sub-command INSERT requires at least three arguments.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& listName = args[1];
|
|
|
|
|
|
|
|
// expand the variable
|
|
|
|
int index;
|
|
|
|
if (!GetIndexArg(args[2], &index, status.GetMakefile())) {
|
|
|
|
status.SetError(cmStrCat("index: ", args[2], " is not a valid index"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
auto list = GetList(listName, status.GetMakefile());
|
|
|
|
if (!list) {
|
|
|
|
list = cmList{};
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
list->insert_items(index, args.begin() + 3, args.end(),
|
|
|
|
cmList::ExpandElements::No, cmList::EmptyElements::Yes);
|
|
|
|
status.GetMakefile().AddDefinition(listName, list->to_string());
|
|
|
|
return true;
|
|
|
|
} catch (std::out_of_range& e) {
|
|
|
|
status.SetError(e.what());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleJoinCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
if (args.size() != 4) {
|
|
|
|
status.SetError(cmStrCat("sub-command JOIN requires three arguments (",
|
|
|
|
args.size() - 1, " found)."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& listName = args[1];
|
|
|
|
const std::string& glue = args[2];
|
|
|
|
const std::string& variableName = args[3];
|
|
|
|
|
|
|
|
// expand the variable
|
|
|
|
auto list = GetList(listName, status.GetMakefile());
|
|
|
|
|
|
|
|
if (!list) {
|
|
|
|
status.GetMakefile().AddDefinition(variableName, "");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
status.GetMakefile().AddDefinition(variableName, list->join(glue));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleRemoveItemCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
assert(args.size() >= 2);
|
|
|
|
|
|
|
|
if (args.size() == 2) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& listName = args[1];
|
|
|
|
// expand the variable
|
|
|
|
auto list = GetList(listName, status.GetMakefile());
|
|
|
|
|
|
|
|
if (!list) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
status.GetMakefile().AddDefinition(
|
|
|
|
listName, list->remove_items(args.begin() + 2, args.end()).to_string());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleReverseCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
assert(args.size() >= 2);
|
|
|
|
if (args.size() > 2) {
|
|
|
|
status.SetError("sub-command REVERSE only takes one argument.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& listName = args[1];
|
|
|
|
// expand the variable
|
|
|
|
auto list = GetList(listName, status.GetMakefile());
|
|
|
|
|
|
|
|
if (!list) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
status.GetMakefile().AddDefinition(listName, list->reverse().to_string());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleRemoveDuplicatesCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
assert(args.size() >= 2);
|
|
|
|
if (args.size() > 2) {
|
|
|
|
status.SetError("sub-command REMOVE_DUPLICATES only takes one argument.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& listName = args[1];
|
|
|
|
// expand the variable
|
|
|
|
auto list = GetList(listName, status.GetMakefile());
|
|
|
|
|
|
|
|
if (!list) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
status.GetMakefile().AddDefinition(listName,
|
|
|
|
list->remove_duplicates().to_string());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleTransformCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
if (args.size() < 3) {
|
|
|
|
status.SetError(
|
|
|
|
"sub-command TRANSFORM requires an action to be specified.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Descriptor of action
|
|
|
|
// Action: enum value identifying action
|
|
|
|
// Arity: number of arguments required for the action
|
|
|
|
struct ActionDescriptor
|
|
|
|
{
|
|
|
|
ActionDescriptor(std::string name)
|
|
|
|
: Name(std::move(name))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
ActionDescriptor(std::string name, cmList::TransformAction action,
|
|
|
|
int arity)
|
|
|
|
: Name(std::move(name))
|
|
|
|
, Action(action)
|
|
|
|
, Arity(arity)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
operator const std::string&() const { return this->Name; }
|
|
|
|
|
|
|
|
std::string Name;
|
|
|
|
cmList::TransformAction Action;
|
|
|
|
int Arity = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Build a set of supported actions.
|
|
|
|
std::set<ActionDescriptor,
|
|
|
|
std::function<bool(const std::string&, const std::string&)>>
|
|
|
|
descriptors{ { { "APPEND", cmList::TransformAction::APPEND, 1 },
|
|
|
|
{ "PREPEND", cmList::TransformAction::PREPEND, 1 },
|
|
|
|
{ "TOUPPER", cmList::TransformAction::TOUPPER, 0 },
|
|
|
|
{ "TOLOWER", cmList::TransformAction::TOLOWER, 0 },
|
|
|
|
{ "STRIP", cmList::TransformAction::STRIP, 0 },
|
|
|
|
{ "GENEX_STRIP", cmList::TransformAction::GENEX_STRIP, 0 },
|
|
|
|
{ "REPLACE", cmList::TransformAction::REPLACE, 2 } },
|
|
|
|
[](const std::string& x, const std::string& y) {
|
|
|
|
return x < y;
|
|
|
|
} };
|
|
|
|
|
|
|
|
const std::string& listName = args[1];
|
|
|
|
|
|
|
|
// Parse all possible function parameters
|
|
|
|
using size_type = std::vector<std::string>::size_type;
|
|
|
|
size_type index = 2;
|
|
|
|
|
|
|
|
auto descriptor = descriptors.find(args[index]);
|
|
|
|
|
|
|
|
if (descriptor == descriptors.end()) {
|
|
|
|
status.SetError(
|
|
|
|
cmStrCat(" sub-command TRANSFORM, ", args[index], " invalid action."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Action arguments
|
|
|
|
index += 1;
|
|
|
|
if (args.size() < index + descriptor->Arity) {
|
|
|
|
status.SetError(cmStrCat("sub-command TRANSFORM, action ",
|
|
|
|
descriptor->Name, " expects ", descriptor->Arity,
|
|
|
|
" argument(s)."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string> arguments;
|
|
|
|
index += descriptor->Arity;
|
|
|
|
if (descriptor->Arity > 0) {
|
|
|
|
arguments =
|
|
|
|
std::vector<std::string>(args.begin() + 3, args.begin() + index);
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string REGEX{ "REGEX" };
|
|
|
|
const std::string AT{ "AT" };
|
|
|
|
const std::string FOR{ "FOR" };
|
|
|
|
const std::string OUTPUT_VARIABLE{ "OUTPUT_VARIABLE" };
|
|
|
|
std::unique_ptr<cmList::TransformSelector> selector;
|
|
|
|
std::string outputName = listName;
|
|
|
|
|
|
|
|
try {
|
|
|
|
// handle optional arguments
|
|
|
|
while (args.size() > index) {
|
|
|
|
if ((args[index] == REGEX || args[index] == AT || args[index] == FOR) &&
|
|
|
|
selector) {
|
|
|
|
status.SetError(
|
|
|
|
cmStrCat("sub-command TRANSFORM, selector already specified (",
|
|
|
|
selector->GetTag(), ")."));
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// REGEX selector
|
|
|
|
if (args[index] == REGEX) {
|
|
|
|
if (args.size() == ++index) {
|
|
|
|
status.SetError("sub-command TRANSFORM, selector REGEX expects "
|
|
|
|
"'regular expression' argument.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
selector =
|
|
|
|
cmList::TransformSelector::New<cmList::TransformSelector::REGEX>(
|
|
|
|
args[index]);
|
|
|
|
|
|
|
|
index += 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// AT selector
|
|
|
|
if (args[index] == AT) {
|
|
|
|
// get all specified indexes
|
|
|
|
std::vector<cmList::index_type> indexes;
|
|
|
|
while (args.size() > ++index) {
|
|
|
|
std::size_t pos;
|
|
|
|
int value;
|
|
|
|
|
|
|
|
try {
|
|
|
|
value = std::stoi(args[index], &pos);
|
|
|
|
if (pos != args[index].length()) {
|
|
|
|
// this is not a number, stop processing
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
indexes.push_back(value);
|
|
|
|
} catch (const std::invalid_argument&) {
|
|
|
|
// this is not a number, stop processing
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (indexes.empty()) {
|
|
|
|
status.SetError(
|
|
|
|
"sub-command TRANSFORM, selector AT expects at least one "
|
|
|
|
"numeric value.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
selector =
|
|
|
|
cmList::TransformSelector::New<cmList::TransformSelector::AT>(
|
|
|
|
std::move(indexes));
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FOR selector
|
|
|
|
if (args[index] == FOR) {
|
|
|
|
if (args.size() <= ++index + 1) {
|
|
|
|
status.SetError(
|
|
|
|
"sub-command TRANSFORM, selector FOR expects, at least,"
|
|
|
|
" two arguments.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmList::index_type start = 0;
|
|
|
|
cmList::index_type stop = 0;
|
|
|
|
cmList::index_type step = 1;
|
|
|
|
bool valid = true;
|
|
|
|
try {
|
|
|
|
std::size_t pos;
|
|
|
|
|
|
|
|
start = std::stoi(args[index], &pos);
|
|
|
|
if (pos != args[index].length()) {
|
|
|
|
// this is not a number
|
|
|
|
valid = false;
|
|
|
|
} else {
|
|
|
|
stop = std::stoi(args[++index], &pos);
|
|
|
|
if (pos != args[index].length()) {
|
|
|
|
// this is not a number
|
|
|
|
valid = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (const std::invalid_argument&) {
|
|
|
|
// this is not numbers
|
|
|
|
valid = false;
|
|
|
|
}
|
|
|
|
if (!valid) {
|
|
|
|
status.SetError("sub-command TRANSFORM, selector FOR expects, "
|
|
|
|
"at least, two numeric values.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// try to read a third numeric value for step
|
|
|
|
if (args.size() > ++index) {
|
|
|
|
try {
|
|
|
|
std::size_t pos;
|
|
|
|
|
|
|
|
step = std::stoi(args[index], &pos);
|
|
|
|
if (pos != args[index].length()) {
|
|
|
|
// this is not a number
|
|
|
|
step = 1;
|
|
|
|
} else {
|
|
|
|
index += 1;
|
|
|
|
}
|
|
|
|
} catch (const std::invalid_argument&) {
|
|
|
|
// this is not number, ignore exception
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (step <= 0) {
|
|
|
|
status.SetError("sub-command TRANSFORM, selector FOR expects "
|
|
|
|
"positive numeric value for <step>.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
selector =
|
|
|
|
cmList::TransformSelector::New<cmList::TransformSelector::FOR>(
|
|
|
|
{ start, stop, step });
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// output variable
|
|
|
|
if (args[index] == OUTPUT_VARIABLE) {
|
|
|
|
if (args.size() == ++index) {
|
|
|
|
status.SetError("sub-command TRANSFORM, OUTPUT_VARIABLE "
|
|
|
|
"expects variable name argument.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
outputName = args[index++];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
status.SetError(cmStrCat("sub-command TRANSFORM, '",
|
|
|
|
cmJoin(cmMakeRange(args).advance(index), " "),
|
|
|
|
"': unexpected argument(s)."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// expand the list variable
|
|
|
|
auto list = GetList(listName, status.GetMakefile());
|
|
|
|
|
|
|
|
if (!list) {
|
|
|
|
status.GetMakefile().AddDefinition(outputName, "");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
list->transform(descriptor->Action, arguments, std::move(selector));
|
|
|
|
status.GetMakefile().AddDefinition(outputName, list->to_string());
|
|
|
|
return true;
|
|
|
|
} catch (cmList::transform_error& e) {
|
|
|
|
status.SetError(e.what());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleSortCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
assert(args.size() >= 2);
|
|
|
|
if (args.size() > 8) {
|
|
|
|
status.SetError("sub-command SORT only takes up to six arguments.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
using SortConfig = cmList::SortConfiguration;
|
|
|
|
SortConfig sortConfig;
|
|
|
|
|
|
|
|
size_t argumentIndex = 2;
|
|
|
|
const std::string messageHint = "sub-command SORT ";
|
|
|
|
|
|
|
|
while (argumentIndex < args.size()) {
|
|
|
|
std::string const& option = args[argumentIndex++];
|
|
|
|
if (option == "COMPARE") {
|
|
|
|
if (sortConfig.Compare != SortConfig::CompareMethod::DEFAULT) {
|
|
|
|
std::string error = cmStrCat(messageHint, "option \"", option,
|
|
|
|
"\" has been specified multiple times.");
|
|
|
|
status.SetError(error);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (argumentIndex < args.size()) {
|
|
|
|
std::string const& argument = args[argumentIndex++];
|
|
|
|
if (argument == "STRING") {
|
|
|
|
sortConfig.Compare = SortConfig::CompareMethod::STRING;
|
|
|
|
} else if (argument == "FILE_BASENAME") {
|
|
|
|
sortConfig.Compare = SortConfig::CompareMethod::FILE_BASENAME;
|
|
|
|
} else if (argument == "NATURAL") {
|
|
|
|
sortConfig.Compare = SortConfig::CompareMethod::NATURAL;
|
|
|
|
} else {
|
|
|
|
std::string error =
|
|
|
|
cmStrCat(messageHint, "value \"", argument, "\" for option \"",
|
|
|
|
option, "\" is invalid.");
|
|
|
|
status.SetError(error);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
status.SetError(cmStrCat(messageHint, "missing argument for option \"",
|
|
|
|
option, "\"."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else if (option == "CASE") {
|
|
|
|
if (sortConfig.Case != SortConfig::CaseSensitivity::DEFAULT) {
|
|
|
|
status.SetError(cmStrCat(messageHint, "option \"", option,
|
|
|
|
"\" has been specified multiple times."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (argumentIndex < args.size()) {
|
|
|
|
std::string const& argument = args[argumentIndex++];
|
|
|
|
if (argument == "SENSITIVE") {
|
|
|
|
sortConfig.Case = SortConfig::CaseSensitivity::SENSITIVE;
|
|
|
|
} else if (argument == "INSENSITIVE") {
|
|
|
|
sortConfig.Case = SortConfig::CaseSensitivity::INSENSITIVE;
|
|
|
|
} else {
|
|
|
|
status.SetError(cmStrCat(messageHint, "value \"", argument,
|
|
|
|
"\" for option \"", option,
|
|
|
|
"\" is invalid."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
status.SetError(cmStrCat(messageHint, "missing argument for option \"",
|
|
|
|
option, "\"."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else if (option == "ORDER") {
|
|
|
|
|
|
|
|
if (sortConfig.Order != SortConfig::OrderMode::DEFAULT) {
|
|
|
|
status.SetError(cmStrCat(messageHint, "option \"", option,
|
|
|
|
"\" has been specified multiple times."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (argumentIndex < args.size()) {
|
|
|
|
std::string const& argument = args[argumentIndex++];
|
|
|
|
if (argument == "ASCENDING") {
|
|
|
|
sortConfig.Order = SortConfig::OrderMode::ASCENDING;
|
|
|
|
} else if (argument == "DESCENDING") {
|
|
|
|
sortConfig.Order = SortConfig::OrderMode::DESCENDING;
|
|
|
|
} else {
|
|
|
|
status.SetError(cmStrCat(messageHint, "value \"", argument,
|
|
|
|
"\" for option \"", option,
|
|
|
|
"\" is invalid."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
status.SetError(cmStrCat(messageHint, "missing argument for option \"",
|
|
|
|
option, "\"."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
status.SetError(
|
|
|
|
cmStrCat(messageHint, "option \"", option, "\" is unknown."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& listName = args[1];
|
|
|
|
// expand the variable
|
|
|
|
auto list = GetList(listName, status.GetMakefile());
|
|
|
|
|
|
|
|
if (!list) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
status.GetMakefile().AddDefinition(listName,
|
|
|
|
list->sort(sortConfig).to_string());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleSublistCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
if (args.size() != 5) {
|
|
|
|
status.SetError(cmStrCat("sub-command SUBLIST requires four arguments (",
|
|
|
|
args.size() - 1, " found)."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& listName = args[1];
|
|
|
|
const std::string& variableName = args.back();
|
|
|
|
|
|
|
|
// expand the variable
|
|
|
|
auto list = GetList(listName, status.GetMakefile());
|
|
|
|
|
|
|
|
if (!list || list->empty()) {
|
|
|
|
status.GetMakefile().AddDefinition(variableName, "");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int start;
|
|
|
|
int length;
|
|
|
|
if (!GetIndexArg(args[2], &start, status.GetMakefile())) {
|
|
|
|
status.SetError(cmStrCat("index: ", args[2], " is not a valid index"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!GetIndexArg(args[3], &length, status.GetMakefile())) {
|
|
|
|
status.SetError(cmStrCat("index: ", args[3], " is not a valid index"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (start < 0) {
|
|
|
|
status.SetError(cmStrCat("begin index: ", start, " is out of range 0 - ",
|
|
|
|
list->size() - 1));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (length < -1) {
|
|
|
|
status.SetError(cmStrCat("length: ", length, " should be -1 or greater"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
using size_type = cmList::size_type;
|
|
|
|
|
|
|
|
try {
|
|
|
|
auto sublist = list->sublist(static_cast<size_type>(start),
|
|
|
|
static_cast<size_type>(length));
|
|
|
|
status.GetMakefile().AddDefinition(variableName, sublist.to_string());
|
|
|
|
return true;
|
|
|
|
} catch (std::out_of_range& e) {
|
|
|
|
status.SetError(e.what());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleRemoveAtCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
if (args.size() < 3) {
|
|
|
|
status.SetError("sub-command REMOVE_AT requires at least "
|
|
|
|
"two arguments.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& listName = args[1];
|
|
|
|
// expand the variable
|
|
|
|
auto list = GetList(listName, status.GetMakefile());
|
|
|
|
|
|
|
|
if (!list || list->empty()) {
|
|
|
|
std::ostringstream str;
|
|
|
|
str << "index: ";
|
|
|
|
for (size_t i = 1; i < args.size(); ++i) {
|
|
|
|
str << args[i];
|
|
|
|
if (i != args.size() - 1) {
|
|
|
|
str << ", ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
str << " out of range (0, 0)";
|
|
|
|
status.SetError(str.str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t cc;
|
|
|
|
std::vector<cmList::index_type> removed;
|
|
|
|
for (cc = 2; cc < args.size(); ++cc) {
|
|
|
|
int index;
|
|
|
|
if (!GetIndexArg(args[cc], &index, status.GetMakefile())) {
|
|
|
|
status.SetError(cmStrCat("index: ", args[cc], " is not a valid index"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
removed.push_back(index);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
status.GetMakefile().AddDefinition(
|
|
|
|
listName,
|
|
|
|
list->remove_items(removed.begin(), removed.end()).to_string());
|
|
|
|
return true;
|
|
|
|
} catch (std::out_of_range& e) {
|
|
|
|
status.SetError(e.what());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleFilterCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
if (args.size() < 2) {
|
|
|
|
status.SetError("sub-command FILTER requires a list to be specified.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (args.size() < 3) {
|
|
|
|
status.SetError(
|
|
|
|
"sub-command FILTER requires an operator to be specified.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (args.size() < 4) {
|
|
|
|
status.SetError("sub-command FILTER requires a mode to be specified.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& op = args[2];
|
|
|
|
cmList::FilterMode filterMode;
|
|
|
|
if (op == "INCLUDE") {
|
|
|
|
filterMode = cmList::FilterMode::INCLUDE;
|
|
|
|
} else if (op == "EXCLUDE") {
|
|
|
|
filterMode = cmList::FilterMode::EXCLUDE;
|
|
|
|
} else {
|
|
|
|
status.SetError("sub-command FILTER does not recognize operator " + op);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& listName = args[1];
|
|
|
|
// expand the variable
|
|
|
|
auto list = GetList(listName, status.GetMakefile());
|
|
|
|
|
|
|
|
if (!list) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& mode = args[3];
|
|
|
|
if (mode != "REGEX") {
|
|
|
|
status.SetError("sub-command FILTER does not recognize mode " + mode);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (args.size() != 5) {
|
|
|
|
status.SetError("sub-command FILTER, mode REGEX "
|
|
|
|
"requires five arguments.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const std::string& pattern = args[4];
|
|
|
|
|
|
|
|
try {
|
|
|
|
status.GetMakefile().AddDefinition(
|
|
|
|
listName, list->filter(pattern, filterMode).to_string());
|
|
|
|
return true;
|
|
|
|
} catch (std::invalid_argument& e) {
|
|
|
|
status.SetError(e.what());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
bool cmListCommand(std::vector<std::string> const& args,
|
|
|
|
cmExecutionStatus& status)
|
|
|
|
{
|
|
|
|
if (args.size() < 2) {
|
|
|
|
status.SetError("must be called with at least two arguments.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static cmSubcommandTable const subcommand{
|
|
|
|
{ "LENGTH"_s, HandleLengthCommand },
|
|
|
|
{ "GET"_s, HandleGetCommand },
|
|
|
|
{ "APPEND"_s, HandleAppendCommand },
|
|
|
|
{ "PREPEND"_s, HandlePrependCommand },
|
|
|
|
{ "POP_BACK"_s, HandlePopBackCommand },
|
|
|
|
{ "POP_FRONT"_s, HandlePopFrontCommand },
|
|
|
|
{ "FIND"_s, HandleFindCommand },
|
|
|
|
{ "INSERT"_s, HandleInsertCommand },
|
|
|
|
{ "JOIN"_s, HandleJoinCommand },
|
|
|
|
{ "REMOVE_AT"_s, HandleRemoveAtCommand },
|
|
|
|
{ "REMOVE_ITEM"_s, HandleRemoveItemCommand },
|
|
|
|
{ "REMOVE_DUPLICATES"_s, HandleRemoveDuplicatesCommand },
|
|
|
|
{ "TRANSFORM"_s, HandleTransformCommand },
|
|
|
|
{ "SORT"_s, HandleSortCommand },
|
|
|
|
{ "SUBLIST"_s, HandleSublistCommand },
|
|
|
|
{ "REVERSE"_s, HandleReverseCommand },
|
|
|
|
{ "FILTER"_s, HandleFilterCommand },
|
|
|
|
};
|
|
|
|
|
|
|
|
return subcommand(args[0], args, status);
|
|
|
|
}
|