| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmConfigure.h" // IWYU pragma: keep | |
| #include "cmCMakeString.hxx" | |
| #include <cstdio> | |
| #include <cstdlib> | |
| #include <memory> | |
| #include <stdexcept> | |
| #include <vector> | |
| #include "cmsys/RegularExpression.hxx" | |
| #include "cmCryptoHash.h" | |
| #include "cmGeneratorExpression.h" | |
| #include "cmMakefile.h" | |
| #include "cmPolicies.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmStringReplaceHelper.h" | |
| #include "cmSystemTools.h" | |
| #include "cmTimestamp.h" | |
| #include "cmUuid.h" | |
| namespace cm { | |
| bool CMakeString::Compare(CompOperator op, cm::string_view other) | |
| { | |
| switch (op) { | |
| case CompOperator::EQUAL: | |
| return this->String_ == other; | |
| case CompOperator::LESS: | |
| return this->String_ < other; | |
| case CompOperator::LESS_EQUAL: | |
| return this->String_ <= other; | |
| case CompOperator::GREATER: | |
| return this->String_ > other; | |
| case CompOperator::GREATER_EQUAL: | |
| return this->String_ >= other; | |
| default: | |
| return false; | |
| } | |
| } | |
| CMakeString& CMakeString::Replace(std::string const& matchExpression, | |
| std::string const& replaceExpression, | |
| Regex regex, cmMakefile* makefile) | |
| { | |
| if (regex == Regex::Yes) { | |
| if (makefile) { | |
| makefile->ClearMatches(); | |
| } | |
| cmStringReplaceHelper replaceHelper(matchExpression, replaceExpression, | |
| makefile); | |
| if (!replaceHelper.IsReplaceExpressionValid()) { | |
| throw std::invalid_argument(replaceHelper.GetError()); | |
| } | |
| if (!replaceHelper.IsRegularExpressionValid()) { | |
| throw std::invalid_argument( | |
| cmStrCat("Failed to compile regex \"", matchExpression, '"')); | |
| } | |
| std ::string output; | |
| if (!replaceHelper.Replace(this->String_, output)) { | |
| throw std::runtime_error(replaceHelper.GetError()); | |
| } | |
| this->String_ = std::move(output); | |
| } else { | |
| std::string output = this->String_.str(); | |
| cmsys::SystemTools::ReplaceString(output, matchExpression, | |
| replaceExpression); | |
| this->String_ = std::move(output); | |
| } | |
| return *this; | |
| }; | |
| cmList CMakeString::Match(std::string const& matchExpression, | |
| MatchItems matchItems, cmMakefile* makefile) const | |
| { | |
| if (makefile) { | |
| makefile->ClearMatches(); | |
| } | |
| // Compile the regular expression. | |
| cmsys::RegularExpression re; | |
| if (!re.compile(matchExpression)) { | |
| throw std::invalid_argument( | |
| cmStrCat("Failed to compile regex \"", matchExpression, '"')); | |
| } | |
| cmList output; | |
| if (matchItems == MatchItems::Once) { | |
| if (re.find(this->String_.data())) { | |
| if (makefile) { | |
| makefile->StoreMatches(re); | |
| } | |
| output.push_back(re.match()); | |
| } | |
| } else { | |
| unsigned optAnchor = 0; | |
| if (makefile && | |
| makefile->GetPolicyStatus(cmPolicies::CMP0186) != cmPolicies::NEW) { | |
| optAnchor = cmsys::RegularExpression::BOL_AT_OFFSET; | |
| } | |
| // Scan through the input for all matches. | |
| std::string::size_type base = 0; | |
| unsigned optNonEmpty = 0; | |
| while (re.find(this->String_.data(), base, optAnchor | optNonEmpty)) { | |
| if (makefile) { | |
| makefile->ClearMatches(); | |
| makefile->StoreMatches(re); | |
| } | |
| output.push_back(re.match()); | |
| base = re.end(); | |
| if (re.start() == this->String_.length()) { | |
| break; | |
| } | |
| if (re.start() == re.end()) { | |
| optNonEmpty = cmsys::RegularExpression::NONEMPTY_AT_OFFSET; | |
| } else { | |
| optNonEmpty = 0; | |
| } | |
| } | |
| } | |
| return output; | |
| } | |
| CMakeString CMakeString::Substring(long begin, long count) const | |
| { | |
| if (begin < 0 || static_cast<size_type>(begin) > this->String_.size()) { | |
| throw std::out_of_range(cmStrCat( | |
| "begin index: ", begin, " is out of range 0 - ", this->String_.size())); | |
| } | |
| if (count < -1) { | |
| throw std::out_of_range( | |
| cmStrCat("end index: ", count, " should be -1 or greater")); | |
| } | |
| return this->String_.substr(static_cast<size_type>(begin), | |
| count == -1 ? npos | |
| : static_cast<size_type>(count)); | |
| } | |
| CMakeString& CMakeString::Strip(StripItems stripItems) | |
| { | |
| if (stripItems == StripItems::Space) { | |
| this->String_ = cmTrimWhitespace(this->String_); | |
| } else { | |
| this->String_ = cmGeneratorExpression::Preprocess( | |
| this->String_, cmGeneratorExpression::StripAllGeneratorExpressions); | |
| } | |
| return *this; | |
| } | |
| CMakeString& CMakeString::Repeat(size_type count) | |
| { | |
| switch (this->Size()) { | |
| case 0u: | |
| // Nothing to do for zero length input strings | |
| break; | |
| case 1u: | |
| // NOTE If the string to repeat consists of the only character, | |
| // use the appropriate constructor. | |
| this->String_ = std::string(count, this->String_[0]); | |
| break; | |
| default: | |
| std::string result; | |
| auto size = this->Size(); | |
| result.reserve(size * count); | |
| for (auto i = 0u; i < count; ++i) { | |
| result.insert(i * size, this->String_.data(), size); | |
| } | |
| this->String_ = std::move(result); | |
| break; | |
| } | |
| return *this; | |
| } | |
| CMakeString& CMakeString::Quote(QuoteItems) | |
| { | |
| std ::string output; | |
| // Escape all regex special characters | |
| cmStringReplaceHelper replaceHelper("([][()+*^.$?|\\\\])", R"(\\\1)"); | |
| if (!replaceHelper.Replace(this->String_, output)) { | |
| throw std::runtime_error(replaceHelper.GetError()); | |
| } | |
| this->String_ = std::move(output); | |
| return *this; | |
| } | |
| CMakeString& CMakeString::Hash(cm::string_view hashAlgorithm) | |
| { | |
| std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(hashAlgorithm)); | |
| if (hash) { | |
| this->String_ = hash->HashString(this->String_); | |
| return *this; | |
| } | |
| throw std::invalid_argument( | |
| cmStrCat(hashAlgorithm, ": invalid hash algorithm.")); | |
| } | |
| CMakeString& CMakeString::FromASCII(string_range codes) | |
| { | |
| std::string output; | |
| output.reserve(codes.size()); | |
| for (auto const& code : codes) { | |
| try { | |
| auto ch = std::stoi(code); | |
| if (ch > 0 && ch < 256) { | |
| output += static_cast<char>(ch); | |
| } else { | |
| throw std::invalid_argument( | |
| cmStrCat("Character with code ", code, " does not exist.")); | |
| } | |
| } catch (...) { | |
| throw std::invalid_argument( | |
| cmStrCat("Character with code ", code, " does not exist.")); | |
| } | |
| } | |
| this->String_ = std::move(output); | |
| return *this; | |
| } | |
| CMakeString& CMakeString::ToHexadecimal(cm::string_view str) | |
| { | |
| std::string output(str.size() * 2, ' '); | |
| std::string::size_type hexIndex = 0; | |
| for (auto const& c : str) { | |
| std::snprintf(&output[hexIndex], 3, "%.2x", c & 0xFFu); | |
| hexIndex += 2; | |
| } | |
| this->String_ = output; | |
| return *this; | |
| } | |
| cm::string_view const CMakeString::RandomDefaultAlphabet{ | |
| "qwertyuiopasdfghjklzxcvbnm" | |
| "QWERTYUIOPASDFGHJKLZXCVBNM" | |
| "0123456789" | |
| }; | |
| bool CMakeString::Seeded = false; | |
| CMakeString& CMakeString::Random(unsigned int seed, std::size_t length, | |
| cm::string_view alphabet) | |
| { | |
| if (alphabet.empty()) { | |
| alphabet = RandomDefaultAlphabet; | |
| } | |
| if (length < 1) { | |
| throw std::out_of_range("Invoked with bad length."); | |
| } | |
| if (!this->Seeded) { | |
| this->Seeded = true; | |
| std::srand(seed); | |
| } | |
| double alphabetSize = static_cast<double>(alphabet.size()); | |
| std::vector<char> result; | |
| result.reserve(length + 1); | |
| for (std::size_t i = 0; i < length; i++) { | |
| auto index = static_cast<std::string::size_type>( | |
| alphabetSize * std::rand() / (RAND_MAX + 1.0)); | |
| result.push_back(alphabet[index]); | |
| } | |
| result.push_back(0); | |
| this->String_ = result.data(); | |
| return *this; | |
| } | |
| CMakeString& CMakeString::Timestamp(cm::string_view format, UTC utc) | |
| { | |
| cmTimestamp timestamp; | |
| this->String_ = timestamp.CurrentTime(format, utc == UTC::Yes); | |
| return *this; | |
| } | |
| CMakeString& CMakeString::UUID(cm::string_view nameSpace, cm::string_view name, | |
| UUIDType type, Case uuidCase) | |
| { | |
| #if !defined(CMAKE_BOOTSTRAP) | |
| cmUuid uuidGenerator; | |
| std::vector<unsigned char> uuidNamespace; | |
| std::string uuid; | |
| if (!uuidGenerator.StringToBinary(nameSpace, uuidNamespace)) { | |
| throw std::invalid_argument("malformed NAMESPACE UUID"); | |
| } | |
| if (type == UUIDType::MD5) { | |
| uuid = uuidGenerator.FromMd5(uuidNamespace, name); | |
| } else if (type == UUIDType::SHA1) { | |
| uuid = uuidGenerator.FromSha1(uuidNamespace, name); | |
| } | |
| if (uuid.empty()) { | |
| throw std::runtime_error("generation failed"); | |
| } | |
| if (uuidCase == Case::Upper) { | |
| uuid = cmSystemTools::UpperCase(uuid); | |
| } | |
| this->String_ = std::move(uuid); | |
| return *this; | |
| #else | |
| throw std::runtime_error("not available during bootstrap"); | |
| #endif | |
| } | |
| } |