diff --git a/Help/command/string.rst b/Help/command/string.rst index d86efc5fcc..24351445fd 100644 --- a/Help/command/string.rst +++ b/Help/command/string.rst @@ -27,6 +27,7 @@ Synopsis string(`STRIP`_ ) string(`GENEX_STRIP`_ ) string(`REPEAT`_ ) + string(`REGEX QUOTE`_ ...) `Comparison`_ string(`COMPARE`_ ) @@ -290,6 +291,16 @@ Manipulation Produce the output string as the input ```` repeated ```` times. +.. signature:: + string(REGEX QUOTE ...) + + .. versionadded:: 4.2 + + Store in an ```` a regular expression matching the ````. + All characters that have special meaning in a regular expressions are + escaped, such that the output string can be used as part of a regular + expression to match the input literally. + Comparison ^^^^^^^^^^ diff --git a/Help/release/dev/string-regex-quote.rst b/Help/release/dev/string-regex-quote.rst new file mode 100644 index 0000000000..3795c7768e --- /dev/null +++ b/Help/release/dev/string-regex-quote.rst @@ -0,0 +1,5 @@ +string-regex-quote +------------------ + +* The :command:`string(REGEX QUOTE)` command was added to + generate a regular expression exactly matching a string. diff --git a/Source/cmStringCommand.cxx b/Source/cmStringCommand.cxx index 85721a3c92..0503033693 100644 --- a/Source/cmStringCommand.cxx +++ b/Source/cmStringCommand.cxx @@ -47,6 +47,8 @@ bool RegexMatchAll(std::vector const& args, cmExecutionStatus& status); bool RegexReplace(std::vector const& args, cmExecutionStatus& status); +bool RegexQuote(std::vector const& args, + cmExecutionStatus& status); bool joinImpl(std::vector const& args, std::string const& glue, size_t varIdx, cmMakefile& makefile); @@ -218,6 +220,14 @@ bool HandleRegexCommand(std::vector const& args, } return RegexReplace(args, status); } + if (mode == "QUOTE") { + if (args.size() < 4) { + status.SetError("sub-command REGEX, mode QUOTE needs " + "at least 4 arguments total to command."); + return false; + } + return RegexQuote(args, status); + } std::string e = "sub-command REGEX does not recognize mode " + mode; status.SetError(e); @@ -356,6 +366,28 @@ bool RegexReplace(std::vector const& args, return true; } +bool RegexQuote(std::vector const& args, + cmExecutionStatus& status) +{ + //"STRING(REGEX QUOTE [...]\n" + std::string const& outvar = args[2]; + std::string const input = + cmJoin(cmMakeRange(args).advance(3), std::string()); + std::string output; + + // Escape all regex special characters + cmStringReplaceHelper replaceHelper("([][()+*^.$?|\\\\])", R"(\\\1)"); + if (!replaceHelper.Replace(input, output)) { + status.SetError( + "sub-command REGEX, mode QUOTE: " + replaceHelper.GetError() + "."); + return false; + } + + // Store the output in the provided variable. + status.GetMakefile().AddDefinition(outvar, output); + return true; +} + bool HandleFindCommand(std::vector const& args, cmExecutionStatus& status) { diff --git a/Tests/StringFileTest/CMakeLists.txt b/Tests/StringFileTest/CMakeLists.txt index f0dd6a6b91..f72dafd788 100644 --- a/Tests/StringFileTest/CMakeLists.txt +++ b/Tests/StringFileTest/CMakeLists.txt @@ -115,6 +115,19 @@ if(NOT "${CMAKE_MATCH_2}" STREQUAL "") message(SEND_ERROR "CMAKE_MATCH_2 wrong: \"${CMAKE_MATCH_2}\", expected empty string") endif() +set(regex_quote "ab|c+12?3[x-z]$(y)\\t\\r\\n.cma*ke^[:alpha:]") +string(REGEX QUOTE regex_quote_output "${regex_quote}") +if(NOT regex_quote MATCHES "^${regex_quote_output}$") + message(SEND_ERROR "string(REGEX QUOTE) problem: \"${regex_quote}\" does not match against \"^${regex_quote_output}$\"") +endif() +string(REPLACE "." "a" nomatch_regex_quote "${regex_quote}") +if(nomatch_regex_quote MATCHES "^${regex_quote_output}$") + message(SEND_ERROR "string(REGEX QUOTE) problem: \"${nomatch_regex_quote}\" matches against \"^${regex_quote_output}$\"") +endif() +string(REGEX QUOTE multi_regex_quote ${regex_quote} ${regex_quote}) +if(NOT "${regex_quote}${regex_quote}" MATCHES "^${multi_regex_quote}$") + message(SEND_ERROR "string(REGEX QUOTE) problem: \"${regex_quote}${regex_quote}\" does not match against \"^${multi_regex_quote}$\"") +endif() string(STRIP " ST1