Merge branch 'release-3.29'
[kiteware-cmake.git] / Source / cmListCommand.cxx
blobebf58418c56e4db0e9fad49960853dd2eb8154f6
1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmListCommand.h"
5 #include <cassert>
6 #include <cstdio>
7 #include <functional>
8 #include <set>
9 #include <sstream>
10 #include <stdexcept>
11 #include <utility>
12 #include <vector>
14 #include <cm/memory>
15 #include <cm/optional>
16 #include <cmext/algorithm>
17 #include <cmext/string_view>
19 #include "cmExecutionStatus.h"
20 #include "cmList.h"
21 #include "cmMakefile.h"
22 #include "cmMessageType.h"
23 #include "cmPolicies.h"
24 #include "cmRange.h"
25 #include "cmStringAlgorithms.h"
26 #include "cmSubcommandTable.h"
27 #include "cmValue.h"
29 namespace {
31 bool GetIndexArg(const std::string& arg, int* idx, cmMakefile& mf)
33 long value;
34 if (!cmStrToLong(arg, &value)) {
35 switch (mf.GetPolicyStatus(cmPolicies::CMP0121)) {
36 case cmPolicies::WARN: {
37 // Default is to warn and use old behavior OLD behavior is to allow
38 // compatibility, so issue a warning and use the previous behavior.
39 std::string warn =
40 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0121),
41 " Invalid list index \"", arg, "\".");
42 mf.IssueMessage(MessageType::AUTHOR_WARNING, warn);
43 CM_FALLTHROUGH;
45 case cmPolicies::OLD:
46 // OLD behavior is to allow compatibility, so just ignore the
47 // situation.
48 break;
49 case cmPolicies::NEW:
50 return false;
51 case cmPolicies::REQUIRED_IF_USED:
52 case cmPolicies::REQUIRED_ALWAYS:
53 std::string msg =
54 cmStrCat(cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0121),
55 " Invalid list index \"", arg, "\".");
56 mf.IssueMessage(MessageType::FATAL_ERROR, msg);
57 break;
61 // Truncation is happening here, but it had always been happening here.
62 *idx = static_cast<int>(value);
64 return true;
67 bool GetListString(std::string& listString, const std::string& var,
68 const cmMakefile& makefile)
70 // get the old value
71 cmValue cacheValue = makefile.GetDefinition(var);
72 if (!cacheValue) {
73 return false;
75 listString = *cacheValue;
76 return true;
79 cm::optional<cmList> GetList(const std::string& var,
80 const cmMakefile& makefile)
82 cm::optional<cmList> list;
84 std::string listString;
85 if (!GetListString(listString, var, makefile)) {
86 return list;
88 // if the size of the list
89 if (listString.empty()) {
90 list.emplace();
91 return list;
93 // expand the variable into a list
94 list.emplace(listString, cmList::EmptyElements::Yes);
95 // if no empty elements then just return
96 if (!cm::contains(*list, std::string())) {
97 return list;
99 // if we have empty elements we need to check policy CMP0007
100 switch (makefile.GetPolicyStatus(cmPolicies::CMP0007)) {
101 case cmPolicies::WARN: {
102 // Default is to warn and use old behavior
103 // OLD behavior is to allow compatibility, so recall
104 // ExpandListArgument without the true which will remove
105 // empty values
106 list->assign(listString);
107 std::string warn =
108 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0007),
109 " List has value = [", listString, "].");
110 makefile.IssueMessage(MessageType::AUTHOR_WARNING, warn);
111 return list;
113 case cmPolicies::OLD:
114 // OLD behavior is to allow compatibility, so recall
115 // ExpandListArgument without the true which will remove
116 // empty values
117 list->assign(listString);
118 return list;
119 case cmPolicies::NEW:
120 return list;
121 case cmPolicies::REQUIRED_IF_USED:
122 case cmPolicies::REQUIRED_ALWAYS:
123 makefile.IssueMessage(
124 MessageType::FATAL_ERROR,
125 cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0007));
126 return {};
128 return list;
131 bool HandleLengthCommand(std::vector<std::string> const& args,
132 cmExecutionStatus& status)
134 if (args.size() != 3) {
135 status.SetError("sub-command LENGTH requires two arguments.");
136 return false;
139 const std::string& listName = args[1];
140 const std::string& variableName = args.back();
142 auto list = GetList(listName, status.GetMakefile());
143 status.GetMakefile().AddDefinition(variableName,
144 std::to_string(list ? list->size() : 0));
146 return true;
149 bool HandleGetCommand(std::vector<std::string> const& args,
150 cmExecutionStatus& status)
152 if (args.size() < 4) {
153 status.SetError("sub-command GET requires at least three arguments.");
154 return false;
157 const std::string& listName = args[1];
158 const std::string& variableName = args.back();
159 // expand the variable
160 auto list = GetList(listName, status.GetMakefile());
161 if (!list) {
162 status.GetMakefile().AddDefinition(variableName, "NOTFOUND");
163 return true;
165 // FIXME: Add policy to make non-existing lists an error like empty lists.
166 if (list->empty()) {
167 status.SetError("GET given empty list");
168 return false;
171 std::vector<int> indexes;
172 for (std::size_t cc = 2; cc < args.size() - 1; cc++) {
173 int index;
174 if (!GetIndexArg(args[cc], &index, status.GetMakefile())) {
175 status.SetError(cmStrCat("index: ", args[cc], " is not a valid index"));
176 return false;
178 indexes.push_back(index);
181 try {
182 auto values = list->get_items(indexes.begin(), indexes.end());
183 status.GetMakefile().AddDefinition(variableName, values.to_string());
184 return true;
185 } catch (std::out_of_range& e) {
186 status.SetError(e.what());
187 return false;
191 bool HandleAppendCommand(std::vector<std::string> const& args,
192 cmExecutionStatus& status)
194 assert(args.size() >= 2);
196 // Skip if nothing to append.
197 if (args.size() < 3) {
198 return true;
201 cmMakefile& makefile = status.GetMakefile();
202 std::string const& listName = args[1];
203 // expand the variable
204 std::string listString;
205 GetListString(listString, listName, makefile);
207 makefile.AddDefinition(
208 listName, cmList::append(listString, args.begin() + 2, args.end()));
209 return true;
212 bool HandlePrependCommand(std::vector<std::string> const& args,
213 cmExecutionStatus& status)
215 assert(args.size() >= 2);
217 // Skip if nothing to prepend.
218 if (args.size() < 3) {
219 return true;
222 cmMakefile& makefile = status.GetMakefile();
223 std::string const& listName = args[1];
224 // expand the variable
225 std::string listString;
226 GetListString(listString, listName, makefile);
228 makefile.AddDefinition(
229 listName, cmList::prepend(listString, args.begin() + 2, args.end()));
230 return true;
233 bool HandlePopBackCommand(std::vector<std::string> const& args,
234 cmExecutionStatus& status)
236 assert(args.size() >= 2);
238 cmMakefile& makefile = status.GetMakefile();
239 auto ai = args.cbegin();
240 ++ai; // Skip subcommand name
241 std::string const& listName = *ai++;
242 auto list = GetList(listName, makefile);
244 if (!list) {
245 // Can't get the list definition... undefine any vars given after.
246 for (; ai != args.cend(); ++ai) {
247 makefile.RemoveDefinition(*ai);
249 return true;
252 if (!list->empty()) {
253 if (ai == args.cend()) {
254 // No variables are given... Just remove one element.
255 list->pop_back();
256 } else {
257 // Ok, assign elements to be removed to the given variables
258 for (; !list->empty() && ai != args.cend(); ++ai) {
259 assert(!ai->empty());
260 makefile.AddDefinition(*ai, list->back());
261 list->pop_back();
263 // Undefine the rest variables if the list gets empty earlier...
264 for (; ai != args.cend(); ++ai) {
265 makefile.RemoveDefinition(*ai);
269 makefile.AddDefinition(listName, list->to_string());
271 } else if (ai !=
272 args.cend()) { // The list is empty, but some args were given
273 // Need to *undefine* 'em all, cuz there are no items to assign...
274 for (; ai != args.cend(); ++ai) {
275 makefile.RemoveDefinition(*ai);
279 return true;
282 bool HandlePopFrontCommand(std::vector<std::string> const& args,
283 cmExecutionStatus& status)
285 assert(args.size() >= 2);
287 cmMakefile& makefile = status.GetMakefile();
288 auto ai = args.cbegin();
289 ++ai; // Skip subcommand name
290 std::string const& listName = *ai++;
291 auto list = GetList(listName, makefile);
293 if (!list) {
294 // Can't get the list definition... undefine any vars given after.
295 for (; ai != args.cend(); ++ai) {
296 makefile.RemoveDefinition(*ai);
298 return true;
301 if (!list->empty()) {
302 if (ai == args.cend()) {
303 // No variables are given... Just remove one element.
304 list->pop_front();
305 } else {
306 // Ok, assign elements to be removed to the given variables
307 auto vi = list->begin();
308 for (; vi != list->end() && ai != args.cend(); ++ai, ++vi) {
309 assert(!ai->empty());
310 makefile.AddDefinition(*ai, *vi);
312 list->erase(list->begin(), vi);
313 // Undefine the rest variables if the list gets empty earlier...
314 for (; ai != args.cend(); ++ai) {
315 makefile.RemoveDefinition(*ai);
319 makefile.AddDefinition(listName, list->to_string());
321 } else if (ai !=
322 args.cend()) { // The list is empty, but some args were given
323 // Need to *undefine* 'em all, cuz there are no items to assign...
324 for (; ai != args.cend(); ++ai) {
325 makefile.RemoveDefinition(*ai);
329 return true;
332 bool HandleFindCommand(std::vector<std::string> const& args,
333 cmExecutionStatus& status)
335 if (args.size() != 4) {
336 status.SetError("sub-command FIND requires three arguments.");
337 return false;
340 const std::string& listName = args[1];
341 const std::string& variableName = args.back();
342 // expand the variable
343 auto list = GetList(listName, status.GetMakefile());
345 if (!list) {
346 status.GetMakefile().AddDefinition(variableName, "-1");
347 return true;
350 auto index = list->find(args[2]);
351 status.GetMakefile().AddDefinition(
352 variableName, index == cmList::npos ? "-1" : std::to_string(index));
353 return true;
356 bool HandleInsertCommand(std::vector<std::string> const& args,
357 cmExecutionStatus& status)
359 if (args.size() < 4) {
360 status.SetError("sub-command INSERT requires at least three arguments.");
361 return false;
364 const std::string& listName = args[1];
366 // expand the variable
367 int index;
368 if (!GetIndexArg(args[2], &index, status.GetMakefile())) {
369 status.SetError(cmStrCat("index: ", args[2], " is not a valid index"));
370 return false;
372 auto list = GetList(listName, status.GetMakefile());
373 if (!list) {
374 list = cmList{};
377 try {
378 list->insert_items(index, args.begin() + 3, args.end(),
379 cmList::ExpandElements::No, cmList::EmptyElements::Yes);
380 status.GetMakefile().AddDefinition(listName, list->to_string());
381 return true;
382 } catch (std::out_of_range& e) {
383 status.SetError(e.what());
384 return false;
388 bool HandleJoinCommand(std::vector<std::string> const& args,
389 cmExecutionStatus& status)
391 if (args.size() != 4) {
392 status.SetError(cmStrCat("sub-command JOIN requires three arguments (",
393 args.size() - 1, " found)."));
394 return false;
397 const std::string& listName = args[1];
398 const std::string& glue = args[2];
399 const std::string& variableName = args[3];
401 // expand the variable
402 auto list = GetList(listName, status.GetMakefile());
404 if (!list) {
405 status.GetMakefile().AddDefinition(variableName, "");
406 return true;
409 status.GetMakefile().AddDefinition(variableName, list->join(glue));
410 return true;
413 bool HandleRemoveItemCommand(std::vector<std::string> const& args,
414 cmExecutionStatus& status)
416 assert(args.size() >= 2);
418 if (args.size() == 2) {
419 return true;
422 const std::string& listName = args[1];
423 // expand the variable
424 auto list = GetList(listName, status.GetMakefile());
426 if (!list) {
427 return true;
430 status.GetMakefile().AddDefinition(
431 listName, list->remove_items(args.begin() + 2, args.end()).to_string());
432 return true;
435 bool HandleReverseCommand(std::vector<std::string> const& args,
436 cmExecutionStatus& status)
438 assert(args.size() >= 2);
439 if (args.size() > 2) {
440 status.SetError("sub-command REVERSE only takes one argument.");
441 return false;
444 const std::string& listName = args[1];
445 // expand the variable
446 auto list = GetList(listName, status.GetMakefile());
448 if (!list) {
449 return true;
452 status.GetMakefile().AddDefinition(listName, list->reverse().to_string());
453 return true;
456 bool HandleRemoveDuplicatesCommand(std::vector<std::string> const& args,
457 cmExecutionStatus& status)
459 assert(args.size() >= 2);
460 if (args.size() > 2) {
461 status.SetError("sub-command REMOVE_DUPLICATES only takes one argument.");
462 return false;
465 const std::string& listName = args[1];
466 // expand the variable
467 auto list = GetList(listName, status.GetMakefile());
469 if (!list) {
470 return true;
473 status.GetMakefile().AddDefinition(listName,
474 list->remove_duplicates().to_string());
475 return true;
478 bool HandleTransformCommand(std::vector<std::string> const& args,
479 cmExecutionStatus& status)
481 if (args.size() < 3) {
482 status.SetError(
483 "sub-command TRANSFORM requires an action to be specified.");
484 return false;
487 // Descriptor of action
488 // Action: enum value identifying action
489 // Arity: number of arguments required for the action
490 struct ActionDescriptor
492 ActionDescriptor(std::string name)
493 : Name(std::move(name))
496 ActionDescriptor(std::string name, cmList::TransformAction action,
497 int arity)
498 : Name(std::move(name))
499 , Action(action)
500 , Arity(arity)
504 operator const std::string&() const { return this->Name; }
506 std::string Name;
507 cmList::TransformAction Action;
508 int Arity = 0;
511 // Build a set of supported actions.
512 std::set<ActionDescriptor,
513 std::function<bool(const std::string&, const std::string&)>>
514 descriptors{ { { "APPEND", cmList::TransformAction::APPEND, 1 },
515 { "PREPEND", cmList::TransformAction::PREPEND, 1 },
516 { "TOUPPER", cmList::TransformAction::TOUPPER, 0 },
517 { "TOLOWER", cmList::TransformAction::TOLOWER, 0 },
518 { "STRIP", cmList::TransformAction::STRIP, 0 },
519 { "GENEX_STRIP", cmList::TransformAction::GENEX_STRIP, 0 },
520 { "REPLACE", cmList::TransformAction::REPLACE, 2 } },
521 [](const std::string& x, const std::string& y) {
522 return x < y;
523 } };
525 const std::string& listName = args[1];
527 // Parse all possible function parameters
528 using size_type = std::vector<std::string>::size_type;
529 size_type index = 2;
531 auto descriptor = descriptors.find(args[index]);
533 if (descriptor == descriptors.end()) {
534 status.SetError(
535 cmStrCat(" sub-command TRANSFORM, ", args[index], " invalid action."));
536 return false;
539 // Action arguments
540 index += 1;
541 if (args.size() < index + descriptor->Arity) {
542 status.SetError(cmStrCat("sub-command TRANSFORM, action ",
543 descriptor->Name, " expects ", descriptor->Arity,
544 " argument(s)."));
545 return false;
548 std::vector<std::string> arguments;
549 index += descriptor->Arity;
550 if (descriptor->Arity > 0) {
551 arguments =
552 std::vector<std::string>(args.begin() + 3, args.begin() + index);
555 const std::string REGEX{ "REGEX" };
556 const std::string AT{ "AT" };
557 const std::string FOR{ "FOR" };
558 const std::string OUTPUT_VARIABLE{ "OUTPUT_VARIABLE" };
559 std::unique_ptr<cmList::TransformSelector> selector;
560 std::string outputName = listName;
562 try {
563 // handle optional arguments
564 while (args.size() > index) {
565 if ((args[index] == REGEX || args[index] == AT || args[index] == FOR) &&
566 selector) {
567 status.SetError(
568 cmStrCat("sub-command TRANSFORM, selector already specified (",
569 selector->GetTag(), ")."));
571 return false;
574 // REGEX selector
575 if (args[index] == REGEX) {
576 if (args.size() == ++index) {
577 status.SetError("sub-command TRANSFORM, selector REGEX expects "
578 "'regular expression' argument.");
579 return false;
582 selector =
583 cmList::TransformSelector::New<cmList::TransformSelector::REGEX>(
584 args[index]);
586 index += 1;
587 continue;
590 // AT selector
591 if (args[index] == AT) {
592 // get all specified indexes
593 std::vector<cmList::index_type> indexes;
594 while (args.size() > ++index) {
595 std::size_t pos;
596 int value;
598 try {
599 value = std::stoi(args[index], &pos);
600 if (pos != args[index].length()) {
601 // this is not a number, stop processing
602 break;
604 indexes.push_back(value);
605 } catch (const std::invalid_argument&) {
606 // this is not a number, stop processing
607 break;
611 if (indexes.empty()) {
612 status.SetError(
613 "sub-command TRANSFORM, selector AT expects at least one "
614 "numeric value.");
615 return false;
618 selector =
619 cmList::TransformSelector::New<cmList::TransformSelector::AT>(
620 std::move(indexes));
622 continue;
625 // FOR selector
626 if (args[index] == FOR) {
627 if (args.size() <= ++index + 1) {
628 status.SetError(
629 "sub-command TRANSFORM, selector FOR expects, at least,"
630 " two arguments.");
631 return false;
634 cmList::index_type start = 0;
635 cmList::index_type stop = 0;
636 cmList::index_type step = 1;
637 bool valid = true;
638 try {
639 std::size_t pos;
641 start = std::stoi(args[index], &pos);
642 if (pos != args[index].length()) {
643 // this is not a number
644 valid = false;
645 } else {
646 stop = std::stoi(args[++index], &pos);
647 if (pos != args[index].length()) {
648 // this is not a number
649 valid = false;
652 } catch (const std::invalid_argument&) {
653 // this is not numbers
654 valid = false;
656 if (!valid) {
657 status.SetError("sub-command TRANSFORM, selector FOR expects, "
658 "at least, two numeric values.");
659 return false;
661 // try to read a third numeric value for step
662 if (args.size() > ++index) {
663 try {
664 std::size_t pos;
666 step = std::stoi(args[index], &pos);
667 if (pos != args[index].length()) {
668 // this is not a number
669 step = 1;
670 } else {
671 index += 1;
673 } catch (const std::invalid_argument&) {
674 // this is not number, ignore exception
678 if (step <= 0) {
679 status.SetError("sub-command TRANSFORM, selector FOR expects "
680 "positive numeric value for <step>.");
681 return false;
684 selector =
685 cmList::TransformSelector::New<cmList::TransformSelector::FOR>(
686 { start, stop, step });
688 continue;
691 // output variable
692 if (args[index] == OUTPUT_VARIABLE) {
693 if (args.size() == ++index) {
694 status.SetError("sub-command TRANSFORM, OUTPUT_VARIABLE "
695 "expects variable name argument.");
696 return false;
699 outputName = args[index++];
700 continue;
703 status.SetError(cmStrCat("sub-command TRANSFORM, '",
704 cmJoin(cmMakeRange(args).advance(index), " "),
705 "': unexpected argument(s)."));
706 return false;
709 // expand the list variable
710 auto list = GetList(listName, status.GetMakefile());
712 if (!list) {
713 status.GetMakefile().AddDefinition(outputName, "");
714 return true;
717 list->transform(descriptor->Action, arguments, std::move(selector));
718 status.GetMakefile().AddDefinition(outputName, list->to_string());
719 return true;
720 } catch (cmList::transform_error& e) {
721 status.SetError(e.what());
722 return false;
726 bool HandleSortCommand(std::vector<std::string> const& args,
727 cmExecutionStatus& status)
729 assert(args.size() >= 2);
730 if (args.size() > 8) {
731 status.SetError("sub-command SORT only takes up to six arguments.");
732 return false;
735 using SortConfig = cmList::SortConfiguration;
736 SortConfig sortConfig;
738 size_t argumentIndex = 2;
739 const std::string messageHint = "sub-command SORT ";
741 while (argumentIndex < args.size()) {
742 std::string const& option = args[argumentIndex++];
743 if (option == "COMPARE") {
744 if (sortConfig.Compare != SortConfig::CompareMethod::DEFAULT) {
745 std::string error = cmStrCat(messageHint, "option \"", option,
746 "\" has been specified multiple times.");
747 status.SetError(error);
748 return false;
750 if (argumentIndex < args.size()) {
751 std::string const& argument = args[argumentIndex++];
752 if (argument == "STRING") {
753 sortConfig.Compare = SortConfig::CompareMethod::STRING;
754 } else if (argument == "FILE_BASENAME") {
755 sortConfig.Compare = SortConfig::CompareMethod::FILE_BASENAME;
756 } else if (argument == "NATURAL") {
757 sortConfig.Compare = SortConfig::CompareMethod::NATURAL;
758 } else {
759 std::string error =
760 cmStrCat(messageHint, "value \"", argument, "\" for option \"",
761 option, "\" is invalid.");
762 status.SetError(error);
763 return false;
765 } else {
766 status.SetError(cmStrCat(messageHint, "missing argument for option \"",
767 option, "\"."));
768 return false;
770 } else if (option == "CASE") {
771 if (sortConfig.Case != SortConfig::CaseSensitivity::DEFAULT) {
772 status.SetError(cmStrCat(messageHint, "option \"", option,
773 "\" has been specified multiple times."));
774 return false;
776 if (argumentIndex < args.size()) {
777 std::string const& argument = args[argumentIndex++];
778 if (argument == "SENSITIVE") {
779 sortConfig.Case = SortConfig::CaseSensitivity::SENSITIVE;
780 } else if (argument == "INSENSITIVE") {
781 sortConfig.Case = SortConfig::CaseSensitivity::INSENSITIVE;
782 } else {
783 status.SetError(cmStrCat(messageHint, "value \"", argument,
784 "\" for option \"", option,
785 "\" is invalid."));
786 return false;
788 } else {
789 status.SetError(cmStrCat(messageHint, "missing argument for option \"",
790 option, "\"."));
791 return false;
793 } else if (option == "ORDER") {
795 if (sortConfig.Order != SortConfig::OrderMode::DEFAULT) {
796 status.SetError(cmStrCat(messageHint, "option \"", option,
797 "\" has been specified multiple times."));
798 return false;
800 if (argumentIndex < args.size()) {
801 std::string const& argument = args[argumentIndex++];
802 if (argument == "ASCENDING") {
803 sortConfig.Order = SortConfig::OrderMode::ASCENDING;
804 } else if (argument == "DESCENDING") {
805 sortConfig.Order = SortConfig::OrderMode::DESCENDING;
806 } else {
807 status.SetError(cmStrCat(messageHint, "value \"", argument,
808 "\" for option \"", option,
809 "\" is invalid."));
810 return false;
812 } else {
813 status.SetError(cmStrCat(messageHint, "missing argument for option \"",
814 option, "\"."));
815 return false;
817 } else {
818 status.SetError(
819 cmStrCat(messageHint, "option \"", option, "\" is unknown."));
820 return false;
824 const std::string& listName = args[1];
825 // expand the variable
826 auto list = GetList(listName, status.GetMakefile());
828 if (!list) {
829 return true;
832 status.GetMakefile().AddDefinition(listName,
833 list->sort(sortConfig).to_string());
834 return true;
837 bool HandleSublistCommand(std::vector<std::string> const& args,
838 cmExecutionStatus& status)
840 if (args.size() != 5) {
841 status.SetError(cmStrCat("sub-command SUBLIST requires four arguments (",
842 args.size() - 1, " found)."));
843 return false;
846 const std::string& listName = args[1];
847 const std::string& variableName = args.back();
849 // expand the variable
850 auto list = GetList(listName, status.GetMakefile());
852 if (!list || list->empty()) {
853 status.GetMakefile().AddDefinition(variableName, "");
854 return true;
857 int start;
858 int length;
859 if (!GetIndexArg(args[2], &start, status.GetMakefile())) {
860 status.SetError(cmStrCat("index: ", args[2], " is not a valid index"));
861 return false;
863 if (!GetIndexArg(args[3], &length, status.GetMakefile())) {
864 status.SetError(cmStrCat("index: ", args[3], " is not a valid index"));
865 return false;
868 if (start < 0) {
869 status.SetError(cmStrCat("begin index: ", start, " is out of range 0 - ",
870 list->size() - 1));
871 return false;
873 if (length < -1) {
874 status.SetError(cmStrCat("length: ", length, " should be -1 or greater"));
875 return false;
878 using size_type = cmList::size_type;
880 try {
881 auto sublist = list->sublist(static_cast<size_type>(start),
882 static_cast<size_type>(length));
883 status.GetMakefile().AddDefinition(variableName, sublist.to_string());
884 return true;
885 } catch (std::out_of_range& e) {
886 status.SetError(e.what());
887 return false;
891 bool HandleRemoveAtCommand(std::vector<std::string> const& args,
892 cmExecutionStatus& status)
894 if (args.size() < 3) {
895 status.SetError("sub-command REMOVE_AT requires at least "
896 "two arguments.");
897 return false;
900 const std::string& listName = args[1];
901 // expand the variable
902 auto list = GetList(listName, status.GetMakefile());
904 if (!list || list->empty()) {
905 std::ostringstream str;
906 str << "index: ";
907 for (size_t i = 1; i < args.size(); ++i) {
908 str << args[i];
909 if (i != args.size() - 1) {
910 str << ", ";
913 str << " out of range (0, 0)";
914 status.SetError(str.str());
915 return false;
918 size_t cc;
919 std::vector<cmList::index_type> removed;
920 for (cc = 2; cc < args.size(); ++cc) {
921 int index;
922 if (!GetIndexArg(args[cc], &index, status.GetMakefile())) {
923 status.SetError(cmStrCat("index: ", args[cc], " is not a valid index"));
924 return false;
926 removed.push_back(index);
929 try {
930 status.GetMakefile().AddDefinition(
931 listName,
932 list->remove_items(removed.begin(), removed.end()).to_string());
933 return true;
934 } catch (std::out_of_range& e) {
935 status.SetError(e.what());
936 return false;
940 bool HandleFilterCommand(std::vector<std::string> const& args,
941 cmExecutionStatus& status)
943 if (args.size() < 2) {
944 status.SetError("sub-command FILTER requires a list to be specified.");
945 return false;
948 if (args.size() < 3) {
949 status.SetError(
950 "sub-command FILTER requires an operator to be specified.");
951 return false;
954 if (args.size() < 4) {
955 status.SetError("sub-command FILTER requires a mode to be specified.");
956 return false;
959 const std::string& op = args[2];
960 cmList::FilterMode filterMode;
961 if (op == "INCLUDE") {
962 filterMode = cmList::FilterMode::INCLUDE;
963 } else if (op == "EXCLUDE") {
964 filterMode = cmList::FilterMode::EXCLUDE;
965 } else {
966 status.SetError("sub-command FILTER does not recognize operator " + op);
967 return false;
970 const std::string& listName = args[1];
971 // expand the variable
972 auto list = GetList(listName, status.GetMakefile());
974 if (!list) {
975 return true;
978 const std::string& mode = args[3];
979 if (mode != "REGEX") {
980 status.SetError("sub-command FILTER does not recognize mode " + mode);
981 return false;
983 if (args.size() != 5) {
984 status.SetError("sub-command FILTER, mode REGEX "
985 "requires five arguments.");
986 return false;
988 const std::string& pattern = args[4];
990 try {
991 status.GetMakefile().AddDefinition(
992 listName, list->filter(pattern, filterMode).to_string());
993 return true;
994 } catch (std::invalid_argument& e) {
995 status.SetError(e.what());
996 return false;
999 } // namespace
1001 bool cmListCommand(std::vector<std::string> const& args,
1002 cmExecutionStatus& status)
1004 if (args.size() < 2) {
1005 status.SetError("must be called with at least two arguments.");
1006 return false;
1009 static cmSubcommandTable const subcommand{
1010 { "LENGTH"_s, HandleLengthCommand },
1011 { "GET"_s, HandleGetCommand },
1012 { "APPEND"_s, HandleAppendCommand },
1013 { "PREPEND"_s, HandlePrependCommand },
1014 { "POP_BACK"_s, HandlePopBackCommand },
1015 { "POP_FRONT"_s, HandlePopFrontCommand },
1016 { "FIND"_s, HandleFindCommand },
1017 { "INSERT"_s, HandleInsertCommand },
1018 { "JOIN"_s, HandleJoinCommand },
1019 { "REMOVE_AT"_s, HandleRemoveAtCommand },
1020 { "REMOVE_ITEM"_s, HandleRemoveItemCommand },
1021 { "REMOVE_DUPLICATES"_s, HandleRemoveDuplicatesCommand },
1022 { "TRANSFORM"_s, HandleTransformCommand },
1023 { "SORT"_s, HandleSortCommand },
1024 { "SUBLIST"_s, HandleSublistCommand },
1025 { "REVERSE"_s, HandleReverseCommand },
1026 { "FILTER"_s, HandleFilterCommand },
1029 return subcommand(args[0], args, status);