Updated formatting of documentation plus a little reorganization.
[cmake.git] / Source / cmStringCommand.cxx
blob776363ee8fd027e1db2d1270457b0fefeaada535
1 /*=========================================================================
3 Program: CMake - Cross-Platform Makefile Generator
4 Module: $RCSfile: cmStringCommand.cxx,v $
5 Language: C++
6 Date: $Date: 2008-08-26 16:54:06 $
7 Version: $Revision: 1.28 $
9 Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved.
10 See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
12 This software is distributed WITHOUT ANY WARRANTY; without even
13 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14 PURPOSE. See the above copyright notices for more information.
16 =========================================================================*/
17 #include "cmStringCommand.h"
18 #include <cmsys/RegularExpression.hxx>
19 #include <cmsys/SystemTools.hxx>
21 #include <stdlib.h> // required for atoi
22 #include <ctype.h>
23 #include <time.h>
25 //----------------------------------------------------------------------------
26 bool cmStringCommand
27 ::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
29 if(args.size() < 1)
31 this->SetError("must be called with at least one argument.");
32 return false;
35 const std::string &subCommand = args[0];
36 if(subCommand == "REGEX")
38 return this->HandleRegexCommand(args);
40 else if(subCommand == "REPLACE")
42 return this->HandleReplaceCommand(args);
44 else if(subCommand == "TOLOWER")
46 return this->HandleToUpperLowerCommand(args, false);
48 else if(subCommand == "TOUPPER")
50 return this->HandleToUpperLowerCommand(args, true);
52 else if(subCommand == "COMPARE")
54 return this->HandleCompareCommand(args);
56 else if(subCommand == "ASCII")
58 return this->HandleAsciiCommand(args);
60 else if(subCommand == "CONFIGURE")
62 return this->HandleConfigureCommand(args);
64 else if(subCommand == "LENGTH")
66 return this->HandleLengthCommand(args);
68 else if(subCommand == "SUBSTRING")
70 return this->HandleSubstringCommand(args);
72 else if(subCommand == "STRIP")
74 return this->HandleStripCommand(args);
76 else if(subCommand == "RANDOM")
78 return this->HandleRandomCommand(args);
81 std::string e = "does not recognize sub-command "+subCommand;
82 this->SetError(e.c_str());
83 return false;
86 //----------------------------------------------------------------------------
87 bool cmStringCommand::HandleToUpperLowerCommand(
88 std::vector<std::string> const& args, bool toUpper)
90 if ( args.size() < 3 )
92 this->SetError("no output variable specified");
93 return false;
96 std::string outvar = args[2];
97 std::string output;
99 if (toUpper)
101 output = cmSystemTools::UpperCase(args[1]);
103 else
105 output = cmSystemTools::LowerCase(args[1]);
108 // Store the output in the provided variable.
109 this->Makefile->AddDefinition(outvar.c_str(), output.c_str());
110 return true;
113 //----------------------------------------------------------------------------
114 bool cmStringCommand::HandleAsciiCommand(std::vector<std::string> const& args)
116 if ( args.size() < 3 )
118 this->SetError("No output variable specified");
119 return false;
121 std::string::size_type cc;
122 std::string outvar = args[args.size()-1];
123 std::string output = "";
124 for ( cc = 1; cc < args.size()-1; cc ++ )
126 int ch = atoi(args[cc].c_str());
127 if ( ch > 0 && ch < 256 )
129 output += static_cast<char>(ch);
131 else
133 std::string error = "Character with code ";
134 error += ch;
135 error += " does not exist.";
136 this->SetError(error.c_str());
137 return false;
140 // Store the output in the provided variable.
141 this->Makefile->AddDefinition(outvar.c_str(), output.c_str());
142 return true;
145 //----------------------------------------------------------------------------
146 bool cmStringCommand::HandleConfigureCommand(
147 std::vector<std::string> const& args)
149 if ( args.size() < 2 )
151 this->SetError("No input string specified.");
152 return false;
154 else if ( args.size() < 3 )
156 this->SetError("No output variable specified.");
157 return false;
160 // Parse options.
161 bool escapeQuotes = false;
162 bool atOnly = false;
163 for(unsigned int i = 3; i < args.size(); ++i)
165 if(args[i] == "@ONLY")
167 atOnly = true;
169 else if(args[i] == "ESCAPE_QUOTES")
171 escapeQuotes = true;
173 else
175 cmOStringStream err;
176 err << "Unrecognized argument \"" << args[i] << "\"";
177 this->SetError(err.str().c_str());
178 return false;
182 // Configure the string.
183 std::string output;
184 this->Makefile->ConfigureString(args[1], output, atOnly, escapeQuotes);
186 // Store the output in the provided variable.
187 this->Makefile->AddDefinition(args[2].c_str(), output.c_str());
189 return true;
192 //----------------------------------------------------------------------------
193 bool cmStringCommand::HandleRegexCommand(std::vector<std::string> const& args)
195 if(args.size() < 2)
197 this->SetError("sub-command REGEX requires a mode to be specified.");
198 return false;
200 std::string mode = args[1];
201 if(mode == "MATCH")
203 if(args.size() < 5)
205 this->SetError("sub-command REGEX, mode MATCH needs "
206 "at least 5 arguments total to command.");
207 return false;
209 return this->RegexMatch(args);
211 else if(mode == "MATCHALL")
213 if(args.size() < 5)
215 this->SetError("sub-command REGEX, mode MATCHALL needs "
216 "at least 5 arguments total to command.");
217 return false;
219 return this->RegexMatchAll(args);
221 else if(mode == "REPLACE")
223 if(args.size() < 6)
225 this->SetError("sub-command REGEX, mode REPLACE needs "
226 "at least 6 arguments total to command.");
227 return false;
229 return this->RegexReplace(args);
232 std::string e = "sub-command REGEX does not recognize mode "+mode;
233 this->SetError(e.c_str());
234 return false;
237 //----------------------------------------------------------------------------
238 bool cmStringCommand::RegexMatch(std::vector<std::string> const& args)
240 //"STRING(REGEX MATCH <regular_expression> <output variable>
241 // <input> [<input>...])\n";
242 std::string regex = args[2];
243 std::string outvar = args[3];
245 // Concatenate all the last arguments together.
246 std::string input = args[4];
247 for(unsigned int i=5; i < args.size(); ++i)
249 input += args[i];
252 this->ClearMatches(this->Makefile);
253 // Compile the regular expression.
254 cmsys::RegularExpression re;
255 if(!re.compile(regex.c_str()))
257 std::string e =
258 "sub-command REGEX, mode MATCH failed to compile regex \""+regex+"\".";
259 this->SetError(e.c_str());
260 return false;
263 // Scan through the input for all matches.
264 std::string output;
265 if(re.find(input.c_str()))
267 this->StoreMatches(this->Makefile, re);
268 std::string::size_type l = re.start();
269 std::string::size_type r = re.end();
270 if(r-l == 0)
272 std::string e =
273 "sub-command REGEX, mode MATCH regex \""+regex+
274 "\" matched an empty string.";
275 this->SetError(e.c_str());
276 return false;
278 output = input.substr(l, r-l);
281 // Store the output in the provided variable.
282 this->Makefile->AddDefinition(outvar.c_str(), output.c_str());
283 return true;
286 //----------------------------------------------------------------------------
287 bool cmStringCommand::RegexMatchAll(std::vector<std::string> const& args)
289 //"STRING(REGEX MATCHALL <regular_expression> <output variable> <input>
290 // [<input>...])\n";
291 std::string regex = args[2];
292 std::string outvar = args[3];
294 // Concatenate all the last arguments together.
295 std::string input = args[4];
296 for(unsigned int i=5; i < args.size(); ++i)
298 input += args[i];
301 this->ClearMatches(this->Makefile);
302 // Compile the regular expression.
303 cmsys::RegularExpression re;
304 if(!re.compile(regex.c_str()))
306 std::string e =
307 "sub-command REGEX, mode MATCHALL failed to compile regex \""+
308 regex+"\".";
309 this->SetError(e.c_str());
310 return false;
313 // Scan through the input for all matches.
314 std::string output;
315 const char* p = input.c_str();
316 while(re.find(p))
318 this->StoreMatches(this->Makefile, re);
319 std::string::size_type l = re.start();
320 std::string::size_type r = re.end();
321 if(r-l == 0)
323 std::string e = "sub-command REGEX, mode MATCHALL regex \""+
324 regex+"\" matched an empty string.";
325 this->SetError(e.c_str());
326 return false;
328 if(output.length() > 0)
330 output += ";";
332 output += std::string(p+l, r-l);
333 p += r;
336 // Store the output in the provided variable.
337 this->Makefile->AddDefinition(outvar.c_str(), output.c_str());
338 return true;
341 //----------------------------------------------------------------------------
342 bool cmStringCommand::RegexReplace(std::vector<std::string> const& args)
344 //"STRING(REGEX REPLACE <regular_expression> <replace_expression>
345 // <output variable> <input> [<input>...])\n"
346 std::string regex = args[2];
347 std::string replace = args[3];
348 std::string outvar = args[4];
350 // Pull apart the replace expression to find the escaped [0-9] values.
351 std::vector<RegexReplacement> replacement;
352 std::string::size_type l = 0;
353 while(l < replace.length())
355 std::string::size_type r = replace.find("\\", l);
356 if(r == std::string::npos)
358 r = replace.length();
359 replacement.push_back(replace.substr(l, r-l));
361 else
363 if(r-l > 0)
365 replacement.push_back(replace.substr(l, r-l));
367 if(r == (replace.length()-1))
369 this->SetError("sub-command REGEX, mode REPLACE: "
370 "replace-expression ends in a backslash.");
371 return false;
373 if((replace[r+1] >= '0') && (replace[r+1] <= '9'))
375 replacement.push_back(replace[r+1]-'0');
377 else if(replace[r+1] == 'n')
379 replacement.push_back("\n");
381 else if(replace[r+1] == '\\')
383 replacement.push_back("\\");
385 else
387 std::string e = "sub-command REGEX, mode REPLACE: Unknown escape \"";
388 e += replace.substr(r, 2);
389 e += "\"in replace-expression.";
390 this->SetError(e.c_str());
391 return false;
393 r += 2;
395 l = r;
398 // Concatenate all the last arguments together.
399 std::string input = args[5];
400 for(unsigned int i=6; i < args.size(); ++i)
402 input += args[i];
405 this->ClearMatches(this->Makefile);
406 // Compile the regular expression.
407 cmsys::RegularExpression re;
408 if(!re.compile(regex.c_str()))
410 std::string e =
411 "sub-command REGEX, mode REPLACE failed to compile regex \""+
412 regex+"\".";
413 this->SetError(e.c_str());
414 return false;
417 // Scan through the input for all matches.
418 std::string output;
419 std::string::size_type base = 0;
420 while(re.find(input.c_str()+base))
422 this->StoreMatches(this->Makefile, re);
423 std::string::size_type l2 = re.start();
424 std::string::size_type r = re.end();
426 // Concatenate the part of the input that was not matched.
427 output += input.substr(base, l2);
429 // Make sure the match had some text.
430 if(r-l2 == 0)
432 std::string e = "sub-command REGEX, mode REPLACE regex \""+
433 regex+"\" matched an empty string.";
434 this->SetError(e.c_str());
435 return false;
438 // Concatenate the replacement for the match.
439 for(unsigned int i=0; i < replacement.size(); ++i)
441 if(replacement[i].number < 0)
443 // This is just a plain-text part of the replacement.
444 output += replacement[i].value;
446 else
448 // Replace with part of the match.
449 int n = replacement[i].number;
450 std::string::size_type start = re.start(n);
451 std::string::size_type end = re.end(n);
452 std::string::size_type len = input.length()-base;
453 if((start != std::string::npos) && (end != std::string::npos) &&
454 (start <= len) && (end <= len))
456 output += input.substr(base+start, end-start);
458 else
460 std::string e =
461 "sub-command REGEX, mode REPLACE: replace expression \""+
462 replace+"\" contains an out-of-range escape for regex \""+
463 regex+"\".";
464 this->SetError(e.c_str());
465 return false;
470 // Move past the match.
471 base += r;
474 // Concatenate the text after the last match.
475 output += input.substr(base, input.length()-base);
477 // Store the output in the provided variable.
478 this->Makefile->AddDefinition(outvar.c_str(), output.c_str());
479 return true;
482 //----------------------------------------------------------------------------
483 void cmStringCommand::ClearMatches(cmMakefile* mf)
485 for (unsigned int i=0; i<10; i++)
487 char name[128];
488 sprintf(name, "CMAKE_MATCH_%d", i);
489 mf->AddDefinition(name, "");
493 //----------------------------------------------------------------------------
494 void cmStringCommand::StoreMatches(cmMakefile* mf,cmsys::RegularExpression& re)
496 for (unsigned int i=0; i<10; i++)
498 char name[128];
499 sprintf(name, "CMAKE_MATCH_%d", i);
500 mf->AddDefinition(name, re.match(i).c_str());
504 //----------------------------------------------------------------------------
505 bool cmStringCommand::HandleCompareCommand(std::vector<std::string> const&
506 args)
508 if(args.size() < 2)
510 this->SetError("sub-command COMPARE requires a mode to be specified.");
511 return false;
513 std::string mode = args[1];
514 if((mode == "EQUAL") || (mode == "NOTEQUAL") ||
515 (mode == "LESS") || (mode == "GREATER"))
517 if(args.size() < 5)
519 std::string e = "sub-command COMPARE, mode ";
520 e += mode;
521 e += " needs at least 5 arguments total to command.";
522 this->SetError(e.c_str());
523 return false;
526 const std::string& left = args[2];
527 const std::string& right = args[3];
528 const std::string& outvar = args[4];
529 bool result;
530 if(mode == "LESS")
532 result = (left < right);
534 else if(mode == "GREATER")
536 result = (left > right);
538 else if(mode == "EQUAL")
540 result = (left == right);
542 else // if(mode == "NOTEQUAL")
544 result = !(left == right);
546 if(result)
548 this->Makefile->AddDefinition(outvar.c_str(), "1");
550 else
552 this->Makefile->AddDefinition(outvar.c_str(), "0");
554 return true;
556 std::string e = "sub-command COMPARE does not recognize mode "+mode;
557 this->SetError(e.c_str());
558 return false;
561 //----------------------------------------------------------------------------
562 bool cmStringCommand::HandleReplaceCommand(std::vector<std::string> const&
563 args)
565 if(args.size() < 5)
567 this->SetError("sub-command REPLACE requires four arguments.");
568 return false;
571 const std::string& matchExpression = args[1];
572 const std::string& replaceExpression = args[2];
573 const std::string& variableName = args[3];
575 std::string input = args[4];
576 for(unsigned int i=5; i < args.size(); ++i)
578 input += args[i];
581 cmsys::SystemTools::ReplaceString(input, matchExpression.c_str(),
582 replaceExpression.c_str());
584 this->Makefile->AddDefinition(variableName.c_str(), input.c_str());
585 return true;
588 //----------------------------------------------------------------------------
589 bool cmStringCommand::HandleSubstringCommand(std::vector<std::string> const&
590 args)
592 if(args.size() != 5)
594 this->SetError("sub-command REPLACE requires four arguments.");
595 return false;
598 const std::string& stringValue = args[1];
599 int begin = atoi(args[2].c_str());
600 int end = atoi(args[3].c_str());
601 const std::string& variableName = args[4];
603 size_t stringLength = stringValue.size();
604 int intStringLength = static_cast<int>(stringLength);
605 if ( begin < 0 || begin > intStringLength )
607 cmOStringStream ostr;
608 ostr << "begin index: " << begin << " is out of range 0 - "
609 << stringLength;
610 this->SetError(ostr.str().c_str());
611 return false;
613 int leftOverLength = intStringLength - begin;
614 if ( end < 0 || end > leftOverLength )
616 cmOStringStream ostr;
617 ostr << "end index: " << end << " is out of range " << 0 << " - "
618 << leftOverLength;
619 this->SetError(ostr.str().c_str());
620 return false;
623 this->Makefile->AddDefinition(variableName.c_str(),
624 stringValue.substr(begin, end).c_str());
625 return true;
628 //----------------------------------------------------------------------------
629 bool cmStringCommand
630 ::HandleLengthCommand(std::vector<std::string> const& args)
632 if(args.size() != 3)
634 this->SetError("sub-command LENGTH requires two arguments.");
635 return false;
638 const std::string& stringValue = args[1];
639 const std::string& variableName = args[2];
641 size_t length = stringValue.size();
642 char buffer[1024];
643 sprintf(buffer, "%d", static_cast<int>(length));
645 this->Makefile->AddDefinition(variableName.c_str(), buffer);
646 return true;
649 //----------------------------------------------------------------------------
650 bool cmStringCommand::HandleStripCommand(
651 std::vector<std::string> const& args)
653 if(args.size() != 3)
655 this->SetError("sub-command LENGTH requires two arguments.");
656 return false;
659 const std::string& stringValue = args[1];
660 const std::string& variableName = args[2];
661 size_t inStringLength = stringValue.size();
662 size_t startPos = inStringLength + 1;
663 size_t endPos = 0;
664 const char* ptr = stringValue.c_str();
665 size_t cc;
666 for ( cc = 0; cc < inStringLength; ++ cc )
668 if ( !isspace(*ptr) )
670 if ( startPos > inStringLength )
672 startPos = cc;
674 endPos = cc;
676 ++ ptr;
679 size_t outLength = 0;
681 // if the input string didn't contain any non-space characters, return
682 // an empty string
683 if (startPos > inStringLength)
685 outLength = 0;
686 startPos = 0;
688 else
690 outLength=endPos - startPos + 1;
693 this->Makefile->AddDefinition(variableName.c_str(),
694 stringValue.substr(startPos, outLength).c_str());
695 return true;
698 //----------------------------------------------------------------------------
699 bool cmStringCommand
700 ::HandleRandomCommand(std::vector<std::string> const& args)
702 if(args.size() < 2 || args.size() == 3 || args.size() == 5)
704 this->SetError("sub-command RANDOM requires at least one argument.");
705 return false;
708 int length = 5;
709 const char cmStringCommandDefaultAlphabet[] = "qwertyuiopasdfghjklzxcvbnm"
710 "QWERTYUIOPASDFGHJKLZXCVBNM"
711 "0123456789";
712 std::string alphabet;
714 if ( args.size() > 3 )
716 size_t i = 1;
717 size_t stopAt = args.size() - 2;
719 for ( ; i < stopAt; ++i )
721 if ( args[i] == "LENGTH" )
723 ++i;
724 length = atoi(args[i].c_str());
726 else if ( args[i] == "ALPHABET" )
728 ++i;
729 alphabet = args[i];
733 if ( !alphabet.size() )
735 alphabet = cmStringCommandDefaultAlphabet;
738 double sizeofAlphabet = alphabet.size();
739 if ( sizeofAlphabet < 1 )
741 this->SetError("sub-command RANDOM invoked with bad alphabet.");
742 return false;
744 if ( length < 1 )
746 this->SetError("sub-command RANDOM invoked with bad length.");
747 return false;
749 const std::string& variableName = args[args.size()-1];
751 std::vector<char> result;
752 srand((int)time(NULL));
753 const char* alphaPtr = alphabet.c_str();
754 int cc;
755 for ( cc = 0; cc < length; cc ++ )
757 int idx=(int) (sizeofAlphabet* rand()/(RAND_MAX+1.0));
758 result.push_back(*(alphaPtr + idx));
760 result.push_back(0);
762 this->Makefile->AddDefinition(variableName.c_str(), &*result.begin());
763 return true;