diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index c6452c8373..4756bac131 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -135,6 +135,8 @@ add_library( cmCMakePresetsGraphReadJSONPackagePresets.cxx cmCMakePresetsGraphReadJSONTestPresets.cxx cmCMakePresetsGraphReadJSONWorkflowPresets.cxx + cmCMakeString.hxx + cmCMakeString.cxx cmCommandLineArgument.h cmCommonTargetGenerator.cxx cmCommonTargetGenerator.h diff --git a/Source/cmCMakeString.cxx b/Source/cmCMakeString.cxx new file mode 100644 index 0000000000..57d5b0e4d0 --- /dev/null +++ b/Source/cmCMakeString.cxx @@ -0,0 +1,326 @@ +/* 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 +#include +#include +#include +#include + +#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 = 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(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(begin), + count == -1 ? npos + : static_cast(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 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(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(alphabet.size()); + std::vector result; + result.reserve(length + 1); + + for (std::size_t i = 0; i < length; i++) { + auto index = static_cast( + 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 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 +} + +} diff --git a/Source/cmCMakeString.hxx b/Source/cmCMakeString.hxx new file mode 100644 index 0000000000..3c29871007 --- /dev/null +++ b/Source/cmCMakeString.hxx @@ -0,0 +1,259 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ + +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include +#include +#include +#include + +#include + +#include "cmList.h" +#include "cmRange.h" +#include "cmString.hxx" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" +#include "cmValue.h" + +class cmMakefile; + +// +// Class offering various string operations which is used by +// * CMake string() command +// * $ generator expression +// + +namespace cm { + +class CMakeString +{ +public: + using size_type = cm::String::size_type; + static auto const npos = cm::String::npos; + + using string_range = cmRange::const_iterator>; + + CMakeString(std::string const& str) + : String_(cm::String::borrow(str)) + { + } + CMakeString(cm::string_view str) + : String_(cm::String::borrow(str)) + { + } + CMakeString(cm::String const& str) + : String_(str) + { + } + CMakeString(cm::String&& str) + : String_(std::move(str)) + { + } + CMakeString(cmValue value) + : String_(cm::String::borrow(*value)) + { + } + CMakeString(string_range range, + cm::string_view separator = cm::string_view{}) + : String_(cmJoin(range, separator)) + { + } + + CMakeString() = default; + CMakeString(CMakeString const&) = default; + CMakeString(CMakeString&&) = default; + + CMakeString& operator=(CMakeString const& string) + { + if (this != &string) { + this->String_ = string.String_; + } + return *this; + } + CMakeString& operator=(CMakeString&& string) noexcept + { + if (this != &string) { + this->String_ = std::move(string.String_); + } + return *this; + } + CMakeString& operator=(cm::string_view string) + { + this->String_ = string; + return *this; + } + + // conversions + string_view view() const noexcept { return this->String_.view(); } + operator cm::string_view() noexcept { return this->String_.view(); } + operator std::string const&() { return this->String_.str(); } + + size_type Size() const { return this->String_.size(); } + size_type Length() const { return this->String_.size(); } + + enum class CompOperator + { + EQUAL, + LESS, + LESS_EQUAL, + GREATER, + GREATER_EQUAL + }; + bool Compare(CompOperator op, cm::string_view other); + + enum class FindFrom + { + Begin, + End + }; + size_type Find(cm::string_view substring, + FindFrom from = FindFrom::Begin) const + { + return from == FindFrom::Begin ? this->String_.find(substring) + : this->String_.rfind(substring); + } + + enum class Regex + { + No, + Yes + }; + // Throw std::invalid_argument if regular expression is invalid + // std::runtime_error if replacement failed + CMakeString& Replace(std::string const& matchExpression, + std::string const& replaceExpression, + Regex regex = Regex::No, + cmMakefile* makefile = nullptr); + + enum class MatchItems + { + Once, + All + }; + // Throw std::invalid_argument if regular expression is invalid + cmList Match(std::string const& matchExpression, + MatchItems matchItems = MatchItems::Once, + cmMakefile* makefile = nullptr) const; + + CMakeString& Append(cm::string_view str) + { + this->String_.append(str); + return *this; + } + CMakeString& Append(string_range range) + { + this->Append(cmJoin(range, {})); + return *this; + } + CMakeString& Prepend(cm::string_view str) + { + this->String_.insert(0, str); + return *this; + } + CMakeString& Prepend(string_range range) + { + this->Prepend(cmJoin(range, {})); + return *this; + } + + CMakeString& ToLower() + { + this->String_ = cmSystemTools::LowerCase(this->String_); + return *this; + } + CMakeString& ToLower(cm::string_view str) + { + this->String_ = cmSystemTools::LowerCase(str); + return *this; + } + CMakeString& ToUpper() + { + this->String_ = cmSystemTools::UpperCase(this->String_); + return *this; + } + CMakeString& ToUpper(cm::string_view str) + { + this->String_ = cmSystemTools::UpperCase(str); + return *this; + } + + // Throw std::out_of_range if pos or count are outside of the expected range + CMakeString Substring(long pos = 0, long count = -1) const; + + enum class StripItems + { + Space, + Genex + }; + CMakeString& Strip(StripItems stripItems = StripItems::Space); + + // Throw std::runtime_error if quoting string failed + enum class QuoteItems + { + Regex + }; + CMakeString& Quote(QuoteItems quoteItems = QuoteItems::Regex); + + CMakeString& Repeat(size_type count); + + // Throw std::invalid_argument if the hash algorithm is invalid + CMakeString& Hash(cm::string_view hashAlgorithm); + + // Throw std::invalid_argument if one of the codes is invalid + CMakeString& FromASCII(string_range codes); + CMakeString& ToHexadecimal() { return this->ToHexadecimal(this->String_); } + CMakeString& ToHexadecimal(cm::string_view str); + + CMakeString& MakeCIdentifier() + { + this->String_ = cmSystemTools::MakeCidentifier(this->String_.str()); + return *this; + } + CMakeString& MakeCIdentifier(std::string const& str) + { + this->String_ = cmSystemTools::MakeCidentifier(str); + return *this; + } + + static cm::string_view const RandomDefaultAlphabet; + // Throw std::invalid_argument if the alphabet is invalid + // std::out_of_range if length is outside valid range + CMakeString& Random(std::size_t length = 5, + cm::string_view alphabet = RandomDefaultAlphabet) + { + return this->Random(cmSystemTools::RandomSeed(), length, alphabet); + } + CMakeString& Random(unsigned int seed, std::size_t length = 5, + cm::string_view alphabet = RandomDefaultAlphabet); + + enum class UTC + { + No, + Yes + }; + CMakeString& Timestamp(cm::string_view format, UTC utc = UTC::No); + + enum class UUIDType + { + MD5, + SHA1 + }; + enum class Case + { + Lower, + Upper + }; + // Throw std::invalid_argument if namespace or the type are invalid + // std::runtime_error if the UUID cannot be generated + CMakeString& UUID(cm::string_view nameSpace, cm::string_view name, + UUIDType type, Case uuidCase = Case::Lower); + +private: + static bool Seeded; + cm::String String_; +}; +} diff --git a/Source/cmStringCommand.cxx b/Source/cmStringCommand.cxx index 0503033693..1631b0208a 100644 --- a/Source/cmStringCommand.cxx +++ b/Source/cmStringCommand.cxx @@ -5,9 +5,9 @@ #include "cmStringCommand.h" -#include #include #include +#include #include #include #include @@ -22,22 +22,14 @@ #include #include -#include "cmsys/RegularExpression.hxx" - -#include "cmCryptoHash.h" +#include "cmCMakeString.hxx" #include "cmExecutionStatus.h" -#include "cmGeneratorExpression.h" +#include "cmList.h" #include "cmMakefile.h" #include "cmMessageType.h" -#include "cmPolicies.h" #include "cmRange.h" #include "cmStringAlgorithms.h" -#include "cmStringReplaceHelper.h" #include "cmSubcommandTable.h" -#include "cmSystemTools.h" -#include "cmTimestamp.h" -#include "cmUuid.h" -#include "cmValue.h" namespace { @@ -62,13 +54,16 @@ bool HandleHashCommand(std::vector const& args, return false; } - std::unique_ptr hash(cmCryptoHash::New(args[0])); - if (hash) { - std::string out = hash->HashString(args[2]); - status.GetMakefile().AddDefinition(args[1], out); + cm::CMakeString data{ args[2] }; + + try { + data.Hash(args[0]); + status.GetMakefile().AddDefinition(args[1], data); return true; + } catch (std::exception const& e) { + status.SetError(e.what()); + return false; } - return false; } bool HandleToUpperLowerCommand(std::vector const& args, @@ -80,16 +75,16 @@ bool HandleToUpperLowerCommand(std::vector const& args, } std::string const& outvar = args[2]; - std::string output; + cm::CMakeString data{ args[1] }; if (toUpper) { - output = cmSystemTools::UpperCase(args[1]); + data.ToUpper(); } else { - output = cmSystemTools::LowerCase(args[1]); + data.ToLower(); } // Store the output in the provided variable. - status.GetMakefile().AddDefinition(outvar, output); + status.GetMakefile().AddDefinition(outvar, data); return true; } @@ -112,23 +107,17 @@ bool HandleAsciiCommand(std::vector const& args, status.SetError("No output variable specified"); return false; } - std::string::size_type cc; - std::string const& outvar = args.back(); - std::string output; - for (cc = 1; cc < args.size() - 1; cc++) { - int ch = atoi(args[cc].c_str()); - if (ch > 0 && ch < 256) { - output += static_cast(ch); - } else { - std::string error = - cmStrCat("Character with code ", args[cc], " does not exist."); - status.SetError(error); - return false; - } + + try { + std::string const& outvar = args.back(); + cm::CMakeString data; + data.FromASCII(cmMakeRange(args).advance(1).retreat(1)); + status.GetMakefile().AddDefinition(outvar, data); + return true; + } catch (std::exception const& e) { + status.SetError(e.what()); + return false; } - // Store the output in the provided variable. - status.GetMakefile().AddDefinition(outvar, output); - return true; } bool HandleHexCommand(std::vector const& args, @@ -138,17 +127,13 @@ bool HandleHexCommand(std::vector const& args, status.SetError("Incorrect number of arguments"); return false; } - auto const& instr = args[1]; + auto const& outvar = args[2]; - std::string output(instr.size() * 2, ' '); + cm::CMakeString data{ args[1] }; - std::string::size_type hexIndex = 0; - for (auto const& c : instr) { - snprintf(&output[hexIndex], 3, "%.2x", c & 0xFFu); - hexIndex += 2; - } + data.ToHexadecimal(); - status.GetMakefile().AddDefinition(outvar, output); + status.GetMakefile().AddDefinition(outvar, data); return true; } @@ -239,33 +224,21 @@ bool RegexMatch(std::vector const& args, { //"STRING(REGEX MATCH // [...])\n"; - std::string const& regex = args[2]; - std::string const& outvar = args[3]; + try { + std::string const& regex = args[2]; + std::string const& outvar = args[3]; + cm::CMakeString data{ cmMakeRange(args).advance(4) }; - status.GetMakefile().ClearMatches(); - // Compile the regular expression. - cmsys::RegularExpression re; - if (!re.compile(regex)) { - std::string e = - "sub-command REGEX, mode MATCH failed to compile regex \"" + regex + - "\"."; - status.SetError(e); + auto result = data.Match(regex, cm::CMakeString::MatchItems::Once, + &status.GetMakefile()); + // Store the result in the provided variable. + status.GetMakefile().AddDefinition(outvar, result.to_string()); + return true; + } catch (std::exception const& e) { + status.SetError( + cmStrCat("sub-command REGEX, mode MATCH: ", e.what(), '.')); return false; } - - // Concatenate all the last arguments together. - std::string input = cmJoin(cmMakeRange(args).advance(4), std::string()); - - // Scan through the input for all matches. - std::string output; - if (re.find(input)) { - status.GetMakefile().StoreMatches(re); - output = re.match(); - } - - // Store the output in the provided variable. - status.GetMakefile().AddDefinition(outvar, output); - return true; } bool RegexMatchAll(std::vector const& args, @@ -273,55 +246,21 @@ bool RegexMatchAll(std::vector const& args, { //"STRING(REGEX MATCHALL // [...])\n"; - std::string const& regex = args[2]; - std::string const& outvar = args[3]; + try { + std::string const& regex = args[2]; + std::string const& outvar = args[3]; + cm::CMakeString data{ cmMakeRange(args).advance(4) }; - status.GetMakefile().ClearMatches(); - // Compile the regular expression. - cmsys::RegularExpression re; - if (!re.compile(regex)) { - std::string e = - "sub-command REGEX, mode MATCHALL failed to compile regex \"" + regex + - "\"."; - status.SetError(e); + auto result = data.Match(regex, cm::CMakeString::MatchItems::All, + &status.GetMakefile()); + // Store the result in the provided variable. + status.GetMakefile().AddDefinition(outvar, result.to_string()); + return true; + } catch (std::exception const& e) { + status.SetError( + cmStrCat("sub-command REGEX, mode MATCHALL: ", e.what(), '.')); return false; } - - // Concatenate all the last arguments together. - std::string input = cmJoin(cmMakeRange(args).advance(4), std::string()); - - unsigned optAnchor = 0; - if (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0186) != - cmPolicies::NEW) { - optAnchor = cmsys::RegularExpression::BOL_AT_OFFSET; - } - - // Scan through the input for all matches. - std::string output; - std::string::size_type base = 0; - unsigned optNonEmpty = 0; - while (re.find(input, base, optAnchor | optNonEmpty)) { - status.GetMakefile().ClearMatches(); - status.GetMakefile().StoreMatches(re); - if (!output.empty() || optNonEmpty) { - output += ";"; - } - output += re.match(); - base = re.end(); - - if (re.start() == input.length()) { - break; - } - if (re.start() == re.end()) { - optNonEmpty = cmsys::RegularExpression::NONEMPTY_AT_OFFSET; - } else { - optNonEmpty = 0; - } - } - - // Store the output in the provided variable. - status.GetMakefile().AddDefinition(outvar, output); - return true; } bool RegexReplace(std::vector const& args, @@ -332,38 +271,20 @@ bool RegexReplace(std::vector const& args, std::string const& regex = args[2]; std::string const& replace = args[3]; std::string const& outvar = args[4]; - cmStringReplaceHelper replaceHelper(regex, replace, &status.GetMakefile()); - if (!replaceHelper.IsReplaceExpressionValid()) { + try { + cm::CMakeString data{ cmMakeRange(args).advance(5) }; + + data.Replace(regex, replace, cm::CMakeString::Regex::Yes, + &status.GetMakefile()); + // Store the result in the provided variable. + status.GetMakefile().AddDefinition(outvar, data); + return true; + } catch (std::exception const& e) { status.SetError( - "sub-command REGEX, mode REPLACE: " + replaceHelper.GetError() + "."); + cmStrCat("sub-command REGEX, mode REPLACE: ", e.what(), '.')); return false; } - - status.GetMakefile().ClearMatches(); - - if (!replaceHelper.IsRegularExpressionValid()) { - std::string e = - "sub-command REGEX, mode REPLACE failed to compile regex \"" + regex + - "\"."; - status.SetError(e); - return false; - } - - // Concatenate all the last arguments together. - std::string const input = - cmJoin(cmMakeRange(args).advance(5), std::string()); - std::string output; - - if (!replaceHelper.Replace(input, output)) { - status.SetError( - "sub-command REGEX, mode REPLACE: " + replaceHelper.GetError() + "."); - return false; - } - - // Store the output in the provided variable. - status.GetMakefile().AddDefinition(outvar, output); - return true; } bool RegexQuote(std::vector const& args, @@ -371,21 +292,19 @@ bool RegexQuote(std::vector const& args, { //"STRING(REGEX QUOTE [...]\n" std::string const& outvar = args[2]; - std::string const input = - cmJoin(cmMakeRange(args).advance(3), std::string()); - std::string output; + cm::CMakeString data{ cmMakeRange(args).advance(3) }; - // Escape all regex special characters - cmStringReplaceHelper replaceHelper("([][()+*^.$?|\\\\])", R"(\\\1)"); - if (!replaceHelper.Replace(input, output)) { + try { + // Escape all regex special characters + data.Quote(); + // Store the output in the provided variable. + status.GetMakefile().AddDefinition(outvar, data); + return true; + } catch (std::exception const& e) { status.SetError( - "sub-command REGEX, mode QUOTE: " + replaceHelper.GetError() + "."); + cmStrCat("sub-command REGEX, mode QUOTE: ", e.what(), '.')); return false; } - - // Store the output in the provided variable. - status.GetMakefile().AddDefinition(outvar, output); - return true; } bool HandleFindCommand(std::vector const& args, @@ -423,19 +342,14 @@ bool HandleFindCommand(std::vector const& args, } // try to find the character and return its position - size_t pos; - if (!reverseMode) { - pos = sstring.find(schar); - } else { - pos = sstring.rfind(schar); - } - if (std::string::npos != pos) { - status.GetMakefile().AddDefinition(outvar, std::to_string(pos)); - return true; - } + auto pos = cm::CMakeString{ sstring }.Find( + schar, + reverseMode ? cm::CMakeString::FindFrom::End + : cm::CMakeString::FindFrom::Begin); + + status.GetMakefile().AddDefinition( + outvar, pos != cm::CMakeString::npos ? std::to_string(pos) : "-1"); - // the character was not found, but this is not really an error - status.GetMakefile().AddDefinition(outvar, "-1"); return true; } @@ -462,25 +376,22 @@ bool HandleCompareCommand(std::vector const& args, std::string const& right = args[3]; std::string const& outvar = args[4]; bool result; + cm::CMakeString::CompOperator op = cm::CMakeString::CompOperator::EQUAL; if (mode == "LESS") { - result = (left < right); + op = cm::CMakeString::CompOperator::LESS; } else if (mode == "LESS_EQUAL") { - result = (left <= right); + op = cm::CMakeString::CompOperator::LESS_EQUAL; } else if (mode == "GREATER") { - result = (left > right); + op = cm::CMakeString::CompOperator::GREATER; } else if (mode == "GREATER_EQUAL") { - result = (left >= right); - } else if (mode == "EQUAL") { - result = (left == right); - } else // if(mode == "NOTEQUAL") - { - result = !(left == right); + op = cm::CMakeString::CompOperator::GREATER_EQUAL; } - if (result) { - status.GetMakefile().AddDefinition(outvar, "1"); - } else { - status.GetMakefile().AddDefinition(outvar, "0"); + result = cm::CMakeString{ left }.Compare(op, right); + if (mode == "NOTEQUAL") { + result = !result; } + + status.GetMakefile().AddDefinition(outvar, result ? "1" : "0"); return true; } std::string e = "sub-command COMPARE does not recognize mode " + mode; @@ -496,17 +407,19 @@ bool HandleReplaceCommand(std::vector const& args, return false; } - std::string const& matchExpression = args[1]; - std::string const& replaceExpression = args[2]; - std::string const& variableName = args[3]; + try { + std::string const& matchExpression = args[1]; + std::string const& replaceExpression = args[2]; + std::string const& variableName = args[3]; + cm::CMakeString data{ cmMakeRange(args).advance(4) }; - std::string input = cmJoin(cmMakeRange(args).advance(4), std::string()); - - cmsys::SystemTools::ReplaceString(input, matchExpression.c_str(), - replaceExpression.c_str()); - - status.GetMakefile().AddDefinition(variableName, input); - return true; + data.Replace(matchExpression, replaceExpression); + status.GetMakefile().AddDefinition(variableName, data); + return true; + } catch (std::exception const& e) { + status.SetError(cmStrCat("sub-command REPLACE: ", e.what(), '.')); + return false; + } } bool HandleSubstringCommand(std::vector const& args, @@ -517,25 +430,19 @@ bool HandleSubstringCommand(std::vector const& args, return false; } - std::string const& stringValue = args[1]; - int begin = atoi(args[2].c_str()); - int end = atoi(args[3].c_str()); - std::string const& variableName = args[4]; + try { + std::string const& stringValue = args[1]; + int begin = atoi(args[2].c_str()); + int end = atoi(args[3].c_str()); + std::string const& variableName = args[4]; - size_t stringLength = stringValue.size(); - int intStringLength = static_cast(stringLength); - if (begin < 0 || begin > intStringLength) { - status.SetError( - cmStrCat("begin index: ", begin, " is out of range 0 - ", stringLength)); + cm::CMakeString data{ stringValue }; + status.GetMakefile().AddDefinition(variableName, + data.Substring(begin, end)); + } catch (std::exception const& e) { + status.SetError(e.what()); return false; } - if (end < -1) { - status.SetError(cmStrCat("end index: ", end, " should be -1 or greater")); - return false; - } - - status.GetMakefile().AddDefinition(variableName, - stringValue.substr(begin, end)); return true; } @@ -550,11 +457,8 @@ bool HandleLengthCommand(std::vector const& args, std::string const& stringValue = args[1]; std::string const& variableName = args[2]; - size_t length = stringValue.size(); - char buffer[1024]; - snprintf(buffer, sizeof(buffer), "%d", static_cast(length)); - - status.GetMakefile().AddDefinition(variableName, buffer); + status.GetMakefile().AddDefinition( + variableName, std::to_string(cm::CMakeString{ stringValue }.Length())); return true; } @@ -572,12 +476,10 @@ bool HandleAppendCommand(std::vector const& args, } auto const& variableName = args[1]; + cm::CMakeString data{ status.GetMakefile().GetDefinition(variableName) }; - cm::string_view oldView{ status.GetMakefile().GetSafeDefinition( - variableName) }; - - auto const newValue = cmJoin(cmMakeRange(args).advance(2), {}, oldView); - status.GetMakefile().AddDefinition(variableName, newValue); + data.Append(cmMakeRange(args).advance(2)); + status.GetMakefile().AddDefinition(variableName, data); return true; } @@ -596,13 +498,10 @@ bool HandlePrependCommand(std::vector const& args, } std::string const& variable = args[1]; + cm::CMakeString data{ status.GetMakefile().GetDefinition(variable) }; - std::string value = cmJoin(cmMakeRange(args).advance(2), std::string()); - cmValue oldValue = status.GetMakefile().GetDefinition(variable); - if (oldValue) { - value += *oldValue; - } - status.GetMakefile().AddDefinition(variable, value); + data.Prepend(cmMakeRange(args).advance(2)); + status.GetMakefile().AddDefinition(variable, data); return true; } @@ -634,9 +533,9 @@ bool joinImpl(std::vector const& args, std::string const& glue, std::string const& variableName = args[varIdx]; // NOTE Items to concat/join placed right after the variable for // both `CONCAT` and `JOIN` sub-commands. - std::string value = cmJoin(cmMakeRange(args).advance(varIdx + 1), glue); + cm::CMakeString data{ cmMakeRange(args).advance(varIdx + 1), glue }; - makefile.AddDefinition(variableName, value); + makefile.AddDefinition(variableName, data); return true; } @@ -652,7 +551,7 @@ bool HandleMakeCIdentifierCommand(std::vector const& args, std::string const& variableName = args[2]; status.GetMakefile().AddDefinition(variableName, - cmSystemTools::MakeCidentifier(input)); + cm::CMakeString{}.MakeCIdentifier(input)); return true; } @@ -664,14 +563,11 @@ bool HandleGenexStripCommand(std::vector const& args, return false; } - std::string const& input = args[1]; - - std::string result = cmGeneratorExpression::Preprocess( - input, cmGeneratorExpression::StripAllGeneratorExpressions); - + cm::CMakeString data{ args[1] }; std::string const& variableName = args[2]; - status.GetMakefile().AddDefinition(variableName, result); + status.GetMakefile().AddDefinition( + variableName, data.Strip(cm::CMakeString::StripItems::Genex)); return true; } @@ -683,36 +579,10 @@ bool HandleStripCommand(std::vector const& args, return false; } - std::string const& stringValue = args[1]; + cm::CMakeString data{ args[1] }; std::string const& variableName = args[2]; - size_t inStringLength = stringValue.size(); - size_t startPos = inStringLength + 1; - size_t endPos = 0; - char const* ptr = stringValue.c_str(); - size_t cc; - for (cc = 0; cc < inStringLength; ++cc) { - if (!cmIsSpace(*ptr)) { - if (startPos > inStringLength) { - startPos = cc; - } - endPos = cc; - } - ++ptr; - } - size_t outLength = 0; - - // if the input string didn't contain any non-space characters, return - // an empty string - if (startPos > inStringLength) { - outLength = 0; - startPos = 0; - } else { - outLength = endPos - startPos + 1; - } - - status.GetMakefile().AddDefinition(variableName, - stringValue.substr(startPos, outLength)); + status.GetMakefile().AddDefinition(variableName, data.Strip()); return true; } @@ -744,30 +614,12 @@ bool HandleRepeatCommand(std::vector const& args, return true; } - auto const& stringValue = args[ArgPos::VALUE]; + cm::CMakeString data{ args[ArgPos::VALUE] }; + data.Repeat(times); + auto const& variableName = args[ArgPos::OUTPUT_VARIABLE]; - auto const inStringLength = stringValue.size(); - std::string result; - switch (inStringLength) { - 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. - result = std::string(times, stringValue[0]); - break; - default: - result = std::string(inStringLength * times, char{}); - for (auto i = 0u; i < times; ++i) { - std::copy(cm::cbegin(stringValue), cm::cend(stringValue), - &result[i * inStringLength]); - } - break; - } - - makefile.AddDefinition(variableName, result); + makefile.AddDefinition(variableName, data); return true; } @@ -779,14 +631,10 @@ bool HandleRandomCommand(std::vector const& args, return false; } - static bool seeded = false; + int length = 5; + cm::string_view alphabet; bool force_seed = false; unsigned int seed = 0; - int length = 5; - char const cmStringCommandDefaultAlphabet[] = "qwertyuiopasdfghjklzxcvbnm" - "QWERTYUIOPASDFGHJKLZXCVBNM" - "0123456789"; - std::string alphabet; if (args.size() > 3) { size_t i = 1; @@ -806,37 +654,22 @@ bool HandleRandomCommand(std::vector const& args, } } } - if (alphabet.empty()) { - alphabet = cmStringCommandDefaultAlphabet; - } - double sizeofAlphabet = static_cast(alphabet.size()); - if (sizeofAlphabet < 1) { - status.SetError("sub-command RANDOM invoked with bad alphabet."); + try { + cm::CMakeString data; + std::string const& variableName = args.back(); + + if (force_seed) { + data.Random(seed, length, alphabet); + } else { + data.Random(length, alphabet); + } + status.GetMakefile().AddDefinition(variableName, data); + return true; + } catch (std::exception const& e) { + status.SetError(cmStrCat("sub-command RANDOM: ", e.what(), '.')); return false; } - if (length < 1) { - status.SetError("sub-command RANDOM invoked with bad length."); - return false; - } - std::string const& variableName = args.back(); - - std::vector result; - - if (!seeded || force_seed) { - seeded = true; - srand(force_seed ? seed : cmSystemTools::RandomSeed()); - } - - char const* alphaPtr = alphabet.c_str(); - for (int cc = 0; cc < length; cc++) { - int idx = static_cast(sizeofAlphabet * rand() / (RAND_MAX + 1.0)); - result.push_back(*(alphaPtr + idx)); - } - result.push_back(0); - - status.GetMakefile().AddDefinition(variableName, result.data()); - return true; } bool HandleTimestampCommand(std::vector const& args, @@ -855,26 +688,28 @@ bool HandleTimestampCommand(std::vector const& args, std::string const& outputVariable = args[argsIndex++]; - std::string formatString; + cm::string_view formatString; if (args.size() > argsIndex && args[argsIndex] != "UTC") { formatString = args[argsIndex++]; } - bool utcFlag = false; + cm::CMakeString::UTC utcFlag = cm::CMakeString::UTC::No; if (args.size() > argsIndex) { if (args[argsIndex] == "UTC") { - utcFlag = true; + utcFlag = cm::CMakeString::UTC::Yes; } else { - std::string e = " TIMESTAMP sub-command does not recognize option " + - args[argsIndex] + "."; + std::string e = + cmStrCat(" TIMESTAMP sub-command does not recognize option ", + args[argsIndex], '.'); status.SetError(e); return false; } } - cmTimestamp timestamp; - std::string result = timestamp.CurrentTime(formatString, utcFlag); - status.GetMakefile().AddDefinition(outputVariable, result); + cm::CMakeString data; + + status.GetMakefile().AddDefinition(outputVariable, + data.Timestamp(formatString, utcFlag)); return true; } @@ -892,10 +727,10 @@ bool HandleUuidCommand(std::vector const& args, std::string const& outputVariable = args[argsIndex++]; - std::string uuidNamespaceString; - std::string uuidName; - std::string uuidType; - bool uuidUpperCase = false; + cm::string_view uuidNamespaceString; + cm::string_view uuidName; + cm::CMakeString::UUIDType uuidType = cm::CMakeString::UUIDType::MD5; + cm::CMakeString::Case uuidCase = cm::CMakeString::Case::Lower; while (args.size() > argsIndex) { if (args[argsIndex] == "NAMESPACE") { @@ -918,50 +753,39 @@ bool HandleUuidCommand(std::vector const& args, status.SetError("UUID sub-command, TYPE requires a value."); return false; } - uuidType = args[argsIndex++]; + if (args[argsIndex] == "MD5") { + uuidType = cm::CMakeString::UUIDType::MD5; + } else if (args[argsIndex] == "SHA1") { + uuidType = cm::CMakeString::UUIDType::SHA1; + } else { + status.SetError( + cmStrCat("UUID sub-command, unknown TYPE '", args[argsIndex], "'.")); + return false; + } + argsIndex++; } else if (args[argsIndex] == "UPPER") { ++argsIndex; - uuidUpperCase = true; + uuidCase = cm::CMakeString::Case::Upper; } else { - std::string e = - "UUID sub-command does not recognize option " + args[argsIndex] + "."; + std::string e = cmStrCat("UUID sub-command does not recognize option ", + args[argsIndex], '.'); status.SetError(e); return false; } } - std::string uuid; - cmUuid uuidGenerator; + try { + cm::CMakeString data; - std::vector uuidNamespace; - if (!uuidGenerator.StringToBinary(uuidNamespaceString, uuidNamespace)) { - status.SetError("UUID sub-command, malformed NAMESPACE UUID."); + data.UUID(uuidNamespaceString, uuidName, uuidType, uuidCase); + status.GetMakefile().AddDefinition(outputVariable, data); + return true; + } catch (std::exception const& e) { + status.SetError(cmStrCat("UUID sub-command, ", e.what(), '.')); return false; } - - if (uuidType == "MD5") { - uuid = uuidGenerator.FromMd5(uuidNamespace, uuidName); - } else if (uuidType == "SHA1") { - uuid = uuidGenerator.FromSha1(uuidNamespace, uuidName); - } else { - std::string e = "UUID sub-command, unknown TYPE '" + uuidType + "'."; - status.SetError(e); - return false; - } - - if (uuid.empty()) { - status.SetError("UUID sub-command, generation failed."); - return false; - } - - if (uuidUpperCase) { - uuid = cmSystemTools::UpperCase(uuid); - } - - status.GetMakefile().AddDefinition(outputVariable, uuid); - return true; #else - status.SetError(cmStrCat(args[0], " not available during bootstrap")); + status.SetError("UUID sub-command not available during bootstrap."); return false; #endif } diff --git a/bootstrap b/bootstrap index 12ba171c1c..b7d608d021 100755 --- a/bootstrap +++ b/bootstrap @@ -314,6 +314,7 @@ CMAKE_CXX_SOURCES="\ cmCMakePath \ cmCMakePathCommand \ cmCMakePolicyCommand \ + cmCMakeString \ cmCPackPropertiesGenerator \ cmCacheManager \ cmCommands \