1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 // NOLINTNEXTLINE(bugprone-reserved-identifier)
4 #define _SCL_SECURE_NO_WARNINGS
6 #include "cmStringCommand.h"
12 #include <initializer_list>
18 #include <cm/iterator>
19 #include <cm/optional>
20 #include <cm/string_view>
21 #include <cmext/string_view>
23 #include <cm3p/json/reader.h>
24 #include <cm3p/json/value.h>
25 #include <cm3p/json/writer.h>
27 #include "cmsys/RegularExpression.hxx"
29 #include "cmCryptoHash.h"
30 #include "cmExecutionStatus.h"
31 #include "cmGeneratorExpression.h"
32 #include "cmMakefile.h"
33 #include "cmMessageType.h"
35 #include "cmStringAlgorithms.h"
36 #include "cmStringReplaceHelper.h"
37 #include "cmSubcommandTable.h"
38 #include "cmSystemTools.h"
39 #include "cmTimestamp.h"
45 bool RegexMatch(std::vector<std::string> const& args,
46 cmExecutionStatus& status);
47 bool RegexMatchAll(std::vector<std::string> const& args,
48 cmExecutionStatus& status);
49 bool RegexReplace(std::vector<std::string> const& args,
50 cmExecutionStatus& status);
52 bool joinImpl(std::vector<std::string> const& args, std::string const& glue,
53 size_t varIdx, cmMakefile& makefile);
55 bool HandleHashCommand(std::vector<std::string> const& args,
56 cmExecutionStatus& status)
58 if (args.size() != 3) {
60 cmStrCat(args[0], " requires an output variable and an input string"));
64 std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0]));
66 std::string out = hash->HashString(args[2]);
67 status.GetMakefile().AddDefinition(args[1], out);
73 bool HandleToUpperLowerCommand(std::vector<std::string> const& args,
74 bool toUpper, cmExecutionStatus& status)
76 if (args.size() < 3) {
77 status.SetError("no output variable specified");
81 std::string const& outvar = args[2];
85 output = cmSystemTools::UpperCase(args[1]);
87 output = cmSystemTools::LowerCase(args[1]);
90 // Store the output in the provided variable.
91 status.GetMakefile().AddDefinition(outvar, output);
95 bool HandleToUpperCommand(std::vector<std::string> const& args,
96 cmExecutionStatus& status)
98 return HandleToUpperLowerCommand(args, true, status);
101 bool HandleToLowerCommand(std::vector<std::string> const& args,
102 cmExecutionStatus& status)
104 return HandleToUpperLowerCommand(args, false, status);
107 bool HandleAsciiCommand(std::vector<std::string> const& args,
108 cmExecutionStatus& status)
110 if (args.size() < 3) {
111 status.SetError("No output variable specified");
114 std::string::size_type cc;
115 std::string const& outvar = args.back();
117 for (cc = 1; cc < args.size() - 1; cc++) {
118 int ch = atoi(args[cc].c_str());
119 if (ch > 0 && ch < 256) {
120 output += static_cast<char>(ch);
123 cmStrCat("Character with code ", args[cc], " does not exist.");
124 status.SetError(error);
128 // Store the output in the provided variable.
129 status.GetMakefile().AddDefinition(outvar, output);
133 bool HandleHexCommand(std::vector<std::string> const& args,
134 cmExecutionStatus& status)
136 if (args.size() != 3) {
137 status.SetError("Incorrect number of arguments");
140 auto const& instr = args[1];
141 auto const& outvar = args[2];
142 std::string output(instr.size() * 2, ' ');
144 std::string::size_type hexIndex = 0;
145 for (auto const& c : instr) {
146 snprintf(&output[hexIndex], 3, "%.2x",
147 static_cast<unsigned char>(c) & 0xFF);
151 status.GetMakefile().AddDefinition(outvar, output);
155 bool HandleConfigureCommand(std::vector<std::string> const& args,
156 cmExecutionStatus& status)
158 if (args.size() < 2) {
159 status.SetError("No input string specified.");
162 if (args.size() < 3) {
163 status.SetError("No output variable specified.");
168 bool escapeQuotes = false;
170 for (unsigned int i = 3; i < args.size(); ++i) {
171 if (args[i] == "@ONLY") {
173 } else if (args[i] == "ESCAPE_QUOTES") {
176 status.SetError(cmStrCat("Unrecognized argument \"", args[i], "\""));
181 // Configure the string.
183 status.GetMakefile().ConfigureString(args[1], output, atOnly, escapeQuotes);
185 // Store the output in the provided variable.
186 status.GetMakefile().AddDefinition(args[2], output);
191 bool HandleRegexCommand(std::vector<std::string> const& args,
192 cmExecutionStatus& status)
194 if (args.size() < 2) {
195 status.SetError("sub-command REGEX requires a mode to be specified.");
198 std::string const& mode = args[1];
199 if (mode == "MATCH") {
200 if (args.size() < 5) {
201 status.SetError("sub-command REGEX, mode MATCH needs "
202 "at least 5 arguments total to command.");
205 return RegexMatch(args, status);
207 if (mode == "MATCHALL") {
208 if (args.size() < 5) {
209 status.SetError("sub-command REGEX, mode MATCHALL needs "
210 "at least 5 arguments total to command.");
213 return RegexMatchAll(args, status);
215 if (mode == "REPLACE") {
216 if (args.size() < 6) {
217 status.SetError("sub-command REGEX, mode REPLACE needs "
218 "at least 6 arguments total to command.");
221 return RegexReplace(args, status);
224 std::string e = "sub-command REGEX does not recognize mode " + mode;
229 bool RegexMatch(std::vector<std::string> const& args,
230 cmExecutionStatus& status)
232 //"STRING(REGEX MATCH <regular_expression> <output variable>
233 // <input> [<input>...])\n";
234 std::string const& regex = args[2];
235 std::string const& outvar = args[3];
237 status.GetMakefile().ClearMatches();
238 // Compile the regular expression.
239 cmsys::RegularExpression re;
240 if (!re.compile(regex)) {
242 "sub-command REGEX, mode MATCH failed to compile regex \"" + regex +
248 // Concatenate all the last arguments together.
249 std::string input = cmJoin(cmMakeRange(args).advance(4), std::string());
251 // Scan through the input for all matches.
253 if (re.find(input)) {
254 status.GetMakefile().StoreMatches(re);
255 std::string::size_type l = re.start();
256 std::string::size_type r = re.end();
258 std::string e = "sub-command REGEX, mode MATCH regex \"" + regex +
259 "\" matched an empty string.";
263 output = input.substr(l, r - l);
266 // Store the output in the provided variable.
267 status.GetMakefile().AddDefinition(outvar, output);
271 bool RegexMatchAll(std::vector<std::string> const& args,
272 cmExecutionStatus& status)
274 //"STRING(REGEX MATCHALL <regular_expression> <output variable> <input>
276 std::string const& regex = args[2];
277 std::string const& outvar = args[3];
279 status.GetMakefile().ClearMatches();
280 // Compile the regular expression.
281 cmsys::RegularExpression re;
282 if (!re.compile(regex)) {
284 "sub-command REGEX, mode MATCHALL failed to compile regex \"" + regex +
290 // Concatenate all the last arguments together.
291 std::string input = cmJoin(cmMakeRange(args).advance(4), std::string());
293 // Scan through the input for all matches.
295 const char* p = input.c_str();
297 status.GetMakefile().ClearMatches();
298 status.GetMakefile().StoreMatches(re);
299 std::string::size_type l = re.start();
300 std::string::size_type r = re.end();
302 std::string e = "sub-command REGEX, mode MATCHALL regex \"" + regex +
303 "\" matched an empty string.";
307 if (!output.empty()) {
310 output += std::string(p + l, r - l);
314 // Store the output in the provided variable.
315 status.GetMakefile().AddDefinition(outvar, output);
319 bool RegexReplace(std::vector<std::string> const& args,
320 cmExecutionStatus& status)
322 //"STRING(REGEX REPLACE <regular_expression> <replace_expression>
323 // <output variable> <input> [<input>...])\n"
324 std::string const& regex = args[2];
325 std::string const& replace = args[3];
326 std::string const& outvar = args[4];
327 cmStringReplaceHelper replaceHelper(regex, replace, &status.GetMakefile());
329 if (!replaceHelper.IsReplaceExpressionValid()) {
331 "sub-command REGEX, mode REPLACE: " + replaceHelper.GetError() + ".");
335 status.GetMakefile().ClearMatches();
337 if (!replaceHelper.IsRegularExpressionValid()) {
339 "sub-command REGEX, mode REPLACE failed to compile regex \"" + regex +
345 // Concatenate all the last arguments together.
346 const std::string input =
347 cmJoin(cmMakeRange(args).advance(5), std::string());
350 if (!replaceHelper.Replace(input, output)) {
352 "sub-command REGEX, mode REPLACE: " + replaceHelper.GetError() + ".");
356 // Store the output in the provided variable.
357 status.GetMakefile().AddDefinition(outvar, output);
361 bool HandleFindCommand(std::vector<std::string> const& args,
362 cmExecutionStatus& status)
364 // check if all required parameters were passed
365 if (args.size() < 4 || args.size() > 5) {
366 status.SetError("sub-command FIND requires 3 or 4 parameters.");
370 // check if the reverse flag was set or not
371 bool reverseMode = false;
372 if (args.size() == 5 && args[4] == "REVERSE") {
376 // if we have 5 arguments the last one must be REVERSE
377 if (args.size() == 5 && args[4] != "REVERSE") {
378 status.SetError("sub-command FIND: unknown last parameter");
382 // local parameter names.
383 const std::string& sstring = args[1];
384 const std::string& schar = args[2];
385 const std::string& outvar = args[3];
387 // ensure that the user cannot accidentally specify REVERSE as a variable
388 if (outvar == "REVERSE") {
389 status.SetError("sub-command FIND does not allow one to select REVERSE as "
390 "the output variable. "
391 "Maybe you missed the actual output variable?");
395 // try to find the character and return its position
398 pos = sstring.find(schar);
400 pos = sstring.rfind(schar);
402 if (std::string::npos != pos) {
403 status.GetMakefile().AddDefinition(outvar, std::to_string(pos));
407 // the character was not found, but this is not really an error
408 status.GetMakefile().AddDefinition(outvar, "-1");
412 bool HandleCompareCommand(std::vector<std::string> const& args,
413 cmExecutionStatus& status)
415 if (args.size() < 2) {
416 status.SetError("sub-command COMPARE requires a mode to be specified.");
419 std::string const& mode = args[1];
420 if ((mode == "EQUAL") || (mode == "NOTEQUAL") || (mode == "LESS") ||
421 (mode == "LESS_EQUAL") || (mode == "GREATER") ||
422 (mode == "GREATER_EQUAL")) {
423 if (args.size() < 5) {
425 cmStrCat("sub-command COMPARE, mode ", mode,
426 " needs at least 5 arguments total to command.");
431 const std::string& left = args[2];
432 const std::string& right = args[3];
433 const std::string& outvar = args[4];
435 if (mode == "LESS") {
436 result = (left < right);
437 } else if (mode == "LESS_EQUAL") {
438 result = (left <= right);
439 } else if (mode == "GREATER") {
440 result = (left > right);
441 } else if (mode == "GREATER_EQUAL") {
442 result = (left >= right);
443 } else if (mode == "EQUAL") {
444 result = (left == right);
445 } else // if(mode == "NOTEQUAL")
447 result = !(left == right);
450 status.GetMakefile().AddDefinition(outvar, "1");
452 status.GetMakefile().AddDefinition(outvar, "0");
456 std::string e = "sub-command COMPARE does not recognize mode " + mode;
461 bool HandleReplaceCommand(std::vector<std::string> const& args,
462 cmExecutionStatus& status)
464 if (args.size() < 5) {
465 status.SetError("sub-command REPLACE requires at least four arguments.");
469 const std::string& matchExpression = args[1];
470 const std::string& replaceExpression = args[2];
471 const std::string& variableName = args[3];
473 std::string input = cmJoin(cmMakeRange(args).advance(4), std::string());
475 cmsys::SystemTools::ReplaceString(input, matchExpression.c_str(),
476 replaceExpression.c_str());
478 status.GetMakefile().AddDefinition(variableName, input);
482 bool HandleSubstringCommand(std::vector<std::string> const& args,
483 cmExecutionStatus& status)
485 if (args.size() != 5) {
486 status.SetError("sub-command SUBSTRING requires four arguments.");
490 const std::string& stringValue = args[1];
491 int begin = atoi(args[2].c_str());
492 int end = atoi(args[3].c_str());
493 const std::string& variableName = args[4];
495 size_t stringLength = stringValue.size();
496 int intStringLength = static_cast<int>(stringLength);
497 if (begin < 0 || begin > intStringLength) {
499 cmStrCat("begin index: ", begin, " is out of range 0 - ", stringLength));
503 status.SetError(cmStrCat("end index: ", end, " should be -1 or greater"));
507 status.GetMakefile().AddDefinition(variableName,
508 stringValue.substr(begin, end));
512 bool HandleLengthCommand(std::vector<std::string> const& args,
513 cmExecutionStatus& status)
515 if (args.size() != 3) {
516 status.SetError("sub-command LENGTH requires two arguments.");
520 const std::string& stringValue = args[1];
521 const std::string& variableName = args[2];
523 size_t length = stringValue.size();
525 snprintf(buffer, sizeof(buffer), "%d", static_cast<int>(length));
527 status.GetMakefile().AddDefinition(variableName, buffer);
531 bool HandleAppendCommand(std::vector<std::string> const& args,
532 cmExecutionStatus& status)
534 if (args.size() < 2) {
535 status.SetError("sub-command APPEND requires at least one argument.");
539 // Skip if nothing to append.
540 if (args.size() < 3) {
544 auto const& variableName = args[1];
546 cm::string_view oldView{ status.GetMakefile().GetSafeDefinition(
549 auto const newValue = cmJoin(cmMakeRange(args).advance(2), {}, oldView);
550 status.GetMakefile().AddDefinition(variableName, newValue);
555 bool HandlePrependCommand(std::vector<std::string> const& args,
556 cmExecutionStatus& status)
558 if (args.size() < 2) {
559 status.SetError("sub-command PREPEND requires at least one argument.");
563 // Skip if nothing to prepend.
564 if (args.size() < 3) {
568 const std::string& variable = args[1];
570 std::string value = cmJoin(cmMakeRange(args).advance(2), std::string());
571 cmValue oldValue = status.GetMakefile().GetDefinition(variable);
575 status.GetMakefile().AddDefinition(variable, value);
579 bool HandleConcatCommand(std::vector<std::string> const& args,
580 cmExecutionStatus& status)
582 if (args.size() < 2) {
583 status.SetError("sub-command CONCAT requires at least one argument.");
587 return joinImpl(args, std::string(), 1, status.GetMakefile());
590 bool HandleJoinCommand(std::vector<std::string> const& args,
591 cmExecutionStatus& status)
593 if (args.size() < 3) {
594 status.SetError("sub-command JOIN requires at least two arguments.");
598 return joinImpl(args, args[1], 2, status.GetMakefile());
601 bool joinImpl(std::vector<std::string> const& args, std::string const& glue,
602 const size_t varIdx, cmMakefile& makefile)
604 std::string const& variableName = args[varIdx];
605 // NOTE Items to concat/join placed right after the variable for
606 // both `CONCAT` and `JOIN` sub-commands.
607 std::string value = cmJoin(cmMakeRange(args).advance(varIdx + 1), glue);
609 makefile.AddDefinition(variableName, value);
613 bool HandleMakeCIdentifierCommand(std::vector<std::string> const& args,
614 cmExecutionStatus& status)
616 if (args.size() != 3) {
617 status.SetError("sub-command MAKE_C_IDENTIFIER requires two arguments.");
621 const std::string& input = args[1];
622 const std::string& variableName = args[2];
624 status.GetMakefile().AddDefinition(variableName,
625 cmSystemTools::MakeCidentifier(input));
629 bool HandleGenexStripCommand(std::vector<std::string> const& args,
630 cmExecutionStatus& status)
632 if (args.size() != 3) {
633 status.SetError("sub-command GENEX_STRIP requires two arguments.");
637 const std::string& input = args[1];
639 std::string result = cmGeneratorExpression::Preprocess(
640 input, cmGeneratorExpression::StripAllGeneratorExpressions);
642 const std::string& variableName = args[2];
644 status.GetMakefile().AddDefinition(variableName, result);
648 bool HandleStripCommand(std::vector<std::string> const& args,
649 cmExecutionStatus& status)
651 if (args.size() != 3) {
652 status.SetError("sub-command STRIP requires two arguments.");
656 const std::string& stringValue = args[1];
657 const std::string& variableName = args[2];
658 size_t inStringLength = stringValue.size();
659 size_t startPos = inStringLength + 1;
661 const char* ptr = stringValue.c_str();
663 for (cc = 0; cc < inStringLength; ++cc) {
664 if (!isspace(*ptr)) {
665 if (startPos > inStringLength) {
673 size_t outLength = 0;
675 // if the input string didn't contain any non-space characters, return
677 if (startPos > inStringLength) {
681 outLength = endPos - startPos + 1;
684 status.GetMakefile().AddDefinition(variableName,
685 stringValue.substr(startPos, outLength));
689 bool HandleRepeatCommand(std::vector<std::string> const& args,
690 cmExecutionStatus& status)
692 cmMakefile& makefile = status.GetMakefile();
694 // `string(REPEAT "<str>" <times> OUTPUT_VARIABLE)`
695 enum ArgPos : std::size_t
704 if (args.size() != ArgPos::TOTAL_ARGS) {
705 makefile.IssueMessage(MessageType::FATAL_ERROR,
706 "sub-command REPEAT requires three arguments.");
711 if (!cmStrToULong(args[ArgPos::TIMES], ×)) {
712 makefile.IssueMessage(MessageType::FATAL_ERROR,
713 "repeat count is not a positive number.");
717 const auto& stringValue = args[ArgPos::VALUE];
718 const auto& variableName = args[ArgPos::OUTPUT_VARIABLE];
719 const auto inStringLength = stringValue.size();
722 switch (inStringLength) {
724 // Nothing to do for zero length input strings
727 // NOTE If the string to repeat consists of the only character,
728 // use the appropriate constructor.
729 result = std::string(times, stringValue[0]);
732 result = std::string(inStringLength * times, char{});
733 for (auto i = 0u; i < times; ++i) {
734 std::copy(cm::cbegin(stringValue), cm::cend(stringValue),
735 &result[i * inStringLength]);
740 makefile.AddDefinition(variableName, result);
744 bool HandleRandomCommand(std::vector<std::string> const& args,
745 cmExecutionStatus& status)
747 if (args.size() < 2 || args.size() == 3 || args.size() == 5) {
748 status.SetError("sub-command RANDOM requires at least one argument.");
752 static bool seeded = false;
753 bool force_seed = false;
754 unsigned int seed = 0;
756 const char cmStringCommandDefaultAlphabet[] = "qwertyuiopasdfghjklzxcvbnm"
757 "QWERTYUIOPASDFGHJKLZXCVBNM"
759 std::string alphabet;
761 if (args.size() > 3) {
763 size_t stopAt = args.size() - 2;
765 for (; i < stopAt; ++i) {
766 if (args[i] == "LENGTH") {
768 length = atoi(args[i].c_str());
769 } else if (args[i] == "ALPHABET") {
772 } else if (args[i] == "RANDOM_SEED") {
774 seed = static_cast<unsigned int>(atoi(args[i].c_str()));
779 if (alphabet.empty()) {
780 alphabet = cmStringCommandDefaultAlphabet;
783 double sizeofAlphabet = static_cast<double>(alphabet.size());
784 if (sizeofAlphabet < 1) {
785 status.SetError("sub-command RANDOM invoked with bad alphabet.");
789 status.SetError("sub-command RANDOM invoked with bad length.");
792 const std::string& variableName = args.back();
794 std::vector<char> result;
796 if (!seeded || force_seed) {
798 srand(force_seed ? seed : cmSystemTools::RandomSeed());
801 const char* alphaPtr = alphabet.c_str();
802 for (int cc = 0; cc < length; cc++) {
803 int idx = static_cast<int>(sizeofAlphabet * rand() / (RAND_MAX + 1.0));
804 result.push_back(*(alphaPtr + idx));
808 status.GetMakefile().AddDefinition(variableName, result.data());
812 bool HandleTimestampCommand(std::vector<std::string> const& args,
813 cmExecutionStatus& status)
815 if (args.size() < 2) {
816 status.SetError("sub-command TIMESTAMP requires at least one argument.");
819 if (args.size() > 4) {
820 status.SetError("sub-command TIMESTAMP takes at most three arguments.");
824 unsigned int argsIndex = 1;
826 const std::string& outputVariable = args[argsIndex++];
828 std::string formatString;
829 if (args.size() > argsIndex && args[argsIndex] != "UTC") {
830 formatString = args[argsIndex++];
833 bool utcFlag = false;
834 if (args.size() > argsIndex) {
835 if (args[argsIndex] == "UTC") {
838 std::string e = " TIMESTAMP sub-command does not recognize option " +
839 args[argsIndex] + ".";
845 cmTimestamp timestamp;
846 std::string result = timestamp.CurrentTime(formatString, utcFlag);
847 status.GetMakefile().AddDefinition(outputVariable, result);
852 bool HandleUuidCommand(std::vector<std::string> const& args,
853 cmExecutionStatus& status)
855 #if !defined(CMAKE_BOOTSTRAP)
856 unsigned int argsIndex = 1;
858 if (args.size() < 2) {
859 status.SetError("UUID sub-command requires an output variable.");
863 const std::string& outputVariable = args[argsIndex++];
865 std::string uuidNamespaceString;
866 std::string uuidName;
867 std::string uuidType;
868 bool uuidUpperCase = false;
870 while (args.size() > argsIndex) {
871 if (args[argsIndex] == "NAMESPACE") {
873 if (argsIndex >= args.size()) {
874 status.SetError("UUID sub-command, NAMESPACE requires a value.");
877 uuidNamespaceString = args[argsIndex++];
878 } else if (args[argsIndex] == "NAME") {
880 if (argsIndex >= args.size()) {
881 status.SetError("UUID sub-command, NAME requires a value.");
884 uuidName = args[argsIndex++];
885 } else if (args[argsIndex] == "TYPE") {
887 if (argsIndex >= args.size()) {
888 status.SetError("UUID sub-command, TYPE requires a value.");
891 uuidType = args[argsIndex++];
892 } else if (args[argsIndex] == "UPPER") {
894 uuidUpperCase = true;
897 "UUID sub-command does not recognize option " + args[argsIndex] + ".";
904 cmUuid uuidGenerator;
906 std::vector<unsigned char> uuidNamespace;
907 if (!uuidGenerator.StringToBinary(uuidNamespaceString, uuidNamespace)) {
908 status.SetError("UUID sub-command, malformed NAMESPACE UUID.");
912 if (uuidType == "MD5") {
913 uuid = uuidGenerator.FromMd5(uuidNamespace, uuidName);
914 } else if (uuidType == "SHA1") {
915 uuid = uuidGenerator.FromSha1(uuidNamespace, uuidName);
917 std::string e = "UUID sub-command, unknown TYPE '" + uuidType + "'.";
923 status.SetError("UUID sub-command, generation failed.");
928 uuid = cmSystemTools::UpperCase(uuid);
931 status.GetMakefile().AddDefinition(outputVariable, uuid);
934 status.SetError(cmStrCat(args[0], " not available during bootstrap"));
939 #if !defined(CMAKE_BOOTSTRAP)
941 // Helpers for string(JSON ...)
942 struct Args : cmRange<typename std::vector<std::string>::const_iterator>
944 using cmRange<typename std::vector<std::string>::const_iterator>::cmRange;
946 auto PopFront(cm::string_view error) -> const std::string&;
947 auto PopBack(cm::string_view error) -> const std::string&;
950 class json_error : public std::runtime_error
953 json_error(std::initializer_list<cm::string_view> message,
954 cm::optional<Args> errorPath = cm::nullopt)
955 : std::runtime_error(cmCatViews(message))
957 std::move(errorPath) // NOLINT(performance-move-const-arg)
961 cm::optional<Args> ErrorPath;
964 const std::string& Args::PopFront(cm::string_view error)
967 throw json_error({ error });
969 const std::string& res = *this->begin();
974 const std::string& Args::PopBack(cm::string_view error)
977 throw json_error({ error });
979 const std::string& res = *(this->end() - 1);
984 cm::string_view JsonTypeToString(Json::ValueType type)
987 case Json::ValueType::nullValue:
989 case Json::ValueType::intValue:
990 case Json::ValueType::uintValue:
991 case Json::ValueType::realValue:
993 case Json::ValueType::stringValue:
995 case Json::ValueType::booleanValue:
997 case Json::ValueType::arrayValue:
999 case Json::ValueType::objectValue:
1002 throw json_error({ "invalid JSON type found"_s });
1006 const std::string& str, cm::optional<Args> const& progress = cm::nullopt,
1007 Json::ArrayIndex max = std::numeric_limits<Json::ArrayIndex>::max())
1009 unsigned long lindex;
1010 if (!cmStrToULong(str, &lindex)) {
1011 throw json_error({ "expected an array index, got: '"_s, str, "'"_s },
1014 Json::ArrayIndex index = static_cast<Json::ArrayIndex>(lindex);
1016 cmAlphaNum sizeStr{ max };
1017 throw json_error({ "expected an index less than "_s, sizeStr.View(),
1018 " got '"_s, str, "'"_s },
1024 Json::Value& ResolvePath(Json::Value& json, Args path)
1026 Json::Value* search = &json;
1028 for (auto curr = path.begin(); curr != path.end(); ++curr) {
1029 const std::string& field = *curr;
1030 Args progress{ path.begin(), curr + 1 };
1032 if (search->isArray()) {
1033 auto index = ParseIndex(field, progress, search->size());
1034 search = &(*search)[index];
1036 } else if (search->isObject()) {
1037 if (!search->isMember(field)) {
1038 const auto progressStr = cmJoin(progress, " "_s);
1039 throw json_error({ "member '"_s, progressStr, "' not found"_s },
1042 search = &(*search)[field];
1044 const auto progressStr = cmJoin(progress, " "_s);
1046 { "invalid path '"_s, progressStr,
1047 "', need element of OBJECT or ARRAY type to lookup '"_s, field,
1048 "' got "_s, JsonTypeToString(search->type()) },
1055 Json::Value ReadJson(const std::string& jsonstr)
1057 Json::CharReaderBuilder builder;
1058 builder["collectComments"] = false;
1059 auto jsonReader = std::unique_ptr<Json::CharReader>(builder.newCharReader());
1062 if (!jsonReader->parse(jsonstr.data(), jsonstr.data() + jsonstr.size(),
1064 throw json_error({ "failed parsing json string: "_s, error });
1068 std::string WriteJson(const Json::Value& value)
1070 Json::StreamWriterBuilder writer;
1071 writer["indentation"] = " ";
1072 writer["commentStyle"] = "None";
1073 return Json::writeString(writer, value);
1078 bool HandleJSONCommand(std::vector<std::string> const& arguments,
1079 cmExecutionStatus& status)
1081 #if !defined(CMAKE_BOOTSTRAP)
1083 auto& makefile = status.GetMakefile();
1084 Args args{ arguments.begin() + 1, arguments.end() };
1086 const std::string* errorVariable = nullptr;
1087 const std::string* outputVariable = nullptr;
1088 bool success = true;
1091 outputVariable = &args.PopFront("missing out-var argument"_s);
1093 if (!args.empty() && *args.begin() == "ERROR_VARIABLE"_s) {
1095 errorVariable = &args.PopFront("missing error-var argument"_s);
1096 makefile.AddDefinition(*errorVariable, "NOTFOUND"_s);
1099 const auto& mode = args.PopFront("missing mode argument"_s);
1100 if (mode != "GET"_s && mode != "TYPE"_s && mode != "MEMBER"_s &&
1101 mode != "LENGTH"_s && mode != "REMOVE"_s && mode != "SET"_s &&
1102 mode != "EQUAL"_s) {
1104 { "got an invalid mode '"_s, mode,
1105 "', expected one of GET, TYPE, MEMBER, LENGTH, REMOVE, SET, "
1109 const auto& jsonstr = args.PopFront("missing json string argument"_s);
1110 Json::Value json = ReadJson(jsonstr);
1112 if (mode == "GET"_s) {
1113 const auto& value = ResolvePath(json, args);
1114 if (value.isObject() || value.isArray()) {
1115 makefile.AddDefinition(*outputVariable, WriteJson(value));
1116 } else if (value.isBool()) {
1117 makefile.AddDefinitionBool(*outputVariable, value.asBool());
1119 makefile.AddDefinition(*outputVariable, value.asString());
1122 } else if (mode == "TYPE"_s) {
1123 const auto& value = ResolvePath(json, args);
1124 makefile.AddDefinition(*outputVariable, JsonTypeToString(value.type()));
1126 } else if (mode == "MEMBER"_s) {
1127 const auto& indexStr = args.PopBack("missing member index"_s);
1128 const auto& value = ResolvePath(json, args);
1129 if (!value.isObject()) {
1130 throw json_error({ "MEMBER needs to be called with an element of "
1131 "type OBJECT, got "_s,
1132 JsonTypeToString(value.type()) },
1135 const auto index = ParseIndex(
1136 indexStr, Args{ args.begin(), args.end() + 1 }, value.size());
1137 const auto memIt = std::next(value.begin(), index);
1138 makefile.AddDefinition(*outputVariable, memIt.name());
1140 } else if (mode == "LENGTH"_s) {
1141 const auto& value = ResolvePath(json, args);
1142 if (!value.isArray() && !value.isObject()) {
1143 throw json_error({ "LENGTH needs to be called with an "
1144 "element of type ARRAY or OBJECT, got "_s,
1145 JsonTypeToString(value.type()) },
1149 cmAlphaNum sizeStr{ value.size() };
1150 makefile.AddDefinition(*outputVariable, sizeStr.View());
1152 } else if (mode == "REMOVE"_s) {
1153 const auto& toRemove =
1154 args.PopBack("missing member or index to remove"_s);
1155 auto& value = ResolvePath(json, args);
1157 if (value.isArray()) {
1158 const auto index = ParseIndex(
1159 toRemove, Args{ args.begin(), args.end() + 1 }, value.size());
1160 Json::Value removed;
1161 value.removeIndex(index, &removed);
1163 } else if (value.isObject()) {
1164 Json::Value removed;
1165 value.removeMember(toRemove, &removed);
1168 throw json_error({ "REMOVE needs to be called with an "
1169 "element of type ARRAY or OBJECT, got "_s,
1170 JsonTypeToString(value.type()) },
1173 makefile.AddDefinition(*outputVariable, WriteJson(json));
1175 } else if (mode == "SET"_s) {
1176 const auto& newValueStr = args.PopBack("missing new value remove"_s);
1177 const auto& toAdd = args.PopBack("missing member name to add"_s);
1178 auto& value = ResolvePath(json, args);
1180 Json::Value newValue = ReadJson(newValueStr);
1181 if (value.isObject()) {
1182 value[toAdd] = newValue;
1183 } else if (value.isArray()) {
1185 ParseIndex(toAdd, Args{ args.begin(), args.end() + 1 });
1186 if (value.isValidIndex(index)) {
1187 value[static_cast<int>(index)] = newValue;
1189 value.append(newValue);
1192 throw json_error({ "SET needs to be called with an "
1193 "element of type OBJECT or ARRAY, got "_s,
1194 JsonTypeToString(value.type()) });
1197 makefile.AddDefinition(*outputVariable, WriteJson(json));
1199 } else if (mode == "EQUAL"_s) {
1200 const auto& jsonstr2 =
1201 args.PopFront("missing second json string argument"_s);
1202 Json::Value json2 = ReadJson(jsonstr2);
1203 makefile.AddDefinitionBool(*outputVariable, json == json2);
1206 } catch (const json_error& e) {
1207 if (outputVariable && e.ErrorPath) {
1208 const auto errorPath = cmJoin(*e.ErrorPath, "-");
1209 makefile.AddDefinition(*outputVariable,
1210 cmCatViews({ errorPath, "-NOTFOUND"_s }));
1211 } else if (outputVariable) {
1212 makefile.AddDefinition(*outputVariable, "NOTFOUND"_s);
1215 if (errorVariable) {
1216 makefile.AddDefinition(*errorVariable, e.what());
1218 status.SetError(cmCatViews({ "sub-command JSON "_s, e.what(), "."_s }));
1224 status.SetError(cmStrCat(arguments[0], " not available during bootstrap"_s));
1231 bool cmStringCommand(std::vector<std::string> const& args,
1232 cmExecutionStatus& status)
1235 status.SetError("must be called with at least one argument.");
1239 static cmSubcommandTable const subcommand{
1240 { "REGEX"_s, HandleRegexCommand },
1241 { "REPLACE"_s, HandleReplaceCommand },
1242 { "MD5"_s, HandleHashCommand },
1243 { "SHA1"_s, HandleHashCommand },
1244 { "SHA224"_s, HandleHashCommand },
1245 { "SHA256"_s, HandleHashCommand },
1246 { "SHA384"_s, HandleHashCommand },
1247 { "SHA512"_s, HandleHashCommand },
1248 { "SHA3_224"_s, HandleHashCommand },
1249 { "SHA3_256"_s, HandleHashCommand },
1250 { "SHA3_384"_s, HandleHashCommand },
1251 { "SHA3_512"_s, HandleHashCommand },
1252 { "TOLOWER"_s, HandleToLowerCommand },
1253 { "TOUPPER"_s, HandleToUpperCommand },
1254 { "COMPARE"_s, HandleCompareCommand },
1255 { "ASCII"_s, HandleAsciiCommand },
1256 { "HEX"_s, HandleHexCommand },
1257 { "CONFIGURE"_s, HandleConfigureCommand },
1258 { "LENGTH"_s, HandleLengthCommand },
1259 { "APPEND"_s, HandleAppendCommand },
1260 { "PREPEND"_s, HandlePrependCommand },
1261 { "CONCAT"_s, HandleConcatCommand },
1262 { "JOIN"_s, HandleJoinCommand },
1263 { "SUBSTRING"_s, HandleSubstringCommand },
1264 { "STRIP"_s, HandleStripCommand },
1265 { "REPEAT"_s, HandleRepeatCommand },
1266 { "RANDOM"_s, HandleRandomCommand },
1267 { "FIND"_s, HandleFindCommand },
1268 { "TIMESTAMP"_s, HandleTimestampCommand },
1269 { "MAKE_C_IDENTIFIER"_s, HandleMakeCIdentifierCommand },
1270 { "GENEX_STRIP"_s, HandleGenexStripCommand },
1271 { "UUID"_s, HandleUuidCommand },
1272 { "JSON"_s, HandleJSONCommand },
1275 return subcommand(args[0], args, status);