Remove dynamic exceptions
[anna.git] / source / core / util / CommandLine.cpp
1 // ANNA - Anna is Not Nothingness Anymore                                                         //
2 //                                                                                                //
3 // (c) Copyright 2005-2015 Eduardo Ramos Testillano & Francisco Ruiz Rayo                         //
4 //                                                                                                //
5 // See project site at http://redmine.teslayout.com/projects/anna-suite                           //
6 // See accompanying file LICENSE or copy at http://www.teslayout.com/projects/public/anna.LICENSE //
7
8
9 #include <iostream>
10
11 #include <anna/config/defines.hpp>
12 #include <anna/core/RuntimeException.hpp>
13 #include <anna/xml/xml.hpp>
14
15 #include <anna/core/util/CommandLine.hpp>
16 #include <anna/core/util/Tokenizer.hpp>
17
18 using namespace std;
19 using namespace anna;
20
21 //--------------------------------------------------------------------------------
22 // Notifica al Parser que la opcion 'argumentExpression' es soportada
23 //
24 // (1) Verifica que la opcion no haya sido registrada previamente.
25 //--------------------------------------------------------------------------------
26 void CommandLine::add(const char* argumentExpression, Argument::Type type, const char* comment, const bool needValue)
27 {
28   Guard guard(a_mutex, "CommandLine::add");
29
30   std::string arg1 = "", arg2 = "";
31   if (!argumentExpression) { std::cerr << "Invalid argument expression: cannot register NULL literal on command-line" << std::endl; return; }
32
33   anna::Tokenizer expression;
34   expression.apply (argumentExpression, ",");
35   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; }
36
37   anna::Tokenizer::const_iterator tok_it = expression.begin();
38   arg1 = anna::Tokenizer::data(tok_it);
39   // No leading hyphens in the name:
40   if (arg1[0] == '-') { std::cerr << "Invalid argument expression '" << argumentExpression << "': no leading hyphens allowed in argument names for command-line registration" << std::endl; return; }
41
42   tok_it++;
43   if (tok_it != expression.end()) {
44     arg2 = anna::Tokenizer::data(tok_it);
45     // No hyphens in the name:
46     if (arg2[0] == '-') { std::cerr << "Invalid argument expression '" << argumentExpression << "': no leading hyphens allowed in argument names for command-line registration" << std::endl; return; }
47   }
48
49   // swap to have first non empty:
50   if (arg1 == "") { arg1 = arg2; arg2 = ""; }
51
52   // If both provided, one will be single letter and the other, a word:
53   if (arg2 != "") {
54     int s1 = arg1.size();
55     int s2 = arg2.size();
56     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; }
57     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; }
58   }
59
60   // assign:
61   a_arguments.push_back(new Variable(arg1, arg2, type, comment, needValue));
62 }
63
64 void CommandLine::initialize(const char** argv, const int argc, int positionalArguments)
65 noexcept(false) {
66   if (argc < 1) throw RuntimeException("Provided argc < 1 as command-line argv size !", ANNA_FILE_LOCATION);
67   if (positionalArguments < 0) throw RuntimeException("Provided negative number of positional arguments as command-line initializer", ANNA_FILE_LOCATION);
68   if (positionalArguments > (argc-1)) throw RuntimeException("Provided positional arguments > (argc - 1) as command-line initializer", ANNA_FILE_LOCATION);
69   a_positionalArguments = positionalArguments;
70   a_argv = argv;
71   a_argc = argc;
72   a_wasParsed = false;
73 }
74
75 //--------------------------------------------------------------------------------
76 // Verifica que todos los argumentos declarados como obligatorios estan en la
77 // linea de comandos.
78 //--------------------------------------------------------------------------------
79 void CommandLine::verify()
80 noexcept(false) {
81   if(a_argv == NULL)
82     throw RuntimeException("CommandLine was not initialized", ANNA_FILE_LOCATION);
83
84   for(int i = 0, maxi = a_arguments.size(); i < maxi; i ++) {
85     if(a_arguments [i]->getType() == Argument::Mandatory)
86       getValue(a_arguments [i]->getName1().c_str()); // exit program if not found (exitOnFault = true by default)
87   }
88 }
89
90 //--------------------------------------------------------------------------------
91 // Obtiene el valor para opcion 'argumentExpression'.
92 //
93 // (1) Realiza el analisis de la linea de comandos.
94 // (2) Verifica que la opcion recibida existe en la lista de opciones registradas.
95 // (3) Si la opcion 'argumentExpression' existe en la linea de comandos ...
96 //     (3.1) Si el argumentExpression debe ir seguido por un valor/valores y no existe
97 //           ninguno => error.
98 //     (3.2) Si el argumentExpression no necesita valor, simplemente, le asigna una cadena
99 //           que contiene 'true', en otro caso devolvera NULL.
100 //
101 // (4) Si no existe en la linea de comandos y es un argumentExpression obligatorio => error.
102 //
103 // Si el arguemento solicitado no esta en la LC y es opcional devolvera NULL.
104 //--------------------------------------------------------------------------------
105 const char* CommandLine::getValue(const char* argumentExpression, const bool exitOnFault)
106 {
107   const char* result = NULL;
108   const Variable* argument = NULL;
109   bool error = true;
110   bool analized;
111
112   if((analized = analize()) == true) {     // (1)
113     if((argument = search(argumentExpression)) != NULL) {     // (2)
114       error = false;
115       result = argument->getValue();
116
117       if(argument->getIsOn() == true) {   // (3)
118         if(argument->getNeedValue() == true && result == NULL)   // (3.1)
119           error = true;
120         else if(argument->getNeedValue() == false) {
121           if(result != NULL)
122             error = true;
123           else
124             result = "true";
125         }
126       } else if(argument->getType() == Argument::Mandatory)  // (4)
127         error = true;
128     }
129   }
130
131   if(analized == false) {
132     printUsage();
133     exit(-1);
134   } else if(error == true && exitOnFault == true) {
135     cout << "Variable: '" << argumentExpression << "' is not valid" << endl << endl;
136     printUsage();
137     exit(-1);
138   }
139
140   return result;
141 }
142
143 // Auxiliary function:
144 // Returns first no-leading hyphen position
145 int removeLeadingHyphens(std::string &argv) {
146   int startPos = -1;
147   // Move until no hyphen:
148   for (int i=0; i < argv.size(); i++) 
149     if (argv[i] != '-') { startPos = i; argv = argv.substr(i); break; }
150
151   // No word found:
152   if (startPos == -1) return -1;
153
154   // one hyphen:
155   if (startPos == 1)
156     if (argv.size() != 1) return -1;
157
158   // two hyphens:
159   if (startPos == 2)
160     if (argv.size() == 1) return -1;
161
162   // more than two !
163   if (startPos > 2) return -1;
164
165   return startPos;
166 }
167
168 //--------------------------------------------------------------------------------
169 // Analize command line to store the arguments
170 //--------------------------------------------------------------------------------
171 bool CommandLine::analize()
172 {
173   Variable* variable;
174   bool result = true;
175   int i = a_positionalArguments + 1;
176   string aux;
177
178   if(a_wasParsed == true) // already analyzed
179     return true;
180
181   Guard guard(a_mutex, "CommandLine::analyze");
182
183   if(a_wasParsed == true)
184     return true;
185
186   // Control state:
187   while(i < a_argc && result == true) {
188
189     aux = a_argv[i];
190     if (::removeLeadingHyphens(aux) == -1) {
191       result = false;
192       break;
193     }
194     if((variable = const_cast <Variable*>(search(aux.c_str()))) != NULL) {
195       variable->setIsOn(true);
196       if (variable->getNeedValue()) {
197         i++;
198         if (i < a_argc) { variable->setValue(a_argv[i]); }
199         else { result = false ; break; }
200       }
201     }
202     else {
203       cout << "Variable: " << aux << " unreconized" << endl;
204       result = false;
205       break;
206     }
207
208     i++;
209   } // while
210
211   return (a_wasParsed = result);
212 }
213
214 const CommandLine::Variable* CommandLine::search(const char *argumentExpression) const
215 {
216   if (!argumentExpression) return NULL;
217   std::string name1, name2, arg;
218
219   vector <Variable*>::const_iterator arg_min(a_arguments.begin()), arg_max(a_arguments.end()), arg_it;
220
221   // Tokenize:
222   anna::Tokenizer expression;
223   expression.apply (argumentExpression, ",");
224   if (expression.size() > 2) return NULL;
225
226   anna::Tokenizer::const_iterator tok_it;
227   for (tok_it = expression.begin(); tok_it != expression.end(); tok_it++) { // one or two
228     arg = anna::Tokenizer::data(tok_it);
229     for(arg_it = arg_min; arg_it != arg_max; arg_it++) {
230       name1 = (*arg_it)->getName1();
231       name2 = (*arg_it)->getName2();
232       if ((name1 != "") && (name1 == arg)) return *arg_it;
233       if ((name2 != "") && (name2 == arg)) return *arg_it;
234     }
235   }
236
237   return NULL;
238 }
239
240 string CommandLine::Variable::getHelpExpression() const
241 {
242   std::string long_name, short_name, result = "";
243
244   int s1 = a_name1.size();
245   int s2 = a_name2.size();
246   if ((s1 + s2) > 2) { // both provided
247     if (s1 > 1) { short_name = a_name2; long_name = a_name1; }
248     else { short_name = a_name1; long_name = a_name2; }
249     if (short_name != "") {
250       result += "-";
251       result += short_name;
252       result += "|";
253     }
254     if (long_name != "") {
255       result += "--";
256       result += long_name;
257     }
258   }
259   else {
260     result = "-";
261     if (s1 > 1) result = "-";
262     result += a_name1;
263   }
264
265   return result;
266 }
267
268 void CommandLine::printUsage() const
269 {
270   int i, maxi(a_arguments.size());
271   cout << "Usage: " << a_argv [0] << " ";
272
273   for(i = 0; i < maxi; i ++)
274     cout << a_arguments [i]->asString() << " ";
275
276   cout << endl << "Where: " << endl;
277
278   for(i = 0; i < maxi; i ++) {
279     cout << "   " << a_arguments [i]->getHelpExpression() << ": " << endl;
280     cout << "     " << a_arguments [i]->getComment() << endl;
281   }
282 }
283  
284 string CommandLine::Variable::asString() const
285 {
286   string result;
287   result = ((a_type == Argument::Optional) ? "[ " : "");
288   result += getHelpExpression();
289   if(a_needValue == true) result += " <value>"; // result += " <value_"; result += a_name; result += ">";
290   if(a_type == Argument::Optional) result += " ]";
291
292   return result;
293 }
294
295 string CommandLine::asString() const
296 {
297   string result = "Provided command-line parameters:\n\n";
298   vector <Variable*>::const_iterator ii, maxii;
299   const char *value;
300
301   for(int pos = 1; pos <= a_positionalArguments; pos++)
302     result += anna::functions::asString("Positional argument [%d]: %s\n", pos, getPositional(pos));
303
304   for(ii = a_arguments.begin(), maxii = a_arguments.end(); ii != maxii; ii ++) {
305     value = (*ii)->getValue();
306
307     if(value) {
308       result += (*ii)->getHelpExpression();
309       result += ": ";
310       result += value;
311       result += "\n";
312     }
313   }
314
315   return result;
316 }
317
318 xml::Node* CommandLine::asXML(xml::Node* parent) const {
319   xml::Node* result = parent->createChild("CommandLine");
320   vector <Variable*>::const_iterator ii, maxii;
321   const char *value;
322
323   for(int pos = 1; pos <= a_positionalArguments; pos++)
324     result->createAttribute(anna::functions::asString("PositionalArgument_%d", pos).c_str(), getPositional(pos));
325
326   for(ii = a_arguments.begin(), maxii = a_arguments.end(); ii != maxii; ii ++) {
327     value = (*ii)->getValue();
328
329     if(value)
330       result->createAttribute((*ii)->getHelpExpression().c_str(), value);
331   }
332
333   return result;
334 }
335