Browse Source

ENH: Teach separate_arguments() to parse commands

This adds UNIX_COMMAND and WINDOWS_COMMAND modes to the command.
These modes parse unix- and windows-style command lines.
Brad King 16 years ago
parent
commit
b23b1800a5

+ 85 - 9
Source/cmSeparateArgumentsCommand.cxx

@@ -20,19 +20,95 @@
 bool cmSeparateArgumentsCommand
 ::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
 {
-  if(args.size() != 1 )
+  if(args.empty())
     {
-    this->SetError("called with incorrect number of arguments");
+    this->SetError("must be given at least one argument.");
     return false;
     }
-  const char* cacheValue = this->Makefile->GetDefinition(args[0].c_str());
-  if(!cacheValue)
+
+  std::string var;
+  std::string command;
+  enum Mode { ModeOld, ModeUnix, ModeWindows };
+  Mode mode = ModeOld;
+  enum Doing { DoingNone, DoingVariable, DoingMode, DoingCommand };
+  Doing doing = DoingVariable;
+  for(unsigned int i=0; i < args.size(); ++i)
+    {
+    if(doing == DoingVariable)
+      {
+      var = args[i];
+      doing = DoingMode;
+      }
+    else if(doing == DoingMode && args[i] == "UNIX_COMMAND")
+      {
+      mode = ModeUnix;
+      doing = DoingCommand;
+      }
+    else if(doing == DoingMode && args[i] == "WINDOWS_COMMAND")
+      {
+      mode = ModeWindows;
+      doing = DoingCommand;
+      }
+    else if(doing == DoingCommand)
+      {
+      command = args[i];
+      doing = DoingNone;
+      }
+    else
+      {
+      cmOStringStream e;
+      e << "given unknown argument " << args[i];
+      this->SetError(e.str().c_str());
+      return false;
+      }
+    }
+
+  if(mode == ModeOld)
+    {
+    // Original space-replacement version of command.
+    if(const char* def = this->Makefile->GetDefinition(var.c_str()))
+      {
+      std::string value = def;
+      cmSystemTools::ReplaceString(value, " ", ";");
+      this->Makefile->AddDefinition(var.c_str(), value.c_str());
+      }
+    }
+  else
     {
-    return true;
+    // Parse the command line.
+    std::vector<std::string> vec;
+    if(mode == ModeUnix)
+      {
+      cmSystemTools::ParseUnixCommandLine(command.c_str(), vec);
+      }
+    else // if(mode == ModeWindows)
+      {
+      cmSystemTools::ParseWindowsCommandLine(command.c_str(), vec);
+      }
+
+    // Construct the result list value.
+    std::string value;
+    const char* sep = "";
+    for(std::vector<std::string>::const_iterator vi = vec.begin();
+        vi != vec.end(); ++vi)
+      {
+      // Separate from the previous argument.
+      value += sep;
+      sep = ";";
+
+      // Preserve semicolons.
+      for(std::string::const_iterator si = vi->begin();
+          si != vi->end(); ++si)
+        {
+        if(*si == ';')
+          {
+          value += '\\';
+          }
+        value += *si;
+        }
+      }
+    this->Makefile->AddDefinition(var.c_str(), value.c_str());
     }
-  std::string value = cacheValue;
-  cmSystemTools::ReplaceString(value," ", ";");
-  this->Makefile->AddDefinition(args[0].c_str(), value.c_str());
+
   return true;
 }
-

+ 17 - 1
Source/cmSeparateArgumentsCommand.h

@@ -58,7 +58,7 @@ public:
   virtual const char* GetTerseDocumentation() 
     {
     return 
-      "Split space separated arguments into a semi-colon separated list.";
+      "Parse space-separated arguments into a semicolon-separated list.";
     }
   
   /**
@@ -67,6 +67,22 @@ public:
   virtual const char* GetFullDocumentation()
     {
     return
+      "  separate_arguments(<var> <UNIX|WINDOWS>_COMMAND \"<args>\")\n"
+      "Parses a unix- or windows-style command-line string \"<args>\" and "
+      "stores a semicolon-separated list of the arguments in <var>.  "
+      "The entire command line must be given in one \"<args>\" argument."
+      "\n"
+      "The UNIX_COMMAND mode separates arguments by unquoted whitespace.  "
+      "It recognizes both single-quote and double-quote pairs.  "
+      "A backslash escapes the next literal character (\\\" is \"); "
+      "there are no special escapes (\\n is just n)."
+      "\n"
+      "The WINDOWS_COMMAND mode parses a windows command-line using the "
+      "same syntax the runtime library uses to construct argv at startup.  "
+      "It separates arguments by whitespace that is not double-quoted.  "
+      "Backslashes are literal unless they precede double-quotes.  "
+      "See the MSDN article \"Parsing C Command-Line Arguments\" for details."
+      "\n"
       "  separate_arguments(VARIABLE)\n"
       "Convert the value of VARIABLE to a semi-colon separated list.  "
       "All spaces are replaced with ';'.  This helps with generating "

+ 1 - 0
Tests/CMakeTests/CMakeLists.txt

@@ -18,6 +18,7 @@ AddCMakeTest(GetFilenameComponentRealpath "")
 AddCMakeTest(Version "")
 AddCMakeTest(Message "")
 AddCMakeTest(File "")
+AddCMakeTest(SeparateArguments "")
 
 SET(GetPrerequisites_PreArgs
   "-DCTEST_CONFIGURATION_TYPE:STRING=\\\${CTEST_CONFIGURATION_TYPE}"

+ 25 - 0
Tests/CMakeTests/SeparateArgumentsTest.cmake.in

@@ -0,0 +1,25 @@
+set(old_out "a b  c")
+separate_arguments(old_out)
+set(old_exp "a;b;;c")
+
+set(unix_cmd "a \"b c\" 'd e' \";\" \\ \\'\\\" '\\'' \"\\\"\"")
+set(unix_exp "a;b c;d e;\;; '\";';\"")
+separate_arguments(unix_out UNIX_COMMAND "${unix_cmd}")
+
+set(windows_cmd "a \"b c\" 'd e' \";\" \\ \"c:\\windows\\path\\\\\" \\\"")
+set(windows_exp "a;b c;'d;e';\;;\\;c:\\windows\\path\\;\"")
+separate_arguments(windows_out WINDOWS_COMMAND "${windows_cmd}")
+
+foreach(mode old unix windows)
+  if(NOT "${${mode}_out}" STREQUAL "${${mode}_exp}")
+    message(FATAL_ERROR "separate_arguments ${mode}-style failed.  "
+      "Expected\n  [${${mode}_exp}]\nbut got\n  [${${mode}_out}]\n")
+  endif()
+endforeach()
+
+set(nothing)
+separate_arguments(nothing)
+if(DEFINED nothing)
+  message(FATAL_ERROR "separate_arguments null-case failed: "
+    "nothing=[${nothing}]")
+endif()