Remove dynamic exceptions
[anna.git] / source / core / util / CommandLine.cpp
index 32d1f74..42dfd17 100644 (file)
@@ -1,37 +1,9 @@
-// ANNA - Anna is Not 'N' Anymore
-//
-// (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
-//
-// https://bitbucket.org/testillano/anna
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions
-// are met:
-//
-//     * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-//     * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-//
-// Authors: eduardo.ramos.testillano@gmail.com
-//          cisco.tierra@gmail.com
+// ANNA - Anna is Not Nothingness Anymore                                                         //
+//                                                                                                //
+// (c) Copyright 2005-2015 Eduardo Ramos Testillano & Francisco Ruiz Rayo                         //
+//                                                                                                //
+// See project site at http://redmine.teslayout.com/projects/anna-suite                           //
+// See accompanying file LICENSE or copy at http://www.teslayout.com/projects/public/anna.LICENSE //
 
 
 #include <iostream>
 #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)
-throw() {
+void CommandLine::add(const char* argumentExpression, Argument::Type type, const char* comment, const bool needValue)
+{
   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));
+}
+
+void CommandLine::initialize(const char** argv, const int argc, int positionalArguments)
+noexcept(false) {
+  if (argc < 1) throw RuntimeException("Provided argc < 1 as command-line argv size !", ANNA_FILE_LOCATION);
+  if (positionalArguments < 0) throw RuntimeException("Provided negative number of positional arguments as command-line initializer", ANNA_FILE_LOCATION);
+  if (positionalArguments > (argc-1)) throw RuntimeException("Provided positional arguments > (argc - 1) as command-line initializer", ANNA_FILE_LOCATION);
+  a_positionalArguments = positionalArguments;
+  a_argv = argv;
+  a_argc = argc;
+  a_wasParsed = false;
 }
 
 //--------------------------------------------------------------------------------
-// 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()
-throw(RuntimeException) {
+noexcept(false) {
   if(a_argv == NULL)
     throw RuntimeException("CommandLine was not initialized", ANNA_FILE_LOCATION);
 
   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)
-throw() {
+const char* CommandLine::getValue(const char* argumentExpression, const bool exitOnFault)
+{
   const char* result = NULL;
   const Variable* argument = NULL;
   bool error = true;
   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 +132,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,92 +140,135 @@ throw() {
   return result;
 }
 
+// Auxiliary function:
+// Returns first no-leading hyphen position
+int removeLeadingHyphens(std::string &argv) {
+  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;
+  int i = a_positionalArguments + 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
-throw() {
-  const Variable* result = NULL;
-  vector <Variable*>::const_iterator ii, maxii;
+const CommandLine::Variable* CommandLine::search(const char *argumentExpression) const
+{
+  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
+{
+  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;
 }
 
 void CommandLine::printUsage() const
-throw() {
+{
   int i, maxi(a_arguments.size());
-  cout << "Use: " << a_argv [0] << " ";
+  cout << "Usage: " << a_argv [0] << " ";
 
   for(i = 0; i < maxi; i ++)
     cout << a_arguments [i]->asString() << " ";
@@ -223,42 +276,36 @@ 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";
   vector <Variable*>::const_iterator ii, maxii;
   const char *value;
 
+  for(int pos = 1; pos <= a_positionalArguments; pos++)
+    result += anna::functions::asString("Positional argument [%d]: %s\n", pos, getPositional(pos));
+
   for(ii = a_arguments.begin(), maxii = a_arguments.end(); ii != maxii; ii ++) {
     value = (*ii)->getValue();
 
     if(value) {
-      result += (*ii)->getName();
+      result += (*ii)->getHelpExpression();
       result += ": ";
       result += value;
       result += "\n";
@@ -268,19 +315,21 @@ throw() {
   return result;
 }
 
-xml::Node* CommandLine::asXML(xml::Node* parent) const throw() {
+xml::Node* CommandLine::asXML(xml::Node* parent) const {
   xml::Node* result = parent->createChild("CommandLine");
   vector <Variable*>::const_iterator ii, maxii;
   const char *value;
 
+  for(int pos = 1; pos <= a_positionalArguments; pos++)
+    result->createAttribute(anna::functions::asString("PositionalArgument_%d", pos).c_str(), getPositional(pos));
+
   for(ii = a_arguments.begin(), maxii = a_arguments.end(); ii != maxii; ii ++) {
     value = (*ii)->getValue();
 
     if(value)
-      result->createAttribute((*ii)->getName().c_str(), value);
+      result->createAttribute((*ii)->getHelpExpression().c_str(), value);
   }
 
   return result;
 }
 
-