Coverage Report

Created: 2026-04-29 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmStringCommand.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
// NOLINTNEXTLINE(bugprone-reserved-identifier)
4
#define _SCL_SECURE_NO_WARNINGS
5
6
#include "cmStringCommand.h"
7
8
#include <algorithm>
9
#include <cstdio>
10
#include <cstdlib>
11
#include <exception>
12
#include <limits>
13
#include <memory>
14
#include <stdexcept>
15
#include <utility>
16
17
#include <cm/iterator>
18
#include <cm/optional>
19
#include <cm/string_view>
20
#include <cmext/string_view>
21
22
#include <cm3p/json/reader.h>
23
#include <cm3p/json/value.h>
24
#include <cm3p/json/writer.h>
25
26
#include "cmCMakeString.hxx"
27
#include "cmExecutionStatus.h"
28
#include "cmList.h"
29
#include "cmMakefile.h"
30
#include "cmMessageType.h"
31
#include "cmRange.h"
32
#include "cmStringAlgorithms.h"
33
#include "cmSubcommandTable.h"
34
35
namespace {
36
37
bool RegexMatch(std::vector<std::string> const& args,
38
                cmExecutionStatus& status);
39
bool RegexMatchAll(std::vector<std::string> const& args,
40
                   cmExecutionStatus& status);
41
bool RegexReplace(std::vector<std::string> const& args,
42
                  cmExecutionStatus& status);
43
bool RegexQuote(std::vector<std::string> const& args,
44
                cmExecutionStatus& status);
45
46
bool joinImpl(std::vector<std::string> const& args, std::string const& glue,
47
              size_t varIdx, cmMakefile& makefile);
48
49
bool HandleHashCommand(std::vector<std::string> const& args,
50
                       cmExecutionStatus& status)
51
0
{
52
0
  if (args.size() != 3) {
53
0
    status.SetError(
54
0
      cmStrCat(args[0], " requires an output variable and an input string"));
55
0
    return false;
56
0
  }
57
58
0
  cm::CMakeString data{ args[2] };
59
60
0
  try {
61
0
    data.Hash(args[0]);
62
0
    status.GetMakefile().AddDefinition(args[1], data);
63
0
    return true;
64
0
  } catch (std::exception const& e) {
65
0
    status.SetError(e.what());
66
0
    return false;
67
0
  }
68
0
}
69
70
bool HandleToUpperLowerCommand(std::vector<std::string> const& args,
71
                               bool toUpper, cmExecutionStatus& status)
72
0
{
73
0
  if (args.size() < 3) {
74
0
    status.SetError("no output variable specified");
75
0
    return false;
76
0
  }
77
78
0
  std::string const& outvar = args[2];
79
0
  cm::CMakeString data{ args[1] };
80
81
0
  if (toUpper) {
82
0
    data.ToUpper();
83
0
  } else {
84
0
    data.ToLower();
85
0
  }
86
87
  // Store the output in the provided variable.
88
0
  status.GetMakefile().AddDefinition(outvar, data);
89
0
  return true;
90
0
}
91
92
bool HandleToUpperCommand(std::vector<std::string> const& args,
93
                          cmExecutionStatus& status)
94
0
{
95
0
  return HandleToUpperLowerCommand(args, true, status);
96
0
}
97
98
bool HandleToLowerCommand(std::vector<std::string> const& args,
99
                          cmExecutionStatus& status)
100
0
{
101
0
  return HandleToUpperLowerCommand(args, false, status);
102
0
}
103
104
bool HandleAsciiCommand(std::vector<std::string> const& args,
105
                        cmExecutionStatus& status)
106
0
{
107
0
  if (args.size() < 3) {
108
0
    status.SetError("No output variable specified");
109
0
    return false;
110
0
  }
111
112
0
  try {
113
0
    std::string const& outvar = args.back();
114
0
    cm::CMakeString data;
115
0
    data.FromASCII(cmMakeRange(args).advance(1).retreat(1));
116
0
    status.GetMakefile().AddDefinition(outvar, data);
117
0
    return true;
118
0
  } catch (std::exception const& e) {
119
0
    status.SetError(e.what());
120
0
    return false;
121
0
  }
122
0
}
123
124
bool HandleHexCommand(std::vector<std::string> const& args,
125
                      cmExecutionStatus& status)
126
0
{
127
0
  if (args.size() != 3) {
128
0
    status.SetError("Incorrect number of arguments");
129
0
    return false;
130
0
  }
131
132
0
  auto const& outvar = args[2];
133
0
  cm::CMakeString data{ args[1] };
134
135
0
  data.ToHexadecimal();
136
137
0
  status.GetMakefile().AddDefinition(outvar, data);
138
0
  return true;
139
0
}
140
141
bool HandleConfigureCommand(std::vector<std::string> const& args,
142
                            cmExecutionStatus& status)
143
0
{
144
0
  if (args.size() < 2) {
145
0
    status.SetError("No input string specified.");
146
0
    return false;
147
0
  }
148
0
  if (args.size() < 3) {
149
0
    status.SetError("No output variable specified.");
150
0
    return false;
151
0
  }
152
153
  // Parse options.
154
0
  bool escapeQuotes = false;
155
0
  bool atOnly = false;
156
0
  for (unsigned int i = 3; i < args.size(); ++i) {
157
0
    if (args[i] == "@ONLY") {
158
0
      atOnly = true;
159
0
    } else if (args[i] == "ESCAPE_QUOTES") {
160
0
      escapeQuotes = true;
161
0
    } else {
162
0
      status.SetError(cmStrCat("Unrecognized argument \"", args[i], '"'));
163
0
      return false;
164
0
    }
165
0
  }
166
167
  // Configure the string.
168
0
  std::string output;
169
0
  status.GetMakefile().ConfigureString(args[1], output, atOnly, escapeQuotes);
170
171
  // Store the output in the provided variable.
172
0
  status.GetMakefile().AddDefinition(args[2], output);
173
174
0
  return true;
175
0
}
176
177
bool HandleRegexCommand(std::vector<std::string> const& args,
178
                        cmExecutionStatus& status)
179
0
{
180
0
  if (args.size() < 2) {
181
0
    status.SetError("sub-command REGEX requires a mode to be specified.");
182
0
    return false;
183
0
  }
184
0
  std::string const& mode = args[1];
185
0
  if (mode == "MATCH") {
186
0
    if (args.size() < 5) {
187
0
      status.SetError("sub-command REGEX, mode MATCH needs "
188
0
                      "at least 5 arguments total to command.");
189
0
      return false;
190
0
    }
191
0
    return RegexMatch(args, status);
192
0
  }
193
0
  if (mode == "MATCHALL") {
194
0
    if (args.size() < 5) {
195
0
      status.SetError("sub-command REGEX, mode MATCHALL needs "
196
0
                      "at least 5 arguments total to command.");
197
0
      return false;
198
0
    }
199
0
    return RegexMatchAll(args, status);
200
0
  }
201
0
  if (mode == "REPLACE") {
202
0
    if (args.size() < 6) {
203
0
      status.SetError("sub-command REGEX, mode REPLACE needs "
204
0
                      "at least 6 arguments total to command.");
205
0
      return false;
206
0
    }
207
0
    return RegexReplace(args, status);
208
0
  }
209
0
  if (mode == "QUOTE") {
210
0
    if (args.size() < 4) {
211
0
      status.SetError("sub-command REGEX, mode QUOTE needs "
212
0
                      "at least 4 arguments total to command.");
213
0
      return false;
214
0
    }
215
0
    return RegexQuote(args, status);
216
0
  }
217
218
0
  std::string e = "sub-command REGEX does not recognize mode " + mode;
219
0
  status.SetError(e);
220
0
  return false;
221
0
}
222
223
bool RegexMatch(std::vector<std::string> const& args,
224
                cmExecutionStatus& status)
225
0
{
226
  //"STRING(REGEX MATCH <regular_expression> <output variable>
227
  // <input> [<input>...])\n";
228
0
  try {
229
0
    std::string const& regex = args[2];
230
0
    std::string const& outvar = args[3];
231
0
    cm::CMakeString data{ cmMakeRange(args).advance(4) };
232
233
0
    auto result = data.Match(regex, cm::CMakeString::MatchItems::Once,
234
0
                             &status.GetMakefile());
235
    // Store the result in the provided variable.
236
0
    status.GetMakefile().AddDefinition(outvar, result.to_string());
237
0
    return true;
238
0
  } catch (std::exception const& e) {
239
0
    status.SetError(
240
0
      cmStrCat("sub-command REGEX, mode MATCH: ", e.what(), '.'));
241
0
    return false;
242
0
  }
243
0
}
244
245
bool RegexMatchAll(std::vector<std::string> const& args,
246
                   cmExecutionStatus& status)
247
0
{
248
  //"STRING(REGEX MATCHALL <regular_expression> <output variable> <input>
249
  // [<input>...])\n";
250
0
  try {
251
0
    std::string const& regex = args[2];
252
0
    std::string const& outvar = args[3];
253
0
    cm::CMakeString data{ cmMakeRange(args).advance(4) };
254
255
0
    auto result = data.Match(regex, cm::CMakeString::MatchItems::All,
256
0
                             &status.GetMakefile());
257
    // Store the result in the provided variable.
258
0
    status.GetMakefile().AddDefinition(outvar, result.to_string());
259
0
    return true;
260
0
  } catch (std::exception const& e) {
261
0
    status.SetError(
262
0
      cmStrCat("sub-command REGEX, mode MATCHALL: ", e.what(), '.'));
263
0
    return false;
264
0
  }
265
0
}
266
267
bool RegexReplace(std::vector<std::string> const& args,
268
                  cmExecutionStatus& status)
269
0
{
270
  //"STRING(REGEX REPLACE <regular_expression> <replace_expression>
271
  // <output variable> <input> [<input>...])\n"
272
0
  std::string const& regex = args[2];
273
0
  std::string const& replace = args[3];
274
0
  std::string const& outvar = args[4];
275
276
0
  try {
277
0
    cm::CMakeString data{ cmMakeRange(args).advance(5) };
278
279
0
    data.Replace(regex, replace, cm::CMakeString::Regex::Yes,
280
0
                 &status.GetMakefile());
281
    // Store the result in the provided variable.
282
0
    status.GetMakefile().AddDefinition(outvar, data);
283
0
    return true;
284
0
  } catch (std::exception const& e) {
285
0
    status.SetError(
286
0
      cmStrCat("sub-command REGEX, mode REPLACE: ", e.what(), '.'));
287
0
    return false;
288
0
  }
289
0
}
290
291
bool RegexQuote(std::vector<std::string> const& args,
292
                cmExecutionStatus& status)
293
0
{
294
  //"STRING(REGEX QUOTE <output variable> <input> [<input>...]\n"
295
0
  std::string const& outvar = args[2];
296
0
  cm::CMakeString data{ cmMakeRange(args).advance(3) };
297
298
0
  try {
299
    // Escape all regex special characters
300
0
    data.Quote();
301
    // Store the output in the provided variable.
302
0
    status.GetMakefile().AddDefinition(outvar, data);
303
0
    return true;
304
0
  } catch (std::exception const& e) {
305
0
    status.SetError(
306
0
      cmStrCat("sub-command REGEX, mode QUOTE: ", e.what(), '.'));
307
0
    return false;
308
0
  }
309
0
}
310
311
bool HandleFindCommand(std::vector<std::string> const& args,
312
                       cmExecutionStatus& status)
313
0
{
314
  // check if all required parameters were passed
315
0
  if (args.size() < 4 || args.size() > 5) {
316
0
    status.SetError("sub-command FIND requires 3 or 4 parameters.");
317
0
    return false;
318
0
  }
319
320
  // check if the reverse flag was set or not
321
0
  bool reverseMode = false;
322
0
  if (args.size() == 5 && args[4] == "REVERSE") {
323
0
    reverseMode = true;
324
0
  }
325
326
  // if we have 5 arguments the last one must be REVERSE
327
0
  if (args.size() == 5 && args[4] != "REVERSE") {
328
0
    status.SetError("sub-command FIND: unknown last parameter");
329
0
    return false;
330
0
  }
331
332
  // local parameter names.
333
0
  std::string const& sstring = args[1];
334
0
  std::string const& schar = args[2];
335
0
  std::string const& outvar = args[3];
336
337
  // ensure that the user cannot accidentally specify REVERSE as a variable
338
0
  if (outvar == "REVERSE") {
339
0
    status.SetError("sub-command FIND does not allow one to select REVERSE as "
340
0
                    "the output variable.  "
341
0
                    "Maybe you missed the actual output variable?");
342
0
    return false;
343
0
  }
344
345
  // try to find the character and return its position
346
0
  auto pos = cm::CMakeString{ sstring }.Find(
347
0
    schar,
348
0
    reverseMode ? cm::CMakeString::FindFrom::End
349
0
                : cm::CMakeString::FindFrom::Begin);
350
351
0
  status.GetMakefile().AddDefinition(
352
0
    outvar, pos != cm::CMakeString::npos ? std::to_string(pos) : "-1");
353
354
0
  return true;
355
0
}
356
357
bool HandleCompareCommand(std::vector<std::string> const& args,
358
                          cmExecutionStatus& status)
359
0
{
360
0
  if (args.size() < 2) {
361
0
    status.SetError("sub-command COMPARE requires a mode to be specified.");
362
0
    return false;
363
0
  }
364
0
  std::string const& mode = args[1];
365
0
  if ((mode == "EQUAL") || (mode == "NOTEQUAL") || (mode == "LESS") ||
366
0
      (mode == "LESS_EQUAL") || (mode == "GREATER") ||
367
0
      (mode == "GREATER_EQUAL")) {
368
0
    if (args.size() < 5) {
369
0
      std::string e =
370
0
        cmStrCat("sub-command COMPARE, mode ", mode,
371
0
                 " needs at least 5 arguments total to command.");
372
0
      status.SetError(e);
373
0
      return false;
374
0
    }
375
376
0
    std::string const& left = args[2];
377
0
    std::string const& right = args[3];
378
0
    std::string const& outvar = args[4];
379
0
    bool result;
380
0
    cm::CMakeString::CompOperator op = cm::CMakeString::CompOperator::EQUAL;
381
0
    if (mode == "LESS") {
382
0
      op = cm::CMakeString::CompOperator::LESS;
383
0
    } else if (mode == "LESS_EQUAL") {
384
0
      op = cm::CMakeString::CompOperator::LESS_EQUAL;
385
0
    } else if (mode == "GREATER") {
386
0
      op = cm::CMakeString::CompOperator::GREATER;
387
0
    } else if (mode == "GREATER_EQUAL") {
388
0
      op = cm::CMakeString::CompOperator::GREATER_EQUAL;
389
0
    }
390
0
    result = cm::CMakeString{ left }.Compare(op, right);
391
0
    if (mode == "NOTEQUAL") {
392
0
      result = !result;
393
0
    }
394
395
0
    status.GetMakefile().AddDefinition(outvar, result ? "1" : "0");
396
0
    return true;
397
0
  }
398
0
  std::string e = "sub-command COMPARE does not recognize mode " + mode;
399
0
  status.SetError(e);
400
0
  return false;
401
0
}
402
403
bool HandleReplaceCommand(std::vector<std::string> const& args,
404
                          cmExecutionStatus& status)
405
0
{
406
0
  if (args.size() < 5) {
407
0
    status.SetError("sub-command REPLACE requires at least four arguments.");
408
0
    return false;
409
0
  }
410
411
0
  try {
412
0
    std::string const& matchExpression = args[1];
413
0
    std::string const& replaceExpression = args[2];
414
0
    std::string const& variableName = args[3];
415
0
    cm::CMakeString data{ cmMakeRange(args).advance(4) };
416
417
0
    data.Replace(matchExpression, replaceExpression);
418
0
    status.GetMakefile().AddDefinition(variableName, data);
419
0
    return true;
420
0
  } catch (std::exception const& e) {
421
0
    status.SetError(cmStrCat("sub-command REPLACE: ", e.what(), '.'));
422
0
    return false;
423
0
  }
424
0
}
425
426
bool HandleSubstringCommand(std::vector<std::string> const& args,
427
                            cmExecutionStatus& status)
428
0
{
429
0
  if (args.size() != 5) {
430
0
    status.SetError("sub-command SUBSTRING requires four arguments.");
431
0
    return false;
432
0
  }
433
434
0
  try {
435
0
    std::string const& stringValue = args[1];
436
0
    int begin = atoi(args[2].c_str());
437
0
    int end = atoi(args[3].c_str());
438
0
    std::string const& variableName = args[4];
439
440
0
    cm::CMakeString data{ stringValue };
441
0
    status.GetMakefile().AddDefinition(variableName,
442
0
                                       data.Substring(begin, end));
443
0
  } catch (std::exception const& e) {
444
0
    status.SetError(e.what());
445
0
    return false;
446
0
  }
447
0
  return true;
448
0
}
449
450
bool HandleLengthCommand(std::vector<std::string> const& args,
451
                         cmExecutionStatus& status)
452
0
{
453
0
  if (args.size() != 3) {
454
0
    status.SetError("sub-command LENGTH requires two arguments.");
455
0
    return false;
456
0
  }
457
458
0
  std::string const& stringValue = args[1];
459
0
  std::string const& variableName = args[2];
460
461
0
  status.GetMakefile().AddDefinition(
462
0
    variableName, std::to_string(cm::CMakeString{ stringValue }.Length()));
463
0
  return true;
464
0
}
465
466
bool HandleAppendCommand(std::vector<std::string> const& args,
467
                         cmExecutionStatus& status)
468
0
{
469
0
  if (args.size() < 2) {
470
0
    status.SetError("sub-command APPEND requires at least one argument.");
471
0
    return false;
472
0
  }
473
474
  // Skip if nothing to append.
475
0
  if (args.size() < 3) {
476
0
    return true;
477
0
  }
478
479
0
  auto const& variableName = args[1];
480
0
  cm::CMakeString data{ status.GetMakefile().GetDefinition(variableName) };
481
482
0
  data.Append(cmMakeRange(args).advance(2));
483
0
  status.GetMakefile().AddDefinition(variableName, data);
484
485
0
  return true;
486
0
}
487
488
bool HandlePrependCommand(std::vector<std::string> const& args,
489
                          cmExecutionStatus& status)
490
0
{
491
0
  if (args.size() < 2) {
492
0
    status.SetError("sub-command PREPEND requires at least one argument.");
493
0
    return false;
494
0
  }
495
496
  // Skip if nothing to prepend.
497
0
  if (args.size() < 3) {
498
0
    return true;
499
0
  }
500
501
0
  std::string const& variable = args[1];
502
0
  cm::CMakeString data{ status.GetMakefile().GetDefinition(variable) };
503
504
0
  data.Prepend(cmMakeRange(args).advance(2));
505
0
  status.GetMakefile().AddDefinition(variable, data);
506
0
  return true;
507
0
}
508
509
bool HandleConcatCommand(std::vector<std::string> const& args,
510
                         cmExecutionStatus& status)
511
0
{
512
0
  if (args.size() < 2) {
513
0
    status.SetError("sub-command CONCAT requires at least one argument.");
514
0
    return false;
515
0
  }
516
517
0
  return joinImpl(args, std::string(), 1, status.GetMakefile());
518
0
}
519
520
bool HandleJoinCommand(std::vector<std::string> const& args,
521
                       cmExecutionStatus& status)
522
0
{
523
0
  if (args.size() < 3) {
524
0
    status.SetError("sub-command JOIN requires at least two arguments.");
525
0
    return false;
526
0
  }
527
528
0
  return joinImpl(args, args[1], 2, status.GetMakefile());
529
0
}
530
531
bool joinImpl(std::vector<std::string> const& args, std::string const& glue,
532
              size_t const varIdx, cmMakefile& makefile)
533
0
{
534
0
  std::string const& variableName = args[varIdx];
535
  // NOTE Items to concat/join placed right after the variable for
536
  // both `CONCAT` and `JOIN` sub-commands.
537
0
  cm::CMakeString data{ cmMakeRange(args).advance(varIdx + 1), glue };
538
539
0
  makefile.AddDefinition(variableName, data);
540
0
  return true;
541
0
}
542
543
bool HandleMakeCIdentifierCommand(std::vector<std::string> const& args,
544
                                  cmExecutionStatus& status)
545
0
{
546
0
  if (args.size() != 3) {
547
0
    status.SetError("sub-command MAKE_C_IDENTIFIER requires two arguments.");
548
0
    return false;
549
0
  }
550
551
0
  std::string const& input = args[1];
552
0
  std::string const& variableName = args[2];
553
554
0
  status.GetMakefile().AddDefinition(variableName,
555
0
                                     cm::CMakeString{}.MakeCIdentifier(input));
556
0
  return true;
557
0
}
558
559
bool HandleGenexStripCommand(std::vector<std::string> const& args,
560
                             cmExecutionStatus& status)
561
0
{
562
0
  if (args.size() != 3) {
563
0
    status.SetError("sub-command GENEX_STRIP requires two arguments.");
564
0
    return false;
565
0
  }
566
567
0
  cm::CMakeString data{ args[1] };
568
0
  std::string const& variableName = args[2];
569
570
0
  status.GetMakefile().AddDefinition(
571
0
    variableName, data.Strip(cm::CMakeString::StripItems::Genex));
572
0
  return true;
573
0
}
574
575
bool HandleStripCommand(std::vector<std::string> const& args,
576
                        cmExecutionStatus& status)
577
0
{
578
0
  if (args.size() != 3) {
579
0
    status.SetError("sub-command STRIP requires two arguments.");
580
0
    return false;
581
0
  }
582
583
0
  cm::CMakeString data{ args[1] };
584
0
  std::string const& variableName = args[2];
585
586
0
  status.GetMakefile().AddDefinition(variableName, data.Strip());
587
0
  return true;
588
0
}
589
590
bool HandleRepeatCommand(std::vector<std::string> const& args,
591
                         cmExecutionStatus& status)
592
0
{
593
0
  cmMakefile& makefile = status.GetMakefile();
594
595
  // `string(REPEAT "<str>" <times> OUTPUT_VARIABLE)`
596
0
  enum ArgPos : std::size_t
597
0
  {
598
0
    SUB_COMMAND,
599
0
    VALUE,
600
0
    TIMES,
601
0
    OUTPUT_VARIABLE,
602
0
    TOTAL_ARGS
603
0
  };
604
605
0
  if (args.size() != ArgPos::TOTAL_ARGS) {
606
0
    makefile.IssueMessage(MessageType::FATAL_ERROR,
607
0
                          "sub-command REPEAT requires three arguments.");
608
0
    return true;
609
0
  }
610
611
0
  unsigned long times;
612
0
  if (!cmStrToULong(args[ArgPos::TIMES], &times)) {
613
0
    makefile.IssueMessage(MessageType::FATAL_ERROR,
614
0
                          "repeat count is not a positive number.");
615
0
    return true;
616
0
  }
617
618
0
  cm::CMakeString data{ args[ArgPos::VALUE] };
619
0
  data.Repeat(times);
620
621
0
  auto const& variableName = args[ArgPos::OUTPUT_VARIABLE];
622
623
0
  makefile.AddDefinition(variableName, data);
624
0
  return true;
625
0
}
626
627
bool HandleRandomCommand(std::vector<std::string> const& args,
628
                         cmExecutionStatus& status)
629
0
{
630
0
  if (args.size() < 2 || args.size() == 3 || args.size() == 5) {
631
0
    status.SetError("sub-command RANDOM requires at least one argument.");
632
0
    return false;
633
0
  }
634
635
0
  int length = 5;
636
0
  cm::string_view alphabet;
637
0
  bool force_seed = false;
638
0
  unsigned int seed = 0;
639
640
0
  if (args.size() > 3) {
641
0
    size_t i = 1;
642
0
    size_t stopAt = args.size() - 2;
643
644
0
    for (; i < stopAt; ++i) {
645
0
      if (args[i] == "LENGTH") {
646
0
        ++i;
647
0
        length = atoi(args[i].c_str());
648
0
      } else if (args[i] == "ALPHABET") {
649
0
        ++i;
650
0
        alphabet = args[i];
651
0
      } else if (args[i] == "RANDOM_SEED") {
652
0
        ++i;
653
0
        seed = static_cast<unsigned int>(atoi(args[i].c_str()));
654
0
        force_seed = true;
655
0
      }
656
0
    }
657
0
  }
658
659
0
  try {
660
0
    cm::CMakeString data;
661
0
    std::string const& variableName = args.back();
662
663
0
    if (force_seed) {
664
0
      data.Random(seed, length, alphabet);
665
0
    } else {
666
0
      data.Random(length, alphabet);
667
0
    }
668
0
    status.GetMakefile().AddDefinition(variableName, data);
669
0
    return true;
670
0
  } catch (std::exception const& e) {
671
0
    status.SetError(cmStrCat("sub-command RANDOM: ", e.what(), '.'));
672
0
    return false;
673
0
  }
674
0
}
675
676
bool HandleTimestampCommand(std::vector<std::string> const& args,
677
                            cmExecutionStatus& status)
678
0
{
679
0
  if (args.size() < 2) {
680
0
    status.SetError("sub-command TIMESTAMP requires at least one argument.");
681
0
    return false;
682
0
  }
683
0
  if (args.size() > 4) {
684
0
    status.SetError("sub-command TIMESTAMP takes at most three arguments.");
685
0
    return false;
686
0
  }
687
688
0
  unsigned int argsIndex = 1;
689
690
0
  std::string const& outputVariable = args[argsIndex++];
691
692
0
  cm::string_view formatString;
693
0
  if (args.size() > argsIndex && args[argsIndex] != "UTC") {
694
0
    formatString = args[argsIndex++];
695
0
  }
696
697
0
  cm::CMakeString::UTC utcFlag = cm::CMakeString::UTC::No;
698
0
  if (args.size() > argsIndex) {
699
0
    if (args[argsIndex] == "UTC") {
700
0
      utcFlag = cm::CMakeString::UTC::Yes;
701
0
    } else {
702
0
      std::string e =
703
0
        cmStrCat(" TIMESTAMP sub-command does not recognize option ",
704
0
                 args[argsIndex], '.');
705
0
      status.SetError(e);
706
0
      return false;
707
0
    }
708
0
  }
709
710
0
  cm::CMakeString data;
711
712
0
  status.GetMakefile().AddDefinition(outputVariable,
713
0
                                     data.Timestamp(formatString, utcFlag));
714
715
0
  return true;
716
0
}
717
718
bool HandleUuidCommand(std::vector<std::string> const& args,
719
                       cmExecutionStatus& status)
720
0
{
721
0
#if !defined(CMAKE_BOOTSTRAP)
722
0
  unsigned int argsIndex = 1;
723
724
0
  if (args.size() < 2) {
725
0
    status.SetError("UUID sub-command requires an output variable.");
726
0
    return false;
727
0
  }
728
729
0
  std::string const& outputVariable = args[argsIndex++];
730
731
0
  cm::string_view uuidNamespaceString;
732
0
  cm::string_view uuidName;
733
0
  cm::CMakeString::UUIDType uuidType = cm::CMakeString::UUIDType::MD5;
734
0
  cm::CMakeString::Case uuidCase = cm::CMakeString::Case::Lower;
735
736
0
  while (args.size() > argsIndex) {
737
0
    if (args[argsIndex] == "NAMESPACE") {
738
0
      ++argsIndex;
739
0
      if (argsIndex >= args.size()) {
740
0
        status.SetError("UUID sub-command, NAMESPACE requires a value.");
741
0
        return false;
742
0
      }
743
0
      uuidNamespaceString = args[argsIndex++];
744
0
    } else if (args[argsIndex] == "NAME") {
745
0
      ++argsIndex;
746
0
      if (argsIndex >= args.size()) {
747
0
        status.SetError("UUID sub-command, NAME requires a value.");
748
0
        return false;
749
0
      }
750
0
      uuidName = args[argsIndex++];
751
0
    } else if (args[argsIndex] == "TYPE") {
752
0
      ++argsIndex;
753
0
      if (argsIndex >= args.size()) {
754
0
        status.SetError("UUID sub-command, TYPE requires a value.");
755
0
        return false;
756
0
      }
757
0
      if (args[argsIndex] == "MD5") {
758
0
        uuidType = cm::CMakeString::UUIDType::MD5;
759
0
      } else if (args[argsIndex] == "SHA1") {
760
0
        uuidType = cm::CMakeString::UUIDType::SHA1;
761
0
      } else {
762
0
        status.SetError(
763
0
          cmStrCat("UUID sub-command, unknown TYPE '", args[argsIndex], "'."));
764
0
        return false;
765
0
      }
766
0
      argsIndex++;
767
0
    } else if (args[argsIndex] == "UPPER") {
768
0
      ++argsIndex;
769
0
      uuidCase = cm::CMakeString::Case::Upper;
770
0
    } else {
771
0
      std::string e = cmStrCat("UUID sub-command does not recognize option ",
772
0
                               args[argsIndex], '.');
773
0
      status.SetError(e);
774
0
      return false;
775
0
    }
776
0
  }
777
778
0
  try {
779
0
    cm::CMakeString data;
780
781
0
    data.UUID(uuidNamespaceString, uuidName, uuidType, uuidCase);
782
0
    status.GetMakefile().AddDefinition(outputVariable, data);
783
0
    return true;
784
0
  } catch (std::exception const& e) {
785
0
    status.SetError(cmStrCat("UUID sub-command, ", e.what(), '.'));
786
0
    return false;
787
0
  }
788
#else
789
  status.SetError("UUID sub-command not available during bootstrap.");
790
  return false;
791
#endif
792
0
}
793
794
#if !defined(CMAKE_BOOTSTRAP)
795
796
// Helpers for string(JSON ...)
797
struct Args : cmRange<typename std::vector<std::string>::const_iterator>
798
{
799
  using cmRange<typename std::vector<std::string>::const_iterator>::cmRange;
800
801
  auto PopFront(cm::string_view error) -> std::string const&;
802
  auto PopBack(cm::string_view error) -> std::string const&;
803
};
804
805
class json_error : public std::runtime_error
806
{
807
public:
808
  json_error(std::string const& message,
809
             cm::optional<Args> errorPath = cm::nullopt)
810
0
    : std::runtime_error(message)
811
0
    , ErrorPath{
812
0
      std::move(errorPath) // NOLINT(performance-move-const-arg)
813
0
    }
814
0
  {
815
0
  }
816
  cm::optional<Args> ErrorPath;
817
};
818
819
std::string const& Args::PopFront(cm::string_view error)
820
0
{
821
0
  if (this->empty()) {
822
0
    throw json_error(std::string(error));
823
0
  }
824
0
  std::string const& res = *this->begin();
825
0
  this->advance(1);
826
0
  return res;
827
0
}
828
829
std::string const& Args::PopBack(cm::string_view error)
830
0
{
831
0
  if (this->empty()) {
832
0
    throw json_error(std::string(error));
833
0
  }
834
0
  std::string const& res = *(this->end() - 1);
835
0
  this->retreat(1);
836
0
  return res;
837
0
}
838
839
cm::string_view JsonTypeToString(Json::ValueType type)
840
0
{
841
0
  switch (type) {
842
0
    case Json::ValueType::nullValue:
843
0
      return "NULL"_s;
844
0
    case Json::ValueType::intValue:
845
0
    case Json::ValueType::uintValue:
846
0
    case Json::ValueType::realValue:
847
0
      return "NUMBER"_s;
848
0
    case Json::ValueType::stringValue:
849
0
      return "STRING"_s;
850
0
    case Json::ValueType::booleanValue:
851
0
      return "BOOLEAN"_s;
852
0
    case Json::ValueType::arrayValue:
853
0
      return "ARRAY"_s;
854
0
    case Json::ValueType::objectValue:
855
0
      return "OBJECT"_s;
856
0
  }
857
0
  throw json_error("invalid JSON type found");
858
0
}
859
860
int ParseIndex(
861
  std::string const& str, cm::optional<Args> const& progress = cm::nullopt,
862
  Json::ArrayIndex max = std::numeric_limits<Json::ArrayIndex>::max())
863
0
{
864
0
  unsigned long lindex;
865
0
  if (!cmStrToULong(str, &lindex)) {
866
0
    throw json_error(cmStrCat("expected an array index, got: '"_s, str, "'"_s),
867
0
                     progress);
868
0
  }
869
0
  Json::ArrayIndex index = static_cast<Json::ArrayIndex>(lindex);
870
0
  if (index >= max) {
871
0
    cmAlphaNum sizeStr{ max };
872
0
    throw json_error(cmStrCat("expected an index less than "_s, sizeStr.View(),
873
0
                              " got '"_s, str, "'"_s),
874
0
                     progress);
875
0
  }
876
0
  return index;
877
0
}
878
879
Json::Value& ResolvePath(Json::Value& json, Args path)
880
0
{
881
0
  Json::Value* search = &json;
882
883
0
  for (auto curr = path.begin(); curr != path.end(); ++curr) {
884
0
    std::string const& field = *curr;
885
0
    Args progress{ path.begin(), curr + 1 };
886
887
0
    if (search->isArray()) {
888
0
      auto index = ParseIndex(field, progress, search->size());
889
0
      search = &(*search)[index];
890
891
0
    } else if (search->isObject()) {
892
0
      if (!search->isMember(field)) {
893
0
        auto const progressStr = cmJoin(progress, " "_s);
894
0
        throw json_error(cmStrCat("member '"_s, progressStr, "' not found"_s),
895
0
                         progress);
896
0
      }
897
0
      search = &(*search)[field];
898
0
    } else {
899
0
      auto const progressStr = cmJoin(progress, " "_s);
900
0
      throw json_error(
901
0
        cmStrCat("invalid path '"_s, progressStr,
902
0
                 "', need element of OBJECT or ARRAY type to lookup '"_s,
903
0
                 field, "' got "_s, JsonTypeToString(search->type())),
904
0
        progress);
905
0
    }
906
0
  }
907
0
  return *search;
908
0
}
909
910
Json::Value ReadJson(std::string const& jsonstr)
911
0
{
912
0
  Json::CharReaderBuilder builder;
913
0
  builder["collectComments"] = false;
914
0
  auto jsonReader = std::unique_ptr<Json::CharReader>(builder.newCharReader());
915
0
  Json::Value json;
916
0
  std::string error;
917
0
  if (!jsonReader->parse(jsonstr.data(), jsonstr.data() + jsonstr.size(),
918
0
                         &json, &error)) {
919
0
    throw json_error(
920
0
      cmStrCat("failed parsing json string:\n"_s, jsonstr, '\n', error));
921
0
  }
922
0
  return json;
923
0
}
924
std::string WriteJson(Json::Value const& value)
925
0
{
926
0
  Json::StreamWriterBuilder writer;
927
0
  writer["indentation"] = "  ";
928
0
  writer["commentStyle"] = "None";
929
0
  return Json::writeString(writer, value);
930
0
}
931
932
bool JsonPartialMatch(Json::Value const& pattern, Json::Value const& actual)
933
0
{
934
0
  if (pattern.type() != actual.type()) {
935
0
    return false;
936
0
  }
937
0
  switch (pattern.type()) {
938
0
    case Json::nullValue:
939
0
    case Json::intValue:
940
0
    case Json::uintValue:
941
0
    case Json::realValue:
942
0
    case Json::stringValue:
943
0
    case Json::booleanValue:
944
0
      return pattern == actual;
945
946
0
    case Json::objectValue: {
947
0
      std::vector<std::string> const keys = pattern.getMemberNames();
948
0
      return std::all_of(keys.begin(), keys.end(),
949
0
                         [&](std::string const& key) {
950
0
                           return actual.isMember(key) &&
951
0
                             JsonPartialMatch(pattern[key], actual[key]);
952
0
                         });
953
0
    }
954
955
0
    case Json::arrayValue: {
956
0
      if (actual.size() < pattern.size()) {
957
0
        return false;
958
0
      }
959
0
      std::vector<bool> matched(actual.size(), false);
960
0
      for (Json::Value const& p : pattern) {
961
0
        bool found = false;
962
0
        for (Json::ArrayIndex j = 0; j < actual.size(); ++j) {
963
0
          if (matched[j]) {
964
0
            continue;
965
0
          }
966
0
          if (JsonPartialMatch(p, actual[j])) {
967
0
            matched[j] = true;
968
0
            found = true;
969
0
            break;
970
0
          }
971
0
        }
972
0
        if (!found) {
973
0
          return false;
974
0
        }
975
0
      }
976
0
      return true;
977
0
    }
978
0
  }
979
0
  return false;
980
0
}
981
982
#endif
983
984
bool HandleJSONCommand(std::vector<std::string> const& arguments,
985
                       cmExecutionStatus& status)
986
0
{
987
0
#if !defined(CMAKE_BOOTSTRAP)
988
989
0
  auto& makefile = status.GetMakefile();
990
0
  Args args{ arguments.begin() + 1, arguments.end() };
991
992
0
  std::string const* errorVariable = nullptr;
993
0
  std::string const* outputVariable = nullptr;
994
0
  bool success = true;
995
996
0
  try {
997
0
    outputVariable = &args.PopFront("missing out-var argument"_s);
998
999
0
    if (!args.empty() && *args.begin() == "ERROR_VARIABLE"_s) {
1000
0
      args.PopFront("");
1001
0
      errorVariable = &args.PopFront("missing error-var argument"_s);
1002
0
      makefile.AddDefinition(*errorVariable, "NOTFOUND"_s);
1003
0
    }
1004
1005
0
    auto const& mode = args.PopFront("missing mode argument"_s);
1006
0
    if (mode != "GET"_s && mode != "GET_RAW"_s && mode != "TYPE"_s &&
1007
0
        mode != "MEMBER"_s && mode != "LENGTH"_s && mode != "REMOVE"_s &&
1008
0
        mode != "SET"_s && mode != "EQUAL"_s && mode != "STRING_ENCODE"_s &&
1009
0
        mode != "PARTIAL_EQUAL"_s) {
1010
0
      throw json_error(cmStrCat(
1011
0
        "got an invalid mode '"_s, mode,
1012
0
        "', expected one of GET, GET_RAW, TYPE, MEMBER, LENGTH, REMOVE, SET, "
1013
0
        " EQUAL, PARTIAL_EQUAL, STRING_ENCODE"_s));
1014
0
    }
1015
1016
0
    auto const& jsonstr = args.PopFront("missing json string argument"_s);
1017
1018
0
    if (mode == "STRING_ENCODE"_s) {
1019
0
      Json::Value json(jsonstr);
1020
0
      makefile.AddDefinition(*outputVariable, WriteJson(json));
1021
0
    } else {
1022
0
      Json::Value json = ReadJson(jsonstr);
1023
1024
0
      if (mode == "GET"_s) {
1025
0
        auto const& value = ResolvePath(json, args);
1026
0
        if (value.isObject() || value.isArray()) {
1027
0
          makefile.AddDefinition(*outputVariable, WriteJson(value));
1028
0
        } else if (value.isBool()) {
1029
0
          makefile.AddDefinitionBool(*outputVariable, value.asBool());
1030
0
        } else {
1031
0
          makefile.AddDefinition(*outputVariable, value.asString());
1032
0
        }
1033
1034
0
      } else if (mode == "GET_RAW"_s) {
1035
0
        auto const& value = ResolvePath(json, args);
1036
0
        makefile.AddDefinition(*outputVariable, WriteJson(value));
1037
1038
0
      } else if (mode == "TYPE"_s) {
1039
0
        auto const& value = ResolvePath(json, args);
1040
0
        makefile.AddDefinition(*outputVariable,
1041
0
                               JsonTypeToString(value.type()));
1042
1043
0
      } else if (mode == "MEMBER"_s) {
1044
0
        auto const& indexStr = args.PopBack("missing member index"_s);
1045
0
        auto const& value = ResolvePath(json, args);
1046
0
        if (!value.isObject()) {
1047
0
          throw json_error(
1048
0
            cmStrCat("MEMBER needs to be called with an element of "
1049
0
                     "type OBJECT, got "_s,
1050
0
                     JsonTypeToString(value.type())),
1051
0
            args);
1052
0
        }
1053
0
        auto const index = ParseIndex(
1054
0
          indexStr, Args{ args.begin(), args.end() + 1 }, value.size());
1055
0
        auto const memIt = std::next(value.begin(), index);
1056
0
        makefile.AddDefinition(*outputVariable, memIt.name());
1057
1058
0
      } else if (mode == "LENGTH"_s) {
1059
0
        auto const& value = ResolvePath(json, args);
1060
0
        if (!value.isArray() && !value.isObject()) {
1061
0
          throw json_error(cmStrCat("LENGTH needs to be called with an "
1062
0
                                    "element of type ARRAY or OBJECT, got "_s,
1063
0
                                    JsonTypeToString(value.type())),
1064
0
                           args);
1065
0
        }
1066
1067
0
        cmAlphaNum sizeStr{ value.size() };
1068
0
        makefile.AddDefinition(*outputVariable, sizeStr.View());
1069
1070
0
      } else if (mode == "REMOVE"_s) {
1071
0
        auto const& toRemove =
1072
0
          args.PopBack("missing member or index to remove"_s);
1073
0
        auto& value = ResolvePath(json, args);
1074
1075
0
        if (value.isArray()) {
1076
0
          auto const index = ParseIndex(
1077
0
            toRemove, Args{ args.begin(), args.end() + 1 }, value.size());
1078
0
          Json::Value removed;
1079
0
          value.removeIndex(index, &removed);
1080
1081
0
        } else if (value.isObject()) {
1082
0
          Json::Value removed;
1083
0
          value.removeMember(toRemove, &removed);
1084
1085
0
        } else {
1086
0
          throw json_error(cmStrCat("REMOVE needs to be called with an "
1087
0
                                    "element of type ARRAY or OBJECT, got "_s,
1088
0
                                    JsonTypeToString(value.type())),
1089
0
                           args);
1090
0
        }
1091
0
        makefile.AddDefinition(*outputVariable, WriteJson(json));
1092
1093
0
      } else if (mode == "SET"_s) {
1094
0
        auto const& newValueStr = args.PopBack("missing new value remove"_s);
1095
0
        auto const& toAdd = args.PopBack("missing member name to add"_s);
1096
0
        auto& value = ResolvePath(json, args);
1097
1098
0
        Json::Value newValue = ReadJson(newValueStr);
1099
0
        if (value.isObject()) {
1100
0
          value[toAdd] = newValue;
1101
0
        } else if (value.isArray()) {
1102
0
          auto const index =
1103
0
            ParseIndex(toAdd, Args{ args.begin(), args.end() + 1 });
1104
0
          if (value.isValidIndex(index)) {
1105
0
            value[static_cast<int>(index)] = newValue;
1106
0
          } else {
1107
0
            value.append(newValue);
1108
0
          }
1109
0
        } else {
1110
0
          throw json_error(cmStrCat("SET needs to be called with an "
1111
0
                                    "element of type OBJECT or ARRAY, got "_s,
1112
0
                                    JsonTypeToString(value.type())));
1113
0
        }
1114
1115
0
        makefile.AddDefinition(*outputVariable, WriteJson(json));
1116
1117
0
      } else if (mode == "EQUAL"_s) {
1118
0
        auto const& jsonstr2 =
1119
0
          args.PopFront("missing second json string argument"_s);
1120
0
        Json::Value json2 = ReadJson(jsonstr2);
1121
0
        makefile.AddDefinitionBool(*outputVariable, json == json2);
1122
0
      } else if (mode == "PARTIAL_EQUAL"_s) {
1123
0
        auto const& jsonstr2 =
1124
0
          args.PopFront("missing second json string argument"_s);
1125
0
        Json::Value json2 = ReadJson(jsonstr2);
1126
0
        makefile.AddDefinitionBool(*outputVariable,
1127
0
                                   JsonPartialMatch(json, json2));
1128
0
      }
1129
0
    }
1130
1131
0
  } catch (json_error const& e) {
1132
0
    if (outputVariable && e.ErrorPath) {
1133
0
      auto const errorPath = cmJoin(*e.ErrorPath, "-");
1134
0
      makefile.AddDefinition(*outputVariable,
1135
0
                             cmStrCat(errorPath, "-NOTFOUND"_s));
1136
0
    } else if (outputVariable) {
1137
0
      makefile.AddDefinition(*outputVariable, "NOTFOUND"_s);
1138
0
    }
1139
1140
0
    if (errorVariable) {
1141
0
      makefile.AddDefinition(*errorVariable, e.what());
1142
0
    } else {
1143
0
      status.SetError(cmStrCat("sub-command JSON "_s, e.what(), "."_s));
1144
0
      success = false;
1145
0
    }
1146
0
  }
1147
0
  return success;
1148
#else
1149
  status.SetError(cmStrCat(arguments[0], " not available during bootstrap"_s));
1150
  return false;
1151
#endif
1152
0
}
1153
1154
} // namespace
1155
1156
bool cmStringCommand(std::vector<std::string> const& args,
1157
                     cmExecutionStatus& status)
1158
0
{
1159
0
  if (args.empty()) {
1160
0
    status.SetError("must be called with at least one argument.");
1161
0
    return false;
1162
0
  }
1163
1164
0
  static cmSubcommandTable const subcommand{
1165
0
    { "REGEX"_s, HandleRegexCommand },
1166
0
    { "REPLACE"_s, HandleReplaceCommand },
1167
0
    { "MD5"_s, HandleHashCommand },
1168
0
    { "SHA1"_s, HandleHashCommand },
1169
0
    { "SHA224"_s, HandleHashCommand },
1170
0
    { "SHA256"_s, HandleHashCommand },
1171
0
    { "SHA384"_s, HandleHashCommand },
1172
0
    { "SHA512"_s, HandleHashCommand },
1173
0
    { "SHA3_224"_s, HandleHashCommand },
1174
0
    { "SHA3_256"_s, HandleHashCommand },
1175
0
    { "SHA3_384"_s, HandleHashCommand },
1176
0
    { "SHA3_512"_s, HandleHashCommand },
1177
0
    { "TOLOWER"_s, HandleToLowerCommand },
1178
0
    { "TOUPPER"_s, HandleToUpperCommand },
1179
0
    { "COMPARE"_s, HandleCompareCommand },
1180
0
    { "ASCII"_s, HandleAsciiCommand },
1181
0
    { "HEX"_s, HandleHexCommand },
1182
0
    { "CONFIGURE"_s, HandleConfigureCommand },
1183
0
    { "LENGTH"_s, HandleLengthCommand },
1184
0
    { "APPEND"_s, HandleAppendCommand },
1185
0
    { "PREPEND"_s, HandlePrependCommand },
1186
0
    { "CONCAT"_s, HandleConcatCommand },
1187
0
    { "JOIN"_s, HandleJoinCommand },
1188
0
    { "SUBSTRING"_s, HandleSubstringCommand },
1189
0
    { "STRIP"_s, HandleStripCommand },
1190
0
    { "REPEAT"_s, HandleRepeatCommand },
1191
0
    { "RANDOM"_s, HandleRandomCommand },
1192
0
    { "FIND"_s, HandleFindCommand },
1193
0
    { "TIMESTAMP"_s, HandleTimestampCommand },
1194
0
    { "MAKE_C_IDENTIFIER"_s, HandleMakeCIdentifierCommand },
1195
0
    { "GENEX_STRIP"_s, HandleGenexStripCommand },
1196
0
    { "UUID"_s, HandleUuidCommand },
1197
0
    { "JSON"_s, HandleJSONCommand },
1198
0
  };
1199
1200
0
  return subcommand(args[0], args, status);
1201
0
}