GNU-style for command line. No positional arguments accepted. Supported single/double...
[anna.git] / source / core / util / CommandLine.cpp
index 2764e45..74c34e1 100644 (file)
 #include <anna/xml/xml.hpp>
 
 #include <anna/core/util/CommandLine.hpp>
+#include <anna/core/util/Tokenizer.hpp>
 
 using namespace std;
 using namespace anna;
 
-#ifndef TOKENINDICATOR
-#define TOKENINDICATOR '-'
-#endif
-
 //--------------------------------------------------------------------------------
-// Notifica al Parser que la opcion 'argumentName' es soportada
+// Notifica al Parser que la opcion 'argumentExpression' es soportada
 //
 // (1) Verifica que la opcion no haya sido registrada previamente.
 //--------------------------------------------------------------------------------
-void CommandLine::add(const char* argumentName, Argument::Type type, const char* comment, const bool needValue)
+void CommandLine::add(const char* argumentExpression, Argument::Type type, const char* comment, const bool needValue)
 throw() {
   Guard guard(a_mutex, "CommandLine::add");
 
-  if(search(argumentName) == NULL)
-    a_arguments.push_back(new Variable(argumentName, type, comment, needValue));
+  std::string arg1 = "", arg2 = "";
+  if (!argumentExpression) { std::cerr << "Invalid argument expression: cannot register NULL literal on command-line" << std::endl; return; }
+
+  anna::Tokenizer expression;
+  expression.apply (argumentExpression, ",");
+  if (expression.size() > 2) { std::cerr << "Invalid argument expression '" << argumentExpression << "': only '<letter>[,<word>]' or '<word>[,<letter>]' are supported for command-line registration" << std::endl; return; }
+
+  anna::Tokenizer::const_iterator tok_it = expression.begin();
+  arg1 = anna::Tokenizer::data(tok_it);
+  // No leading hyphens in the name:
+  if (arg1[0] == '-') { std::cerr << "Invalid argument expression '" << argumentExpression << "': no leading hyphens allowed in argument names for command-line registration" << std::endl; return; }
+
+  tok_it++;
+  if (tok_it != expression.end()) {
+    arg2 = anna::Tokenizer::data(tok_it);
+    // No hyphens in the name:
+    if (arg2[0] == '-') { std::cerr << "Invalid argument expression '" << argumentExpression << "': no leading hyphens allowed in argument names for command-line registration" << std::endl; return; }
+  }
+
+  // swap to have first non empty:
+  if (arg1 == "") { arg1 = arg2; arg2 = ""; }
+
+  // If both provided, one will be single letter and the other, a word:
+  if (arg2 != "") {
+    int s1 = arg1.size();
+    int s2 = arg2.size();
+    if ((s1 + s2) == 2) { std::cerr << "Invalid argument expression '" << argumentExpression << "': only '<letter>[,<word>]' or '<word>[,<letter>]' are supported for command-line registration (provided two letters !)" << std::endl; return; }
+    if ((s1 != 1) && (s2 != 1)) { std::cerr << "Invalid argument expression '" << argumentExpression << "': only '<letter>[,<word>]' or '<word>[,<letter>]' are supported for command-line registration (provided two words !)" << std::endl; return; }
+  }
+
+  // assign:
+  a_arguments.push_back(new Variable(arg1, arg2, type, comment, needValue));
 }
 
 //--------------------------------------------------------------------------------
-// Verifica que todos los argumentNameos declarados como obligatorios estan en la
+// Verifica que todos los argumentos declarados como obligatorios estan en la
 // linea de comandos.
 //--------------------------------------------------------------------------------
 void CommandLine::verify()
@@ -73,26 +100,26 @@ throw(RuntimeException) {
 
   for(int i = 0, maxi = a_arguments.size(); i < maxi; i ++) {
     if(a_arguments [i]->getType() == Argument::Mandatory)
-      getValue(a_arguments [i]->getName().c_str());     // JEDS 24/09/2003
+      getValue(a_arguments [i]->getName1().c_str()); // exit program if not found (exitOnFault = true by default)
   }
 }
 
 //--------------------------------------------------------------------------------
-// Obtiene el valor para opcion 'argumentName'.
+// Obtiene el valor para opcion 'argumentExpression'.
 //
 // (1) Realiza el analisis de la linea de comandos.
 // (2) Verifica que la opcion recibida existe en la lista de opciones registradas.
-// (3) Si la opcion 'argumentName' existe en la linea de comandos ...
-//     (3.1) Si el argumentNameo debe ir seguido por un valor/valores y no existe
+// (3) Si la opcion 'argumentExpression' existe en la linea de comandos ...
+//     (3.1) Si el argumentExpression debe ir seguido por un valor/valores y no existe
 //           ninguno => error.
-//     (3.2) Si el argumentNameo no necesita valor, simplemente, le asigna una cadena
+//     (3.2) Si el argumentExpression no necesita valor, simplemente, le asigna una cadena
 //           que contiene 'true', en otro caso devolvera NULL.
 //
-// (4) Si no existe en la linea de comandos y es un argumentNameo obligatorio => error.
+// (4) Si no existe en la linea de comandos y es un argumentExpression obligatorio => error.
 //
 // Si el arguemento solicitado no esta en la LC y es opcional devolvera NULL.
 //--------------------------------------------------------------------------------
-const char* CommandLine::getValue(const char* argumentName, const bool exitOnFault)
+const char* CommandLine::getValue(const char* argumentExpression, const bool exitOnFault)
 throw() {
   const char* result = NULL;
   const Variable* argument = NULL;
@@ -100,7 +127,7 @@ throw() {
   bool analized;
 
   if((analized = analize()) == true) {     // (1)
-    if((argument = search(argumentName)) != NULL) {     // (2)
+    if((argument = search(argumentExpression)) != NULL) {     // (2)
       error = false;
       result = argument->getValue();
 
@@ -122,7 +149,7 @@ throw() {
     printUsage();
     exit(-1);
   } else if(error == true && exitOnFault == true) {
-    cout << "Variable: '" << argumentName << "' is not valid" << endl << endl;
+    cout << "Variable: '" << argumentExpression << "' is not valid" << endl << endl;
     printUsage();
     exit(-1);
   }
@@ -130,84 +157,127 @@ throw() {
   return result;
 }
 
+// Auxiliary function:
+// Returns first no-leading hyphen position
+int removeLeadingHyphens(std::string &argv) throw() {
+  int startPos = -1;
+  // Move until no hyphen:
+  for (int i=0; i < argv.size(); i++) 
+    if (argv[i] != '-') { startPos = i; argv = argv.substr(i); break; }
+
+  // No word found:
+  if (startPos == -1) return -1;
+
+  // one hyphen:
+  if (startPos == 1)
+    if (argv.size() != 1) return -1;
+
+  // two hyphens:
+  if (startPos == 2)
+    if (argv.size() == 1) return -1;
+
+  // more than two !
+  if (startPos > 2) return -1;
+
+  return startPos;
+}
+
 //--------------------------------------------------------------------------------
-// Analiza la linea de comandos para completar la informacion de los argumentNameos.
-//
-// (1) Comprueba que el analisis no se ha completado con existo previamente.
-// (2) El primer argumentNameo despues del nombre del ejecutable debe comenzar por
-//     el caracter de indicador de argumentNameo.
-// (3) Comprueba si el argumentNameo de la LC esta registrado como posible argumentNameo.
-// (4) Recoge todo lo que haya entre el argumentNameo actual y el siguiente argumentNameo
-//     (si lo hay) y todo esto lo asigna como valor del argumentNameo actual.
-// (5) Quita el ltimo car�ter
+// Analize command line to store the arguments
 //--------------------------------------------------------------------------------
 bool CommandLine::analize()
 throw() {
-  Variable* argument;
+  Variable* variable;
   bool result = true;
   int i = 1;
   string aux;
 
-  if(a_wasParsed == true)
+  if(a_wasParsed == true) // already analyzed
     return true;
 
-  Guard guard(a_mutex, "CommandLine::analize");
+  Guard guard(a_mutex, "CommandLine::analyze");
 
   if(a_wasParsed == true)
     return true;
 
+  // Control state:
   while(i < a_argc && result == true) {
-    if(i == 1 && a_argv [1][0] != TOKENINDICATOR) {  // (2)
+
+    aux = a_argv[i];
+    if (::removeLeadingHyphens(aux) == -1) {
       result = false;
       break;
     }
-
-    if((argument = const_cast <Variable*>(search(a_argv [i]))) != NULL) {    // (3)
-      aux = "";
-      argument->setIsOn(true);
-      // @Eduardo (allow dashes on values)
-//         while (++ i < a_argc && a_argv [i][0] != TOKENINDICATOR) { // (4)
-//            aux += a_argv [i];
-//            aux += " ";
-//         }
-      bool notArgument = true;
-
-      while(notArgument && (++ i < a_argc)) {  // (4)
-        if(a_argv [i][0] == TOKENINDICATOR)
-          notArgument = (search(a_argv[i]) == NULL);
-
-        if(notArgument) {
-          aux += a_argv [i];
-          aux += " ";
-        }
+    if((variable = const_cast <Variable*>(search(aux.c_str()))) != NULL) {
+      variable->setIsOn(true);
+      if (variable->getNeedValue()) {
+        i++;
+        if (i < a_argc) { variable->setValue(a_argv[i]); }
+        else { result = false ; break; }
       }
-
-      if(aux.length() > 0) {
-        aux.erase(aux.length() - 1, 1);    // 5)
-        argument->setValue(aux.c_str());    // JEDS 24/09/2003
-      }
-    } else {
-      cout << "Variable: " << a_argv [i] << " unreconized" << endl;
+    }
+    else {
+      cout << "Variable: " << aux << " unreconized" << endl;
       result = false;
+      break;
     }
-  }
+
+    i++;
+  } // while
 
   return (a_wasParsed = result);
 }
 
-const CommandLine::Variable* CommandLine::search(const char *argumentName) const
+const CommandLine::Variable* CommandLine::search(const char *argumentExpression) const
 throw() {
-  const Variable* result = NULL;
-  vector <Variable*>::const_iterator ii, maxii;
+  if (!argumentExpression) return NULL;
+  std::string name1, name2, arg;
+
+  vector <Variable*>::const_iterator arg_min(a_arguments.begin()), arg_max(a_arguments.end()), arg_it;
+
+  // Tokenize:
+  anna::Tokenizer expression;
+  expression.apply (argumentExpression, ",");
+  if (expression.size() > 2) return NULL;
+
+  anna::Tokenizer::const_iterator tok_it;
+  for (tok_it = expression.begin(); tok_it != expression.end(); tok_it++) { // one or two
+    arg = anna::Tokenizer::data(tok_it);
+    for(arg_it = arg_min; arg_it != arg_max; arg_it++) {
+      name1 = (*arg_it)->getName1();
+      name2 = (*arg_it)->getName2();
+      if ((name1 != "") && (name1 == arg)) return *arg_it;
+      if ((name2 != "") && (name2 == arg)) return *arg_it;
+    }
+  }
 
-  if(*argumentName == TOKENINDICATOR) argumentName ++;
+  return NULL;
+}
 
-  for(ii = a_arguments.begin(), maxii = a_arguments.end(); ii != maxii; ii ++) {
-    if(anna_strcmp((*ii)->getName().c_str(), argumentName) == 0) {
-      result = *ii;
-      break;
+string CommandLine::Variable::getHelpExpression() const
+throw() {
+  std::string long_name, short_name, result = "";
+
+  int s1 = a_name1.size();
+  int s2 = a_name2.size();
+  if ((s1 + s2) > 2) { // both provided
+    if (s1 > 1) { short_name = a_name2; long_name = a_name1; }
+    else { short_name = a_name1; long_name = a_name2; }
+    if (short_name != "") {
+      result += "-";
+      result += short_name;
+      result += "|";
+    }
+    if (long_name != "") {
+      result += "--";
+      result += long_name;
     }
   }
+  else {
+    result = "-";
+    if (s1 > 1) result = "-";
+    result += a_name1;
+  }
 
   return result;
 }
@@ -223,31 +293,22 @@ throw() {
   cout << endl << "Where: " << endl;
 
   for(i = 0; i < maxi; i ++) {
-    cout << "   " << a_arguments [i]->getName() << ": " << endl;
+    cout << "   " << a_arguments [i]->getHelpExpression() << ": " << endl;
     cout << "     " << a_arguments [i]->getComment() << endl;
   }
 }
-
 string CommandLine::Variable::asString() const
 throw() {
   string result;
   result = ((a_type == Argument::Optional) ? "[ " : "");
-  result += TOKENINDICATOR;
-  result += a_name;
-
-  if(a_needValue == true) {
-    result += " <value_";
-    result += a_name;
-    result += ">";
-  }
-
-  if(a_type == Argument::Optional)
-    result += " ]";
+  result += getHelpExpression();
+  if(a_needValue == true) result += " <value>"; // result += " <value_"; result += a_name; result += ">";
+  if(a_type == Argument::Optional) result += " ]";
 
   return result;
 }
 
-
 string CommandLine::asString() const
 throw() {
   string result = "Provided command-line parameters:\n\n";
@@ -258,7 +319,7 @@ throw() {
     value = (*ii)->getValue();
 
     if(value) {
-      result += (*ii)->getName();
+      result += (*ii)->getHelpExpression();
       result += ": ";
       result += value;
       result += "\n";
@@ -277,10 +338,9 @@ xml::Node* CommandLine::asXML(xml::Node* parent) const throw() {
     value = (*ii)->getValue();
 
     if(value)
-      result->createAttribute((*ii)->getName().c_str(), value);
+      result->createAttribute((*ii)->getHelpExpression().c_str(), value);
   }
 
   return result;
 }
 
-