From 0205f882ae252998686f65b843a758268b4c62bc Mon Sep 17 00:00:00 2001 From: Ashley Whetter Date: Thu, 28 Jan 2016 21:29:10 +0000 Subject: [PATCH] list: Add FILTER subcommand (#3986) Create a `list(FILTER)` command to filter lists by regular expression. --- Help/command/list.rst | 13 ++- Help/release/dev/list-FILTER-command.rst | 5 + Source/cmListCommand.cxx | 109 ++++++++++++++++++ Source/cmListCommand.h | 6 + .../RunCMake/list/EmptyFilterRegex-result.txt | 1 + .../RunCMake/list/EmptyFilterRegex-stderr.txt | 0 Tests/RunCMake/list/EmptyFilterRegex.cmake | 2 + Tests/RunCMake/list/FILTER-NotList-result.txt | 1 + Tests/RunCMake/list/FILTER-NotList-stderr.txt | 4 + Tests/RunCMake/list/FILTER-NotList.cmake | 2 + .../list/FILTER-REGEX-InvalidMode-result.txt | 1 + .../list/FILTER-REGEX-InvalidMode-stderr.txt | 4 + .../list/FILTER-REGEX-InvalidMode.cmake | 2 + .../FILTER-REGEX-InvalidOperator-result.txt | 1 + .../FILTER-REGEX-InvalidOperator-stderr.txt | 4 + .../list/FILTER-REGEX-InvalidOperator.cmake | 2 + .../list/FILTER-REGEX-InvalidRegex-result.txt | 1 + .../list/FILTER-REGEX-InvalidRegex-stderr.txt | 4 + .../list/FILTER-REGEX-InvalidRegex.cmake | 2 + .../FILTER-REGEX-TooManyArguments-result.txt | 1 + .../FILTER-REGEX-TooManyArguments-stderr.txt | 4 + .../list/FILTER-REGEX-TooManyArguments.cmake | 2 + .../list/FILTER-REGEX-Valid0-result.txt | 1 + .../list/FILTER-REGEX-Valid0-stderr.txt | 2 + Tests/RunCMake/list/FILTER-REGEX-Valid0.cmake | 4 + .../list/FILTER-REGEX-Valid1-result.txt | 1 + .../list/FILTER-REGEX-Valid1-stderr.txt | 2 + Tests/RunCMake/list/FILTER-REGEX-Valid1.cmake | 4 + Tests/RunCMake/list/RunCMakeTest.cmake | 9 ++ 29 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 Help/release/dev/list-FILTER-command.rst create mode 100644 Tests/RunCMake/list/EmptyFilterRegex-result.txt create mode 100644 Tests/RunCMake/list/EmptyFilterRegex-stderr.txt create mode 100644 Tests/RunCMake/list/EmptyFilterRegex.cmake create mode 100644 Tests/RunCMake/list/FILTER-NotList-result.txt create mode 100644 Tests/RunCMake/list/FILTER-NotList-stderr.txt create mode 100644 Tests/RunCMake/list/FILTER-NotList.cmake create mode 100644 Tests/RunCMake/list/FILTER-REGEX-InvalidMode-result.txt create mode 100644 Tests/RunCMake/list/FILTER-REGEX-InvalidMode-stderr.txt create mode 100644 Tests/RunCMake/list/FILTER-REGEX-InvalidMode.cmake create mode 100644 Tests/RunCMake/list/FILTER-REGEX-InvalidOperator-result.txt create mode 100644 Tests/RunCMake/list/FILTER-REGEX-InvalidOperator-stderr.txt create mode 100644 Tests/RunCMake/list/FILTER-REGEX-InvalidOperator.cmake create mode 100644 Tests/RunCMake/list/FILTER-REGEX-InvalidRegex-result.txt create mode 100644 Tests/RunCMake/list/FILTER-REGEX-InvalidRegex-stderr.txt create mode 100644 Tests/RunCMake/list/FILTER-REGEX-InvalidRegex.cmake create mode 100644 Tests/RunCMake/list/FILTER-REGEX-TooManyArguments-result.txt create mode 100644 Tests/RunCMake/list/FILTER-REGEX-TooManyArguments-stderr.txt create mode 100644 Tests/RunCMake/list/FILTER-REGEX-TooManyArguments.cmake create mode 100644 Tests/RunCMake/list/FILTER-REGEX-Valid0-result.txt create mode 100644 Tests/RunCMake/list/FILTER-REGEX-Valid0-stderr.txt create mode 100644 Tests/RunCMake/list/FILTER-REGEX-Valid0.cmake create mode 100644 Tests/RunCMake/list/FILTER-REGEX-Valid1-result.txt create mode 100644 Tests/RunCMake/list/FILTER-REGEX-Valid1-stderr.txt create mode 100644 Tests/RunCMake/list/FILTER-REGEX-Valid1.cmake diff --git a/Help/command/list.rst b/Help/command/list.rst index a7a05c7af..f6b75bc19 100644 --- a/Help/command/list.rst +++ b/Help/command/list.rst @@ -9,6 +9,7 @@ List operations. list(GET [ ...] ) list(APPEND [ ...]) + list(FILTER REGEX ) list(FIND ) list(INSERT [ ...]) list(REMOVE_ITEM [ ...]) @@ -23,6 +24,12 @@ List operations. ``APPEND`` will append elements to the list. +``FILTER`` will include or remove items from the list that match the +mode's pattern. +In ``REGEX`` mode, items will be matched against the given regular expression. +For more information on regular expressions see also the :command:`string` +command. + ``FIND`` will return the index of the element specified in the list or -1 if it wasn't found. @@ -38,9 +45,9 @@ difference is that ``REMOVE_ITEM`` will remove the given items, while ``SORT`` sorts the list in-place alphabetically. -The list subcommands ``APPEND``, ``INSERT``, ``REMOVE_AT``, ``REMOVE_ITEM``, -``REMOVE_DUPLICATES``, ``REVERSE`` and ``SORT`` may create new values for -the list within the current CMake variable scope. Similar to the +The list subcommands ``APPEND``, ``INSERT``, ``FILTER``, ``REMOVE_AT``, +``REMOVE_ITEM``, ``REMOVE_DUPLICATES``, ``REVERSE`` and ``SORT`` may create new +values for the list within the current CMake variable scope. Similar to the :command:`set` command, the LIST command creates new variable values in the current scope, even if the list itself is actually defined in a parent scope. To propagate the results of these operations upwards, use diff --git a/Help/release/dev/list-FILTER-command.rst b/Help/release/dev/list-FILTER-command.rst new file mode 100644 index 000000000..3fee4f054 --- /dev/null +++ b/Help/release/dev/list-FILTER-command.rst @@ -0,0 +1,5 @@ +list-FILTER-command +------------------- + +* The :command:`list` command gained a ``FILTER`` sub-command to filter + list elements by regular expression. diff --git a/Source/cmListCommand.cxx b/Source/cmListCommand.cxx index 6041fb77d..15a1af560 100644 --- a/Source/cmListCommand.cxx +++ b/Source/cmListCommand.cxx @@ -14,6 +14,7 @@ #include #include "cmAlgorithms.h" +#include #include // required for atoi #include #include @@ -68,6 +69,10 @@ bool cmListCommand { return this->HandleReverseCommand(args); } + if(subCommand == "FILTER") + { + return this->HandleFilterCommand(args); + } std::string e = "does not recognize sub-command "+subCommand; this->SetError(e); @@ -517,3 +522,107 @@ bool cmListCommand::HandleRemoveAtCommand( return true; } +//---------------------------------------------------------------------------- +bool cmListCommand::HandleFilterCommand( + std::vector const& args) +{ + if(args.size() < 2) + { + this->SetError("sub-command FILTER requires a list to be specified."); + return false; + } + + if(args.size() < 3) + { + this->SetError("sub-command FILTER requires an operator to be specified."); + return false; + } + + if(args.size() < 4) + { + this->SetError("sub-command FILTER requires a mode to be specified."); + return false; + } + + const std::string& listName = args[1]; + // expand the variable + std::vector varArgsExpanded; + if ( !this->GetList(varArgsExpanded, listName) ) + { + this->SetError("sub-command FILTER requires list to be present."); + return false; + } + + const std::string& op = args[2]; + bool includeMatches; + if(op == "INCLUDE") + { + includeMatches = true; + } + else if(op == "EXCLUDE") + { + includeMatches = false; + } + else + { + this->SetError("sub-command FILTER does not recognize operator " + op); + return false; + } + + const std::string& mode = args[3]; + if(mode == "REGEX") + { + if(args.size() != 5) + { + this->SetError("sub-command FILTER, mode REGEX " + "requires five arguments."); + return false; + } + return this->FilterRegex(args, includeMatches, listName, varArgsExpanded); + } + + this->SetError("sub-command FILTER does not recognize mode " + mode); + return false; +} + +//---------------------------------------------------------------------------- +class MatchesRegex { +public: + MatchesRegex(cmsys::RegularExpression& in_regex, bool in_includeMatches) + : regex(in_regex), includeMatches(in_includeMatches) {} + + bool operator()(const std::string& target) { + return regex.find(target) ^ includeMatches; + } + +private: + cmsys::RegularExpression& regex; + const bool includeMatches; +}; + +bool cmListCommand::FilterRegex(std::vector const& args, + bool includeMatches, + std::string const& listName, + std::vector& varArgsExpanded) +{ + const std::string& pattern = args[4]; + cmsys::RegularExpression regex(pattern); + if(!regex.is_valid()) + { + std::string error = "sub-command FILTER, mode REGEX "; + error += "failed to compile regex \""; + error += pattern; + error += "\"."; + this->SetError(error); + return false; + } + + std::vector::iterator argsBegin = varArgsExpanded.begin(); + std::vector::iterator argsEnd = varArgsExpanded.end(); + std::vector::iterator newArgsEnd = + std::remove_if(argsBegin, argsEnd, MatchesRegex(regex, includeMatches)); + + std::string value = cmJoin(cmMakeRange(argsBegin, newArgsEnd), ";"); + this->Makefile->AddDefinition(listName, value.c_str()); + return true; +} diff --git a/Source/cmListCommand.h b/Source/cmListCommand.h index 5ea1d9f18..25edee8a2 100644 --- a/Source/cmListCommand.h +++ b/Source/cmListCommand.h @@ -58,6 +58,12 @@ protected: bool HandleRemoveDuplicatesCommand(std::vector const& args); bool HandleSortCommand(std::vector const& args); bool HandleReverseCommand(std::vector const& args); + bool HandleFilterCommand(std::vector const& args); + bool FilterRegex(std::vector const& args, + bool includeMatches, + std::string const& listName, + std::vector& varArgsExpanded + ); bool GetList(std::vector& list, const std::string& var); diff --git a/Tests/RunCMake/list/EmptyFilterRegex-result.txt b/Tests/RunCMake/list/EmptyFilterRegex-result.txt new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/Tests/RunCMake/list/EmptyFilterRegex-result.txt @@ -0,0 +1 @@ +0 diff --git a/Tests/RunCMake/list/EmptyFilterRegex-stderr.txt b/Tests/RunCMake/list/EmptyFilterRegex-stderr.txt new file mode 100644 index 000000000..e69de29bb diff --git a/Tests/RunCMake/list/EmptyFilterRegex.cmake b/Tests/RunCMake/list/EmptyFilterRegex.cmake new file mode 100644 index 000000000..33849cd4a --- /dev/null +++ b/Tests/RunCMake/list/EmptyFilterRegex.cmake @@ -0,0 +1,2 @@ +set(mylist "") +list(FILTER mylist INCLUDE REGEX "^FILTER_THIS_.+") diff --git a/Tests/RunCMake/list/FILTER-NotList-result.txt b/Tests/RunCMake/list/FILTER-NotList-result.txt new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-NotList-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/list/FILTER-NotList-stderr.txt b/Tests/RunCMake/list/FILTER-NotList-stderr.txt new file mode 100644 index 000000000..159c28dd4 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-NotList-stderr.txt @@ -0,0 +1,4 @@ +^CMake Error at FILTER-NotList.cmake:2 \(list\): + list sub-command FILTER requires list to be present. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\)$ diff --git a/Tests/RunCMake/list/FILTER-NotList.cmake b/Tests/RunCMake/list/FILTER-NotList.cmake new file mode 100644 index 000000000..1e15635b9 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-NotList.cmake @@ -0,0 +1,2 @@ +unset(nosuchlist) +list(FILTER nosuchlist EXCLUDE REGEX "^FILTER_THIS_.+") diff --git a/Tests/RunCMake/list/FILTER-REGEX-InvalidMode-result.txt b/Tests/RunCMake/list/FILTER-REGEX-InvalidMode-result.txt new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-InvalidMode-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/list/FILTER-REGEX-InvalidMode-stderr.txt b/Tests/RunCMake/list/FILTER-REGEX-InvalidMode-stderr.txt new file mode 100644 index 000000000..7f783fab8 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-InvalidMode-stderr.txt @@ -0,0 +1,4 @@ +^CMake Error at FILTER-REGEX-InvalidMode.cmake:2 \(list\): + list sub-command FILTER does not recognize mode NOOP +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\)$ diff --git a/Tests/RunCMake/list/FILTER-REGEX-InvalidMode.cmake b/Tests/RunCMake/list/FILTER-REGEX-InvalidMode.cmake new file mode 100644 index 000000000..e02b9291a --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-InvalidMode.cmake @@ -0,0 +1,2 @@ +set(mylist FILTER_THIS_BIT DO_NOT_FILTER_THIS thisisanitem FILTER_THIS_THING) +list(FILTER mylist EXCLUDE NOOP "^FILTER_THIS_.+") diff --git a/Tests/RunCMake/list/FILTER-REGEX-InvalidOperator-result.txt b/Tests/RunCMake/list/FILTER-REGEX-InvalidOperator-result.txt new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-InvalidOperator-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/list/FILTER-REGEX-InvalidOperator-stderr.txt b/Tests/RunCMake/list/FILTER-REGEX-InvalidOperator-stderr.txt new file mode 100644 index 000000000..94f2427d9 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-InvalidOperator-stderr.txt @@ -0,0 +1,4 @@ +^CMake Error at FILTER-REGEX-InvalidOperator.cmake:2 \(list\): + list sub-command FILTER does not recognize operator RM +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\)$ diff --git a/Tests/RunCMake/list/FILTER-REGEX-InvalidOperator.cmake b/Tests/RunCMake/list/FILTER-REGEX-InvalidOperator.cmake new file mode 100644 index 000000000..96246225e --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-InvalidOperator.cmake @@ -0,0 +1,2 @@ +set(mylist FILTER_THIS_BIT DO_NOT_FILTER_THIS thisisanitem FILTER_THIS_THING) +list(FILTER mylist RM REGEX "^FILTER_THIS_.+") diff --git a/Tests/RunCMake/list/FILTER-REGEX-InvalidRegex-result.txt b/Tests/RunCMake/list/FILTER-REGEX-InvalidRegex-result.txt new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-InvalidRegex-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/list/FILTER-REGEX-InvalidRegex-stderr.txt b/Tests/RunCMake/list/FILTER-REGEX-InvalidRegex-stderr.txt new file mode 100644 index 000000000..9e98cbf2a --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-InvalidRegex-stderr.txt @@ -0,0 +1,4 @@ +^CMake Error at FILTER-REGEX-InvalidRegex.cmake:2 \(list\): + list sub-command FILTER, mode REGEX failed to compile regex "UHOH!\)\(". +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\)$ diff --git a/Tests/RunCMake/list/FILTER-REGEX-InvalidRegex.cmake b/Tests/RunCMake/list/FILTER-REGEX-InvalidRegex.cmake new file mode 100644 index 000000000..0641062b5 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-InvalidRegex.cmake @@ -0,0 +1,2 @@ +set(mylist FILTER_THIS_BIT DO_NOT_FILTER_THIS thisisanitem FILTER_THIS_THING) +list(FILTER mylist EXCLUDE REGEX "UHOH!)(") diff --git a/Tests/RunCMake/list/FILTER-REGEX-TooManyArguments-result.txt b/Tests/RunCMake/list/FILTER-REGEX-TooManyArguments-result.txt new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-TooManyArguments-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/list/FILTER-REGEX-TooManyArguments-stderr.txt b/Tests/RunCMake/list/FILTER-REGEX-TooManyArguments-stderr.txt new file mode 100644 index 000000000..ec7f41c34 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-TooManyArguments-stderr.txt @@ -0,0 +1,4 @@ +^CMake Error at FILTER-REGEX-TooManyArguments.cmake:2 \(list\): + list sub-command FILTER, mode REGEX requires five arguments. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\)$ diff --git a/Tests/RunCMake/list/FILTER-REGEX-TooManyArguments.cmake b/Tests/RunCMake/list/FILTER-REGEX-TooManyArguments.cmake new file mode 100644 index 000000000..d9cd8eba5 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-TooManyArguments.cmake @@ -0,0 +1,2 @@ +set(mylist FILTER_THIS_BIT DO_NOT_FILTER_THIS thisisanitem FILTER_THIS_THING) +list(FILTER mylist EXCLUDE REGEX "^FILTER_THIS_.+" one_too_many) diff --git a/Tests/RunCMake/list/FILTER-REGEX-Valid0-result.txt b/Tests/RunCMake/list/FILTER-REGEX-Valid0-result.txt new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-Valid0-result.txt @@ -0,0 +1 @@ +0 diff --git a/Tests/RunCMake/list/FILTER-REGEX-Valid0-stderr.txt b/Tests/RunCMake/list/FILTER-REGEX-Valid0-stderr.txt new file mode 100644 index 000000000..d9ba38dbc --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-Valid0-stderr.txt @@ -0,0 +1,2 @@ +^mylist was: FILTER_THIS_BIT;DO_NOT_FILTER_THIS;thisisanitem;FILTER_THIS_THING +mylist is: DO_NOT_FILTER_THIS;thisisanitem$ diff --git a/Tests/RunCMake/list/FILTER-REGEX-Valid0.cmake b/Tests/RunCMake/list/FILTER-REGEX-Valid0.cmake new file mode 100644 index 000000000..f395e61b0 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-Valid0.cmake @@ -0,0 +1,4 @@ +set(mylist FILTER_THIS_BIT DO_NOT_FILTER_THIS thisisanitem FILTER_THIS_THING) +message("mylist was: ${mylist}") +list(FILTER mylist EXCLUDE REGEX "^FILTER_THIS_.+") +message("mylist is: ${mylist}") diff --git a/Tests/RunCMake/list/FILTER-REGEX-Valid1-result.txt b/Tests/RunCMake/list/FILTER-REGEX-Valid1-result.txt new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-Valid1-result.txt @@ -0,0 +1 @@ +0 diff --git a/Tests/RunCMake/list/FILTER-REGEX-Valid1-stderr.txt b/Tests/RunCMake/list/FILTER-REGEX-Valid1-stderr.txt new file mode 100644 index 000000000..5e5280d30 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-Valid1-stderr.txt @@ -0,0 +1,2 @@ +^mylist was: FILTER_THIS_BIT;DO_NOT_FILTER_THIS;thisisanitem;FILTER_THIS_THING +mylist is: FILTER_THIS_BIT;FILTER_THIS_THING$ diff --git a/Tests/RunCMake/list/FILTER-REGEX-Valid1.cmake b/Tests/RunCMake/list/FILTER-REGEX-Valid1.cmake new file mode 100644 index 000000000..e65928191 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-REGEX-Valid1.cmake @@ -0,0 +1,4 @@ +set(mylist FILTER_THIS_BIT DO_NOT_FILTER_THIS thisisanitem FILTER_THIS_THING) +message("mylist was: ${mylist}") +list(FILTER mylist INCLUDE REGEX "^FILTER_THIS_.+") +message("mylist is: ${mylist}") diff --git a/Tests/RunCMake/list/RunCMakeTest.cmake b/Tests/RunCMake/list/RunCMakeTest.cmake index 25d6a0367..b002ab388 100644 --- a/Tests/RunCMake/list/RunCMakeTest.cmake +++ b/Tests/RunCMake/list/RunCMakeTest.cmake @@ -1,5 +1,6 @@ include(RunCMake) +run_cmake(EmptyFilterRegex) run_cmake(EmptyGet0) run_cmake(EmptyRemoveAt0) run_cmake(EmptyInsert-1) @@ -8,17 +9,25 @@ run_cmake(NoArguments) run_cmake(InvalidSubcommand) run_cmake(GET-CMP0007-WARN) +run_cmake(FILTER-REGEX-InvalidRegex) run_cmake(GET-InvalidIndex) run_cmake(INSERT-InvalidIndex) run_cmake(REMOVE_AT-InvalidIndex) +run_cmake(FILTER-REGEX-TooManyArguments) run_cmake(LENGTH-TooManyArguments) run_cmake(REMOVE_DUPLICATES-TooManyArguments) run_cmake(REVERSE-TooManyArguments) run_cmake(SORT-TooManyArguments) +run_cmake(FILTER-NotList) run_cmake(REMOVE_AT-NotList) run_cmake(REMOVE_DUPLICATES-NotList) run_cmake(REMOVE_ITEM-NotList) run_cmake(REVERSE-NotList) run_cmake(SORT-NotList) + +run_cmake(FILTER-REGEX-InvalidMode) +run_cmake(FILTER-REGEX-InvalidOperator) +run_cmake(FILTER-REGEX-Valid0) +run_cmake(FILTER-REGEX-Valid1)