Browse Source

Merge topic 'list-prepend-and-pop-subcommands'

a84e509844 list: add sub-commands PREPEND, POP_BACK, POP_FRONT

Acked-by: Kitware Robot <[email protected]>
Merge-request: !2980
Brad King 6 years ago
parent
commit
e0fbb7bf08

+ 34 - 2
Help/command/list.rst

@@ -21,6 +21,9 @@ Synopsis
     list(`APPEND`_ <list> [<element>...])
     list(`FILTER`_ <list> {INCLUDE | EXCLUDE} REGEX <regex>)
     list(`INSERT`_ <list> <index> [<element>...])
+    list(`POP_BACK`_ <list> [<out-var>...])
+    list(`POP_FRONT`_ <list> [<out-var>...])
+    list(`PREPEND`_ <list> [<element>...])
     list(`REMOVE_ITEM`_ <list> <value>...)
     list(`REMOVE_AT`_ <list> <index>...)
     list(`REMOVE_DUPLICATES`_ <list>)
@@ -33,8 +36,9 @@ Synopsis
 Introduction
 ^^^^^^^^^^^^
 
-The list subcommands ``APPEND``, ``INSERT``, ``FILTER``, ``REMOVE_AT``,
-``REMOVE_ITEM``, ``REMOVE_DUPLICATES``, ``REVERSE`` and ``SORT`` may create
+The list subcommands ``APPEND``, ``INSERT``, ``FILTER``, ``PREPEND``,
+``POP_BACK``, ``POP_FRONT``, ``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
@@ -142,6 +146,34 @@ For more information on regular expressions see also the
 
 Inserts elements to the list to the specified location.
 
+.. _POP_BACK:
+
+.. code-block:: cmake
+
+  list(POP_BACK <list> [<out-var>...])
+
+If no variable name is given, removes exactly one element. Otherwise,
+assign the last element's value to the given variable and removes it,
+up to the last variable name given.
+
+.. _POP_FRONT:
+
+.. code-block:: cmake
+
+  list(POP_FRONT <list> [<out-var>...])
+
+If no variable name is given, removes exactly one element. Otherwise,
+assign the first element's value to the given variable and removes it,
+up to the last variable name given.
+
+.. _PREPEND:
+
+.. code-block:: cmake
+
+  list(PREPEND <list> [<element> ...])
+
+Insert elements to the 0th position in the list.
+
 .. _REMOVE_ITEM:
 
 .. code-block:: cmake

+ 4 - 0
Help/release/dev/list-prepend-and-pop-subcommands.rst

@@ -0,0 +1,4 @@
+list-prepend-and-pop-subcommands
+--------------------------------
+
+* :command:`list` learned new sub-commands ``PREPEND``, ``POP_FRONT`` and ``POP_BACK``.

+ 134 - 4
Source/cmListCommand.cxx

@@ -42,6 +42,15 @@ bool cmListCommand::InitialPass(std::vector<std::string> const& args,
   if (subCommand == "APPEND") {
     return this->HandleAppendCommand(args);
   }
+  if (subCommand == "PREPEND") {
+    return this->HandlePrependCommand(args);
+  }
+  if (subCommand == "POP_BACK") {
+    return this->HandlePopBackCommand(args);
+  }
+  if (subCommand == "POP_FRONT") {
+    return this->HandlePopFrontCommand(args);
+  }
   if (subCommand == "FIND") {
     return this->HandleFindCommand(args);
   }
@@ -222,20 +231,141 @@ bool cmListCommand::HandleAppendCommand(std::vector<std::string> const& args)
     return true;
   }
 
-  const std::string& listName = args[1];
+  std::string const& listName = args[1];
   // expand the variable
   std::string listString;
   this->GetListString(listString, listName);
 
-  if (!listString.empty() && !args.empty()) {
-    listString += ";";
+  // If `listString` or `args` is empty, no need to append `;`,
+  // then index is going to be `1` and points to the end-of-string ";"
+  auto const offset =
+    std::string::size_type(listString.empty() || args.empty());
+  listString += &";"[offset] + cmJoin(cmMakeRange(args).advance(2), ";");
+
+  this->Makefile->AddDefinition(listName, listString.c_str());
+  return true;
+}
+
+bool cmListCommand::HandlePrependCommand(std::vector<std::string> const& args)
+{
+  assert(args.size() >= 2);
+
+  // Skip if nothing to prepend.
+  if (args.size() < 3) {
+    return true;
   }
-  listString += cmJoin(cmMakeRange(args).advance(2), ";");
+
+  std::string const& listName = args[1];
+  // expand the variable
+  std::string listString;
+  this->GetListString(listString, listName);
+
+  // If `listString` or `args` is empty, no need to append `;`,
+  // then `offset` is going to be `1` and points to the end-of-string ";"
+  auto const offset =
+    std::string::size_type(listString.empty() || args.empty());
+  listString.insert(0,
+                    cmJoin(cmMakeRange(args).advance(2), ";") + &";"[offset]);
 
   this->Makefile->AddDefinition(listName, listString.c_str());
   return true;
 }
 
+bool cmListCommand::HandlePopBackCommand(std::vector<std::string> const& args)
+{
+  assert(args.size() >= 2);
+
+  auto ai = args.cbegin();
+  ++ai; // Skip subcommand name
+  std::string const& listName = *ai++;
+  std::vector<std::string> varArgsExpanded;
+  if (!this->GetList(varArgsExpanded, listName)) {
+    // Can't get the list definition... undefine any vars given after.
+    for (; ai != args.cend(); ++ai) {
+      this->Makefile->RemoveDefinition(*ai);
+    }
+    return true;
+  }
+
+  if (!varArgsExpanded.empty()) {
+    if (ai == args.cend()) {
+      // No variables are given... Just remove one element.
+      varArgsExpanded.pop_back();
+    } else {
+      // Ok, assign elements to be removed to the given variables
+      for (; !varArgsExpanded.empty() && ai != args.cend(); ++ai) {
+        assert(!ai->empty());
+        this->Makefile->AddDefinition(*ai, varArgsExpanded.back().c_str());
+        varArgsExpanded.pop_back();
+      }
+      // Undefine the rest variables if the list gets empty earlier...
+      for (; ai != args.cend(); ++ai) {
+        this->Makefile->RemoveDefinition(*ai);
+      }
+    }
+
+    this->Makefile->AddDefinition(listName,
+                                  cmJoin(varArgsExpanded, ";").c_str());
+
+  } else if (ai !=
+             args.cend()) { // The list is empty, but some args were given
+    // Need to *undefine* 'em all, cuz there are no items to assign...
+    for (; ai != args.cend(); ++ai) {
+      this->Makefile->RemoveDefinition(*ai);
+    }
+  }
+
+  return true;
+}
+
+bool cmListCommand::HandlePopFrontCommand(std::vector<std::string> const& args)
+{
+  assert(args.size() >= 2);
+
+  auto ai = args.cbegin();
+  ++ai; // Skip subcommand name
+  std::string const& listName = *ai++;
+  std::vector<std::string> varArgsExpanded;
+  if (!this->GetList(varArgsExpanded, listName)) {
+    // Can't get the list definition... undefine any vars given after.
+    for (; ai != args.cend(); ++ai) {
+      this->Makefile->RemoveDefinition(*ai);
+    }
+    return true;
+  }
+
+  if (!varArgsExpanded.empty()) {
+    if (ai == args.cend()) {
+      // No variables are given... Just remove one element.
+      varArgsExpanded.erase(varArgsExpanded.begin());
+    } else {
+      // Ok, assign elements to be removed to the given variables
+      auto vi = varArgsExpanded.begin();
+      for (; vi != varArgsExpanded.end() && ai != args.cend(); ++ai, ++vi) {
+        assert(!ai->empty());
+        this->Makefile->AddDefinition(*ai, varArgsExpanded.front().c_str());
+      }
+      varArgsExpanded.erase(varArgsExpanded.begin(), vi);
+      // Undefine the rest variables if the list gets empty earlier...
+      for (; ai != args.cend(); ++ai) {
+        this->Makefile->RemoveDefinition(*ai);
+      }
+    }
+
+    this->Makefile->AddDefinition(listName,
+                                  cmJoin(varArgsExpanded, ";").c_str());
+
+  } else if (ai !=
+             args.cend()) { // The list is empty, but some args were given
+    // Need to *undefine* 'em all, cuz there are no items to assign...
+    for (; ai != args.cend(); ++ai) {
+      this->Makefile->RemoveDefinition(*ai);
+    }
+  }
+
+  return true;
+}
+
 bool cmListCommand::HandleFindCommand(std::vector<std::string> const& args)
 {
   if (args.size() != 4) {

+ 3 - 0
Source/cmListCommand.h

@@ -35,6 +35,9 @@ protected:
   bool HandleLengthCommand(std::vector<std::string> const& args);
   bool HandleGetCommand(std::vector<std::string> const& args);
   bool HandleAppendCommand(std::vector<std::string> const& args);
+  bool HandlePrependCommand(std::vector<std::string> const& args);
+  bool HandlePopBackCommand(std::vector<std::string> const& args);
+  bool HandlePopFrontCommand(std::vector<std::string> const& args);
   bool HandleFindCommand(std::vector<std::string> const& args);
   bool HandleInsertCommand(std::vector<std::string> const& args);
   bool HandleJoinCommand(std::vector<std::string> const& args);

+ 1 - 0
Tests/RunCMake/list/POP_BACK-NoArgs-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/list/POP_BACK-NoArgs-stderr.txt

@@ -0,0 +1 @@
+list must be called with at least two arguments

+ 1 - 0
Tests/RunCMake/list/POP_BACK-NoArgs.cmake

@@ -0,0 +1 @@
+list(POP_FRONT)

+ 79 - 0
Tests/RunCMake/list/POP_BACK.cmake

@@ -0,0 +1,79 @@
+cmake_policy(SET CMP0054 NEW)
+
+function(assert_expected_list_len list_var expected_size)
+    list(LENGTH ${list_var} _size)
+    if(NOT _size EQUAL ${expected_size})
+        message(FATAL_ERROR "list size expected to be `${expected_size}`, got `${_size}` instead")
+    endif()
+endfunction()
+
+# Pop from undefined list
+list(POP_BACK test)
+if(DEFINED test)
+    message(FATAL_ERROR "`test` expected to be undefined")
+endif()
+
+# Pop from empty list
+set(test)
+list(POP_BACK test)
+if(DEFINED test)
+    message(FATAL_ERROR "`test` expected to be undefined")
+endif()
+
+# Default pop from 1-item list
+list(APPEND test one)
+list(POP_BACK test)
+assert_expected_list_len(test 0)
+
+# Pop from 1-item list to var
+list(APPEND test one)
+list(POP_BACK test one)
+assert_expected_list_len(test 0)
+if(NOT DEFINED one)
+    message(FATAL_ERROR "`one` expected to be defined")
+endif()
+if(NOT one STREQUAL "one")
+    message(FATAL_ERROR "`one` has unexpected value `${one}`")
+endif()
+
+unset(one)
+unset(two)
+
+# Pop from 1-item list to vars
+list(APPEND test one)
+list(POP_BACK test one two)
+assert_expected_list_len(test 0)
+if(NOT DEFINED one)
+    message(FATAL_ERROR "`one` expected to be defined")
+endif()
+if(NOT one STREQUAL "one")
+    message(FATAL_ERROR "`one` has unexpected value `${one}`")
+endif()
+if(DEFINED two)
+    message(FATAL_ERROR "`two` expected to be undefined")
+endif()
+
+unset(one)
+unset(two)
+
+# Default pop from 2-item list
+list(APPEND test one two)
+list(POP_BACK test)
+assert_expected_list_len(test 1)
+if(NOT test STREQUAL "one")
+    message(FATAL_ERROR "`test` has unexpected value `${test}`")
+endif()
+
+# Pop from 2-item list
+list(APPEND test two)
+list(POP_BACK test two)
+assert_expected_list_len(test 1)
+if(NOT DEFINED two)
+    message(FATAL_ERROR "`two` expected to be defined")
+endif()
+if(NOT two STREQUAL "two")
+    message(FATAL_ERROR "`two` has unexpected value `${two}`")
+endif()
+if(NOT test STREQUAL "one")
+    message(FATAL_ERROR "`test` has unexpected value `${test}`")
+endif()

+ 1 - 0
Tests/RunCMake/list/POP_FRONT-NoArgs-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/list/POP_FRONT-NoArgs-stderr.txt

@@ -0,0 +1 @@
+list must be called with at least two arguments

+ 1 - 0
Tests/RunCMake/list/POP_FRONT-NoArgs.cmake

@@ -0,0 +1 @@
+list(POP_BACK)

+ 79 - 0
Tests/RunCMake/list/POP_FRONT.cmake

@@ -0,0 +1,79 @@
+cmake_policy(SET CMP0054 NEW)
+
+function(assert_expected_list_len list_var expected_size)
+    list(LENGTH ${list_var} _size)
+    if(NOT _size EQUAL ${expected_size})
+        message(FATAL_ERROR "list size expected to be `${expected_size}`, got `${_size}` instead")
+    endif()
+endfunction()
+
+# Pop from undefined list
+list(POP_FRONT test)
+if(DEFINED test)
+    message(FATAL_ERROR "`test` expected to be undefined")
+endif()
+
+# Pop from empty list
+set(test)
+list(POP_FRONT test)
+if(DEFINED test)
+    message(FATAL_ERROR "`test` expected to be undefined")
+endif()
+
+# Default pop from 1-item list
+list(APPEND test one)
+list(POP_FRONT test)
+assert_expected_list_len(test 0)
+
+# Pop from 1-item list to var
+list(APPEND test one)
+list(POP_FRONT test one)
+assert_expected_list_len(test 0)
+if(NOT DEFINED one)
+    message(FATAL_ERROR "`one` expected to be defined")
+endif()
+if(NOT one STREQUAL "one")
+    message(FATAL_ERROR "`one` has unexpected value `${one}`")
+endif()
+
+unset(one)
+unset(two)
+
+# Pop from 1-item list to vars
+list(APPEND test one)
+list(POP_FRONT test one two)
+assert_expected_list_len(test 0)
+if(NOT DEFINED one)
+    message(FATAL_ERROR "`one` expected to be defined")
+endif()
+if(NOT one STREQUAL "one")
+    message(FATAL_ERROR "`one` has unexpected value `${one}`")
+endif()
+if(DEFINED two)
+    message(FATAL_ERROR "`two` expected to be undefined")
+endif()
+
+unset(one)
+unset(two)
+
+# Default pop from 2-item list
+list(APPEND test one two)
+list(POP_FRONT test)
+assert_expected_list_len(test 1)
+if(NOT test STREQUAL "two")
+    message(FATAL_ERROR "`test` has unexpected value `${test}`")
+endif()
+
+# Pop from 2-item list
+list(PREPEND test one)
+list(POP_FRONT test one)
+assert_expected_list_len(test 1)
+if(NOT DEFINED one)
+    message(FATAL_ERROR "`one` expected to be defined")
+endif()
+if(NOT one STREQUAL "one")
+    message(FATAL_ERROR "`one` has unexpected value `${one}`")
+endif()
+if(NOT test STREQUAL "two")
+    message(FATAL_ERROR "`test` has unexpected value `${test}`")
+endif()

+ 1 - 0
Tests/RunCMake/list/PREPEND-NoArgs-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/list/PREPEND-NoArgs-stderr.txt

@@ -0,0 +1 @@
+list must be called with at least two arguments

+ 1 - 0
Tests/RunCMake/list/PREPEND-NoArgs.cmake

@@ -0,0 +1 @@
+list(PREPEND)

+ 33 - 0
Tests/RunCMake/list/PREPEND.cmake

@@ -0,0 +1,33 @@
+list(PREPEND test)
+if(test)
+    message(FATAL_ERROR "failed")
+endif()
+
+list(PREPEND test satu)
+if(NOT test STREQUAL "satu")
+    message(FATAL_ERROR "failed")
+endif()
+
+list(PREPEND test dua)
+if(NOT test STREQUAL "dua;satu")
+    message(FATAL_ERROR "failed")
+endif()
+
+list(PREPEND test tiga)
+if(NOT test STREQUAL "tiga;dua;satu")
+    message(FATAL_ERROR "failed")
+endif()
+
+# Scope test
+function(foo)
+    list(PREPEND test empat)
+    if(NOT test STREQUAL "empat;tiga;dua;satu")
+        message(FATAL_ERROR "failed")
+    endif()
+endfunction()
+
+foo()
+
+if(NOT test STREQUAL "tiga;dua;satu")
+    message(FATAL_ERROR "failed")
+endif()

+ 12 - 0
Tests/RunCMake/list/RunCMakeTest.cmake

@@ -98,3 +98,15 @@ run_cmake(SORT-NoCaseOption)
 
 # Successful tests
 run_cmake(SORT)
+
+# argument tests
+run_cmake(PREPEND-NoArgs)
+# Successful tests
+run_cmake(PREPEND)
+
+# argument tests
+run_cmake(POP_BACK-NoArgs)
+run_cmake(POP_FRONT-NoArgs)
+# Successful tests
+run_cmake(POP_BACK)
+run_cmake(POP_FRONT)