| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst 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 "cmPolicies.h" | |
| #include "cmRange.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSubcommandTable.h" | |
| #include "cmValue.h" | |
| namespace { | |
| bool GetIndexArg(std::string const& 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. | |
| mf.IssuePolicyWarning( | |
| cmPolicies::CMP0121, {}, | |
| cmStrCat("Invalid list index \""_s, arg, "\"."_s)); | |
| CM_FALLTHROUGH; | |
| } | |
| case cmPolicies::OLD: | |
| // OLD behavior is to allow compatibility, so just ignore the | |
| // situation. | |
| break; | |
| case cmPolicies::NEW: | |
| return false; | |
| } | |
| } | |
| // Truncation is happening here, but it had always been happening here. | |
| *idx = static_cast<int>(value); | |
| return true; | |
| } | |
| bool GetListString(std::string& listString, std::string const& var, | |
| cmMakefile const& makefile) | |
| { | |
| // get the old value | |
| cmValue cacheValue = makefile.GetDefinition(var); | |
| if (!cacheValue) { | |
| return false; | |
| } | |
| listString = *cacheValue; | |
| return true; | |
| } | |
| cm::optional<cmList> GetList(std::string const& var, | |
| cmMakefile const& 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; | |
| } | |
| 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; | |
| } | |
| std::string const& listName = args[1]; | |
| std::string const& 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; | |
| } | |
| std::string const& listName = args[1]; | |
| std::string const& 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; | |
| } | |
| std::string const& listName = args[1]; | |
| std::string const& 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; | |
| } | |
| std::string const& 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; | |
| } | |
| std::string const& listName = args[1]; | |
| std::string const& glue = args[2]; | |
| std::string const& 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; | |
| } | |
| std::string const& 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; | |
| } | |
| std::string const& 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; | |
| } | |
| std::string const& 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 std::string const&() 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(std::string const&, std::string const&)>> | |
| 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 }, | |
| { "APPLY", cmList::TransformAction::APPLY, 1 } }, | |
| [](std::string const& x, std::string const& y) { | |
| return x < y; | |
| } }; | |
| std::string const& 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); | |
| } | |
| std::string const REGEX{ "REGEX" }; | |
| std::string const AT{ "AT" }; | |
| std::string const FOR{ "FOR" }; | |
| std::string const PREDICATE{ "PREDICATE" }; | |
| std::string const 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 || | |
| args[index] == PREDICATE) && | |
| 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 (std::invalid_argument const&) { | |
| // 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 (std::invalid_argument const&) { | |
| // 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 (std::invalid_argument const&) { | |
| // 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; | |
| } | |
| // PREDICATE selector | |
| if (args[index] == PREDICATE) { | |
| if (args.size() == ++index) { | |
| status.SetError("sub-command TRANSFORM, selector PREDICATE expects " | |
| "'function name' argument."); | |
| return false; | |
| } | |
| selector = cmList::TransformSelector::NewPREDICATE( | |
| args[index], status.GetMakefile()); | |
| index += 1; | |
| 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; | |
| } | |
| if (!selector) { | |
| selector = cmList::TransformSelector::New(); | |
| } | |
| selector->Makefile = &status.GetMakefile(); | |
| if (descriptor->Action == cmList::TransformAction::APPLY) { | |
| list->transform(descriptor->Action, arguments.front(), | |
| status.GetMakefile(), std::move(selector)); | |
| } else { | |
| 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; | |
| bool hasComparator = false; | |
| size_t argumentIndex = 2; | |
| std::string const messageHint = "sub-command SORT "; | |
| while (argumentIndex < args.size()) { | |
| std::string const& option = args[argumentIndex++]; | |
| if (option == "COMPARE") { | |
| if (hasComparator) { | |
| status.SetError(cmStrCat(messageHint, | |
| "option \"COMPARE\" is incompatible " | |
| "with \"COMPARATOR\".")); | |
| return false; | |
| } | |
| 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 if (option == "COMPARATOR") { | |
| if (hasComparator) { | |
| status.SetError(cmStrCat(messageHint, "option \"", option, | |
| "\" has been specified multiple times.")); | |
| return false; | |
| } | |
| if (sortConfig.Compare != SortConfig::CompareMethod::DEFAULT) { | |
| status.SetError(cmStrCat(messageHint, | |
| "option \"COMPARATOR\" is incompatible " | |
| "with \"COMPARE\".")); | |
| return false; | |
| } | |
| if (argumentIndex < args.size()) { | |
| sortConfig.ComparatorFunction = args[argumentIndex++]; | |
| sortConfig.Compare = SortConfig::CompareMethod::COMPARATOR; | |
| hasComparator = true; | |
| } else { | |
| status.SetError(cmStrCat(messageHint, "missing argument for option \"", | |
| option, "\".")); | |
| return false; | |
| } | |
| } else { | |
| status.SetError( | |
| cmStrCat(messageHint, "option \"", option, "\" is unknown.")); | |
| return false; | |
| } | |
| } | |
| std::string const& listName = args[1]; | |
| // expand the variable | |
| auto list = GetList(listName, status.GetMakefile()); | |
| if (!list) { | |
| return true; | |
| } | |
| if (hasComparator) { | |
| try { | |
| status.GetMakefile().AddDefinition( | |
| listName, list->sort(sortConfig, status.GetMakefile()).to_string()); | |
| return true; | |
| } catch (std::invalid_argument& e) { | |
| status.SetError(e.what()); | |
| return false; | |
| } | |
| } | |
| 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; | |
| } | |
| std::string const& listName = args[1]; | |
| std::string const& 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; | |
| } | |
| std::string const& 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; | |
| } | |
| std::string const& 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; | |
| } | |
| std::string const& listName = args[1]; | |
| // expand the variable | |
| auto list = GetList(listName, status.GetMakefile()); | |
| if (!list) { | |
| return true; | |
| } | |
| std::string const& mode = args[3]; | |
| if (mode == "REGEX") { | |
| if (args.size() != 5) { | |
| status.SetError("sub-command FILTER, mode REGEX " | |
| "requires five arguments."); | |
| return false; | |
| } | |
| std::string const& 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; | |
| } | |
| } | |
| if (mode == "PREDICATE") { | |
| if (args.size() != 5) { | |
| status.SetError("sub-command FILTER, mode PREDICATE " | |
| "requires five arguments."); | |
| return false; | |
| } | |
| std::string const& functionName = args[4]; | |
| try { | |
| status.GetMakefile().AddDefinition( | |
| listName, | |
| list->filter(functionName, filterMode, status.GetMakefile()) | |
| .to_string()); | |
| return true; | |
| } catch (std::invalid_argument& e) { | |
| status.SetError(e.what()); | |
| return false; | |
| } | |
| } | |
| status.SetError("sub-command FILTER does not recognize mode " + mode); | |
| 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); | |
| } |