39f2cdb8f62fbda1a0a8e195c3683a7b0d777c31
[anna.git] / source / core / util / CommandLine.cpp
1 // ANNA - Anna is Not Nothingness Anymore
2 //
3 // (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
4 //
5 // http://redmine.teslayout.com/projects/anna-suite
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 //
11 //     * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 //     * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
16 // distribution.
17 //     *  Neither the name of the copyright holder nor the names of its
18 // contributors may be used to endorse or promote products derived from
19 // this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 // Authors: eduardo.ramos.testillano@gmail.com
34 //          cisco.tierra@gmail.com
35
36
37 #include <iostream>
38
39 #include <anna/config/defines.hpp>
40 #include <anna/core/RuntimeException.hpp>
41 #include <anna/xml/xml.hpp>
42
43 #include <anna/core/util/CommandLine.hpp>
44 #include <anna/core/util/Tokenizer.hpp>
45
46 using namespace std;
47 using namespace anna;
48
49 //--------------------------------------------------------------------------------
50 // Notifica al Parser que la opcion 'argumentExpression' es soportada
51 //
52 // (1) Verifica que la opcion no haya sido registrada previamente.
53 //--------------------------------------------------------------------------------
54 void CommandLine::add(const char* argumentExpression, Argument::Type type, const char* comment, const bool needValue)
55 throw() {
56   Guard guard(a_mutex, "CommandLine::add");
57
58   std::string arg1 = "", arg2 = "";
59   if (!argumentExpression) { std::cerr << "Invalid argument expression: cannot register NULL literal on command-line" << std::endl; return; }
60
61   anna::Tokenizer expression;
62   expression.apply (argumentExpression, ",");
63   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; }
64
65   anna::Tokenizer::const_iterator tok_it = expression.begin();
66   arg1 = anna::Tokenizer::data(tok_it);
67   // No leading hyphens in the name:
68   if (arg1[0] == '-') { std::cerr << "Invalid argument expression '" << argumentExpression << "': no leading hyphens allowed in argument names for command-line registration" << std::endl; return; }
69
70   tok_it++;
71   if (tok_it != expression.end()) {
72     arg2 = anna::Tokenizer::data(tok_it);
73     // No hyphens in the name:
74     if (arg2[0] == '-') { std::cerr << "Invalid argument expression '" << argumentExpression << "': no leading hyphens allowed in argument names for command-line registration" << std::endl; return; }
75   }
76
77   // swap to have first non empty:
78   if (arg1 == "") { arg1 = arg2; arg2 = ""; }
79
80   // If both provided, one will be single letter and the other, a word:
81   if (arg2 != "") {
82     int s1 = arg1.size();
83     int s2 = arg2.size();
84     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; }
85     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; }
86   }
87
88   // assign:
89   a_arguments.push_back(new Variable(arg1, arg2, type, comment, needValue));
90 }
91
92 void CommandLine::initialize(const char** argv, const int argc, int positionalArguments)
93 throw(RuntimeException) {
94   if (argc < 1) throw RuntimeException("Provided argc < 1 as command-line argv size !", ANNA_FILE_LOCATION);
95   if (positionalArguments < 0) throw RuntimeException("Provided negative number of positional arguments as command-line initializer", ANNA_FILE_LOCATION);
96   if (positionalArguments > (argc-1)) throw RuntimeException("Provided positional arguments > (argc - 1) as command-line initializer", ANNA_FILE_LOCATION);
97   a_positionalArguments = positionalArguments;
98   a_argv = argv;
99   a_argc = argc;
100   a_wasParsed = false;
101 }
102
103 //--------------------------------------------------------------------------------
104 // Verifica que todos los argumentos declarados como obligatorios estan en la
105 // linea de comandos.
106 //--------------------------------------------------------------------------------
107 void CommandLine::verify()
108 throw(RuntimeException) {
109   if(a_argv == NULL)
110     throw RuntimeException("CommandLine was not initialized", ANNA_FILE_LOCATION);
111
112   for(int i = 0, maxi = a_arguments.size(); i < maxi; i ++) {
113     if(a_arguments [i]->getType() == Argument::Mandatory)
114       getValue(a_arguments [i]->getName1().c_str()); // exit program if not found (exitOnFault = true by default)
115   }
116 }
117
118 //--------------------------------------------------------------------------------
119 // Obtiene el valor para opcion 'argumentExpression'.
120 //
121 // (1) Realiza el analisis de la linea de comandos.
122 // (2) Verifica que la opcion recibida existe en la lista de opciones registradas.
123 // (3) Si la opcion 'argumentExpression' existe en la linea de comandos ...
124 //     (3.1) Si el argumentExpression debe ir seguido por un valor/valores y no existe
125 //           ninguno => error.
126 //     (3.2) Si el argumentExpression no necesita valor, simplemente, le asigna una cadena
127 //           que contiene 'true', en otro caso devolvera NULL.
128 //
129 // (4) Si no existe en la linea de comandos y es un argumentExpression obligatorio => error.
130 //
131 // Si el arguemento solicitado no esta en la LC y es opcional devolvera NULL.
132 //--------------------------------------------------------------------------------
133 const char* CommandLine::getValue(const char* argumentExpression, const bool exitOnFault)
134 throw() {
135   const char* result = NULL;
136   const Variable* argument = NULL;
137   bool error = true;
138   bool analized;
139
140   if((analized = analize()) == true) {     // (1)
141     if((argument = search(argumentExpression)) != NULL) {     // (2)
142       error = false;
143       result = argument->getValue();
144
145       if(argument->getIsOn() == true) {   // (3)
146         if(argument->getNeedValue() == true && result == NULL)   // (3.1)
147           error = true;
148         else if(argument->getNeedValue() == false) {
149           if(result != NULL)
150             error = true;
151           else
152             result = "true";
153         }
154       } else if(argument->getType() == Argument::Mandatory)  // (4)
155         error = true;
156     }
157   }
158
159   if(analized == false) {
160     printUsage();
161     exit(-1);
162   } else if(error == true && exitOnFault == true) {
163     cout << "Variable: '" << argumentExpression << "' is not valid" << endl << endl;
164     printUsage();
165     exit(-1);
166   }
167
168   return result;
169 }
170
171 // Auxiliary function:
172 // Returns first no-leading hyphen position
173 int removeLeadingHyphens(std::string &argv) throw() {
174   int startPos = -1;
175   // Move until no hyphen:
176   for (int i=0; i < argv.size(); i++) 
177     if (argv[i] != '-') { startPos = i; argv = argv.substr(i); break; }
178
179   // No word found:
180   if (startPos == -1) return -1;
181
182   // one hyphen:
183   if (startPos == 1)
184     if (argv.size() != 1) return -1;
185
186   // two hyphens:
187   if (startPos == 2)
188     if (argv.size() == 1) return -1;
189
190   // more than two !
191   if (startPos > 2) return -1;
192
193   return startPos;
194 }
195
196 //--------------------------------------------------------------------------------
197 // Analize command line to store the arguments
198 //--------------------------------------------------------------------------------
199 bool CommandLine::analize()
200 throw() {
201   Variable* variable;
202   bool result = true;
203   int i = a_positionalArguments + 1;
204   string aux;
205
206   if(a_wasParsed == true) // already analyzed
207     return true;
208
209   Guard guard(a_mutex, "CommandLine::analyze");
210
211   if(a_wasParsed == true)
212     return true;
213
214   // Control state:
215   while(i < a_argc && result == true) {
216
217     aux = a_argv[i];
218     if (::removeLeadingHyphens(aux) == -1) {
219       result = false;
220       break;
221     }
222     if((variable = const_cast <Variable*>(search(aux.c_str()))) != NULL) {
223       variable->setIsOn(true);
224       if (variable->getNeedValue()) {
225         i++;
226         if (i < a_argc) { variable->setValue(a_argv[i]); }
227         else { result = false ; break; }
228       }
229     }
230     else {
231       cout << "Variable: " << aux << " unreconized" << endl;
232       result = false;
233       break;
234     }
235
236     i++;
237   } // while
238
239   return (a_wasParsed = result);
240 }
241
242 const CommandLine::Variable* CommandLine::search(const char *argumentExpression) const
243 throw() {
244   if (!argumentExpression) return NULL;
245   std::string name1, name2, arg;
246
247   vector <Variable*>::const_iterator arg_min(a_arguments.begin()), arg_max(a_arguments.end()), arg_it;
248
249   // Tokenize:
250   anna::Tokenizer expression;
251   expression.apply (argumentExpression, ",");
252   if (expression.size() > 2) return NULL;
253
254   anna::Tokenizer::const_iterator tok_it;
255   for (tok_it = expression.begin(); tok_it != expression.end(); tok_it++) { // one or two
256     arg = anna::Tokenizer::data(tok_it);
257     for(arg_it = arg_min; arg_it != arg_max; arg_it++) {
258       name1 = (*arg_it)->getName1();
259       name2 = (*arg_it)->getName2();
260       if ((name1 != "") && (name1 == arg)) return *arg_it;
261       if ((name2 != "") && (name2 == arg)) return *arg_it;
262     }
263   }
264
265   return NULL;
266 }
267
268 string CommandLine::Variable::getHelpExpression() const
269 throw() {
270   std::string long_name, short_name, result = "";
271
272   int s1 = a_name1.size();
273   int s2 = a_name2.size();
274   if ((s1 + s2) > 2) { // both provided
275     if (s1 > 1) { short_name = a_name2; long_name = a_name1; }
276     else { short_name = a_name1; long_name = a_name2; }
277     if (short_name != "") {
278       result += "-";
279       result += short_name;
280       result += "|";
281     }
282     if (long_name != "") {
283       result += "--";
284       result += long_name;
285     }
286   }
287   else {
288     result = "-";
289     if (s1 > 1) result = "-";
290     result += a_name1;
291   }
292
293   return result;
294 }
295
296 void CommandLine::printUsage() const
297 throw() {
298   int i, maxi(a_arguments.size());
299   cout << "Use: " << a_argv [0] << " ";
300
301   for(i = 0; i < maxi; i ++)
302     cout << a_arguments [i]->asString() << " ";
303
304   cout << endl << "Where: " << endl;
305
306   for(i = 0; i < maxi; i ++) {
307     cout << "   " << a_arguments [i]->getHelpExpression() << ": " << endl;
308     cout << "     " << a_arguments [i]->getComment() << endl;
309   }
310 }
311  
312 string CommandLine::Variable::asString() const
313 throw() {
314   string result;
315   result = ((a_type == Argument::Optional) ? "[ " : "");
316   result += getHelpExpression();
317   if(a_needValue == true) result += " <value>"; // result += " <value_"; result += a_name; result += ">";
318   if(a_type == Argument::Optional) result += " ]";
319
320   return result;
321 }
322
323 string CommandLine::asString() const
324 throw() {
325   string result = "Provided command-line parameters:\n\n";
326   vector <Variable*>::const_iterator ii, maxii;
327   const char *value;
328
329   for(int pos = 1; pos <= a_positionalArguments; pos++)
330     result += anna::functions::asString("Positional argument [%d]: %s\n", pos, getPositional(pos));
331
332   for(ii = a_arguments.begin(), maxii = a_arguments.end(); ii != maxii; ii ++) {
333     value = (*ii)->getValue();
334
335     if(value) {
336       result += (*ii)->getHelpExpression();
337       result += ": ";
338       result += value;
339       result += "\n";
340     }
341   }
342
343   return result;
344 }
345
346 xml::Node* CommandLine::asXML(xml::Node* parent) const throw() {
347   xml::Node* result = parent->createChild("CommandLine");
348   vector <Variable*>::const_iterator ii, maxii;
349   const char *value;
350
351   for(int pos = 1; pos <= a_positionalArguments; pos++)
352     result->createAttribute(anna::functions::asString("PositionalArgument_%d", pos).c_str(), getPositional(pos));
353
354   for(ii = a_arguments.begin(), maxii = a_arguments.end(); ii != maxii; ii ++) {
355     value = (*ii)->getValue();
356
357     if(value)
358       result->createAttribute((*ii)->getHelpExpression().c_str(), value);
359   }
360
361   return result;
362 }
363